{t("agents.lastHeartbeat", "Last heartbeat")}
-{agent.lastHeartbeatAt ? relativeTime(agent.lastHeartbeatAt) : t("agents.never", "Never")}
+{agent.lastHeartbeatAt ? relativeTime(agent.lastHeartbeatAt, t) : t("agents.never", "Never")}
{t("agents.nextExpected", "Next expected")}
-{nextHeartbeatAt ? relativeTime(nextHeartbeatAt) : t("agents.notScheduled", "Not scheduled")}
+{nextHeartbeatAt ? relativeTime(nextHeartbeatAt, t) : t("agents.notScheduled", "Not scheduled")}
{t("agents.interval", "Interval")}
@@ -1306,7 +1315,7 @@ function DashboardTab({ return ({errorText}
diff --git a/packages/dashboard/app/components/AgentImportModal.tsx b/packages/dashboard/app/components/AgentImportModal.tsx
index b830ca8c4e..4b321edbb4 100644
--- a/packages/dashboard/app/components/AgentImportModal.tsx
+++ b/packages/dashboard/app/components/AgentImportModal.tsx
@@ -432,18 +432,18 @@ export function AgentImportModal({ isOpen, onClose, onImported, projectId, initi
const selectedAgentCount = selectedAgentNames.length;
const selectedSkillCount = selectedSkillNames.length;
- const selectedAgentLabel = `${selectedAgentCount} Agent${selectedAgentCount !== 1 ? "s" : ""}`;
- const selectedSkillLabel = `${selectedSkillCount} Skill${selectedSkillCount !== 1 ? "s" : ""}`;
+ const selectedAgentLabel = t("agents.selectedAgentLabel", "{{count}} Agent{{plural}}", { count: selectedAgentCount, plural: selectedAgentCount !== 1 ? "s" : "" });
+ const selectedSkillLabel = t("agents.selectedSkillLabel", "{{count}} Skill{{plural}}", { count: selectedSkillCount, plural: selectedSkillCount !== 1 ? "s" : "" });
const importActionLabel = selectedAgentCount > 0 && selectedSkillCount > 0
? `${selectedAgentLabel} + ${selectedSkillLabel}`
: selectedSkillCount > 0
? selectedSkillLabel
: selectedAgentLabel;
const importLoadingLabel = selectedAgentCount > 0 && selectedSkillCount > 0
- ? `Importing ${selectedAgentCount} agent${selectedAgentCount !== 1 ? "s" : ""} and ${selectedSkillCount} skill${selectedSkillCount !== 1 ? "s" : ""}...`
+ ? t("agents.importingAgentsAndSkills", "Importing {{agentCount}} agent{{agentPlural}} and {{skillCount}} skill{{skillPlural}}...", { agentCount: selectedAgentCount, agentPlural: selectedAgentCount !== 1 ? "s" : "", skillCount: selectedSkillCount, skillPlural: selectedSkillCount !== 1 ? "s" : "" })
: selectedSkillCount > 0
- ? `Importing ${selectedSkillCount} skill${selectedSkillCount !== 1 ? "s" : ""}...`
- : `Importing ${selectedAgentCount} agent${selectedAgentCount !== 1 ? "s" : ""}...`;
+ ? t("agents.importingSkills", "Importing {{count}} skill{{plural}}...", { count: selectedSkillCount, plural: selectedSkillCount !== 1 ? "s" : "" })
+ : t("agents.importingAgents", "Importing {{count}} agent{{plural}}...", { count: selectedAgentCount, plural: selectedAgentCount !== 1 ? "s" : "" });
const toggleAgentSelection = (name: string) => {
setSelectedAgentNames((current) => (
@@ -647,7 +647,7 @@ export function AgentImportModal({ isOpen, onClose, onImported, projectId, initi
{/* Text area for paste */}
- These settings govern durable provisioning tools only: fn_agent_create and fn_agent_delete.
- Ephemeral fn_spawn_agent requests stay under the task/agent mutation approval gate (FN-3973).
+ }}
+ />
{selectedEval.taskId} · {selectedEval.runId}
-{t("evals.overallScore", "Overall score: {{score}}", { score: selectedEval.overallScore ?? "n/a" })}
+{t("evals.overallScore", "Overall score: {{score}}", { score: selectedEval.overallScore ?? t("evals.naPlaceholder", "n/a") })}
mergeAdvanceAutoSync in Settings to
- have the merger reconcile automatically, or run git reset --hard HEAD to
- snap forward manually.
+ , resetCode: }}
+ />
- Each entry is a Fusion task whose squash commit advanced the
- integration branch ref ({status.integrationBranch ?? "main"}).
- The auto-sync outcome says whether your working tree
- was also fast-forwarded to that new tip.
+
clean-sync / synced-with-edits-restored — working tree is in sync; nothing to do.reachable / subsumed / orphaned / superseded — already handled (including history rewrites where equivalent content already landed, original SHAs disappeared, or HEAD is already aligned to the rewritten integration tip).pending + off / not run — auto-sync is disabled in Settings; the branch ref moved but your worktree didn't follow.pending + stash-failed / would-conflict / similar — auto-sync tried but couldn't reconcile (usually local edits collide with the new commit). }}
+ />
+ , c3: , c4: }}
+ />
+ }}
+ />
+ , c3: }}
+ />
+
- Fix: Fusion only shows Sync working tree when at least one advance is genuinely pending and HEAD is not aligned with the integration tip. If entries are already handled (subsumed/orphaned/reachable/superseded), no sync action is offered.
+ }}
+ />
{event.description}
@@ -4147,11 +4145,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr{description}
@@ -4304,7 +4298,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr {showSummaryBlock && (openclaw CLI as a subprocess. Each
- Fusion prompt is dispatched via{" "}
- openclaw agent --local --json; the agent definition,
- model, and thinking level are resolved by OpenClaw. This card only
- sets overrides and the binary path.
- >
+ }}
+ />
}
busy={busy}
toast={toast}
@@ -236,14 +234,18 @@ export function OpenClawRuntimeCard() {
binary?.available === false ? (
- openclaw not detected. Install the upstream agent:
+
npm install -g openclaw
- openclaw on GitHub + {t("openclaw.githubLink", "openclaw on GitHub")}
{revealed} : null}
{session.title}
- {isError ? t("sessionBanner.failed", "Failed") : TYPE_LABELS[session.type]} + {isError ? t("sessionBanner.failed", "Failed") : t(TYPE_LABEL_KEYS[session.type].key, TYPE_LABEL_KEYS[session.type].defaultVal)}
{t("stashRecovery.noOrphans", "No orphaned merger autostashes found.")}
{t("todo.manageDescription", "Manage reusable todo lists for your project.")}