titan-agent 6.1.0-alpha.56 → 6.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
 
47
47
  ## 🪵 NEW in v6.1 — Mission Chat + Desk view
48
48
 
49
- > Status: `v6.1.0-alpha.56` — live on npm at **both** `@latest` and `@alpha`.
49
+ > Status: `v6.1.0-beta.2` — live on npm at `@latest` and `@beta`. Alpha series concluded with `6.1.0-alpha.57`; the schema promoted to beta once features stabilized.
50
50
  > Install with `npm i -g titan-agent` (or `npm update -g titan-agent`).
51
51
  > The v6.0 "Presence" feature set below still applies — v6.1.0 layers
52
52
  > a beautiful new surface on top of it.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ const TZ = "America/Los_Angeles";
3
+ function formatCurrentDateContext(now = /* @__PURE__ */ new Date()) {
4
+ const dateLabel = new Intl.DateTimeFormat("en-US", {
5
+ timeZone: TZ,
6
+ weekday: "long",
7
+ year: "numeric",
8
+ month: "long",
9
+ day: "numeric"
10
+ }).format(now);
11
+ const timeLabel = new Intl.DateTimeFormat("en-US", {
12
+ timeZone: TZ,
13
+ hour: "numeric",
14
+ minute: "2-digit",
15
+ hour12: true
16
+ }).format(now);
17
+ const tzShortFmt = new Intl.DateTimeFormat("en-US", {
18
+ timeZone: TZ,
19
+ timeZoneName: "short"
20
+ });
21
+ const tzShortPart = tzShortFmt.formatToParts(now).find((p) => p.type === "timeZoneName");
22
+ const tzShort = tzShortPart?.value ?? "PT";
23
+ const utcOffset = tzShort === "PDT" ? "UTC-7" : tzShort === "PST" ? "UTC-8" : "";
24
+ const offsetSuffix = utcOffset ? `, ${utcOffset}` : "";
25
+ return [
26
+ `Today's date is ${dateLabel}.`,
27
+ `Current time is ${timeLabel} Pacific (${TZ}${offsetSuffix}).`,
28
+ `Use this when interpreting relative times like "now", "today", "yesterday", "last quarter", "end of March", etc. Anything described as "current" / "latest" / "recent" refers to information dated AFTER this timestamp.`
29
+ ].join("\n");
30
+ }
31
+ export {
32
+ formatCurrentDateContext
33
+ };
34
+ //# sourceMappingURL=dateContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/agent/dateContext.ts"],"sourcesContent":["/**\n * TITAN — Date Context for Sub-Agents\n *\n * Single-purpose helper that renders a short, human-readable \"current time\"\n * block to inject into sub-agent system prompts. Sub-agents previously had\n * no notion of \"now\", which broke missions that depended on relative-time\n * interpretation (e.g. \"the advances since end of March\").\n *\n * Pure function. No I/O. Pacific time (America/Los_Angeles) — Tony's TZ.\n */\n\nconst TZ = 'America/Los_Angeles';\n\n/**\n * Returns a short prompt block describing the current date/time in Pacific\n * time. Pure — accepts an optional Date for deterministic testing.\n */\nexport function formatCurrentDateContext(now: Date = new Date()): string {\n // Weekday, Month D, YYYY (e.g. \"Friday, May 15, 2026\")\n const dateLabel = new Intl.DateTimeFormat('en-US', {\n timeZone: TZ,\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n }).format(now);\n\n // h:mm AM/PM (e.g. \"9:14 PM\")\n const timeLabel = new Intl.DateTimeFormat('en-US', {\n timeZone: TZ,\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n }).format(now);\n\n // Resolve DST suffix (PDT vs PST) and UTC offset dynamically.\n const tzShortFmt = new Intl.DateTimeFormat('en-US', {\n timeZone: TZ,\n timeZoneName: 'short',\n });\n const tzShortPart = tzShortFmt.formatToParts(now).find((p) => p.type === 'timeZoneName');\n const tzShort = tzShortPart?.value ?? 'PT'; // \"PDT\" in May, \"PST\" in winter\n const utcOffset = tzShort === 'PDT' ? 'UTC-7' : tzShort === 'PST' ? 'UTC-8' : '';\n\n const offsetSuffix = utcOffset ? `, ${utcOffset}` : '';\n\n return [\n `Today's date is ${dateLabel}.`,\n `Current time is ${timeLabel} Pacific (${TZ}${offsetSuffix}).`,\n `Use this when interpreting relative times like \"now\", \"today\", \"yesterday\", \"last quarter\", \"end of March\", etc. Anything described as \"current\" / \"latest\" / \"recent\" refers to information dated AFTER this timestamp.`,\n ].join('\\n');\n}\n"],"mappings":";AAWA,MAAM,KAAK;AAMJ,SAAS,yBAAyB,MAAY,oBAAI,KAAK,GAAW;AAErE,QAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,IAC/C,UAAU;AAAA,IACV,SAAS;AAAA,IACT,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,EACT,CAAC,EAAE,OAAO,GAAG;AAGb,QAAM,YAAY,IAAI,KAAK,eAAe,SAAS;AAAA,IAC/C,UAAU;AAAA,IACV,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,EACZ,CAAC,EAAE,OAAO,GAAG;AAGb,QAAM,aAAa,IAAI,KAAK,eAAe,SAAS;AAAA,IAChD,UAAU;AAAA,IACV,cAAc;AAAA,EAClB,CAAC;AACD,QAAM,cAAc,WAAW,cAAc,GAAG,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,cAAc;AACvF,QAAM,UAAU,aAAa,SAAS;AACtC,QAAM,YAAY,YAAY,QAAQ,UAAU,YAAY,QAAQ,UAAU;AAE9E,QAAM,eAAe,YAAY,KAAK,SAAS,KAAK;AAEpD,SAAO;AAAA,IACH,mBAAmB,SAAS;AAAA,IAC5B,mBAAmB,SAAS,aAAa,EAAE,GAAG,YAAY;AAAA,IAC1D;AAAA,EACJ,EAAE,KAAK,IAAI;AACf;","names":[]}
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { readFileSync, existsSync, statSync } from "fs";
2
3
  import logger from "../utils/logger.js";
3
4
  import { createGoal } from "./goals.js";
4
5
  import {
@@ -14,10 +15,72 @@ import {
14
15
  getMissionByGoalId,
15
16
  listMissions,
16
17
  setLinkedGoal,
17
- setLinkedIssue
18
+ setLinkedIssue,
19
+ updateArtifact
18
20
  } from "./missionRoom.js";
19
21
  import { onAgentEvent } from "./agentEvents.js";
20
22
  import { createIssue, updateIssue, addIssueComment } from "./commandPost.js";
23
+ const notebookFillTimers = /* @__PURE__ */ new Map();
24
+ function htmlToNotebookText(html) {
25
+ if (!html) return "";
26
+ let s = html;
27
+ s = s.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "");
28
+ s = s.replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, "");
29
+ s = s.replace(/<head\b[^>]*>[\s\S]*?<\/head>/gi, "");
30
+ s = s.replace(/<(?:br|hr)\s*\/?>/gi, "\n");
31
+ s = s.replace(/<\/(?:p|h[1-6]|li|tr|div|section|article|figure|figcaption|blockquote)\s*>/gi, "\n\n");
32
+ s = s.replace(/<[^>]+>/g, "");
33
+ s = s.replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#39;/gi, "'").replace(/&#(\d+);/g, (_m, n) => String.fromCharCode(Number(n)));
34
+ s = s.replace(/[ \t]+/g, " ");
35
+ s = s.replace(/\n{3,}/g, "\n\n");
36
+ return s.trim();
37
+ }
38
+ function streamFillNotebook(missionId, agentId, plainText) {
39
+ if (!plainText) return;
40
+ const prior = notebookFillTimers.get(missionId);
41
+ if (prior) clearInterval(prior);
42
+ const total = plainText.length;
43
+ const TARGET_MS = 4e3;
44
+ const TICK_MS = 80;
45
+ const ticks = Math.max(20, Math.ceil(TARGET_MS / TICK_MS));
46
+ const chunkChars = Math.max(20, Math.ceil(total / ticks));
47
+ let offset = 0;
48
+ const id = setInterval(() => {
49
+ offset = Math.min(total, offset + chunkChars);
50
+ const slice = plainText.slice(0, offset);
51
+ try {
52
+ updateArtifact(missionId, agentId, slice);
53
+ } catch (err) {
54
+ logger.debug(COMPONENT, `streamFillNotebook stopped on ${missionId}: ${err.message}`);
55
+ clearInterval(id);
56
+ notebookFillTimers.delete(missionId);
57
+ return;
58
+ }
59
+ if (offset >= total) {
60
+ clearInterval(id);
61
+ notebookFillTimers.delete(missionId);
62
+ }
63
+ }, TICK_MS);
64
+ notebookFillTimers.set(missionId, id);
65
+ }
66
+ function maybeFillNotebookFromFileArtifact(missionId, agentId, artifacts) {
67
+ const file = artifacts.find(
68
+ (a) => a.type === "file" && typeof a.ref === "string" && a.ref.length > 0
69
+ );
70
+ if (!file) return;
71
+ try {
72
+ if (!existsSync(file.ref)) return;
73
+ const stat = statSync(file.ref);
74
+ if (stat.size > 1024 * 1024) return;
75
+ const raw = readFileSync(file.ref, "utf-8");
76
+ const isHtml = /\.html?$/i.test(file.ref) || /<html[\s>]/i.test(raw);
77
+ const plain = isHtml ? htmlToNotebookText(raw) : raw;
78
+ if (plain.trim().length === 0) return;
79
+ streamFillNotebook(missionId, agentId, plain);
80
+ } catch (err) {
81
+ logger.debug(COMPONENT, `maybeFillNotebookFromFileArtifact skipped: ${err.message}`);
82
+ }
83
+ }
21
84
  const COMPONENT = "MissionLifecycle";
22
85
  const lifecycles = /* @__PURE__ */ new Map();
23
86
  let globalBusUnsub = null;
@@ -77,6 +140,13 @@ function ensureGlobalBusBridge() {
77
140
  if (sources.length >= 12) break;
78
141
  }
79
142
  }
143
+ if (sources.length > 0) {
144
+ maybeFillNotebookFromFileArtifact(
145
+ mission.id,
146
+ agentId,
147
+ sources.map((s) => ({ type: s.type, ref: s.ref }))
148
+ );
149
+ }
80
150
  const reasoning = looksLikeInternalErrorTrace(rawReasoning) ? null : rawReasoning;
81
151
  const toolsUsed = Array.isArray(data.toolsUsed) ? data.toolsUsed.slice(0, 6) : [];
82
152
  const actions = toolsUsed.map((t) => ({ name: "used", detail: t }));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/agent/missionLifecycle.ts"],"sourcesContent":["/**\n * TITAN — Mission lifecycle adapter (v6.1.0)\n *\n * Connects a freshly-created Mission Room to the existing goal driver\n * + Command Post pipeline. When a mission is created:\n *\n * 1. Create a Goal (in the goals subsystem) with title = goal text,\n * tagged with the mission id and play id so the bridge below can\n * filter events back to the right room.\n * 2. Subscribe to the agent message bus so each specialist's tool\n * calls / answers / progress messages get rewritten as a chat\n * message in the mission thread.\n * 3. Subscribe to the Command Post approval queue so blocking\n * questions filed against this mission's goal show up as inline\n * question messages in the chat.\n *\n * The bridge is one-way (driver → mission room). User actions (replies\n * to questions, status toggles) go through the mission router and\n * propagate to the goal driver / Command Post via existing APIs.\n *\n * Idempotent: subscribing a second mission doesn't double-fire. We\n * track per-mission unsubscribe functions in `lifecycles`.\n */\nimport logger from '../utils/logger.js';\nimport { createGoal } from './goals.js';\nimport {\n postAgentMessage,\n postSystemMessage,\n setMemberState,\n appendMemberActivity,\n setStatus,\n raiseQuestion,\n recordCost,\n ensureMember,\n getMission,\n getMissionByGoalId,\n listMissions,\n setLinkedGoal,\n setLinkedIssue,\n type MissionRoom,\n type MissionStatus,\n} from './missionRoom.js';\nimport { onAgentEvent, type AgentEvent } from './agentEvents.js';\nimport { createIssue, updateIssue, addIssueComment } from './commandPost.js';\n\nconst COMPONENT = 'MissionLifecycle';\n\n// Per-mission cleanup handlers. When a mission completes, we tear down\n// these subscriptions so the bus doesn't leak listeners.\nconst lifecycles = new Map<string, Array<() => void>>();\n\n/**\n * v6.1.0-alpha.1 — a SINGLE global subscription to the shared\n * agentEvents bus. Every mission piggybacks off this; we filter by\n * goalId at dispatch time. This is much cheaper than N per-mission\n * subscriptions (which is what alpha.0's broken bridge attempted) and\n * survives the case where the goal driver routes to a specialist that\n * isn't on the predicted Plays team — we add them dynamically.\n */\nlet globalBusUnsub: (() => void) | null = null;\nfunction ensureGlobalBusBridge(): void {\n if (globalBusUnsub) return;\n globalBusUnsub = onAgentEvent((ev: AgentEvent) => {\n // The goalDriver-emitted events carry data.goalId; events from\n // ad-hoc spawns (CLI, channels) don't, and we silently ignore\n // those — they aren't part of a mission.\n const data = ev.data ?? {};\n const goalId = typeof data.goalId === 'string' ? data.goalId : undefined;\n if (!goalId) return;\n const mission = getMissionByGoalId(goalId);\n if (!mission) return;\n const agentId = (ev.agentId ?? ev.agentName ?? '').toLowerCase();\n if (!agentId) return;\n try {\n switch (ev.type) {\n case 'agent_spawn': {\n ensureMember(mission.id, agentId);\n const subtaskTitle = typeof data.subtaskTitle === 'string' ? data.subtaskTitle : 'something';\n setMemberState(mission.id, agentId, 'working', shortenActivity(subtaskTitle));\n break;\n }\n case 'tool_call': {\n const name = typeof data.name === 'string' ? data.name : 'tool';\n ensureMember(mission.id, agentId);\n setMemberState(mission.id, agentId, 'working', shortenActivity(`running ${name}`));\n // v6.1.0-alpha.31 — also push a live activity sticky\n // to the desk for research-y tool calls. Tony's\n // direct ask: \"I liked the way we had it before\n // when the Agents put sticky notes on the desk\n // when working, with their research data so I can\n // see what they are doing.\" The activityLog\n // accumulates here per-agent and the canvas reads\n // it into draggable sticky notes.\n try {\n const args = (typeof data.args === 'object' && data.args !== null)\n ? (data.args as Record<string, unknown>)\n : {};\n const sticky = buildActivitySticky(name, args);\n if (sticky) {\n appendMemberActivity(mission.id, agentId, sticky);\n }\n } catch (err) {\n logger.debug(COMPONENT, `Activity sticky build threw: ${(err as Error).message}`);\n }\n break;\n }\n case 'tool_end': {\n // Don't transition state — the next tool_call or agent_done\n // will overwrite. Just no-op (avoids flicker).\n break;\n }\n case 'agent_done': {\n ensureMember(mission.id, agentId);\n const rawReasoning = typeof data.reasoning === 'string' && data.reasoning.trim().length > 0\n ? data.reasoning.trim()\n : null;\n // v6.1.0-alpha.12 — pluck the specialist's concrete\n // artifacts (URLs visited, files written, facts\n // memorized) so the chat can render them as clickable\n // sources. Pre-alpha.12 these were silently discarded —\n // a \"Successfully researched\" reasoning summary made it\n // to chat but the actual URLs/files behind it did not,\n // so the user couldn't follow up on anything.\n const sources: Array<{ type: 'url' | 'file' | 'fact' | 'report'; ref: string; description?: string }> = [];\n if (Array.isArray(data.artifacts)) {\n for (const raw of data.artifacts) {\n if (!raw || typeof raw !== 'object') continue; // skip null/undefined/primitive\n const a = raw as Record<string, unknown>;\n const t = typeof a.type === 'string' ? a.type : '';\n const ref = typeof a.ref === 'string' ? a.ref : '';\n if (!ref) continue;\n if (t !== 'url' && t !== 'file' && t !== 'fact' && t !== 'report') continue;\n sources.push({\n type: t as 'url' | 'file' | 'fact' | 'report',\n ref,\n description: typeof a.description === 'string' ? a.description : undefined,\n });\n if (sources.length >= 12) break;\n }\n }\n // v6.1.0-alpha.7 — scrub internal-error stack traces.\n // When a spawn fails, the `reasoning` field can be the\n // raw error chain: \"Parser could not extract JSON...\n // Sub-agent error: All providers failed: HTTP 429\n // <html>...\" That's pure jargon for users. Detect it\n // and replace with the friendly fallback. The original\n // text is preserved on `meta.failureDetail` so power\n // users can still see it via click-to-expand.\n const reasoning = looksLikeInternalErrorTrace(rawReasoning)\n ? null\n : rawReasoning;\n const toolsUsed = Array.isArray(data.toolsUsed)\n ? (data.toolsUsed as string[]).slice(0, 6)\n : [];\n const actions = toolsUsed.map(t => ({ name: 'used', detail: t }));\n const status = typeof data.status === 'string' ? data.status : 'done';\n // v6.1.0-alpha.4 — pass the rich context as the message\n // `meta` field. The chat UI hides it by default and\n // surfaces it when the user clicks the bubble — keeps\n // the thread readable while making \"what actually\n // happened here\" one tap away.\n //\n // v6.1.0-alpha.7 — if the raw reasoning was a scrubbed\n // internal-error trace, stash it on `meta.failureDetail`\n // so power users can still see it via click-to-expand.\n // The chat surface stays clean.\n const meta: Record<string, unknown> = {\n subtaskTitle: typeof data.subtaskTitle === 'string' ? data.subtaskTitle : undefined,\n status,\n durationMs: typeof data.durationMs === 'number' ? data.durationMs : undefined,\n tokensUsed: typeof data.tokensUsed === 'number' ? data.tokensUsed : undefined,\n costUsd: typeof data.costUsd === 'number' ? data.costUsd : undefined,\n model: typeof data.model === 'string' ? data.model : undefined,\n };\n if (rawReasoning && rawReasoning !== reasoning) {\n meta.failureDetail = rawReasoning;\n }\n // v6.1.0-alpha.1 — every agent_done emits SOMETHING into\n // the chat. The pre-fix path returned nothing when\n // `reasoning` was empty, which is common on cloud\n // specialists that return JSON-only responses (their\n // reasoning field is empty because the *artifact* is\n // the value). The chat shouldn't go silent.\n // v6.1.0-alpha.12 — also pass `sources` so URLs +\n // files the specialist worked with render clickably.\n const sourcesArg = sources.length > 0 ? sources : undefined;\n if (reasoning) {\n postAgentMessage(mission.id, agentId, reasoning, actions.length > 0 ? actions : undefined, meta, sourcesArg);\n } else if (status === 'failed') {\n postAgentMessage(\n mission.id,\n agentId,\n `I ran into trouble on this one and couldn't finish — handing back to the team.`,\n actions.length > 0 ? actions : undefined,\n meta,\n sourcesArg,\n );\n } else if (status === 'needs_info' || status === 'blocked') {\n postAgentMessage(\n mission.id,\n agentId,\n `I have a quick question before I can finish — see below.`,\n actions.length > 0 ? actions : undefined,\n meta,\n sourcesArg,\n );\n } else {\n // status === 'done' with empty reasoning. Common with\n // JSON-only specialists. Don't go silent — say something\n // honest about what they did.\n const summary = toolsUsed.length > 0\n ? `Done — used ${toolsUsed.slice(0, 3).join(', ')}.`\n : `Done.`;\n postAgentMessage(mission.id, agentId, summary, actions.length > 0 ? actions : undefined, meta, sourcesArg);\n }\n setMemberState(mission.id, agentId, 'idle', undefined);\n const tokens = typeof data.tokensUsed === 'number' ? data.tokensUsed : 0;\n const cost = typeof data.costUsd === 'number' ? data.costUsd : 0;\n if (tokens > 0 || cost > 0) {\n recordCost(mission.id, tokens, cost);\n }\n break;\n }\n }\n } catch (err) {\n logger.debug(COMPONENT, `Bridge dispatch threw for ${mission.id}/${ev.type}: ${(err as Error).message}`);\n }\n });\n logger.info(COMPONENT, 'Global agent-event bridge attached (goalId → mission room dispatch)');\n}\n\n/** The wire that the gateway / missions router calls when a mission is\n * created. Returns the linked goal id. */\nexport async function startMissionWork(mission: MissionRoom): Promise<string | null> {\n try {\n // Make sure the global event bridge is alive. Idempotent.\n ensureGlobalBusBridge();\n\n // Tag the goal with the mission + play id so message-bus event\n // bridges can filter \"which mission did this belong to.\"\n const goal = createGoal({\n title: mission.goal,\n description: `Mission ${mission.id}` + (mission.playId ? ` (play: ${mission.playId})` : ''),\n tags: [\n `mission:${mission.id}`,\n mission.playId ? `play:${mission.playId}` : 'play:generic',\n ],\n // v6.1.0-alpha.5 — Mission Chat goals are user-initiated. The\n // autonomous-creation rate limit (10/hour) + active-goals cap\n // exists to throttle runaway self-mod / self-repair loops, not\n // to throttle the user typing into the chat. Bypass it so\n // missions never silently fail with \"Couldn't start the team:\n // rate limited\" after the user's 11th request in an hour.\n force: true,\n });\n // v6.1.0-alpha.1 — link the goal id INSIDE startMissionWork so any\n // event the goal driver fires (even before the missions router gets\n // back to set it) can resolve the mission via getMissionByGoalId.\n // The router's redundant setLinkedGoal call is now a no-op safety\n // net rather than the source of truth.\n setLinkedGoal(mission.id, goal.id);\n\n // v6.1.0-alpha.10 — auto-create a Command Post issue for this\n // mission. The issue becomes the durable audit trail (every\n // significant lifecycle event mirrors as a comment) and the\n // Command Post Issues panel + Mission Chat / Canvas surface the\n // same record. Failure to create the issue should never block\n // the mission itself — it's audit, not behavior.\n let issueIdent: string | undefined;\n try {\n const issue = createIssue({\n title: mission.goal,\n description: `Mission ${mission.id}` + (mission.playId ? ` · play: ${mission.playId}` : '') +\n `\\n\\nLinked goal: ${goal.id}` +\n `\\n\\nTeam: ${mission.team.map(t => t.name).join(', ') || '(forming)'}`,\n priority: 'medium',\n createdByUser: 'mission-chat',\n goalId: goal.id,\n });\n setLinkedIssue(mission.id, issue.id);\n issueIdent = issue.identifier;\n updateIssue(issue.id, { status: 'in_progress' });\n addIssueComment(\n issue.id,\n `Mission opened with team: ${mission.team.map(t => t.name).join(', ') || '(forming)'}.`,\n { user: 'mission-chat' },\n );\n } catch (issueErr) {\n logger.warn(COMPONENT, `Issue link skipped for mission ${mission.id}: ${(issueErr as Error).message}`);\n }\n logger.info(\n COMPONENT,\n `Mission ${mission.id} linked to goal ${goal.id}` +\n (issueIdent ? ` and issue ${issueIdent}` : '') +\n ` (play=${mission.playId})`,\n );\n\n // Mark every Plays-predicted member as \"ready\" so the team strip\n // lights up immediately. The goal driver routes for real, and the\n // global bridge will narrow each member's `currentActivity` (and\n // ADD new members if the driver picks specialists Plays didn't\n // predict).\n for (const member of mission.team) {\n setMemberState(mission.id, member.agentId, 'idle', 'standing by');\n }\n\n // The approval bridge + goal-lifecycle bridge are both\n // per-mission — they filter by goalId, so we register them\n // scoped to the goal we just created.\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(mission.id, goal.id));\n cleanups.push(await wireGoalLifecycleBridge(mission.id, goal.id));\n lifecycles.set(mission.id, cleanups);\n\n return goal.id;\n } catch (err) {\n logger.error(COMPONENT, `Failed to start mission ${mission.id}: ${(err as Error).message}`);\n setStatus(mission.id, 'failed', `Couldn't start the team: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Called when the UI posts a user message into a mission. Two paths:\n *\n * 1. **Mission is live** (working/blocked/paused/forming) — broadcast\n * the user's note to every registered specialist mailbox so the\n * next agent loop picks it up. Same behavior as alpha.1+.\n *\n * 2. **Mission is terminal** (done/failed) — v6.1.0-alpha.13: reopen\n * the mission with this message as a new direction. Creates a\n * fresh Goal, re-wires the lifecycle bridges, flips status back\n * to 'working', posts a \"Picking this back up…\" system note,\n * mirrors to the linked Command Post issue. The DriverScheduler\n * picks up the new active goal on its next tick (~10s).\n */\nexport async function handleUserMessage(missionId: string, content: string): Promise<void> {\n const room = getMission(missionId);\n if (!room) return;\n const isTerminal = room.status === 'done' || room.status === 'failed';\n if (isTerminal) {\n await reopenMissionWithFollowUp(missionId, content);\n return;\n }\n // For v1 we use the existing messageBus. We don't know which\n // specialist is \"current\" — broadcast to every registered member's\n // mailbox so whichever one is in-flight picks the note up at the\n // start of its next round. Mailbox names match the specialist ids.\n //\n // v6.1.0-alpha.1 — only dispatch to mailboxes that are actually\n // REGISTERED right now. Specialists register their mailbox at spawn\n // time and unregister when done. If no team member is mid-spawn,\n // there's no mailbox to deliver to — that's fine, the user's note\n // is already in the chat thread and the goal driver will see it\n // when it next decides which subtask to schedule. Sending to an\n // unregistered mailbox would emit a noisy warn for every team\n // member every time.\n try {\n const { sendMessage, hasMailbox } = await import('./messageBus.js');\n const recipients = room.team.map(t => t.agentId);\n let delivered = 0;\n for (const to of recipients) {\n if (!hasMailbox(to)) continue; // skip non-registered to avoid noisy warns\n const result = sendMessage('user', to, content, { priority: 'urgent' });\n if (result) delivered++;\n }\n if (delivered === 0) {\n logger.debug(COMPONENT, `User message recorded in chat; no specialist mailboxes were live to receive it (team will pick up at next subtask).`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `messageBus dispatch skipped: ${(err as Error).message}`);\n }\n}\n\n/**\n * v6.1.0-alpha.13 — reopen a terminal mission with a follow-up direction.\n *\n * Creates a brand-new Goal with the user's new content as the title.\n * The mission keeps its id + history; the chat thread continues\n * organically; the team strip keeps showing previous helpers (and\n * picks up new ones automatically as the new goal spawns specialists\n * via the dynamic-team logic from alpha.1).\n *\n * Re-wires the per-mission bridges (approval bridge, goal-lifecycle\n * bridge) since the previous bridges were torn down when the mission\n * first reached its terminal state. The agent-event bridge is global\n * and looks up missions by goalId at dispatch time — it picks up the\n * new goal automatically without re-registration.\n */\nasync function reopenMissionWithFollowUp(missionId: string, newContent: string): Promise<void> {\n const room = getMission(missionId);\n if (!room) return;\n try {\n ensureGlobalBusBridge();\n const newGoal = createGoal({\n title: newContent,\n description:\n `Follow-up on mission ${missionId} (continued from previous goal ${room.goalId ?? '?'}).\\n\\n` +\n `Original mission: \"${room.goal}\"\\n\\n` +\n `User wants: ${newContent}`,\n tags: [\n `mission:${missionId}`,\n room.playId ? `play:${room.playId}` : 'play:generic',\n 'mission-followup',\n ],\n // Same rationale as startMissionWork — user-initiated, not\n // autonomous; bypass the 10-goals-per-hour rate limit.\n force: true,\n });\n setLinkedGoal(missionId, newGoal.id);\n // Tear down any stale bridges (in case completion teardown\n // didn't run yet) and wire fresh ones for the new goal.\n teardownMissionWork(missionId);\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(missionId, newGoal.id));\n cleanups.push(await wireGoalLifecycleBridge(missionId, newGoal.id));\n lifecycles.set(missionId, cleanups);\n\n // Wake the team back up in the team strip.\n for (const member of room.team) {\n setMemberState(missionId, member.agentId, 'idle', 'getting back on it');\n }\n\n // Chat surface: explain what just happened.\n postSystemMessage(\n missionId,\n 'Picking this back up — the team is taking another swing with your new direction.',\n 'mission_resumed',\n );\n setStatus(missionId, 'working');\n\n // Mirror to the linked Command Post issue.\n if (room.issueId) {\n try {\n updateIssue(room.issueId, { status: 'in_progress' });\n addIssueComment(\n room.issueId,\n `User picked the mission back up: \"${newContent.slice(0, 200)}${newContent.length > 200 ? '…' : ''}\"`,\n { user: 'mission-chat' },\n );\n } catch { /* mirror failure never blocks the reopen */ }\n }\n\n logger.info(\n COMPONENT,\n `Mission ${missionId} reopened — new goal ${newGoal.id} linked (was: ${room.goalId ?? 'unset'})`,\n );\n } catch (err) {\n logger.error(COMPONENT, `Reopen failed for mission ${missionId}: ${(err as Error).message}`);\n // Surface the failure to the user instead of going silent.\n try {\n postSystemMessage(\n missionId,\n `Couldn't pick this back up: ${(err as Error).message}. Try starting a fresh mission instead.`,\n 'mission_resume_failed',\n );\n } catch { /* ok */ }\n }\n}\n\n/** Called when the UI toggles status (pause / resume). */\nexport async function handleStatusChange(missionId: string, status: MissionStatus): Promise<void> {\n if (status !== 'paused' && status !== 'working') return;\n try {\n // Look up the linked goal and toggle its driver state via the\n // existing user-controls surface. We import dynamically so the\n // lifecycle module doesn't have a top-level goal-driver\n // dependency (matches the rest of the file's pattern).\n const room = (await import('./missionRoom.js')).getMission(missionId);\n if (!room?.goalId) return;\n const driver = await import('./goalDriver.js');\n if (status === 'paused' && typeof driver.pauseDriver === 'function') {\n driver.pauseDriver(room.goalId);\n } else if (status === 'working' && typeof driver.resumeDriverControl === 'function') {\n driver.resumeDriverControl(room.goalId);\n }\n } catch (err) {\n logger.debug(COMPONENT, `Couldn't toggle driver state for ${missionId}: ${(err as Error).message}`);\n }\n}\n\n/** Tear down a mission's bridges. Called when the goal completes,\n * fails, or the mission is deleted. */\nexport function teardownMissionWork(missionId: string): void {\n const cleanups = lifecycles.get(missionId);\n if (!cleanups) return;\n for (const fn of cleanups) {\n try { fn(); }\n catch (err) { logger.debug(COMPONENT, `Cleanup threw: ${(err as Error).message}`); }\n }\n lifecycles.delete(missionId);\n}\n\n/**\n * v6.1.0-alpha.25 — re-attach event/approval/goal-lifecycle bridges\n * for missions that were already in-flight when the service was\n * restarted.\n *\n * **The bug this fixes (caught 2026-05-13):**\n *\n * The lifecycle bridges (`ensureGlobalBusBridge`, `wireApprovalBridge`,\n * `wireGoalLifecycleBridge`) live as in-memory subscriptions on the\n * agent-event bus + Command Post approval store + titanEvents bus.\n * They're attached only from `startMissionWork()` (new missions) and\n * `reopenMissionWithFollowUp()` (user reopens a stopped mission).\n *\n * On a service restart, those module-level subscriptions are gone.\n * Missions on disk in `status: working | forming | blocked` whose\n * goal driver keeps running (driver state is persisted; spawns\n * continue from where they left off) silently emit events into the\n * void — no listener catches them. From the user's POV the desk\n * shows \"Writer is working\" but no agent_done message ever lands,\n * the artifact paper stays blank, the cost stays $0.00.\n *\n * Hit Tony with the MLK essay mission on 2026-05-13 21:17 PT: the\n * spawn started seconds after the alpha.24 deploy restarted the\n * service. Writer talked to Ollama for 25s, returned needs_info,\n * and the lifecycle dropped the event because no bridge was\n * attached for that mission's goalId.\n *\n * **The fix:**\n *\n * Call this on server bootstrap. It scans every persisted mission,\n * and for each one still in a non-terminal status with a linked\n * `goalId`:\n * 1. Calls `ensureGlobalBusBridge()` (idempotent — only attaches\n * the global agent-event listener once).\n * 2. Wires the per-mission approval + goal-lifecycle bridges and\n * tracks the cleanups in the lifecycles map so\n * `teardownMissionWork()` still works.\n *\n * Returns the count of missions re-attached for logging.\n */\nexport async function reattachMissionBridgesOnStartup(): Promise<number> {\n let count = 0;\n let scanned = 0;\n try {\n const missions = listMissions();\n for (const m of missions) {\n scanned += 1;\n // Skip missions that have reached terminal state — their\n // driver is dormant, no events expected.\n if (m.status === 'done' || m.status === 'failed') continue;\n // Skip missions never linked to a goal.\n if (!m.goalId) continue;\n // Skip if we've already wired this mission this process\n // (defensive — shouldn't happen on startup but harmless).\n if (lifecycles.has(m.id)) continue;\n try {\n ensureGlobalBusBridge();\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(m.id, m.goalId));\n cleanups.push(await wireGoalLifecycleBridge(m.id, m.goalId));\n lifecycles.set(m.id, cleanups);\n count += 1;\n logger.info(COMPONENT, `Re-attached lifecycle bridges for mission ${m.id} (goal ${m.goalId}, status ${m.status})`);\n } catch (err) {\n logger.warn(COMPONENT, `Re-attach failed for mission ${m.id}: ${(err as Error).message}`);\n }\n }\n if (count > 0) {\n logger.info(COMPONENT, `Startup re-attach complete — ${count}/${scanned} mission(s) wired back to the bus`);\n } else if (scanned > 0) {\n logger.debug(COMPONENT, `Startup re-attach — ${scanned} mission(s) scanned, none needed re-wiring`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Startup re-attach scan threw: ${(err as Error).message}`);\n }\n return count;\n}\n\n// ── Issue mirror ───────────────────────────────────────────────────\n//\n// v6.1.0-alpha.10 — mirror big mission lifecycle events to the linked\n// Command Post issue. Keeps the issue useful as the durable audit\n// trail without spamming it with every chat message (chat thread\n// already serves that purpose). What we mirror:\n//\n// - Mission start (in startMissionWork): \"Mission opened with team: …\"\n// - Question raised: `Sage asked: \"<question>\"`\n// - Question answered: `User answered: \"<answer>\"`\n// - Mission complete / failed: the same one-liner posted to chat,\n// plus an issue status update (done / cancelled).\n//\n// Every individual agent_message is NOT mirrored — too noisy.\n// Subtask-grain events stay in the chat thread.\n\ninterface IssueMirror {\n status?: 'in_progress' | 'in_review' | 'done' | 'blocked' | 'cancelled';\n comment?: string;\n}\n\nfunction mirrorToIssue(missionId: string, mirror: IssueMirror): void {\n try {\n const room = getMission(missionId);\n const issueId = room?.issueId;\n if (!issueId) return;\n if (mirror.comment) {\n addIssueComment(issueId, mirror.comment, { user: 'mission-chat' });\n }\n if (mirror.status) {\n updateIssue(issueId, { status: mirror.status });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Issue mirror skipped: ${(err as Error).message}`);\n }\n}\n\n// ── Bridges ────────────────────────────────────────────────────────\n\n/**\n * Subscribe to goal-lifecycle events on the titanEvents bus and\n * translate goal completion / failure into:\n * 1. a system message in the mission thread (\"Mission complete.\" or\n * \"Couldn't finish this one — here's what we have so far.\")\n * 2. a status transition (working → done | failed)\n *\n * Without this, the mission room sat in 'working' indefinitely after\n * the underlying goal completed — the chat showed an idle team strip\n * and no closure message, which looked like the mission was stuck even\n * when it had successfully finished.\n */\nasync function wireGoalLifecycleBridge(missionId: string, goalId: string): Promise<() => void> {\n const { titanEvents } = await import('./daemon.js');\n const completedHandler = (data: unknown) => {\n try {\n const d = (data ?? {}) as Record<string, unknown>;\n if (d.goalId !== goalId) return;\n const durationMs = typeof d.durationMs === 'number' ? d.durationMs : 0;\n const seconds = Math.max(1, Math.round(durationMs / 1000));\n const specialistsUsed = Array.isArray(d.specialistsUsed) ? (d.specialistsUsed as string[]) : [];\n const namesList = specialistsUsed.length > 0\n ? ` with help from ${specialistsUsed.slice(0, 5).join(', ')}`\n : '';\n const completionLine = `Mission complete in ${seconds}s${namesList}.`;\n postSystemMessage(missionId, completionLine, 'mission_complete');\n setStatus(missionId, 'done');\n // v6.1.0-alpha.10 — mirror to the linked Command Post issue.\n mirrorToIssue(missionId, { status: 'done', comment: completionLine });\n // Mission reached a terminal state — tear down its bridges so\n // we don't leak event-bus listeners. Defer one tick so any\n // remaining synchronous work inside this handler chain runs\n // before the bridges go away.\n setImmediate(() => teardownMissionWork(missionId));\n } catch (err) {\n logger.debug(COMPONENT, `goal:completed handler threw: ${(err as Error).message}`);\n }\n };\n const failedHandler = (data: unknown) => {\n try {\n const d = (data ?? {}) as Record<string, unknown>;\n if (d.goalId !== goalId) return;\n const retries = typeof d.retries === 'number' ? d.retries : 0;\n const failureLine = retries > 0\n ? `Couldn't finish this one after ${retries} retry attempt(s). Take a look at what we did manage and tell me what to try next.`\n : `Couldn't finish this one. Take a look at what we did manage and tell me what to try next.`;\n postSystemMessage(missionId, failureLine, 'mission_failed');\n setStatus(missionId, 'failed');\n mirrorToIssue(missionId, { status: 'cancelled', comment: failureLine });\n setImmediate(() => teardownMissionWork(missionId));\n } catch (err) {\n logger.debug(COMPONENT, `goal:failed handler threw: ${(err as Error).message}`);\n }\n };\n titanEvents.on('goal:completed', completedHandler);\n titanEvents.on('goal:failed', failedHandler);\n return () => {\n titanEvents.off('goal:completed', completedHandler);\n titanEvents.off('goal:failed', failedHandler);\n };\n}\n\n/** Subscribe to the Command Post approval queue and translate any\n * approval filed against this mission's goal into an inline question\n * message. v6.1.0-alpha.1 — uses the real titanEvents bus\n * ('commandpost:approval:created' event added in commandPost.ts).\n * Returns an unsubscribe. */\nasync function wireApprovalBridge(missionId: string, goalId: string): Promise<() => void> {\n const { titanEvents } = await import('./daemon.js');\n const handler = (approval: CPApprovalLike) => {\n try {\n // Filter: only approvals tied to this mission's goal.\n const payload = (approval.payload ?? {}) as Record<string, unknown>;\n if (payload.goalId !== goalId) return;\n const agentId = String(payload.specialist ?? payload.subtaskKind ?? approval.requestedBy ?? 'sage').toLowerCase();\n const rawContent = String(\n payload.question\n ?? payload.allQuestions\n ?? approval.title\n ?? approval.reason\n ?? `${approval.type} approval needed`\n );\n // Strip the goalDriver boilerplate prefix (alpha.5).\n const stripped = stripDriverBoilerplate(rawContent);\n // v6.1.0-alpha.29 — when stripping leaves a generic fallback\n // (or anything ≤ that fallback's information density), enrich\n // the question with payload context so the user actually knows\n // what's happening. Pre-alpha.29 the user saw a bare \"I need\n // more direction to keep going. What should I focus on?\" with\n // no clue that Writer was stuck on the MLK essay because the\n // structured-output reformat failed.\n const content = enrichBlockedQuestion(stripped, payload);\n const quickReplies = Array.isArray(payload.quickReplies)\n ? (payload.quickReplies as string[]).slice(0, 4)\n : defaultQuickReplies(approval);\n raiseQuestion({\n missionId,\n agentId,\n content,\n approvalId: approval.id,\n quickReplies,\n });\n // v6.1.0-alpha.10 — mirror to issue audit trail.\n mirrorToIssue(missionId, {\n comment: `${agentId} asked: \"${content.slice(0, 200)}${content.length > 200 ? '…' : ''}\"`,\n status: 'blocked',\n });\n } catch (err) {\n logger.debug(COMPONENT, `Approval bridge handler threw: ${(err as Error).message}`);\n }\n };\n titanEvents.on('commandpost:approval:created', handler);\n return () => titanEvents.off('commandpost:approval:created', handler);\n}\n\n/**\n * v6.1.0-alpha.29 — when stripDriverBoilerplate leaves us with the\n * generic fallback (\"I need more direction to keep going. What should\n * I focus on?\"), the user has zero signal about WHY the agent is\n * stuck or what they were trying to do. The approval payload carries\n * the missing context (subtaskTitle, specialist, lastError, attempts);\n * this composer weaves it into a friendlier question.\n *\n * Decision matrix:\n * 1. If `content` already reads like a specific question from the\n * specialist (>= 30 chars AND not equal to the generic fallback),\n * leave it alone — the specialist gave us something real.\n * 2. Otherwise build a contextual fallback from `payload`:\n * \"<Specialist> was working on <subtaskTitle> (attempt N).\n * Last hurdle: <lastError, scrubbed>.\n * What should they focus on?\"\n * Empty fields are dropped gracefully so a missing `lastError`\n * doesn't produce \"Last hurdle: undefined.\"\n * 3. Internal-error traces in `lastError` (Parser could not extract\n * JSON, HTTP 429, <!doctype html>, etc.) get scrubbed via\n * `looksLikeInternalErrorTrace` — they're not useful to the user\n * and reading them as \"what went wrong\" would be misleading.\n */\nfunction enrichBlockedQuestion(stripped: string, payload: Record<string, unknown>): string {\n const GENERIC = 'I need more direction to keep going. What should I focus on?';\n const isGeneric = stripped === GENERIC || stripped.trim().length < 30;\n if (!isGeneric) return stripped;\n\n const specialist = typeof payload.specialist === 'string' ? capitalize(payload.specialist) : 'Your helper';\n const subtaskTitle = typeof payload.subtaskTitle === 'string' ? payload.subtaskTitle : null;\n const attempts = typeof payload.attempts === 'number' ? payload.attempts : null;\n const rawLastError = typeof payload.lastError === 'string' ? payload.lastError : null;\n // Scrub internal traces — they're noise.\n const lastError = rawLastError && !looksLikeInternalErrorTrace(rawLastError)\n ? rawLastError.slice(0, 200).trim()\n : null;\n\n const parts: string[] = [];\n if (subtaskTitle) {\n parts.push(`${specialist} is working on \"${shortenLine(subtaskTitle, 80)}\"`);\n } else {\n parts.push(`${specialist} is working on your mission`);\n }\n if (attempts && attempts > 1) {\n parts[parts.length - 1] += ` (attempt ${attempts})`;\n }\n parts[parts.length - 1] += ' but got stuck.';\n\n if (lastError) {\n parts.push(`Last hurdle: ${lastError}${rawLastError && rawLastError.length > 200 ? '…' : ''}`);\n } else if (rawLastError) {\n // We scrubbed it as an internal trace — give the user a heads-up\n // it exists without dumping the trace itself.\n parts.push(`There was a technical hiccup the team couldn't recover from on their own.`);\n }\n\n parts.push(`What should they focus on? Pick a reply below — or type your own.`);\n return parts.join(' ');\n}\n\nfunction capitalize(s: string): string {\n if (!s) return s;\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nfunction shortenLine(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max - 1).trimEnd() + '…';\n}\n\n/**\n * Strip the goalDriver's auto-generated boilerplate from blocked-approval\n * question text so it reads like a person's question. The driver wraps\n * the specialist's actual question (or a fallback) with internal context\n * (subtask title, attempt count, specialist id) that's noise to the user.\n *\n * Patterns matched:\n * - `Goal \"...\" is stuck on subtask \"...\" (attempt N, specialist: X).\\n\\n`\n * - `Goal \"...\" — subtask \"...\" failed after N attempt(s) with specialist X.\\n\\n`\n * - `Goal \"...\" — subtask \"...\" is blocked after N attempt(s) with specialist X. The specialist could not complete the task and needs guidance on how to proceed.`\n */\nexport function stripDriverBoilerplate(text: string): string {\n if (!text || typeof text !== 'string') return text ?? '';\n let cleaned = text;\n // Pattern 1: prefix → \"Goal '...' is stuck on subtask '...' (...).\\n\\n<real question>\"\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+is\\s+stuck\\s+on\\s+subtask\\s+\"[^\"]*\"\\s+\\([^)]*\\)\\.\\s*\\n+/i,\n '',\n );\n // Pattern 2: prefix → \"Goal '...' — subtask '...' failed after N attempt(s) with specialist X.\\n\\nError: ...\"\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+—\\s+subtask\\s+\"[^\"]*\"\\s+failed\\s+after\\s+\\d+\\s+attempt\\(s\\)\\s+with\\s+specialist\\s+\\S+\\.\\s*\\n+/i,\n '',\n );\n // Pattern 3: fallback (no real question, just the driver's placeholder)\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+—\\s+subtask\\s+\"[^\"]*\"\\s+is\\s+blocked\\s+after\\s+\\d+\\s+attempt\\(s\\)\\s+with\\s+specialist\\s+\\S+\\.\\s*The\\s+specialist\\s+could\\s+not\\s+complete\\s+the\\s+task\\s+and\\s+needs\\s+guidance\\s+on\\s+how\\s+to\\s+proceed\\.?\\s*/i,\n '',\n );\n cleaned = cleaned.trim();\n // If we stripped everything, fall back to a friendly default — the\n // user shouldn't see an empty question.\n if (cleaned.length === 0) {\n return 'I need more direction to keep going. What should I focus on?';\n }\n return cleaned;\n}\n\n/**\n * Detect internal error-chain text that should NOT be shown to the user\n * as the specialist's \"reasoning.\" Examples seen in the wild post-alpha.5:\n *\n * `Parser could not extract JSON from specialist response. Raw (200 chars):\n * Sub-agent error: All providers failed: Provider ollama/glm-5:cloud\n * failed: [HTTP 429] Ollama error (429): <!doctype html>...`\n *\n * The bridge scrubs these strings and falls through to the friendly\n * fallback (\"I ran into trouble on this one and couldn't finish — handing\n * back to the team.\") rather than dumping a stack trace into the chat.\n * The raw text is preserved on the message's `meta.failureDetail` so it\n * stays available via the click-to-expand details panel.\n */\nexport function looksLikeInternalErrorTrace(text: string | null | undefined): boolean {\n if (!text || typeof text !== 'string') return false;\n const markers = [\n /Parser could not extract JSON/i,\n /Sub-agent error:/i,\n /All providers failed/i,\n /HTTP\\s+\\d{3}.*Ollama error/i,\n /Circuit breaker OPEN for/i,\n /<!doctype html>/i,\n /\\bToo Many Requests\\b/i,\n ];\n return markers.some(re => re.test(text));\n}\n\n/** Sensible default reply set for the common approval kinds. */\nfunction defaultQuickReplies(approval: CPApprovalLike): string[] {\n const payloadKind = (approval.payload as Record<string, unknown> | undefined)?.kind;\n if (payloadKind === 'driver_blocked') return ['Use your best judgment', 'Pause for me', 'Try a different angle'];\n if (payloadKind === 'self_repair') return ['Approve fix', 'Skip', 'Tell me more'];\n return ['Approve', 'Skip'];\n}\n\n// ── Types shared with the commandPost module ───────────────────────\n//\n// We don't import the full CPApproval shape because that would couple\n// the mission lifecycle to internal commandPost types that may reshape.\n// Duck-type only what we need.\n\ninterface CPApprovalLike {\n id: string;\n type?: string;\n title?: string;\n reason?: string;\n requestedBy?: string;\n payload?: Record<string, unknown>;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\n/**\n * Bound the activity string that flows into MissionMember.currentActivity.\n *\n * v6.1.0-alpha.14 hotfix — was capping at 80 chars, which truncated mid-word\n * on long user prompts (\"Scout I want you to research AI Agents and how they\n * help bus…\") and there was no way to read the full text from the chat\n * surface. The 80-char cap was a holdover from when this was rendered in a\n * tiny tooltip; the actual typing pill in the chat has plenty of room.\n *\n * Bumped to 240 so even long goal prompts come through readable. The cap\n * exists only to bound the JSON payload size flowing through the bus and\n * SSE stream; the UI applies its own visual treatment (wrap + max-width +\n * title attribute) so even this length renders cleanly.\n */\n/**\n * v6.1.0-alpha.31 — turn a raw `tool_call` event into a friendly\n * sticky-note entry for the agent's activityLog. Returns null if the\n * tool isn't one we want to surface (silent background tools like\n * `memory_store`, `system_info`, etc. shouldn't clutter the desk).\n *\n * Each known research/work tool maps to:\n * - icon: a single emoji that reads at sticky-note scale\n * - activity: one-line summary (\"searched\", \"fetched\", \"wrote\")\n * - detail: the most informative argument value, truncated\n *\n * Unknown tools fall through to a generic \"used <name>\" — visible but\n * lightweight, so the user still sees activity even for tools we\n * haven't explicitly mapped.\n */\nfunction buildActivitySticky(\n name: string,\n args: Record<string, unknown>,\n): { icon: string; activity: string; detail?: string } | null {\n const pickStr = (...keys: string[]): string | undefined => {\n for (const k of keys) {\n const v = args[k];\n if (typeof v === 'string' && v.trim().length > 0) return v.trim();\n }\n return undefined;\n };\n const truncate = (s: string, max: number) => s.length <= max ? s : s.slice(0, max - 1).trimEnd() + '…';\n\n switch (name) {\n case 'web_search':\n case 'browser_search': {\n const query = pickStr('query', 'q', 'search');\n return { icon: '🔍', activity: 'searched the web', detail: query ? truncate(query, 100) : undefined };\n }\n case 'web_fetch':\n case 'browse_url':\n case 'web_read': {\n const url = pickStr('url', 'href');\n return { icon: '🌐', activity: 'read a page', detail: url ? truncate(url, 100) : undefined };\n }\n case 'web_browse_llm':\n case 'browser': {\n const target = pickStr('url', 'task');\n return { icon: '🌐', activity: 'browsed', detail: target ? truncate(target, 100) : undefined };\n }\n case 'write_file':\n case 'append_file':\n case 'edit_file':\n case 'apply_patch': {\n const path = pickStr('path', 'file', 'filename');\n return { icon: '✍️', activity: 'wrote a file', detail: path ? truncate(path, 100) : undefined };\n }\n case 'read_file': {\n const path = pickStr('path', 'file', 'filename');\n return { icon: '📖', activity: 'read a file', detail: path ? truncate(path, 100) : undefined };\n }\n case 'list_dir': {\n const path = pickStr('path', 'dir');\n return { icon: '📂', activity: 'listed a folder', detail: path ? truncate(path, 100) : undefined };\n }\n case 'shell':\n case 'exec':\n case 'code_exec':\n case 'execute_code': {\n const cmd = pickStr('command', 'cmd', 'code');\n return { icon: '⚙️', activity: 'ran a command', detail: cmd ? truncate(cmd, 100) : undefined };\n }\n case 'memory':\n case 'graph_remember':\n case 'memory_store': {\n const fact = pickStr('content', 'fact', 'value', 'key');\n return { icon: '💡', activity: 'memorized', detail: fact ? truncate(fact, 100) : undefined };\n }\n case 'graph_search':\n case 'graph_recall':\n case 'rag_search':\n case 'kb_search': {\n const q = pickStr('query', 'q');\n return { icon: '🧠', activity: 'recalled', detail: q ? truncate(q, 100) : undefined };\n }\n case 'screenshot':\n case 'browser_screenshot': {\n return { icon: '📷', activity: 'took a screenshot' };\n }\n case 'generate_image':\n case 'edit_image':\n case 'image_gen': {\n const prompt = pickStr('prompt', 'description');\n return { icon: '🎨', activity: 'drew an image', detail: prompt ? truncate(prompt, 100) : undefined };\n }\n case 'analyze_image':\n case 'vision': {\n return { icon: '👁️', activity: 'looked at an image' };\n }\n case 'github_repos':\n case 'github_issues':\n case 'github_prs':\n case 'github_commits':\n case 'github_files': {\n return { icon: '🐙', activity: name.replace('github_', 'checked GitHub '), detail: pickStr('repo', 'owner') };\n }\n // Silent / housekeeping tools — don't surface, keeps the desk tidy.\n case 'system_info':\n case 'current_model':\n case 'sessions_list':\n case 'sessions_history':\n case 'list_uploads':\n case 'list_active_widgets':\n case 'goal_list':\n case 'list_personas':\n case 'get_persona':\n case 'list_spaces':\n case 'interaction_log':\n case 'feedback_submit':\n return null;\n default:\n // Unknown tool — surface a generic sticky so the user still\n // sees the agent is doing something. Avoid noisy parameters.\n return { icon: '🛠️', activity: `used ${name}` };\n }\n}\n\nfunction shortenActivity(s: string | undefined): string | undefined {\n if (!s) return undefined;\n const trimmed = s.trim();\n if (trimmed.length <= 240) return trimmed;\n return trimmed.slice(0, 237).trimEnd() + '…';\n}\n"],"mappings":";AAuBA,OAAO,YAAY;AACnB,SAAS,kBAAkB;AAC3B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGG;AACP,SAAS,oBAAqC;AAC9C,SAAS,aAAa,aAAa,uBAAuB;AAE1D,MAAM,YAAY;AAIlB,MAAM,aAAa,oBAAI,IAA+B;AAUtD,IAAI,iBAAsC;AAC1C,SAAS,wBAA8B;AACnC,MAAI,eAAgB;AACpB,mBAAiB,aAAa,CAAC,OAAmB;AAI9C,UAAM,OAAO,GAAG,QAAQ,CAAC;AACzB,UAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAC/D,QAAI,CAAC,OAAQ;AACb,UAAM,UAAU,mBAAmB,MAAM;AACzC,QAAI,CAAC,QAAS;AACd,UAAM,WAAW,GAAG,WAAW,GAAG,aAAa,IAAI,YAAY;AAC/D,QAAI,CAAC,QAAS;AACd,QAAI;AACA,cAAQ,GAAG,MAAM;AAAA,QACb,KAAK,eAAe;AAChB,uBAAa,QAAQ,IAAI,OAAO;AAChC,gBAAM,eAAe,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe;AACjF,yBAAe,QAAQ,IAAI,SAAS,WAAW,gBAAgB,YAAY,CAAC;AAC5E;AAAA,QACJ;AAAA,QACA,KAAK,aAAa;AACd,gBAAM,OAAO,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AACzD,uBAAa,QAAQ,IAAI,OAAO;AAChC,yBAAe,QAAQ,IAAI,SAAS,WAAW,gBAAgB,WAAW,IAAI,EAAE,CAAC;AASjF,cAAI;AACA,kBAAM,OAAQ,OAAO,KAAK,SAAS,YAAY,KAAK,SAAS,OACtD,KAAK,OACN,CAAC;AACP,kBAAM,SAAS,oBAAoB,MAAM,IAAI;AAC7C,gBAAI,QAAQ;AACR,mCAAqB,QAAQ,IAAI,SAAS,MAAM;AAAA,YACpD;AAAA,UACJ,SAAS,KAAK;AACV,mBAAO,MAAM,WAAW,gCAAiC,IAAc,OAAO,EAAE;AAAA,UACpF;AACA;AAAA,QACJ;AAAA,QACA,KAAK,YAAY;AAGb;AAAA,QACJ;AAAA,QACA,KAAK,cAAc;AACf,uBAAa,QAAQ,IAAI,OAAO;AAChC,gBAAM,eAAe,OAAO,KAAK,cAAc,YAAY,KAAK,UAAU,KAAK,EAAE,SAAS,IACpF,KAAK,UAAU,KAAK,IACpB;AAQN,gBAAM,UAAkG,CAAC;AACzG,cAAI,MAAM,QAAQ,KAAK,SAAS,GAAG;AAC/B,uBAAW,OAAO,KAAK,WAAW;AAC9B,kBAAI,CAAC,OAAO,OAAO,QAAQ,SAAU;AACrC,oBAAM,IAAI;AACV,oBAAM,IAAI,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;AAChD,oBAAM,MAAM,OAAO,EAAE,QAAQ,WAAW,EAAE,MAAM;AAChD,kBAAI,CAAC,IAAK;AACV,kBAAI,MAAM,SAAS,MAAM,UAAU,MAAM,UAAU,MAAM,SAAU;AACnE,sBAAQ,KAAK;AAAA,gBACT,MAAM;AAAA,gBACN;AAAA,gBACA,aAAa,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc;AAAA,cACrE,CAAC;AACD,kBAAI,QAAQ,UAAU,GAAI;AAAA,YAC9B;AAAA,UACJ;AASA,gBAAM,YAAY,4BAA4B,YAAY,IACpD,OACA;AACN,gBAAM,YAAY,MAAM,QAAQ,KAAK,SAAS,IACvC,KAAK,UAAuB,MAAM,GAAG,CAAC,IACvC,CAAC;AACP,gBAAM,UAAU,UAAU,IAAI,QAAM,EAAE,MAAM,QAAQ,QAAQ,EAAE,EAAE;AAChE,gBAAM,SAAS,OAAO,KAAK,WAAW,WAAW,KAAK,SAAS;AAW/D,gBAAM,OAAgC;AAAA,YAClC,cAAc,OAAO,KAAK,iBAAiB,WAAW,KAAK,eAAe;AAAA,YAC1E;AAAA,YACA,YAAY,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAAA,YACpE,YAAY,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAAA,YACpE,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,YAC3D,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,UACzD;AACA,cAAI,gBAAgB,iBAAiB,WAAW;AAC5C,iBAAK,gBAAgB;AAAA,UACzB;AASA,gBAAM,aAAa,QAAQ,SAAS,IAAI,UAAU;AAClD,cAAI,WAAW;AACX,6BAAiB,QAAQ,IAAI,SAAS,WAAW,QAAQ,SAAS,IAAI,UAAU,QAAW,MAAM,UAAU;AAAA,UAC/G,WAAW,WAAW,UAAU;AAC5B;AAAA,cACI,QAAQ;AAAA,cACR;AAAA,cACA;AAAA,cACA,QAAQ,SAAS,IAAI,UAAU;AAAA,cAC/B;AAAA,cACA;AAAA,YACJ;AAAA,UACJ,WAAW,WAAW,gBAAgB,WAAW,WAAW;AACxD;AAAA,cACI,QAAQ;AAAA,cACR;AAAA,cACA;AAAA,cACA,QAAQ,SAAS,IAAI,UAAU;AAAA,cAC/B;AAAA,cACA;AAAA,YACJ;AAAA,UACJ,OAAO;AAIH,kBAAM,UAAU,UAAU,SAAS,IAC7B,oBAAe,UAAU,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,MAC/C;AACN,6BAAiB,QAAQ,IAAI,SAAS,SAAS,QAAQ,SAAS,IAAI,UAAU,QAAW,MAAM,UAAU;AAAA,UAC7G;AACA,yBAAe,QAAQ,IAAI,SAAS,QAAQ,MAAS;AACrD,gBAAM,SAAS,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AACvE,gBAAM,OAAO,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAC/D,cAAI,SAAS,KAAK,OAAO,GAAG;AACxB,uBAAW,QAAQ,IAAI,QAAQ,IAAI;AAAA,UACvC;AACA;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,6BAA6B,QAAQ,EAAE,IAAI,GAAG,IAAI,KAAM,IAAc,OAAO,EAAE;AAAA,IAC3G;AAAA,EACJ,CAAC;AACD,SAAO,KAAK,WAAW,0EAAqE;AAChG;AAIA,eAAsB,iBAAiB,SAA8C;AACjF,MAAI;AAEA,0BAAsB;AAItB,UAAM,OAAO,WAAW;AAAA,MACpB,OAAO,QAAQ;AAAA,MACf,aAAa,WAAW,QAAQ,EAAE,MAAM,QAAQ,SAAS,WAAW,QAAQ,MAAM,MAAM;AAAA,MACxF,MAAM;AAAA,QACF,WAAW,QAAQ,EAAE;AAAA,QACrB,QAAQ,SAAS,QAAQ,QAAQ,MAAM,KAAK;AAAA,MAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,OAAO;AAAA,IACX,CAAC;AAMD,kBAAc,QAAQ,IAAI,KAAK,EAAE;AAQjC,QAAI;AACJ,QAAI;AACA,YAAM,QAAQ,YAAY;AAAA,QACtB,OAAO,QAAQ;AAAA,QACf,aAAa,WAAW,QAAQ,EAAE,MAAM,QAAQ,SAAS,eAAY,QAAQ,MAAM,KAAK,MACpF;AAAA;AAAA,eAAoB,KAAK,EAAE;AAAA;AAAA,QACd,QAAQ,KAAK,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,WAAW;AAAA,QACxE,UAAU;AAAA,QACV,eAAe;AAAA,QACf,QAAQ,KAAK;AAAA,MACjB,CAAC;AACD,qBAAe,QAAQ,IAAI,MAAM,EAAE;AACnC,mBAAa,MAAM;AACnB,kBAAY,MAAM,IAAI,EAAE,QAAQ,cAAc,CAAC;AAC/C;AAAA,QACI,MAAM;AAAA,QACN,6BAA6B,QAAQ,KAAK,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,IAAI,KAAK,WAAW;AAAA,QACpF,EAAE,MAAM,eAAe;AAAA,MAC3B;AAAA,IACJ,SAAS,UAAU;AACf,aAAO,KAAK,WAAW,kCAAkC,QAAQ,EAAE,KAAM,SAAmB,OAAO,EAAE;AAAA,IACzG;AACA,WAAO;AAAA,MACH;AAAA,MACA,WAAW,QAAQ,EAAE,mBAAmB,KAAK,EAAE,MAC9C,aAAa,cAAc,UAAU,KAAK,MAC3C,UAAU,QAAQ,MAAM;AAAA,IAC5B;AAOA,eAAW,UAAU,QAAQ,MAAM;AAC/B,qBAAe,QAAQ,IAAI,OAAO,SAAS,QAAQ,aAAa;AAAA,IACpE;AAKA,UAAM,WAA8B,CAAC;AACrC,aAAS,KAAK,MAAM,mBAAmB,QAAQ,IAAI,KAAK,EAAE,CAAC;AAC3D,aAAS,KAAK,MAAM,wBAAwB,QAAQ,IAAI,KAAK,EAAE,CAAC;AAChE,eAAW,IAAI,QAAQ,IAAI,QAAQ;AAEnC,WAAO,KAAK;AAAA,EAChB,SAAS,KAAK;AACV,WAAO,MAAM,WAAW,2BAA2B,QAAQ,EAAE,KAAM,IAAc,OAAO,EAAE;AAC1F,cAAU,QAAQ,IAAI,UAAU,4BAA6B,IAAc,OAAO,EAAE;AACpF,WAAO;AAAA,EACX;AACJ;AAeA,eAAsB,kBAAkB,WAAmB,SAAgC;AACvF,QAAM,OAAO,WAAW,SAAS;AACjC,MAAI,CAAC,KAAM;AACX,QAAM,aAAa,KAAK,WAAW,UAAU,KAAK,WAAW;AAC7D,MAAI,YAAY;AACZ,UAAM,0BAA0B,WAAW,OAAO;AAClD;AAAA,EACJ;AAcA,MAAI;AACA,UAAM,EAAE,aAAa,WAAW,IAAI,MAAM,OAAO,iBAAiB;AAClE,UAAM,aAAa,KAAK,KAAK,IAAI,OAAK,EAAE,OAAO;AAC/C,QAAI,YAAY;AAChB,eAAW,MAAM,YAAY;AACzB,UAAI,CAAC,WAAW,EAAE,EAAG;AACrB,YAAM,SAAS,YAAY,QAAQ,IAAI,SAAS,EAAE,UAAU,SAAS,CAAC;AACtE,UAAI,OAAQ;AAAA,IAChB;AACA,QAAI,cAAc,GAAG;AACjB,aAAO,MAAM,WAAW,qHAAqH;AAAA,IACjJ;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,MAAM,WAAW,gCAAiC,IAAc,OAAO,EAAE;AAAA,EACpF;AACJ;AAiBA,eAAe,0BAA0B,WAAmB,YAAmC;AAC3F,QAAM,OAAO,WAAW,SAAS;AACjC,MAAI,CAAC,KAAM;AACX,MAAI;AACA,0BAAsB;AACtB,UAAM,UAAU,WAAW;AAAA,MACvB,OAAO;AAAA,MACP,aACI,wBAAwB,SAAS,kCAAkC,KAAK,UAAU,GAAG;AAAA;AAAA,qBAC/D,KAAK,IAAI;AAAA;AAAA,cAChB,UAAU;AAAA,MAC7B,MAAM;AAAA,QACF,WAAW,SAAS;AAAA,QACpB,KAAK,SAAS,QAAQ,KAAK,MAAM,KAAK;AAAA,QACtC;AAAA,MACJ;AAAA;AAAA;AAAA,MAGA,OAAO;AAAA,IACX,CAAC;AACD,kBAAc,WAAW,QAAQ,EAAE;AAGnC,wBAAoB,SAAS;AAC7B,UAAM,WAA8B,CAAC;AACrC,aAAS,KAAK,MAAM,mBAAmB,WAAW,QAAQ,EAAE,CAAC;AAC7D,aAAS,KAAK,MAAM,wBAAwB,WAAW,QAAQ,EAAE,CAAC;AAClE,eAAW,IAAI,WAAW,QAAQ;AAGlC,eAAW,UAAU,KAAK,MAAM;AAC5B,qBAAe,WAAW,OAAO,SAAS,QAAQ,oBAAoB;AAAA,IAC1E;AAGA;AAAA,MACI;AAAA,MACA;AAAA,MACA;AAAA,IACJ;AACA,cAAU,WAAW,SAAS;AAG9B,QAAI,KAAK,SAAS;AACd,UAAI;AACA,oBAAY,KAAK,SAAS,EAAE,QAAQ,cAAc,CAAC;AACnD;AAAA,UACI,KAAK;AAAA,UACL,qCAAqC,WAAW,MAAM,GAAG,GAAG,CAAC,GAAG,WAAW,SAAS,MAAM,WAAM,EAAE;AAAA,UAClG,EAAE,MAAM,eAAe;AAAA,QAC3B;AAAA,MACJ,QAAQ;AAAA,MAA+C;AAAA,IAC3D;AAEA,WAAO;AAAA,MACH;AAAA,MACA,WAAW,SAAS,6BAAwB,QAAQ,EAAE,iBAAiB,KAAK,UAAU,OAAO;AAAA,IACjG;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,MAAM,WAAW,6BAA6B,SAAS,KAAM,IAAc,OAAO,EAAE;AAE3F,QAAI;AACA;AAAA,QACI;AAAA,QACA,+BAAgC,IAAc,OAAO;AAAA,QACrD;AAAA,MACJ;AAAA,IACJ,QAAQ;AAAA,IAAW;AAAA,EACvB;AACJ;AAGA,eAAsB,mBAAmB,WAAmB,QAAsC;AAC9F,MAAI,WAAW,YAAY,WAAW,UAAW;AACjD,MAAI;AAKA,UAAM,QAAQ,MAAM,OAAO,kBAAkB,GAAG,WAAW,SAAS;AACpE,QAAI,CAAC,MAAM,OAAQ;AACnB,UAAM,SAAS,MAAM,OAAO,iBAAiB;AAC7C,QAAI,WAAW,YAAY,OAAO,OAAO,gBAAgB,YAAY;AACjE,aAAO,YAAY,KAAK,MAAM;AAAA,IAClC,WAAW,WAAW,aAAa,OAAO,OAAO,wBAAwB,YAAY;AACjF,aAAO,oBAAoB,KAAK,MAAM;AAAA,IAC1C;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,MAAM,WAAW,oCAAoC,SAAS,KAAM,IAAc,OAAO,EAAE;AAAA,EACtG;AACJ;AAIO,SAAS,oBAAoB,WAAyB;AACzD,QAAM,WAAW,WAAW,IAAI,SAAS;AACzC,MAAI,CAAC,SAAU;AACf,aAAW,MAAM,UAAU;AACvB,QAAI;AAAE,SAAG;AAAA,IAAG,SACL,KAAK;AAAE,aAAO,MAAM,WAAW,kBAAmB,IAAc,OAAO,EAAE;AAAA,IAAG;AAAA,EACvF;AACA,aAAW,OAAO,SAAS;AAC/B;AA0CA,eAAsB,kCAAmD;AACrE,MAAI,QAAQ;AACZ,MAAI,UAAU;AACd,MAAI;AACA,UAAM,WAAW,aAAa;AAC9B,eAAW,KAAK,UAAU;AACtB,iBAAW;AAGX,UAAI,EAAE,WAAW,UAAU,EAAE,WAAW,SAAU;AAElD,UAAI,CAAC,EAAE,OAAQ;AAGf,UAAI,WAAW,IAAI,EAAE,EAAE,EAAG;AAC1B,UAAI;AACA,8BAAsB;AACtB,cAAM,WAA8B,CAAC;AACrC,iBAAS,KAAK,MAAM,mBAAmB,EAAE,IAAI,EAAE,MAAM,CAAC;AACtD,iBAAS,KAAK,MAAM,wBAAwB,EAAE,IAAI,EAAE,MAAM,CAAC;AAC3D,mBAAW,IAAI,EAAE,IAAI,QAAQ;AAC7B,iBAAS;AACT,eAAO,KAAK,WAAW,6CAA6C,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,EAAE,MAAM,GAAG;AAAA,MACrH,SAAS,KAAK;AACV,eAAO,KAAK,WAAW,gCAAgC,EAAE,EAAE,KAAM,IAAc,OAAO,EAAE;AAAA,MAC5F;AAAA,IACJ;AACA,QAAI,QAAQ,GAAG;AACX,aAAO,KAAK,WAAW,qCAAgC,KAAK,IAAI,OAAO,mCAAmC;AAAA,IAC9G,WAAW,UAAU,GAAG;AACpB,aAAO,MAAM,WAAW,4BAAuB,OAAO,4CAA4C;AAAA,IACtG;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,KAAK,WAAW,iCAAkC,IAAc,OAAO,EAAE;AAAA,EACpF;AACA,SAAO;AACX;AAuBA,SAAS,cAAc,WAAmB,QAA2B;AACjE,MAAI;AACA,UAAM,OAAO,WAAW,SAAS;AACjC,UAAM,UAAU,MAAM;AACtB,QAAI,CAAC,QAAS;AACd,QAAI,OAAO,SAAS;AAChB,sBAAgB,SAAS,OAAO,SAAS,EAAE,MAAM,eAAe,CAAC;AAAA,IACrE;AACA,QAAI,OAAO,QAAQ;AACf,kBAAY,SAAS,EAAE,QAAQ,OAAO,OAAO,CAAC;AAAA,IAClD;AAAA,EACJ,SAAS,KAAK;AACV,WAAO,MAAM,WAAW,yBAA0B,IAAc,OAAO,EAAE;AAAA,EAC7E;AACJ;AAgBA,eAAe,wBAAwB,WAAmB,QAAqC;AAC3F,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,aAAa;AAClD,QAAM,mBAAmB,CAAC,SAAkB;AACxC,QAAI;AACA,YAAM,IAAK,QAAQ,CAAC;AACpB,UAAI,EAAE,WAAW,OAAQ;AACzB,YAAM,aAAa,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AACrE,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,aAAa,GAAI,CAAC;AACzD,YAAM,kBAAkB,MAAM,QAAQ,EAAE,eAAe,IAAK,EAAE,kBAA+B,CAAC;AAC9F,YAAM,YAAY,gBAAgB,SAAS,IACrC,mBAAmB,gBAAgB,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,KACzD;AACN,YAAM,iBAAiB,uBAAuB,OAAO,IAAI,SAAS;AAClE,wBAAkB,WAAW,gBAAgB,kBAAkB;AAC/D,gBAAU,WAAW,MAAM;AAE3B,oBAAc,WAAW,EAAE,QAAQ,QAAQ,SAAS,eAAe,CAAC;AAKpE,mBAAa,MAAM,oBAAoB,SAAS,CAAC;AAAA,IACrD,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,iCAAkC,IAAc,OAAO,EAAE;AAAA,IACrF;AAAA,EACJ;AACA,QAAM,gBAAgB,CAAC,SAAkB;AACrC,QAAI;AACA,YAAM,IAAK,QAAQ,CAAC;AACpB,UAAI,EAAE,WAAW,OAAQ;AACzB,YAAM,UAAU,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;AAC5D,YAAM,cAAc,UAAU,IACxB,kCAAkC,OAAO,uFACzC;AACN,wBAAkB,WAAW,aAAa,gBAAgB;AAC1D,gBAAU,WAAW,QAAQ;AAC7B,oBAAc,WAAW,EAAE,QAAQ,aAAa,SAAS,YAAY,CAAC;AACtE,mBAAa,MAAM,oBAAoB,SAAS,CAAC;AAAA,IACrD,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,8BAA+B,IAAc,OAAO,EAAE;AAAA,IAClF;AAAA,EACJ;AACA,cAAY,GAAG,kBAAkB,gBAAgB;AACjD,cAAY,GAAG,eAAe,aAAa;AAC3C,SAAO,MAAM;AACT,gBAAY,IAAI,kBAAkB,gBAAgB;AAClD,gBAAY,IAAI,eAAe,aAAa;AAAA,EAChD;AACJ;AAOA,eAAe,mBAAmB,WAAmB,QAAqC;AACtF,QAAM,EAAE,YAAY,IAAI,MAAM,OAAO,aAAa;AAClD,QAAM,UAAU,CAAC,aAA6B;AAC1C,QAAI;AAEA,YAAM,UAAW,SAAS,WAAW,CAAC;AACtC,UAAI,QAAQ,WAAW,OAAQ;AAC/B,YAAM,UAAU,OAAO,QAAQ,cAAc,QAAQ,eAAe,SAAS,eAAe,MAAM,EAAE,YAAY;AAChH,YAAM,aAAa;AAAA,QACf,QAAQ,YACL,QAAQ,gBACR,SAAS,SACT,SAAS,UACT,GAAG,SAAS,IAAI;AAAA,MACvB;AAEA,YAAM,WAAW,uBAAuB,UAAU;AAQlD,YAAM,UAAU,sBAAsB,UAAU,OAAO;AACvD,YAAM,eAAe,MAAM,QAAQ,QAAQ,YAAY,IAChD,QAAQ,aAA0B,MAAM,GAAG,CAAC,IAC7C,oBAAoB,QAAQ;AAClC,oBAAc;AAAA,QACV;AAAA,QACA;AAAA,QACA;AAAA,QACA,YAAY,SAAS;AAAA,QACrB;AAAA,MACJ,CAAC;AAED,oBAAc,WAAW;AAAA,QACrB,SAAS,GAAG,OAAO,YAAY,QAAQ,MAAM,GAAG,GAAG,CAAC,GAAG,QAAQ,SAAS,MAAM,WAAM,EAAE;AAAA,QACtF,QAAQ;AAAA,MACZ,CAAC;AAAA,IACL,SAAS,KAAK;AACV,aAAO,MAAM,WAAW,kCAAmC,IAAc,OAAO,EAAE;AAAA,IACtF;AAAA,EACJ;AACA,cAAY,GAAG,gCAAgC,OAAO;AACtD,SAAO,MAAM,YAAY,IAAI,gCAAgC,OAAO;AACxE;AAyBA,SAAS,sBAAsB,UAAkB,SAA0C;AACvF,QAAM,UAAU;AAChB,QAAM,YAAY,aAAa,WAAW,SAAS,KAAK,EAAE,SAAS;AACnE,MAAI,CAAC,UAAW,QAAO;AAEvB,QAAM,aAAa,OAAO,QAAQ,eAAe,WAAW,WAAW,QAAQ,UAAU,IAAI;AAC7F,QAAM,eAAe,OAAO,QAAQ,iBAAiB,WAAW,QAAQ,eAAe;AACvF,QAAM,WAAW,OAAO,QAAQ,aAAa,WAAW,QAAQ,WAAW;AAC3E,QAAM,eAAe,OAAO,QAAQ,cAAc,WAAW,QAAQ,YAAY;AAEjF,QAAM,YAAY,gBAAgB,CAAC,4BAA4B,YAAY,IACrE,aAAa,MAAM,GAAG,GAAG,EAAE,KAAK,IAChC;AAEN,QAAM,QAAkB,CAAC;AACzB,MAAI,cAAc;AACd,UAAM,KAAK,GAAG,UAAU,mBAAmB,YAAY,cAAc,EAAE,CAAC,GAAG;AAAA,EAC/E,OAAO;AACH,UAAM,KAAK,GAAG,UAAU,6BAA6B;AAAA,EACzD;AACA,MAAI,YAAY,WAAW,GAAG;AAC1B,UAAM,MAAM,SAAS,CAAC,KAAK,aAAa,QAAQ;AAAA,EACpD;AACA,QAAM,MAAM,SAAS,CAAC,KAAK;AAE3B,MAAI,WAAW;AACX,UAAM,KAAK,gBAAgB,SAAS,GAAG,gBAAgB,aAAa,SAAS,MAAM,WAAM,EAAE,EAAE;AAAA,EACjG,WAAW,cAAc;AAGrB,UAAM,KAAK,2EAA2E;AAAA,EAC1F;AAEA,QAAM,KAAK,wEAAmE;AAC9E,SAAO,MAAM,KAAK,GAAG;AACzB;AAEA,SAAS,WAAW,GAAmB;AACnC,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC;AAChD;AAEA,SAAS,YAAY,GAAW,KAAqB;AACjD,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,SAAO,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,IAAI;AAC3C;AAaO,SAAS,uBAAuB,MAAsB;AACzD,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO,QAAQ;AACtD,MAAI,UAAU;AAEd,YAAU,QAAQ;AAAA,IACd;AAAA,IACA;AAAA,EACJ;AAEA,YAAU,QAAQ;AAAA,IACd;AAAA,IACA;AAAA,EACJ;AAEA,YAAU,QAAQ;AAAA,IACd;AAAA,IACA;AAAA,EACJ;AACA,YAAU,QAAQ,KAAK;AAGvB,MAAI,QAAQ,WAAW,GAAG;AACtB,WAAO;AAAA,EACX;AACA,SAAO;AACX;AAgBO,SAAS,4BAA4B,MAA0C;AAClF,MAAI,CAAC,QAAQ,OAAO,SAAS,SAAU,QAAO;AAC9C,QAAM,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ;AACA,SAAO,QAAQ,KAAK,QAAM,GAAG,KAAK,IAAI,CAAC;AAC3C;AAGA,SAAS,oBAAoB,UAAoC;AAC7D,QAAM,cAAe,SAAS,SAAiD;AAC/E,MAAI,gBAAgB,iBAAkB,QAAO,CAAC,0BAA0B,gBAAgB,uBAAuB;AAC/G,MAAI,gBAAgB,cAAkB,QAAO,CAAC,eAAe,QAAQ,cAAc;AACnF,SAAO,CAAC,WAAW,MAAM;AAC7B;AAgDA,SAAS,oBACL,MACA,MAC0D;AAC1D,QAAM,UAAU,IAAI,SAAuC;AACvD,eAAW,KAAK,MAAM;AAClB,YAAM,IAAI,KAAK,CAAC;AAChB,UAAI,OAAO,MAAM,YAAY,EAAE,KAAK,EAAE,SAAS,EAAG,QAAO,EAAE,KAAK;AAAA,IACpE;AACA,WAAO;AAAA,EACX;AACA,QAAM,WAAW,CAAC,GAAW,QAAgB,EAAE,UAAU,MAAM,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,QAAQ,IAAI;AAEnG,UAAQ,MAAM;AAAA,IACV,KAAK;AAAA,IACL,KAAK,kBAAkB;AACnB,YAAM,QAAQ,QAAQ,SAAS,KAAK,QAAQ;AAC5C,aAAO,EAAE,MAAM,aAAM,UAAU,oBAAoB,QAAQ,QAAQ,SAAS,OAAO,GAAG,IAAI,OAAU;AAAA,IACxG;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,YAAY;AACb,YAAM,MAAM,QAAQ,OAAO,MAAM;AACjC,aAAO,EAAE,MAAM,aAAM,UAAU,eAAe,QAAQ,MAAM,SAAS,KAAK,GAAG,IAAI,OAAU;AAAA,IAC/F;AAAA,IACA,KAAK;AAAA,IACL,KAAK,WAAW;AACZ,YAAM,SAAS,QAAQ,OAAO,MAAM;AACpC,aAAO,EAAE,MAAM,aAAM,UAAU,WAAW,QAAQ,SAAS,SAAS,QAAQ,GAAG,IAAI,OAAU;AAAA,IACjG;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,eAAe;AAChB,YAAM,OAAO,QAAQ,QAAQ,QAAQ,UAAU;AAC/C,aAAO,EAAE,MAAM,gBAAM,UAAU,gBAAgB,QAAQ,OAAO,SAAS,MAAM,GAAG,IAAI,OAAU;AAAA,IAClG;AAAA,IACA,KAAK,aAAa;AACd,YAAM,OAAO,QAAQ,QAAQ,QAAQ,UAAU;AAC/C,aAAO,EAAE,MAAM,aAAM,UAAU,eAAe,QAAQ,OAAO,SAAS,MAAM,GAAG,IAAI,OAAU;AAAA,IACjG;AAAA,IACA,KAAK,YAAY;AACb,YAAM,OAAO,QAAQ,QAAQ,KAAK;AAClC,aAAO,EAAE,MAAM,aAAM,UAAU,mBAAmB,QAAQ,OAAO,SAAS,MAAM,GAAG,IAAI,OAAU;AAAA,IACrG;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,gBAAgB;AACjB,YAAM,MAAM,QAAQ,WAAW,OAAO,MAAM;AAC5C,aAAO,EAAE,MAAM,gBAAM,UAAU,iBAAiB,QAAQ,MAAM,SAAS,KAAK,GAAG,IAAI,OAAU;AAAA,IACjG;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,gBAAgB;AACjB,YAAM,OAAO,QAAQ,WAAW,QAAQ,SAAS,KAAK;AACtD,aAAO,EAAE,MAAM,aAAM,UAAU,aAAa,QAAQ,OAAO,SAAS,MAAM,GAAG,IAAI,OAAU;AAAA,IAC/F;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,aAAa;AACd,YAAM,IAAI,QAAQ,SAAS,GAAG;AAC9B,aAAO,EAAE,MAAM,aAAM,UAAU,YAAY,QAAQ,IAAI,SAAS,GAAG,GAAG,IAAI,OAAU;AAAA,IACxF;AAAA,IACA,KAAK;AAAA,IACL,KAAK,sBAAsB;AACvB,aAAO,EAAE,MAAM,aAAM,UAAU,oBAAoB;AAAA,IACvD;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,aAAa;AACd,YAAM,SAAS,QAAQ,UAAU,aAAa;AAC9C,aAAO,EAAE,MAAM,aAAM,UAAU,iBAAiB,QAAQ,SAAS,SAAS,QAAQ,GAAG,IAAI,OAAU;AAAA,IACvG;AAAA,IACA,KAAK;AAAA,IACL,KAAK,UAAU;AACX,aAAO,EAAE,MAAM,mBAAO,UAAU,qBAAqB;AAAA,IACzD;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK,gBAAgB;AACjB,aAAO,EAAE,MAAM,aAAM,UAAU,KAAK,QAAQ,WAAW,iBAAiB,GAAG,QAAQ,QAAQ,QAAQ,OAAO,EAAE;AAAA,IAChH;AAAA;AAAA,IAEA,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACD,aAAO;AAAA,IACX;AAGI,aAAO,EAAE,MAAM,mBAAO,UAAU,QAAQ,IAAI,GAAG;AAAA,EACvD;AACJ;AAEA,SAAS,gBAAgB,GAA2C;AAChE,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,UAAU,EAAE,KAAK;AACvB,MAAI,QAAQ,UAAU,IAAK,QAAO;AAClC,SAAO,QAAQ,MAAM,GAAG,GAAG,EAAE,QAAQ,IAAI;AAC7C;","names":[]}
1
+ {"version":3,"sources":["../../src/agent/missionLifecycle.ts"],"sourcesContent":["/**\n * TITAN — Mission lifecycle adapter (v6.1.0)\n *\n * Connects a freshly-created Mission Room to the existing goal driver\n * + Command Post pipeline. When a mission is created:\n *\n * 1. Create a Goal (in the goals subsystem) with title = goal text,\n * tagged with the mission id and play id so the bridge below can\n * filter events back to the right room.\n * 2. Subscribe to the agent message bus so each specialist's tool\n * calls / answers / progress messages get rewritten as a chat\n * message in the mission thread.\n * 3. Subscribe to the Command Post approval queue so blocking\n * questions filed against this mission's goal show up as inline\n * question messages in the chat.\n *\n * The bridge is one-way (driver → mission room). User actions (replies\n * to questions, status toggles) go through the mission router and\n * propagate to the goal driver / Command Post via existing APIs.\n *\n * Idempotent: subscribing a second mission doesn't double-fire. We\n * track per-mission unsubscribe functions in `lifecycles`.\n */\nimport { readFileSync, existsSync, statSync } from 'fs';\nimport logger from '../utils/logger.js';\nimport { createGoal } from './goals.js';\nimport {\n postAgentMessage,\n postSystemMessage,\n setMemberState,\n appendMemberActivity,\n setStatus,\n raiseQuestion,\n recordCost,\n ensureMember,\n getMission,\n getMissionByGoalId,\n listMissions,\n setLinkedGoal,\n setLinkedIssue,\n updateArtifact,\n type MissionRoom,\n type MissionStatus,\n} from './missionRoom.js';\nimport { onAgentEvent, type AgentEvent } from './agentEvents.js';\nimport { createIssue, updateIssue, addIssueComment } from './commandPost.js';\n\n// v6.1.0-alpha.57 — track in-flight notebook fills per mission so a\n// second agent_done can cancel the prior typewriter (rather than the\n// two of them racing on `updateArtifact`).\nconst notebookFillTimers = new Map<string, NodeJS.Timeout>();\n\n/**\n * Convert HTML to plain text suitable for the lined-paper notebook\n * view. The notebook component renders `content.split('\\n').filter`\n * and slices to 10 lines per page — it wants prose, not markup.\n *\n * - Strip <script>/<style> blocks (including content)\n * - Convert <br>/<p>/<h1-6>/<li>/<tr> to line breaks\n * - Strip every other tag\n * - Decode the 5 standard entities + numeric refs\n * - Collapse runs of 3+ blank lines to 2\n */\nfunction htmlToNotebookText(html: string): string {\n if (!html) return '';\n let s = html;\n s = s.replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script>/gi, '');\n s = s.replace(/<style\\b[^>]*>[\\s\\S]*?<\\/style>/gi, '');\n s = s.replace(/<head\\b[^>]*>[\\s\\S]*?<\\/head>/gi, '');\n s = s.replace(/<(?:br|hr)\\s*\\/?>/gi, '\\n');\n s = s.replace(/<\\/(?:p|h[1-6]|li|tr|div|section|article|figure|figcaption|blockquote)\\s*>/gi, '\\n\\n');\n s = s.replace(/<[^>]+>/g, '');\n s = s.replace(/&nbsp;/gi, ' ')\n .replace(/&amp;/gi, '&')\n .replace(/&lt;/gi, '<')\n .replace(/&gt;/gi, '>')\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&#(\\d+);/g, (_m, n) => String.fromCharCode(Number(n)));\n s = s.replace(/[ \\t]+/g, ' ');\n s = s.replace(/\\n{3,}/g, '\\n\\n');\n return s.trim();\n}\n\n/**\n * Stream plain text into a mission's artifact buffer in small\n * chunks. Each chunk fires an `artifact_updated` SSE event so the\n * DocumentPaper notebook fills in like someone writing into it.\n *\n * - Default chunk = 40 chars every 80ms → ~500 chars/sec, feels\n * human-paced (faster than typing, slower than dumping)\n * - Cap total fill time at ~6s so a 3-page essay doesn't take\n * forever; long content scales chunk size up\n * - Fire-and-forget — caller doesn't await, but we expose the\n * promise so tests can await on it\n * - Cancelable: a second call for the same mission cancels the\n * prior animation\n */\nfunction streamFillNotebook(missionId: string, agentId: string, plainText: string): void {\n if (!plainText) return;\n // Cancel any in-flight fill for this mission.\n const prior = notebookFillTimers.get(missionId);\n if (prior) clearInterval(prior);\n\n const total = plainText.length;\n // Scale chunk size so total fill time is roughly 3–6s.\n const TARGET_MS = 4000;\n const TICK_MS = 80;\n const ticks = Math.max(20, Math.ceil(TARGET_MS / TICK_MS));\n const chunkChars = Math.max(20, Math.ceil(total / ticks));\n let offset = 0;\n\n const id = setInterval(() => {\n offset = Math.min(total, offset + chunkChars);\n const slice = plainText.slice(0, offset);\n try {\n updateArtifact(missionId, agentId, slice);\n } catch (err) {\n // Mission may have been deleted mid-stream.\n logger.debug(COMPONENT, `streamFillNotebook stopped on ${missionId}: ${(err as Error).message}`);\n clearInterval(id);\n notebookFillTimers.delete(missionId);\n return;\n }\n if (offset >= total) {\n clearInterval(id);\n notebookFillTimers.delete(missionId);\n }\n }, TICK_MS);\n notebookFillTimers.set(missionId, id);\n}\n\n/**\n * If `agent_done` brought a file artifact (HTML/markdown/text the\n * Writer just produced), read it and start the notebook fill so the\n * user watches the document appear on the lined paper. Defensive on\n * everything — bad file, missing file, oversize file all silently\n * skip rather than crashing the lifecycle bridge.\n */\nfunction maybeFillNotebookFromFileArtifact(\n missionId: string,\n agentId: string,\n artifacts: Array<{ type: string; ref: string }>,\n): void {\n const file = artifacts.find(\n (a) => a.type === 'file' && typeof a.ref === 'string' && a.ref.length > 0,\n );\n if (!file) return;\n try {\n if (!existsSync(file.ref)) return;\n const stat = statSync(file.ref);\n // Cap at 1 MB to avoid loading huge accidentally-large files\n // into memory + streaming them as a 25-minute animation.\n if (stat.size > 1024 * 1024) return;\n const raw = readFileSync(file.ref, 'utf-8');\n const isHtml = /\\.html?$/i.test(file.ref) || /<html[\\s>]/i.test(raw);\n const plain = isHtml ? htmlToNotebookText(raw) : raw;\n if (plain.trim().length === 0) return;\n streamFillNotebook(missionId, agentId, plain);\n } catch (err) {\n logger.debug(COMPONENT, `maybeFillNotebookFromFileArtifact skipped: ${(err as Error).message}`);\n }\n}\n\nconst COMPONENT = 'MissionLifecycle';\n\n// Per-mission cleanup handlers. When a mission completes, we tear down\n// these subscriptions so the bus doesn't leak listeners.\nconst lifecycles = new Map<string, Array<() => void>>();\n\n/**\n * v6.1.0-alpha.1 — a SINGLE global subscription to the shared\n * agentEvents bus. Every mission piggybacks off this; we filter by\n * goalId at dispatch time. This is much cheaper than N per-mission\n * subscriptions (which is what alpha.0's broken bridge attempted) and\n * survives the case where the goal driver routes to a specialist that\n * isn't on the predicted Plays team — we add them dynamically.\n */\nlet globalBusUnsub: (() => void) | null = null;\nfunction ensureGlobalBusBridge(): void {\n if (globalBusUnsub) return;\n globalBusUnsub = onAgentEvent((ev: AgentEvent) => {\n // The goalDriver-emitted events carry data.goalId; events from\n // ad-hoc spawns (CLI, channels) don't, and we silently ignore\n // those — they aren't part of a mission.\n const data = ev.data ?? {};\n const goalId = typeof data.goalId === 'string' ? data.goalId : undefined;\n if (!goalId) return;\n const mission = getMissionByGoalId(goalId);\n if (!mission) return;\n const agentId = (ev.agentId ?? ev.agentName ?? '').toLowerCase();\n if (!agentId) return;\n try {\n switch (ev.type) {\n case 'agent_spawn': {\n ensureMember(mission.id, agentId);\n const subtaskTitle = typeof data.subtaskTitle === 'string' ? data.subtaskTitle : 'something';\n setMemberState(mission.id, agentId, 'working', shortenActivity(subtaskTitle));\n break;\n }\n case 'tool_call': {\n const name = typeof data.name === 'string' ? data.name : 'tool';\n ensureMember(mission.id, agentId);\n setMemberState(mission.id, agentId, 'working', shortenActivity(`running ${name}`));\n // v6.1.0-alpha.31 — also push a live activity sticky\n // to the desk for research-y tool calls. Tony's\n // direct ask: \"I liked the way we had it before\n // when the Agents put sticky notes on the desk\n // when working, with their research data so I can\n // see what they are doing.\" The activityLog\n // accumulates here per-agent and the canvas reads\n // it into draggable sticky notes.\n try {\n const args = (typeof data.args === 'object' && data.args !== null)\n ? (data.args as Record<string, unknown>)\n : {};\n const sticky = buildActivitySticky(name, args);\n if (sticky) {\n appendMemberActivity(mission.id, agentId, sticky);\n }\n } catch (err) {\n logger.debug(COMPONENT, `Activity sticky build threw: ${(err as Error).message}`);\n }\n break;\n }\n case 'tool_end': {\n // Don't transition state — the next tool_call or agent_done\n // will overwrite. Just no-op (avoids flicker).\n break;\n }\n case 'agent_done': {\n ensureMember(mission.id, agentId);\n const rawReasoning = typeof data.reasoning === 'string' && data.reasoning.trim().length > 0\n ? data.reasoning.trim()\n : null;\n // v6.1.0-alpha.12 — pluck the specialist's concrete\n // artifacts (URLs visited, files written, facts\n // memorized) so the chat can render them as clickable\n // sources. Pre-alpha.12 these were silently discarded —\n // a \"Successfully researched\" reasoning summary made it\n // to chat but the actual URLs/files behind it did not,\n // so the user couldn't follow up on anything.\n const sources: Array<{ type: 'url' | 'file' | 'fact' | 'report'; ref: string; description?: string }> = [];\n if (Array.isArray(data.artifacts)) {\n for (const raw of data.artifacts) {\n if (!raw || typeof raw !== 'object') continue; // skip null/undefined/primitive\n const a = raw as Record<string, unknown>;\n const t = typeof a.type === 'string' ? a.type : '';\n const ref = typeof a.ref === 'string' ? a.ref : '';\n if (!ref) continue;\n if (t !== 'url' && t !== 'file' && t !== 'fact' && t !== 'report') continue;\n sources.push({\n type: t as 'url' | 'file' | 'fact' | 'report',\n ref,\n description: typeof a.description === 'string' ? a.description : undefined,\n });\n if (sources.length >= 12) break;\n }\n }\n // v6.1.0-alpha.57 — live notebook fill. If the\n // specialist produced a file artifact, stream its\n // plain-text contents into the mission's\n // `room.artifact.content` buffer in small chunks\n // (~4s total). The DocumentPaper component on the\n // canvas watches that buffer and re-renders as it\n // grows, so the user sees the document fill in\n // line-by-line like the AI is writing in a\n // notebook. Fire-and-forget; safe if the file is\n // missing / too large / unreadable.\n if (sources.length > 0) {\n maybeFillNotebookFromFileArtifact(\n mission.id,\n agentId,\n sources.map((s) => ({ type: s.type, ref: s.ref })),\n );\n }\n\n // v6.1.0-alpha.7 — scrub internal-error stack traces.\n // When a spawn fails, the `reasoning` field can be the\n // raw error chain: \"Parser could not extract JSON...\n // Sub-agent error: All providers failed: HTTP 429\n // <html>...\" That's pure jargon for users. Detect it\n // and replace with the friendly fallback. The original\n // text is preserved on `meta.failureDetail` so power\n // users can still see it via click-to-expand.\n const reasoning = looksLikeInternalErrorTrace(rawReasoning)\n ? null\n : rawReasoning;\n const toolsUsed = Array.isArray(data.toolsUsed)\n ? (data.toolsUsed as string[]).slice(0, 6)\n : [];\n const actions = toolsUsed.map(t => ({ name: 'used', detail: t }));\n const status = typeof data.status === 'string' ? data.status : 'done';\n // v6.1.0-alpha.4 — pass the rich context as the message\n // `meta` field. The chat UI hides it by default and\n // surfaces it when the user clicks the bubble — keeps\n // the thread readable while making \"what actually\n // happened here\" one tap away.\n //\n // v6.1.0-alpha.7 — if the raw reasoning was a scrubbed\n // internal-error trace, stash it on `meta.failureDetail`\n // so power users can still see it via click-to-expand.\n // The chat surface stays clean.\n const meta: Record<string, unknown> = {\n subtaskTitle: typeof data.subtaskTitle === 'string' ? data.subtaskTitle : undefined,\n status,\n durationMs: typeof data.durationMs === 'number' ? data.durationMs : undefined,\n tokensUsed: typeof data.tokensUsed === 'number' ? data.tokensUsed : undefined,\n costUsd: typeof data.costUsd === 'number' ? data.costUsd : undefined,\n model: typeof data.model === 'string' ? data.model : undefined,\n };\n if (rawReasoning && rawReasoning !== reasoning) {\n meta.failureDetail = rawReasoning;\n }\n // v6.1.0-alpha.1 — every agent_done emits SOMETHING into\n // the chat. The pre-fix path returned nothing when\n // `reasoning` was empty, which is common on cloud\n // specialists that return JSON-only responses (their\n // reasoning field is empty because the *artifact* is\n // the value). The chat shouldn't go silent.\n // v6.1.0-alpha.12 — also pass `sources` so URLs +\n // files the specialist worked with render clickably.\n const sourcesArg = sources.length > 0 ? sources : undefined;\n if (reasoning) {\n postAgentMessage(mission.id, agentId, reasoning, actions.length > 0 ? actions : undefined, meta, sourcesArg);\n } else if (status === 'failed') {\n postAgentMessage(\n mission.id,\n agentId,\n `I ran into trouble on this one and couldn't finish — handing back to the team.`,\n actions.length > 0 ? actions : undefined,\n meta,\n sourcesArg,\n );\n } else if (status === 'needs_info' || status === 'blocked') {\n postAgentMessage(\n mission.id,\n agentId,\n `I have a quick question before I can finish — see below.`,\n actions.length > 0 ? actions : undefined,\n meta,\n sourcesArg,\n );\n } else {\n // status === 'done' with empty reasoning. Common with\n // JSON-only specialists. Don't go silent — say something\n // honest about what they did.\n const summary = toolsUsed.length > 0\n ? `Done — used ${toolsUsed.slice(0, 3).join(', ')}.`\n : `Done.`;\n postAgentMessage(mission.id, agentId, summary, actions.length > 0 ? actions : undefined, meta, sourcesArg);\n }\n setMemberState(mission.id, agentId, 'idle', undefined);\n const tokens = typeof data.tokensUsed === 'number' ? data.tokensUsed : 0;\n const cost = typeof data.costUsd === 'number' ? data.costUsd : 0;\n if (tokens > 0 || cost > 0) {\n recordCost(mission.id, tokens, cost);\n }\n break;\n }\n }\n } catch (err) {\n logger.debug(COMPONENT, `Bridge dispatch threw for ${mission.id}/${ev.type}: ${(err as Error).message}`);\n }\n });\n logger.info(COMPONENT, 'Global agent-event bridge attached (goalId → mission room dispatch)');\n}\n\n/** The wire that the gateway / missions router calls when a mission is\n * created. Returns the linked goal id. */\nexport async function startMissionWork(mission: MissionRoom): Promise<string | null> {\n try {\n // Make sure the global event bridge is alive. Idempotent.\n ensureGlobalBusBridge();\n\n // Tag the goal with the mission + play id so message-bus event\n // bridges can filter \"which mission did this belong to.\"\n const goal = createGoal({\n title: mission.goal,\n description: `Mission ${mission.id}` + (mission.playId ? ` (play: ${mission.playId})` : ''),\n tags: [\n `mission:${mission.id}`,\n mission.playId ? `play:${mission.playId}` : 'play:generic',\n ],\n // v6.1.0-alpha.5 — Mission Chat goals are user-initiated. The\n // autonomous-creation rate limit (10/hour) + active-goals cap\n // exists to throttle runaway self-mod / self-repair loops, not\n // to throttle the user typing into the chat. Bypass it so\n // missions never silently fail with \"Couldn't start the team:\n // rate limited\" after the user's 11th request in an hour.\n force: true,\n });\n // v6.1.0-alpha.1 — link the goal id INSIDE startMissionWork so any\n // event the goal driver fires (even before the missions router gets\n // back to set it) can resolve the mission via getMissionByGoalId.\n // The router's redundant setLinkedGoal call is now a no-op safety\n // net rather than the source of truth.\n setLinkedGoal(mission.id, goal.id);\n\n // v6.1.0-alpha.10 — auto-create a Command Post issue for this\n // mission. The issue becomes the durable audit trail (every\n // significant lifecycle event mirrors as a comment) and the\n // Command Post Issues panel + Mission Chat / Canvas surface the\n // same record. Failure to create the issue should never block\n // the mission itself — it's audit, not behavior.\n let issueIdent: string | undefined;\n try {\n const issue = createIssue({\n title: mission.goal,\n description: `Mission ${mission.id}` + (mission.playId ? ` · play: ${mission.playId}` : '') +\n `\\n\\nLinked goal: ${goal.id}` +\n `\\n\\nTeam: ${mission.team.map(t => t.name).join(', ') || '(forming)'}`,\n priority: 'medium',\n createdByUser: 'mission-chat',\n goalId: goal.id,\n });\n setLinkedIssue(mission.id, issue.id);\n issueIdent = issue.identifier;\n updateIssue(issue.id, { status: 'in_progress' });\n addIssueComment(\n issue.id,\n `Mission opened with team: ${mission.team.map(t => t.name).join(', ') || '(forming)'}.`,\n { user: 'mission-chat' },\n );\n } catch (issueErr) {\n logger.warn(COMPONENT, `Issue link skipped for mission ${mission.id}: ${(issueErr as Error).message}`);\n }\n logger.info(\n COMPONENT,\n `Mission ${mission.id} linked to goal ${goal.id}` +\n (issueIdent ? ` and issue ${issueIdent}` : '') +\n ` (play=${mission.playId})`,\n );\n\n // Mark every Plays-predicted member as \"ready\" so the team strip\n // lights up immediately. The goal driver routes for real, and the\n // global bridge will narrow each member's `currentActivity` (and\n // ADD new members if the driver picks specialists Plays didn't\n // predict).\n for (const member of mission.team) {\n setMemberState(mission.id, member.agentId, 'idle', 'standing by');\n }\n\n // The approval bridge + goal-lifecycle bridge are both\n // per-mission — they filter by goalId, so we register them\n // scoped to the goal we just created.\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(mission.id, goal.id));\n cleanups.push(await wireGoalLifecycleBridge(mission.id, goal.id));\n lifecycles.set(mission.id, cleanups);\n\n return goal.id;\n } catch (err) {\n logger.error(COMPONENT, `Failed to start mission ${mission.id}: ${(err as Error).message}`);\n setStatus(mission.id, 'failed', `Couldn't start the team: ${(err as Error).message}`);\n return null;\n }\n}\n\n/** Called when the UI posts a user message into a mission. Two paths:\n *\n * 1. **Mission is live** (working/blocked/paused/forming) — broadcast\n * the user's note to every registered specialist mailbox so the\n * next agent loop picks it up. Same behavior as alpha.1+.\n *\n * 2. **Mission is terminal** (done/failed) — v6.1.0-alpha.13: reopen\n * the mission with this message as a new direction. Creates a\n * fresh Goal, re-wires the lifecycle bridges, flips status back\n * to 'working', posts a \"Picking this back up…\" system note,\n * mirrors to the linked Command Post issue. The DriverScheduler\n * picks up the new active goal on its next tick (~10s).\n */\nexport async function handleUserMessage(missionId: string, content: string): Promise<void> {\n const room = getMission(missionId);\n if (!room) return;\n const isTerminal = room.status === 'done' || room.status === 'failed';\n if (isTerminal) {\n await reopenMissionWithFollowUp(missionId, content);\n return;\n }\n // For v1 we use the existing messageBus. We don't know which\n // specialist is \"current\" — broadcast to every registered member's\n // mailbox so whichever one is in-flight picks the note up at the\n // start of its next round. Mailbox names match the specialist ids.\n //\n // v6.1.0-alpha.1 — only dispatch to mailboxes that are actually\n // REGISTERED right now. Specialists register their mailbox at spawn\n // time and unregister when done. If no team member is mid-spawn,\n // there's no mailbox to deliver to — that's fine, the user's note\n // is already in the chat thread and the goal driver will see it\n // when it next decides which subtask to schedule. Sending to an\n // unregistered mailbox would emit a noisy warn for every team\n // member every time.\n try {\n const { sendMessage, hasMailbox } = await import('./messageBus.js');\n const recipients = room.team.map(t => t.agentId);\n let delivered = 0;\n for (const to of recipients) {\n if (!hasMailbox(to)) continue; // skip non-registered to avoid noisy warns\n const result = sendMessage('user', to, content, { priority: 'urgent' });\n if (result) delivered++;\n }\n if (delivered === 0) {\n logger.debug(COMPONENT, `User message recorded in chat; no specialist mailboxes were live to receive it (team will pick up at next subtask).`);\n }\n } catch (err) {\n logger.debug(COMPONENT, `messageBus dispatch skipped: ${(err as Error).message}`);\n }\n}\n\n/**\n * v6.1.0-alpha.13 — reopen a terminal mission with a follow-up direction.\n *\n * Creates a brand-new Goal with the user's new content as the title.\n * The mission keeps its id + history; the chat thread continues\n * organically; the team strip keeps showing previous helpers (and\n * picks up new ones automatically as the new goal spawns specialists\n * via the dynamic-team logic from alpha.1).\n *\n * Re-wires the per-mission bridges (approval bridge, goal-lifecycle\n * bridge) since the previous bridges were torn down when the mission\n * first reached its terminal state. The agent-event bridge is global\n * and looks up missions by goalId at dispatch time — it picks up the\n * new goal automatically without re-registration.\n */\nasync function reopenMissionWithFollowUp(missionId: string, newContent: string): Promise<void> {\n const room = getMission(missionId);\n if (!room) return;\n try {\n ensureGlobalBusBridge();\n const newGoal = createGoal({\n title: newContent,\n description:\n `Follow-up on mission ${missionId} (continued from previous goal ${room.goalId ?? '?'}).\\n\\n` +\n `Original mission: \"${room.goal}\"\\n\\n` +\n `User wants: ${newContent}`,\n tags: [\n `mission:${missionId}`,\n room.playId ? `play:${room.playId}` : 'play:generic',\n 'mission-followup',\n ],\n // Same rationale as startMissionWork — user-initiated, not\n // autonomous; bypass the 10-goals-per-hour rate limit.\n force: true,\n });\n setLinkedGoal(missionId, newGoal.id);\n // Tear down any stale bridges (in case completion teardown\n // didn't run yet) and wire fresh ones for the new goal.\n teardownMissionWork(missionId);\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(missionId, newGoal.id));\n cleanups.push(await wireGoalLifecycleBridge(missionId, newGoal.id));\n lifecycles.set(missionId, cleanups);\n\n // Wake the team back up in the team strip.\n for (const member of room.team) {\n setMemberState(missionId, member.agentId, 'idle', 'getting back on it');\n }\n\n // Chat surface: explain what just happened.\n postSystemMessage(\n missionId,\n 'Picking this back up — the team is taking another swing with your new direction.',\n 'mission_resumed',\n );\n setStatus(missionId, 'working');\n\n // Mirror to the linked Command Post issue.\n if (room.issueId) {\n try {\n updateIssue(room.issueId, { status: 'in_progress' });\n addIssueComment(\n room.issueId,\n `User picked the mission back up: \"${newContent.slice(0, 200)}${newContent.length > 200 ? '…' : ''}\"`,\n { user: 'mission-chat' },\n );\n } catch { /* mirror failure never blocks the reopen */ }\n }\n\n logger.info(\n COMPONENT,\n `Mission ${missionId} reopened — new goal ${newGoal.id} linked (was: ${room.goalId ?? 'unset'})`,\n );\n } catch (err) {\n logger.error(COMPONENT, `Reopen failed for mission ${missionId}: ${(err as Error).message}`);\n // Surface the failure to the user instead of going silent.\n try {\n postSystemMessage(\n missionId,\n `Couldn't pick this back up: ${(err as Error).message}. Try starting a fresh mission instead.`,\n 'mission_resume_failed',\n );\n } catch { /* ok */ }\n }\n}\n\n/** Called when the UI toggles status (pause / resume). */\nexport async function handleStatusChange(missionId: string, status: MissionStatus): Promise<void> {\n if (status !== 'paused' && status !== 'working') return;\n try {\n // Look up the linked goal and toggle its driver state via the\n // existing user-controls surface. We import dynamically so the\n // lifecycle module doesn't have a top-level goal-driver\n // dependency (matches the rest of the file's pattern).\n const room = (await import('./missionRoom.js')).getMission(missionId);\n if (!room?.goalId) return;\n const driver = await import('./goalDriver.js');\n if (status === 'paused' && typeof driver.pauseDriver === 'function') {\n driver.pauseDriver(room.goalId);\n } else if (status === 'working' && typeof driver.resumeDriverControl === 'function') {\n driver.resumeDriverControl(room.goalId);\n }\n } catch (err) {\n logger.debug(COMPONENT, `Couldn't toggle driver state for ${missionId}: ${(err as Error).message}`);\n }\n}\n\n/** Tear down a mission's bridges. Called when the goal completes,\n * fails, or the mission is deleted. */\nexport function teardownMissionWork(missionId: string): void {\n const cleanups = lifecycles.get(missionId);\n if (!cleanups) return;\n for (const fn of cleanups) {\n try { fn(); }\n catch (err) { logger.debug(COMPONENT, `Cleanup threw: ${(err as Error).message}`); }\n }\n lifecycles.delete(missionId);\n}\n\n/**\n * v6.1.0-alpha.25 — re-attach event/approval/goal-lifecycle bridges\n * for missions that were already in-flight when the service was\n * restarted.\n *\n * **The bug this fixes (caught 2026-05-13):**\n *\n * The lifecycle bridges (`ensureGlobalBusBridge`, `wireApprovalBridge`,\n * `wireGoalLifecycleBridge`) live as in-memory subscriptions on the\n * agent-event bus + Command Post approval store + titanEvents bus.\n * They're attached only from `startMissionWork()` (new missions) and\n * `reopenMissionWithFollowUp()` (user reopens a stopped mission).\n *\n * On a service restart, those module-level subscriptions are gone.\n * Missions on disk in `status: working | forming | blocked` whose\n * goal driver keeps running (driver state is persisted; spawns\n * continue from where they left off) silently emit events into the\n * void — no listener catches them. From the user's POV the desk\n * shows \"Writer is working\" but no agent_done message ever lands,\n * the artifact paper stays blank, the cost stays $0.00.\n *\n * Hit Tony with the MLK essay mission on 2026-05-13 21:17 PT: the\n * spawn started seconds after the alpha.24 deploy restarted the\n * service. Writer talked to Ollama for 25s, returned needs_info,\n * and the lifecycle dropped the event because no bridge was\n * attached for that mission's goalId.\n *\n * **The fix:**\n *\n * Call this on server bootstrap. It scans every persisted mission,\n * and for each one still in a non-terminal status with a linked\n * `goalId`:\n * 1. Calls `ensureGlobalBusBridge()` (idempotent — only attaches\n * the global agent-event listener once).\n * 2. Wires the per-mission approval + goal-lifecycle bridges and\n * tracks the cleanups in the lifecycles map so\n * `teardownMissionWork()` still works.\n *\n * Returns the count of missions re-attached for logging.\n */\nexport async function reattachMissionBridgesOnStartup(): Promise<number> {\n let count = 0;\n let scanned = 0;\n try {\n const missions = listMissions();\n for (const m of missions) {\n scanned += 1;\n // Skip missions that have reached terminal state — their\n // driver is dormant, no events expected.\n if (m.status === 'done' || m.status === 'failed') continue;\n // Skip missions never linked to a goal.\n if (!m.goalId) continue;\n // Skip if we've already wired this mission this process\n // (defensive — shouldn't happen on startup but harmless).\n if (lifecycles.has(m.id)) continue;\n try {\n ensureGlobalBusBridge();\n const cleanups: Array<() => void> = [];\n cleanups.push(await wireApprovalBridge(m.id, m.goalId));\n cleanups.push(await wireGoalLifecycleBridge(m.id, m.goalId));\n lifecycles.set(m.id, cleanups);\n count += 1;\n logger.info(COMPONENT, `Re-attached lifecycle bridges for mission ${m.id} (goal ${m.goalId}, status ${m.status})`);\n } catch (err) {\n logger.warn(COMPONENT, `Re-attach failed for mission ${m.id}: ${(err as Error).message}`);\n }\n }\n if (count > 0) {\n logger.info(COMPONENT, `Startup re-attach complete — ${count}/${scanned} mission(s) wired back to the bus`);\n } else if (scanned > 0) {\n logger.debug(COMPONENT, `Startup re-attach — ${scanned} mission(s) scanned, none needed re-wiring`);\n }\n } catch (err) {\n logger.warn(COMPONENT, `Startup re-attach scan threw: ${(err as Error).message}`);\n }\n return count;\n}\n\n// ── Issue mirror ───────────────────────────────────────────────────\n//\n// v6.1.0-alpha.10 — mirror big mission lifecycle events to the linked\n// Command Post issue. Keeps the issue useful as the durable audit\n// trail without spamming it with every chat message (chat thread\n// already serves that purpose). What we mirror:\n//\n// - Mission start (in startMissionWork): \"Mission opened with team: …\"\n// - Question raised: `Sage asked: \"<question>\"`\n// - Question answered: `User answered: \"<answer>\"`\n// - Mission complete / failed: the same one-liner posted to chat,\n// plus an issue status update (done / cancelled).\n//\n// Every individual agent_message is NOT mirrored — too noisy.\n// Subtask-grain events stay in the chat thread.\n\ninterface IssueMirror {\n status?: 'in_progress' | 'in_review' | 'done' | 'blocked' | 'cancelled';\n comment?: string;\n}\n\nfunction mirrorToIssue(missionId: string, mirror: IssueMirror): void {\n try {\n const room = getMission(missionId);\n const issueId = room?.issueId;\n if (!issueId) return;\n if (mirror.comment) {\n addIssueComment(issueId, mirror.comment, { user: 'mission-chat' });\n }\n if (mirror.status) {\n updateIssue(issueId, { status: mirror.status });\n }\n } catch (err) {\n logger.debug(COMPONENT, `Issue mirror skipped: ${(err as Error).message}`);\n }\n}\n\n// ── Bridges ────────────────────────────────────────────────────────\n\n/**\n * Subscribe to goal-lifecycle events on the titanEvents bus and\n * translate goal completion / failure into:\n * 1. a system message in the mission thread (\"Mission complete.\" or\n * \"Couldn't finish this one — here's what we have so far.\")\n * 2. a status transition (working → done | failed)\n *\n * Without this, the mission room sat in 'working' indefinitely after\n * the underlying goal completed — the chat showed an idle team strip\n * and no closure message, which looked like the mission was stuck even\n * when it had successfully finished.\n */\nasync function wireGoalLifecycleBridge(missionId: string, goalId: string): Promise<() => void> {\n const { titanEvents } = await import('./daemon.js');\n const completedHandler = (data: unknown) => {\n try {\n const d = (data ?? {}) as Record<string, unknown>;\n if (d.goalId !== goalId) return;\n const durationMs = typeof d.durationMs === 'number' ? d.durationMs : 0;\n const seconds = Math.max(1, Math.round(durationMs / 1000));\n const specialistsUsed = Array.isArray(d.specialistsUsed) ? (d.specialistsUsed as string[]) : [];\n const namesList = specialistsUsed.length > 0\n ? ` with help from ${specialistsUsed.slice(0, 5).join(', ')}`\n : '';\n const completionLine = `Mission complete in ${seconds}s${namesList}.`;\n postSystemMessage(missionId, completionLine, 'mission_complete');\n setStatus(missionId, 'done');\n // v6.1.0-alpha.10 — mirror to the linked Command Post issue.\n mirrorToIssue(missionId, { status: 'done', comment: completionLine });\n // Mission reached a terminal state — tear down its bridges so\n // we don't leak event-bus listeners. Defer one tick so any\n // remaining synchronous work inside this handler chain runs\n // before the bridges go away.\n setImmediate(() => teardownMissionWork(missionId));\n } catch (err) {\n logger.debug(COMPONENT, `goal:completed handler threw: ${(err as Error).message}`);\n }\n };\n const failedHandler = (data: unknown) => {\n try {\n const d = (data ?? {}) as Record<string, unknown>;\n if (d.goalId !== goalId) return;\n const retries = typeof d.retries === 'number' ? d.retries : 0;\n const failureLine = retries > 0\n ? `Couldn't finish this one after ${retries} retry attempt(s). Take a look at what we did manage and tell me what to try next.`\n : `Couldn't finish this one. Take a look at what we did manage and tell me what to try next.`;\n postSystemMessage(missionId, failureLine, 'mission_failed');\n setStatus(missionId, 'failed');\n mirrorToIssue(missionId, { status: 'cancelled', comment: failureLine });\n setImmediate(() => teardownMissionWork(missionId));\n } catch (err) {\n logger.debug(COMPONENT, `goal:failed handler threw: ${(err as Error).message}`);\n }\n };\n titanEvents.on('goal:completed', completedHandler);\n titanEvents.on('goal:failed', failedHandler);\n return () => {\n titanEvents.off('goal:completed', completedHandler);\n titanEvents.off('goal:failed', failedHandler);\n };\n}\n\n/** Subscribe to the Command Post approval queue and translate any\n * approval filed against this mission's goal into an inline question\n * message. v6.1.0-alpha.1 — uses the real titanEvents bus\n * ('commandpost:approval:created' event added in commandPost.ts).\n * Returns an unsubscribe. */\nasync function wireApprovalBridge(missionId: string, goalId: string): Promise<() => void> {\n const { titanEvents } = await import('./daemon.js');\n const handler = (approval: CPApprovalLike) => {\n try {\n // Filter: only approvals tied to this mission's goal.\n const payload = (approval.payload ?? {}) as Record<string, unknown>;\n if (payload.goalId !== goalId) return;\n const agentId = String(payload.specialist ?? payload.subtaskKind ?? approval.requestedBy ?? 'sage').toLowerCase();\n const rawContent = String(\n payload.question\n ?? payload.allQuestions\n ?? approval.title\n ?? approval.reason\n ?? `${approval.type} approval needed`\n );\n // Strip the goalDriver boilerplate prefix (alpha.5).\n const stripped = stripDriverBoilerplate(rawContent);\n // v6.1.0-alpha.29 — when stripping leaves a generic fallback\n // (or anything ≤ that fallback's information density), enrich\n // the question with payload context so the user actually knows\n // what's happening. Pre-alpha.29 the user saw a bare \"I need\n // more direction to keep going. What should I focus on?\" with\n // no clue that Writer was stuck on the MLK essay because the\n // structured-output reformat failed.\n const content = enrichBlockedQuestion(stripped, payload);\n const quickReplies = Array.isArray(payload.quickReplies)\n ? (payload.quickReplies as string[]).slice(0, 4)\n : defaultQuickReplies(approval);\n raiseQuestion({\n missionId,\n agentId,\n content,\n approvalId: approval.id,\n quickReplies,\n });\n // v6.1.0-alpha.10 — mirror to issue audit trail.\n mirrorToIssue(missionId, {\n comment: `${agentId} asked: \"${content.slice(0, 200)}${content.length > 200 ? '…' : ''}\"`,\n status: 'blocked',\n });\n } catch (err) {\n logger.debug(COMPONENT, `Approval bridge handler threw: ${(err as Error).message}`);\n }\n };\n titanEvents.on('commandpost:approval:created', handler);\n return () => titanEvents.off('commandpost:approval:created', handler);\n}\n\n/**\n * v6.1.0-alpha.29 — when stripDriverBoilerplate leaves us with the\n * generic fallback (\"I need more direction to keep going. What should\n * I focus on?\"), the user has zero signal about WHY the agent is\n * stuck or what they were trying to do. The approval payload carries\n * the missing context (subtaskTitle, specialist, lastError, attempts);\n * this composer weaves it into a friendlier question.\n *\n * Decision matrix:\n * 1. If `content` already reads like a specific question from the\n * specialist (>= 30 chars AND not equal to the generic fallback),\n * leave it alone — the specialist gave us something real.\n * 2. Otherwise build a contextual fallback from `payload`:\n * \"<Specialist> was working on <subtaskTitle> (attempt N).\n * Last hurdle: <lastError, scrubbed>.\n * What should they focus on?\"\n * Empty fields are dropped gracefully so a missing `lastError`\n * doesn't produce \"Last hurdle: undefined.\"\n * 3. Internal-error traces in `lastError` (Parser could not extract\n * JSON, HTTP 429, <!doctype html>, etc.) get scrubbed via\n * `looksLikeInternalErrorTrace` — they're not useful to the user\n * and reading them as \"what went wrong\" would be misleading.\n */\nfunction enrichBlockedQuestion(stripped: string, payload: Record<string, unknown>): string {\n const GENERIC = 'I need more direction to keep going. What should I focus on?';\n const isGeneric = stripped === GENERIC || stripped.trim().length < 30;\n if (!isGeneric) return stripped;\n\n const specialist = typeof payload.specialist === 'string' ? capitalize(payload.specialist) : 'Your helper';\n const subtaskTitle = typeof payload.subtaskTitle === 'string' ? payload.subtaskTitle : null;\n const attempts = typeof payload.attempts === 'number' ? payload.attempts : null;\n const rawLastError = typeof payload.lastError === 'string' ? payload.lastError : null;\n // Scrub internal traces — they're noise.\n const lastError = rawLastError && !looksLikeInternalErrorTrace(rawLastError)\n ? rawLastError.slice(0, 200).trim()\n : null;\n\n const parts: string[] = [];\n if (subtaskTitle) {\n parts.push(`${specialist} is working on \"${shortenLine(subtaskTitle, 80)}\"`);\n } else {\n parts.push(`${specialist} is working on your mission`);\n }\n if (attempts && attempts > 1) {\n parts[parts.length - 1] += ` (attempt ${attempts})`;\n }\n parts[parts.length - 1] += ' but got stuck.';\n\n if (lastError) {\n parts.push(`Last hurdle: ${lastError}${rawLastError && rawLastError.length > 200 ? '…' : ''}`);\n } else if (rawLastError) {\n // We scrubbed it as an internal trace — give the user a heads-up\n // it exists without dumping the trace itself.\n parts.push(`There was a technical hiccup the team couldn't recover from on their own.`);\n }\n\n parts.push(`What should they focus on? Pick a reply below — or type your own.`);\n return parts.join(' ');\n}\n\nfunction capitalize(s: string): string {\n if (!s) return s;\n return s.charAt(0).toUpperCase() + s.slice(1);\n}\n\nfunction shortenLine(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max - 1).trimEnd() + '…';\n}\n\n/**\n * Strip the goalDriver's auto-generated boilerplate from blocked-approval\n * question text so it reads like a person's question. The driver wraps\n * the specialist's actual question (or a fallback) with internal context\n * (subtask title, attempt count, specialist id) that's noise to the user.\n *\n * Patterns matched:\n * - `Goal \"...\" is stuck on subtask \"...\" (attempt N, specialist: X).\\n\\n`\n * - `Goal \"...\" — subtask \"...\" failed after N attempt(s) with specialist X.\\n\\n`\n * - `Goal \"...\" — subtask \"...\" is blocked after N attempt(s) with specialist X. The specialist could not complete the task and needs guidance on how to proceed.`\n */\nexport function stripDriverBoilerplate(text: string): string {\n if (!text || typeof text !== 'string') return text ?? '';\n let cleaned = text;\n // Pattern 1: prefix → \"Goal '...' is stuck on subtask '...' (...).\\n\\n<real question>\"\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+is\\s+stuck\\s+on\\s+subtask\\s+\"[^\"]*\"\\s+\\([^)]*\\)\\.\\s*\\n+/i,\n '',\n );\n // Pattern 2: prefix → \"Goal '...' — subtask '...' failed after N attempt(s) with specialist X.\\n\\nError: ...\"\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+—\\s+subtask\\s+\"[^\"]*\"\\s+failed\\s+after\\s+\\d+\\s+attempt\\(s\\)\\s+with\\s+specialist\\s+\\S+\\.\\s*\\n+/i,\n '',\n );\n // Pattern 3: fallback (no real question, just the driver's placeholder)\n cleaned = cleaned.replace(\n /^Goal\\s+\"[^\"]*\"\\s+—\\s+subtask\\s+\"[^\"]*\"\\s+is\\s+blocked\\s+after\\s+\\d+\\s+attempt\\(s\\)\\s+with\\s+specialist\\s+\\S+\\.\\s*The\\s+specialist\\s+could\\s+not\\s+complete\\s+the\\s+task\\s+and\\s+needs\\s+guidance\\s+on\\s+how\\s+to\\s+proceed\\.?\\s*/i,\n '',\n );\n cleaned = cleaned.trim();\n // If we stripped everything, fall back to a friendly default — the\n // user shouldn't see an empty question.\n if (cleaned.length === 0) {\n return 'I need more direction to keep going. What should I focus on?';\n }\n return cleaned;\n}\n\n/**\n * Detect internal error-chain text that should NOT be shown to the user\n * as the specialist's \"reasoning.\" Examples seen in the wild post-alpha.5:\n *\n * `Parser could not extract JSON from specialist response. Raw (200 chars):\n * Sub-agent error: All providers failed: Provider ollama/glm-5:cloud\n * failed: [HTTP 429] Ollama error (429): <!doctype html>...`\n *\n * The bridge scrubs these strings and falls through to the friendly\n * fallback (\"I ran into trouble on this one and couldn't finish — handing\n * back to the team.\") rather than dumping a stack trace into the chat.\n * The raw text is preserved on the message's `meta.failureDetail` so it\n * stays available via the click-to-expand details panel.\n */\nexport function looksLikeInternalErrorTrace(text: string | null | undefined): boolean {\n if (!text || typeof text !== 'string') return false;\n const markers = [\n /Parser could not extract JSON/i,\n /Sub-agent error:/i,\n /All providers failed/i,\n /HTTP\\s+\\d{3}.*Ollama error/i,\n /Circuit breaker OPEN for/i,\n /<!doctype html>/i,\n /\\bToo Many Requests\\b/i,\n ];\n return markers.some(re => re.test(text));\n}\n\n/** Sensible default reply set for the common approval kinds. */\nfunction defaultQuickReplies(approval: CPApprovalLike): string[] {\n const payloadKind = (approval.payload as Record<string, unknown> | undefined)?.kind;\n if (payloadKind === 'driver_blocked') return ['Use your best judgment', 'Pause for me', 'Try a different angle'];\n if (payloadKind === 'self_repair') return ['Approve fix', 'Skip', 'Tell me more'];\n return ['Approve', 'Skip'];\n}\n\n// ── Types shared with the commandPost module ───────────────────────\n//\n// We don't import the full CPApproval shape because that would couple\n// the mission lifecycle to internal commandPost types that may reshape.\n// Duck-type only what we need.\n\ninterface CPApprovalLike {\n id: string;\n type?: string;\n title?: string;\n reason?: string;\n requestedBy?: string;\n payload?: Record<string, unknown>;\n}\n\n// ── Helpers ────────────────────────────────────────────────────────\n\n/**\n * Bound the activity string that flows into MissionMember.currentActivity.\n *\n * v6.1.0-alpha.14 hotfix — was capping at 80 chars, which truncated mid-word\n * on long user prompts (\"Scout I want you to research AI Agents and how they\n * help bus…\") and there was no way to read the full text from the chat\n * surface. The 80-char cap was a holdover from when this was rendered in a\n * tiny tooltip; the actual typing pill in the chat has plenty of room.\n *\n * Bumped to 240 so even long goal prompts come through readable. The cap\n * exists only to bound the JSON payload size flowing through the bus and\n * SSE stream; the UI applies its own visual treatment (wrap + max-width +\n * title attribute) so even this length renders cleanly.\n */\n/**\n * v6.1.0-alpha.31 — turn a raw `tool_call` event into a friendly\n * sticky-note entry for the agent's activityLog. Returns null if the\n * tool isn't one we want to surface (silent background tools like\n * `memory_store`, `system_info`, etc. shouldn't clutter the desk).\n *\n * Each known research/work tool maps to:\n * - icon: a single emoji that reads at sticky-note scale\n * - activity: one-line summary (\"searched\", \"fetched\", \"wrote\")\n * - detail: the most informative argument value, truncated\n *\n * Unknown tools fall through to a generic \"used <name>\" — visible but\n * lightweight, so the user still sees activity even for tools we\n * haven't explicitly mapped.\n */\nfunction buildActivitySticky(\n name: string,\n args: Record<string, unknown>,\n): { icon: string; activity: string; detail?: string } | null {\n const pickStr = (...keys: string[]): string | undefined => {\n for (const k of keys) {\n const v = args[k];\n if (typeof v === 'string' && v.trim().length > 0) return v.trim();\n }\n return undefined;\n };\n const truncate = (s: string, max: number) => s.length <= max ? s : s.slice(0, max - 1).trimEnd() + '…';\n\n switch (name) {\n case 'web_search':\n case 'browser_search': {\n const query = pickStr('query', 'q', 'search');\n return { icon: '🔍', activity: 'searched the web', detail: query ? truncate(query, 100) : undefined };\n }\n case 'web_fetch':\n case 'browse_url':\n case 'web_read': {\n const url = pickStr('url', 'href');\n return { icon: '🌐', activity: 'read a page', detail: url ? truncate(url, 100) : undefined };\n }\n case 'web_browse_llm':\n case 'browser': {\n const target = pickStr('url', 'task');\n return { icon: '🌐', activity: 'browsed', detail: target ? truncate(target, 100) : undefined };\n }\n case 'write_file':\n case 'append_file':\n case 'edit_file':\n case 'apply_patch': {\n const path = pickStr('path', 'file', 'filename');\n return { icon: '✍️', activity: 'wrote a file', detail: path ? truncate(path, 100) : undefined };\n }\n case 'read_file': {\n const path = pickStr('path', 'file', 'filename');\n return { icon: '📖', activity: 'read a file', detail: path ? truncate(path, 100) : undefined };\n }\n case 'list_dir': {\n const path = pickStr('path', 'dir');\n return { icon: '📂', activity: 'listed a folder', detail: path ? truncate(path, 100) : undefined };\n }\n case 'shell':\n case 'exec':\n case 'code_exec':\n case 'execute_code': {\n const cmd = pickStr('command', 'cmd', 'code');\n return { icon: '⚙️', activity: 'ran a command', detail: cmd ? truncate(cmd, 100) : undefined };\n }\n case 'memory':\n case 'graph_remember':\n case 'memory_store': {\n const fact = pickStr('content', 'fact', 'value', 'key');\n return { icon: '💡', activity: 'memorized', detail: fact ? truncate(fact, 100) : undefined };\n }\n case 'graph_search':\n case 'graph_recall':\n case 'rag_search':\n case 'kb_search': {\n const q = pickStr('query', 'q');\n return { icon: '🧠', activity: 'recalled', detail: q ? truncate(q, 100) : undefined };\n }\n case 'screenshot':\n case 'browser_screenshot': {\n return { icon: '📷', activity: 'took a screenshot' };\n }\n case 'generate_image':\n case 'edit_image':\n case 'image_gen': {\n const prompt = pickStr('prompt', 'description');\n return { icon: '🎨', activity: 'drew an image', detail: prompt ? truncate(prompt, 100) : undefined };\n }\n case 'analyze_image':\n case 'vision': {\n return { icon: '👁️', activity: 'looked at an image' };\n }\n case 'github_repos':\n case 'github_issues':\n case 'github_prs':\n case 'github_commits':\n case 'github_files': {\n return { icon: '🐙', activity: name.replace('github_', 'checked GitHub '), detail: pickStr('repo', 'owner') };\n }\n // Silent / housekeeping tools — don't surface, keeps the desk tidy.\n case 'system_info':\n case 'current_model':\n case 'sessions_list':\n case 'sessions_history':\n case 'list_uploads':\n case 'list_active_widgets':\n case 'goal_list':\n case 'list_personas':\n case 'get_persona':\n case 'list_spaces':\n case 'interaction_log':\n case 'feedback_submit':\n return null;\n default:\n // Unknown tool — surface a generic sticky so the user still\n // sees the agent is doing something. Avoid noisy parameters.\n return { icon: '🛠️', activity: `used ${name}` };\n }\n}\n\nfunction shortenActivity(s: string | undefined): string | undefined {\n if (!s) return undefined;\n const trimmed = s.trim();\n if (trimmed.length <= 240) return trimmed;\n return trimmed.slice(0, 237).trimEnd() + '…';\n}\n"],"mappings":"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","names":[]}
@@ -9,6 +9,7 @@ import { getActivePersonaContent } from "../personas/manager.js";
9
9
  import { assembleSystemPrompt } from "./systemPromptParts.js";
10
10
  import { recordJournalEvent } from "./durableJournal.js";
11
11
  import { emitAgentEvent } from "./agentEvents.js";
12
+ import { formatCurrentDateContext } from "./dateContext.js";
12
13
  const COMPONENT = "SubAgent";
13
14
  const activeSubAgentIds = /* @__PURE__ */ new Set();
14
15
  const SUB_AGENT_TEMPLATES = {
@@ -381,8 +382,12 @@ async function spawnSubAgent(config) {
381
382
  persona: config.persona || "default",
382
383
  mode: "minimal"
383
384
  });
385
+ const dateContext = formatCurrentDateContext();
384
386
  let systemPrompt = `${titanCore}
385
387
 
388
+ ## Current Context
389
+ ${dateContext}
390
+
386
391
  ## Role
387
392
  ${roleTemplate}`;
388
393
  const personaId = config.persona;