@agimon-ai/doompi-team 0.0.1-alpha.73 → 0.0.1-alpha.76

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.
@@ -78,6 +78,23 @@ function publishRuns(directEvents, sessionId, jobs, nativeRuns) {
78
78
  runs.push(...[...nativeRuns.values()].map(presentNativeRun));
79
79
  directEvents.publish(require_webSubagents.SUBAGENT_RUNS_TYPE, sessionId, { runs });
80
80
  }
81
+ const TEAM_COST_STATUS = "doom-team-cost";
82
+ const COST_DECIMALS = 6;
83
+ function updateTeamCost(client, jobs, nativeRuns, costs, previousText, attached, force = false) {
84
+ const trackedCosts = /* @__PURE__ */ new Map();
85
+ for (const job of jobs) if (job.cost !== void 0 && Number.isFinite(job.cost)) trackedCosts.set(job.runId, job.cost);
86
+ for (const [runId, run] of nativeRuns) {
87
+ if (trackedCosts.has(runId) || run.cost === void 0 || !Number.isFinite(run.cost)) continue;
88
+ costs.set(runId, run.cost);
89
+ }
90
+ for (const [runId, cost] of trackedCosts) costs.set(runId, cost);
91
+ let total = 0;
92
+ for (const cost of costs.values()) total += cost;
93
+ const text = total > 0 ? String(Number(total.toFixed(COST_DECIMALS))) : void 0;
94
+ if (!force && text === previousText) return previousText;
95
+ if (attached) client.setStatus(TEAM_COST_STATUS, text);
96
+ return text;
97
+ }
81
98
  const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, host: serverHost, agent: host }) => {
82
99
  if (!host) throw new Error("Team session requires the headless host.");
83
100
  if (serverHost.context.environment === void 0) throw new Error("Team headless facet requires an admitted session environment.");
@@ -100,6 +117,26 @@ const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, h
100
117
  tracker: runtime.asyncJobTracker,
101
118
  loadConfig: () => require_index$2.loadConfig().config
102
119
  });
120
+ const nativeRuns = /* @__PURE__ */ new Map();
121
+ const childCosts = /* @__PURE__ */ new Map();
122
+ let costText;
123
+ let activityAttached = false;
124
+ const publishRunSnapshot = () => {
125
+ if (activityAttached) publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);
126
+ };
127
+ const publishCost = (force = false) => {
128
+ costText = updateTeamCost(execution.client, jobs.list(), nativeRuns, childCosts, costText, activityAttached, force);
129
+ };
130
+ const unsubscribeCostTracker = runtime.asyncJobTracker.subscribe(execution.sessionId, () => {
131
+ publishCost();
132
+ publishRunSnapshot();
133
+ });
134
+ const unsubscribeCostNative = require_index$12.subscribeNativeRunProjection(execution.sessionId, (runs) => {
135
+ nativeRuns.clear();
136
+ for (const run of runs) nativeRuns.set(run.runId, run);
137
+ publishCost();
138
+ publishRunSnapshot();
139
+ });
103
140
  let deliveries = Promise.resolve();
104
141
  runtime.completionNotifier.attachHost({ sendMessage: (message, options) => {
105
142
  const details = message.details ?? [];
@@ -160,13 +197,8 @@ const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, h
160
197
  activities: [{
161
198
  name: "@agimon-ai/doompi-team",
162
199
  async start(activityContext) {
163
- const nativeRuns = /* @__PURE__ */ new Map();
164
- const publishRunSnapshot = () => publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);
165
- const cleanups = [runtime.asyncJobTracker.subscribe(execution.sessionId, publishRunSnapshot), require_index$12.subscribeNativeRunProjection(execution.sessionId, (runs) => {
166
- nativeRuns.clear();
167
- for (const run of runs) nativeRuns.set(run.runId, run);
168
- publishRunSnapshot();
169
- })];
200
+ activityAttached = true;
201
+ publishCost(true);
170
202
  publishRunSnapshot();
171
203
  try {
172
204
  const discovered = runtime.discovery.discover(execution.cwd, "both").agents;
@@ -191,11 +223,14 @@ const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, h
191
223
  });
192
224
  channel.bindMainSession(activityContext.sessionId);
193
225
  return () => {
194
- for (const cleanup of cleanups.reverse()) cleanup();
226
+ if (!activityAttached) return;
227
+ activityAttached = false;
195
228
  channel.dispose();
229
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
196
230
  };
197
231
  } catch (error) {
198
- for (const cleanup of cleanups.reverse()) cleanup();
232
+ activityAttached = false;
233
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
199
234
  throw error;
200
235
  }
201
236
  }
@@ -204,6 +239,8 @@ const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, h
204
239
  runtime.pollScheduler.start();
205
240
  },
206
241
  async onDispose() {
242
+ activityAttached = false;
243
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
207
244
  channel.dispose();
208
245
  runtime.completionNotifier.dispose();
209
246
  const suspension = require_index$6.suspendScopeRuns({
@@ -215,6 +252,11 @@ const root = (0, _agimon_ai_doompi_core_extension_file.defineRoot)(({ context, h
215
252
  const nativeShutdown = runtime.nativeRuns.close().finally(() => require_index$12.nativeRunProjection.dispose(execution.sessionId));
216
253
  const outcomes = await Promise.allSettled([suspension, nativeShutdown]);
217
254
  for (const outcome of outcomes) if (outcome.status === "rejected") process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);
255
+ unsubscribeCostTracker();
256
+ unsubscribeCostNative();
257
+ nativeRuns.clear();
258
+ childCosts.clear();
259
+ costText = void 0;
218
260
  bridge.abandonAll();
219
261
  runtime.dispose();
220
262
  runtime.asyncJobTracker.stop();
@@ -1 +1 @@
1
- {"version":3,"file":"root.server.cjs","names":["SUBAGENT_RUNS_TYPE","defineRoot","createTeamExtensionRuntime","readDoomChildSessionService","createSessionScope","createDelegationBridge","loadConfig","formatCompletionHeadline","SUBAGENT_NOTIFY_MESSAGE_TYPE","toModelInfo","DOOM_DELEGATION_SERVICE","createBackgroundWorkService","DOOM_BACKGROUND_WORK_SERVICE","DOOM_SUBAGENT_POLICY_SERVICE","createSubagentPolicyService","subscribeNativeRunProjection","SUBAGENT_CATALOG_TYPE","presentCatalog","catalogModels","resolveActiveTeamModelSpecs","openScopeAsync","formatSuspendedRuns","suspendScopeRuns","nativeRunProjection"],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts"],"sourcesContent":["import { DOOM_BACKGROUND_WORK_SERVICE } from '@agimon-ai/doompi-core/background-work';\nimport { readDoomChildSessionService } from '@agimon-ai/doompi-core/child';\nimport { DOOM_DELEGATION_SERVICE } from '@agimon-ai/doompi-core/delegation';\nimport { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomHeadlessActivity } from '@agimon-ai/doompi-core/headless';\nimport type { DoomDirectEventBus } from '@agimon-ai/doompi-core/hub-channel';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/server-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { DOOM_SUBAGENT_POLICY_SERVICE } from '../../../../../schemas/subagentPolicy';\nimport { resolveActiveTeamModelSpecs } from '../../../../../services/agentDiscovery';\nimport type { NativeAsyncJobProjection, TrackedAsyncJob } from '../../../../../services/asyncJobTracker';\nimport { createBackgroundWorkService } from '../../../../../services/backgroundWorkService';\nimport { loadConfig } from '../../../../../services/config';\nimport { createDelegationBridge } from '../../../../../services/delegationBridge';\nimport { registerDirectRunBackgroundWork } from '../../../../../services/directRunBackgroundWork';\nimport { toModelInfo } from '../../../../../services/modelInfo';\nimport { nativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport { subscribeNativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport type { NativeTeamTransport } from '../../../../../services/nativeTeamChannel';\nimport { formatCompletionHeadline, SUBAGENT_NOTIFY_MESSAGE_TYPE } from '../../../../../services/notify';\nimport { openScopeAsync, suspendScopeRuns } from '../../../../../services/sessionLifecycle';\nimport { createSessionScope } from '../../../../../services/sessionPaths';\nimport { createSubagentPolicyService } from '../../../../../services/subagentPolicyService';\nimport { formatSuspendedRuns } from '../../../../../services/suspendedRuns';\nimport { createTeamExtensionRuntime } from '../../../../../services/teamRuntime';\nimport { catalogModels, presentCatalog } from '../../../../../services/webSubagentCatalog';\nimport {\n SUBAGENT_CATALOG_TYPE,\n SUBAGENT_RUNS_TYPE,\n type SubagentRun,\n type SubagentRunState,\n} from '../../../../../types/webSubagents';\n\ninterface SessionRunProjection extends SubagentRun {\n /** Host-private child transcript path consumed by the hub, not the browser. */\n sessionFile?: string;\n}\n\nconst TERMINAL_RUN_STATES = new Set(['completed', 'complete', 'failed', 'stopped', 'paused']);\n\nfunction runState(status: string): SubagentRunState {\n if (status === 'completed' || status === 'complete') return 'done';\n if (status === 'failed') return 'failed';\n if (status === 'stopped' || status === 'paused') return 'stopped';\n if (status === 'queued') return 'queued';\n return 'running';\n}\n\nfunction presentTrackedRun(run: TrackedAsyncJob): SessionRunProjection | undefined {\n if (!run.agent || !run.status || run.startedAt === undefined) return undefined;\n const lastUpdate = run.updatedAt ?? run.startedAt;\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task ?? '',\n cwd: run.cwd ?? '',\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: lastUpdate } : {}),\n lastUpdate,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction presentNativeRun(run: NativeAsyncJobProjection): SessionRunProjection {\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task,\n cwd: run.cwd,\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: run.updatedAt } : {}),\n lastUpdate: run.updatedAt,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction publishRuns(\n directEvents: DoomDirectEventBus,\n sessionId: string,\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n): void {\n const runs = jobs\n .filter((job) => !job.native)\n .map(presentTrackedRun)\n .filter((run): run is SessionRunProjection => run !== undefined);\n runs.push(...[...nativeRuns.values()].map(presentNativeRun));\n directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });\n}\n\nconst root = defineRoot(({ context, host: serverHost, agent: host }: DoomServerPluginContext) => {\n if (!host) throw new Error('Team session requires the headless host.');\n if (serverHost.context.environment === undefined) {\n throw new Error('Team headless facet requires an admitted session environment.');\n }\n const directEvents = serverHost.context.directEvents;\n if (directEvents === undefined) {\n throw new Error('Team headless facet requires host-owned direct events.');\n }\n const execution = host.context;\n const runtime = createTeamExtensionRuntime(undefined, {\n environment: serverHost.context.environment,\n childSessions: { get: () => readDoomChildSessionService(context) },\n nativeRunProjection,\n });\n const scope = createSessionScope(execution.sessionId);\n runtime.management.bindSessionScope(scope);\n const jobs = runtime.asyncJobTracker.forSession(execution.sessionId, scope);\n const bridge = createDelegationBridge({\n planner: runtime.spawnPlanner,\n management: runtime.management,\n waiter: runtime.subagentWaiter,\n scheduler: runtime.pollScheduler,\n tracker: runtime.asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n\n // Three jobs the Pi facet gets from one `ExtensionAPI.sendMessage`, which a\n // headless session has to do by hand:\n //\n // toast `client.notify` - operator-facing only. It writes a\n // `doom-notification` custom entry whose body is flattened to a\n // single line and capped at 4096 characters, and the model never\n // sees custom entries. So it gets a headline, not the result.\n // transcript `session.appendCustomEntry` - the durable record, under the\n // same custom type the TUI renderer uses, with the full\n // multi-line content and the structured details.\n // wake `session.admitPrompt(content, 'steer')` - the only call that\n // puts the completion in front of the model. 'steer' means \"how\n // to deliver if a turn is running\"; an idle agent is woken\n // either way. `session.prompt(_, 'steer')` would NOT do this: it\n // is enqueue-only and parks the message on an idle lane.\n //\n // Serialised through one tail promise. Failures bypass the notifier's batcher\n // (`services/notify`), so two runs failing in the same tick produce two\n // deliveries; sequencing them keeps each one observing settled lane state.\n let deliveries: Promise<void> = Promise.resolve();\n runtime.completionNotifier.attachHost({\n sendMessage: (message, options) => {\n const details = message.details ?? [];\n const headline = details.length > 0 ? formatCompletionHeadline(details) : message.content;\n const failed = details.some((detail) => detail.status !== 'completed');\n const delivery = deliveries.then(async () => {\n await execution.client.notify({ body: headline, level: failed ? 'warning' : 'info' });\n await execution.session.appendCustomEntry(SUBAGENT_NOTIFY_MESSAGE_TYPE, {\n content: message.content,\n details,\n });\n // `triggerTurn: false` has no faithful headless mapping: there is no\n // way to put text in model context without starting or joining a turn\n // (`appendCustomEntry` is journal-only). Keep today's behaviour - the\n // operator sees it, the model does not wake - rather than guessing.\n if (options?.triggerTurn === false) return;\n // Matching the voice facet, which also refuses rather than degrading:\n // `session.prompt(_, 'steer')` is NOT a fallback here, because it is\n // enqueue-only and would park the completion on an idle lane - the\n // original bug. Refusing surfaces as non-delivery, which keeps\n // `ResultWatcher`'s claim alive for a retry.\n if (!execution.session.admitPrompt) throw new Error('The session cannot admit a completion prompt.');\n await execution.session.admitPrompt(message.content, 'steer');\n });\n // The notifier reports non-delivery on a rejection, which keeps\n // `ResultWatcher`'s claim alive for a retry. The tail must not inherit\n // that rejection, or one failed completion would poison every later one.\n deliveries = delivery.catch(() => undefined);\n return delivery;\n },\n });\n const transport: NativeTeamTransport = {\n sendMessage: ((message) =>\n void execution.client.notify({\n body:\n typeof message.content === 'string'\n ? message.content\n : message.content.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n level: 'info',\n })) as NativeTeamTransport['sendMessage'],\n sendUserMessage: ((message) =>\n void execution.session.prompt(\n typeof message === 'string'\n ? message\n : message.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n 'steer',\n )) as NativeTeamTransport['sendUserMessage'],\n };\n const channel = runtime.teamChannel.createHeadlessRuntime(transport);\n const serverService = (ctx: Context) => {\n ctx.plugin((providerContext) => {\n const availableModels = execution.model ? [toModelInfo(execution.model)] : [];\n providerContext.provide(\n DOOM_DELEGATION_SERVICE,\n bridge.createService(providerContext, {\n sessionId: execution.sessionId,\n sessionScope: createSessionScope(execution.sessionId),\n availableModels,\n ...(execution.model ? { parentModel: execution.model } : {}),\n // Read fresh per request, exactly as the Pi facet does at root.cli.ts.\n // Omitting this is what made every headless `context: 'fork'`\n // delegation report that the parent had no capturable branch.\n captureForkSource: () => execution.session?.forkSource?.(),\n }),\n );\n const backgroundWork = createBackgroundWorkService(providerContext);\n providerContext.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n registerDirectRunBackgroundWork(providerContext, backgroundWork, execution.sessionId, runtime.asyncJobTracker);\n providerContext.provide(DOOM_SUBAGENT_POLICY_SERVICE, createSubagentPolicyService(runtime.capabilityPolicies));\n });\n };\n\n const environment = serverHost.context.environment;\n const activity: DoomHeadlessActivity = {\n name: '@agimon-ai/doompi-team',\n async start(activityContext) {\n const nativeRuns = new Map<string, NativeAsyncJobProjection>();\n const publishRunSnapshot = (): void => publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);\n const cleanups = [\n runtime.asyncJobTracker.subscribe(execution.sessionId, publishRunSnapshot),\n subscribeNativeRunProjection(execution.sessionId, (runs) => {\n nativeRuns.clear();\n for (const run of runs) nativeRuns.set(run.runId, run);\n publishRunSnapshot();\n }),\n ];\n publishRunSnapshot();\n try {\n const discovered = runtime.discovery.discover(execution.cwd, 'both').agents;\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: presentCatalog(discovered),\n models: catalogModels(discovered, resolveActiveTeamModelSpecs(environment) ?? []),\n });\n } catch (error) {\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: [],\n models: [],\n warning: error instanceof Error ? error.message : String(error),\n });\n }\n try {\n const opened = await openScopeAsync(scope);\n if (opened.suspended.length)\n await activityContext.client.notify({ body: formatSuspendedRuns(opened.suspended), level: 'info' });\n channel.bindMainSession(activityContext.sessionId);\n return () => {\n for (const cleanup of cleanups.reverse()) cleanup();\n channel.dispose();\n };\n } catch (error) {\n for (const cleanup of cleanups.reverse()) cleanup();\n throw error;\n }\n },\n };\n\n return {\n value: {\n runtime,\n execution,\n scope,\n jobs,\n bridge,\n channel,\n directEvents,\n environment: serverHost.context.environment,\n },\n services: [serverService],\n activities: [activity],\n // Mirrors `root.cli.ts`, which starts the scheduler in its own `onStart`.\n // Without this the delegation bridge's progress subscription never ticks\n // and `scheduler.wake()` is a no-op, so the pre-timeout nudge a long\n // delegation gets in the TUI never fires headless. `onDispose` below\n // already stops it; this is the missing half of that pair.\n onStart() {\n runtime.pollScheduler.start();\n },\n async onDispose() {\n channel.dispose();\n runtime.completionNotifier.dispose();\n const suspension = suspendScopeRuns({\n scope,\n reason: 'headless session ended',\n jobs: jobs.list(),\n stop: (runId, reason) => runtime.management.stop(runId, reason),\n });\n const nativeShutdown = runtime.nativeRuns.close().finally(() => nativeRunProjection.dispose(execution.sessionId));\n const outcomes = await Promise.allSettled([suspension, nativeShutdown]);\n for (const outcome of outcomes) {\n if (outcome.status === 'rejected')\n process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);\n }\n bridge.abandonAll();\n runtime.dispose();\n runtime.asyncJobTracker.stop();\n runtime.pollScheduler.stop();\n },\n };\n});\n\nexport type TeamServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n"],"mappings":"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"}
1
+ {"version":3,"file":"root.server.cjs","names":["SUBAGENT_RUNS_TYPE","defineRoot","createTeamExtensionRuntime","readDoomChildSessionService","createSessionScope","createDelegationBridge","loadConfig","subscribeNativeRunProjection","formatCompletionHeadline","SUBAGENT_NOTIFY_MESSAGE_TYPE","toModelInfo","DOOM_DELEGATION_SERVICE","createBackgroundWorkService","DOOM_BACKGROUND_WORK_SERVICE","DOOM_SUBAGENT_POLICY_SERVICE","createSubagentPolicyService","SUBAGENT_CATALOG_TYPE","presentCatalog","catalogModels","resolveActiveTeamModelSpecs","openScopeAsync","formatSuspendedRuns","suspendScopeRuns","nativeRunProjection"],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts"],"sourcesContent":["import { DOOM_BACKGROUND_WORK_SERVICE } from '@agimon-ai/doompi-core/background-work';\nimport { readDoomChildSessionService } from '@agimon-ai/doompi-core/child';\nimport { DOOM_DELEGATION_SERVICE } from '@agimon-ai/doompi-core/delegation';\nimport { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomHeadlessActivity } from '@agimon-ai/doompi-core/headless';\nimport type { DoomDirectEventBus } from '@agimon-ai/doompi-core/hub-channel';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/server-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { DOOM_SUBAGENT_POLICY_SERVICE } from '../../../../../schemas/subagentPolicy';\nimport { resolveActiveTeamModelSpecs } from '../../../../../services/agentDiscovery';\nimport type { NativeAsyncJobProjection, TrackedAsyncJob } from '../../../../../services/asyncJobTracker';\nimport { createBackgroundWorkService } from '../../../../../services/backgroundWorkService';\nimport { loadConfig } from '../../../../../services/config';\nimport { createDelegationBridge } from '../../../../../services/delegationBridge';\nimport { registerDirectRunBackgroundWork } from '../../../../../services/directRunBackgroundWork';\nimport { toModelInfo } from '../../../../../services/modelInfo';\nimport { nativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport { subscribeNativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport type { NativeTeamTransport } from '../../../../../services/nativeTeamChannel';\nimport { formatCompletionHeadline, SUBAGENT_NOTIFY_MESSAGE_TYPE } from '../../../../../services/notify';\nimport { openScopeAsync, suspendScopeRuns } from '../../../../../services/sessionLifecycle';\nimport { createSessionScope } from '../../../../../services/sessionPaths';\nimport { createSubagentPolicyService } from '../../../../../services/subagentPolicyService';\nimport { formatSuspendedRuns } from '../../../../../services/suspendedRuns';\nimport { createTeamExtensionRuntime } from '../../../../../services/teamRuntime';\nimport { catalogModels, presentCatalog } from '../../../../../services/webSubagentCatalog';\nimport {\n SUBAGENT_CATALOG_TYPE,\n SUBAGENT_RUNS_TYPE,\n type SubagentRun,\n type SubagentRunState,\n} from '../../../../../types/webSubagents';\n\ninterface SessionRunProjection extends SubagentRun {\n /** Host-private child transcript path consumed by the hub, not the browser. */\n sessionFile?: string;\n}\n\nconst TERMINAL_RUN_STATES = new Set(['completed', 'complete', 'failed', 'stopped', 'paused']);\n\nfunction runState(status: string): SubagentRunState {\n if (status === 'completed' || status === 'complete') return 'done';\n if (status === 'failed') return 'failed';\n if (status === 'stopped' || status === 'paused') return 'stopped';\n if (status === 'queued') return 'queued';\n return 'running';\n}\n\nfunction presentTrackedRun(run: TrackedAsyncJob): SessionRunProjection | undefined {\n if (!run.agent || !run.status || run.startedAt === undefined) return undefined;\n const lastUpdate = run.updatedAt ?? run.startedAt;\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task ?? '',\n cwd: run.cwd ?? '',\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: lastUpdate } : {}),\n lastUpdate,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction presentNativeRun(run: NativeAsyncJobProjection): SessionRunProjection {\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task,\n cwd: run.cwd,\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: run.updatedAt } : {}),\n lastUpdate: run.updatedAt,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction publishRuns(\n directEvents: DoomDirectEventBus,\n sessionId: string,\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n): void {\n const runs = jobs\n .filter((job) => !job.native)\n .map(presentTrackedRun)\n .filter((run): run is SessionRunProjection => run !== undefined);\n runs.push(...[...nativeRuns.values()].map(presentNativeRun));\n directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });\n}\n\nconst TEAM_COST_STATUS = 'doom-team-cost';\nconst COST_DECIMALS = 6;\n\nfunction updateTeamCost(\n client: { setStatus(source: string, text: string | undefined): void },\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n costs: Map<string, number>,\n previousText: string | undefined,\n attached: boolean,\n force = false,\n): string | undefined {\n const trackedCosts = new Map<string, number>();\n for (const job of jobs) {\n if (job.cost !== undefined && Number.isFinite(job.cost)) trackedCosts.set(job.runId, job.cost);\n }\n for (const [runId, run] of nativeRuns) {\n if (trackedCosts.has(runId) || run.cost === undefined || !Number.isFinite(run.cost)) continue;\n costs.set(runId, run.cost);\n }\n for (const [runId, cost] of trackedCosts) costs.set(runId, cost);\n\n let total = 0;\n for (const cost of costs.values()) total += cost;\n const text = total > 0 ? String(Number(total.toFixed(COST_DECIMALS))) : undefined;\n if (!force && text === previousText) return previousText;\n if (attached) client.setStatus(TEAM_COST_STATUS, text);\n return text;\n}\n\nconst root = defineRoot(({ context, host: serverHost, agent: host }: DoomServerPluginContext) => {\n if (!host) throw new Error('Team session requires the headless host.');\n if (serverHost.context.environment === undefined) {\n throw new Error('Team headless facet requires an admitted session environment.');\n }\n const directEvents = serverHost.context.directEvents;\n if (directEvents === undefined) {\n throw new Error('Team headless facet requires host-owned direct events.');\n }\n const execution = host.context;\n const runtime = createTeamExtensionRuntime(undefined, {\n environment: serverHost.context.environment,\n childSessions: { get: () => readDoomChildSessionService(context) },\n nativeRunProjection,\n });\n const scope = createSessionScope(execution.sessionId);\n runtime.management.bindSessionScope(scope);\n const jobs = runtime.asyncJobTracker.forSession(execution.sessionId, scope);\n const bridge = createDelegationBridge({\n planner: runtime.spawnPlanner,\n management: runtime.management,\n waiter: runtime.subagentWaiter,\n scheduler: runtime.pollScheduler,\n tracker: runtime.asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const nativeRuns = new Map<string, NativeAsyncJobProjection>();\n const childCosts = new Map<string, number>();\n let costText: string | undefined;\n let activityAttached = false;\n const publishRunSnapshot = (): void => {\n if (activityAttached) publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);\n };\n const publishCost = (force = false): void => {\n costText = updateTeamCost(execution.client, jobs.list(), nativeRuns, childCosts, costText, activityAttached, force);\n };\n const unsubscribeCostTracker = runtime.asyncJobTracker.subscribe(execution.sessionId, () => {\n publishCost();\n publishRunSnapshot();\n });\n const unsubscribeCostNative = subscribeNativeRunProjection(execution.sessionId, (runs) => {\n nativeRuns.clear();\n for (const run of runs) nativeRuns.set(run.runId, run);\n publishCost();\n publishRunSnapshot();\n });\n\n // Three jobs the Pi facet gets from one `ExtensionAPI.sendMessage`, which a\n // headless session has to do by hand:\n //\n // toast `client.notify` - operator-facing only. It writes a\n // `doom-notification` custom entry whose body is flattened to a\n // single line and capped at 4096 characters, and the model never\n // sees custom entries. So it gets a headline, not the result.\n // transcript `session.appendCustomEntry` - the durable record, under the\n // same custom type the TUI renderer uses, with the full\n // multi-line content and the structured details.\n // wake `session.admitPrompt(content, 'steer')` - the only call that\n // puts the completion in front of the model. 'steer' means \"how\n // to deliver if a turn is running\"; an idle agent is woken\n // either way. `session.prompt(_, 'steer')` would NOT do this: it\n // is enqueue-only and parks the message on an idle lane.\n //\n // Serialised through one tail promise. Failures bypass the notifier's batcher\n // (`services/notify`), so two runs failing in the same tick produce two\n // deliveries; sequencing them keeps each one observing settled lane state.\n let deliveries: Promise<void> = Promise.resolve();\n runtime.completionNotifier.attachHost({\n sendMessage: (message, options) => {\n const details = message.details ?? [];\n const headline = details.length > 0 ? formatCompletionHeadline(details) : message.content;\n const failed = details.some((detail) => detail.status !== 'completed');\n const delivery = deliveries.then(async () => {\n await execution.client.notify({ body: headline, level: failed ? 'warning' : 'info' });\n await execution.session.appendCustomEntry(SUBAGENT_NOTIFY_MESSAGE_TYPE, {\n content: message.content,\n details,\n });\n // `triggerTurn: false` has no faithful headless mapping: there is no\n // way to put text in model context without starting or joining a turn\n // (`appendCustomEntry` is journal-only). Keep today's behaviour - the\n // operator sees it, the model does not wake - rather than guessing.\n if (options?.triggerTurn === false) return;\n // Matching the voice facet, which also refuses rather than degrading:\n // `session.prompt(_, 'steer')` is NOT a fallback here, because it is\n // enqueue-only and would park the completion on an idle lane - the\n // original bug. Refusing surfaces as non-delivery, which keeps\n // `ResultWatcher`'s claim alive for a retry.\n if (!execution.session.admitPrompt) throw new Error('The session cannot admit a completion prompt.');\n await execution.session.admitPrompt(message.content, 'steer');\n });\n // The notifier reports non-delivery on a rejection, which keeps\n // `ResultWatcher`'s claim alive for a retry. The tail must not inherit\n // that rejection, or one failed completion would poison every later one.\n deliveries = delivery.catch(() => undefined);\n return delivery;\n },\n });\n const transport: NativeTeamTransport = {\n sendMessage: ((message) =>\n void execution.client.notify({\n body:\n typeof message.content === 'string'\n ? message.content\n : message.content.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n level: 'info',\n })) as NativeTeamTransport['sendMessage'],\n sendUserMessage: ((message) =>\n void execution.session.prompt(\n typeof message === 'string'\n ? message\n : message.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n 'steer',\n )) as NativeTeamTransport['sendUserMessage'],\n };\n const channel = runtime.teamChannel.createHeadlessRuntime(transport);\n const serverService = (ctx: Context) => {\n ctx.plugin((providerContext) => {\n const availableModels = execution.model ? [toModelInfo(execution.model)] : [];\n providerContext.provide(\n DOOM_DELEGATION_SERVICE,\n bridge.createService(providerContext, {\n sessionId: execution.sessionId,\n sessionScope: createSessionScope(execution.sessionId),\n availableModels,\n ...(execution.model ? { parentModel: execution.model } : {}),\n // Read fresh per request, exactly as the Pi facet does at root.cli.ts.\n // Omitting this is what made every headless `context: 'fork'`\n // delegation report that the parent had no capturable branch.\n captureForkSource: () => execution.session?.forkSource?.(),\n }),\n );\n const backgroundWork = createBackgroundWorkService(providerContext);\n providerContext.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n registerDirectRunBackgroundWork(providerContext, backgroundWork, execution.sessionId, runtime.asyncJobTracker);\n providerContext.provide(DOOM_SUBAGENT_POLICY_SERVICE, createSubagentPolicyService(runtime.capabilityPolicies));\n });\n };\n\n const environment = serverHost.context.environment;\n const activity: DoomHeadlessActivity = {\n name: '@agimon-ai/doompi-team',\n async start(activityContext) {\n activityAttached = true;\n publishCost(true);\n publishRunSnapshot();\n try {\n const discovered = runtime.discovery.discover(execution.cwd, 'both').agents;\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: presentCatalog(discovered),\n models: catalogModels(discovered, resolveActiveTeamModelSpecs(environment) ?? []),\n });\n } catch (error) {\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: [],\n models: [],\n warning: error instanceof Error ? error.message : String(error),\n });\n }\n try {\n const opened = await openScopeAsync(scope);\n if (opened.suspended.length)\n await activityContext.client.notify({ body: formatSuspendedRuns(opened.suspended), level: 'info' });\n channel.bindMainSession(activityContext.sessionId);\n return () => {\n if (!activityAttached) return;\n activityAttached = false;\n channel.dispose();\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n };\n } catch (error) {\n activityAttached = false;\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n throw error;\n }\n },\n };\n\n return {\n value: {\n runtime,\n execution,\n scope,\n jobs,\n bridge,\n channel,\n directEvents,\n environment: serverHost.context.environment,\n },\n services: [serverService],\n activities: [activity],\n // Mirrors `root.cli.ts`, which starts the scheduler in its own `onStart`.\n // Without this the delegation bridge's progress subscription never ticks\n // and `scheduler.wake()` is a no-op, so the pre-timeout nudge a long\n // delegation gets in the TUI never fires headless. `onDispose` below\n // already stops it; this is the missing half of that pair.\n onStart() {\n runtime.pollScheduler.start();\n },\n async onDispose() {\n activityAttached = false;\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n channel.dispose();\n runtime.completionNotifier.dispose();\n const suspension = suspendScopeRuns({\n scope,\n reason: 'headless session ended',\n jobs: jobs.list(),\n stop: (runId, reason) => runtime.management.stop(runId, reason),\n });\n const nativeShutdown = runtime.nativeRuns.close().finally(() => nativeRunProjection.dispose(execution.sessionId));\n const outcomes = await Promise.allSettled([suspension, nativeShutdown]);\n for (const outcome of outcomes) {\n if (outcome.status === 'rejected')\n process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);\n }\n unsubscribeCostTracker();\n unsubscribeCostNative();\n nativeRuns.clear();\n childCosts.clear();\n costText = undefined;\n bridge.abandonAll();\n runtime.dispose();\n runtime.asyncJobTracker.stop();\n runtime.pollScheduler.stop();\n },\n };\n});\n\nexport type TeamServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n"],"mappings":"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"}
@@ -78,6 +78,23 @@ function publishRuns(directEvents, sessionId, jobs, nativeRuns) {
78
78
  runs.push(...[...nativeRuns.values()].map(presentNativeRun));
79
79
  directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });
80
80
  }
81
+ const TEAM_COST_STATUS = "doom-team-cost";
82
+ const COST_DECIMALS = 6;
83
+ function updateTeamCost(client, jobs, nativeRuns, costs, previousText, attached, force = false) {
84
+ const trackedCosts = /* @__PURE__ */ new Map();
85
+ for (const job of jobs) if (job.cost !== void 0 && Number.isFinite(job.cost)) trackedCosts.set(job.runId, job.cost);
86
+ for (const [runId, run] of nativeRuns) {
87
+ if (trackedCosts.has(runId) || run.cost === void 0 || !Number.isFinite(run.cost)) continue;
88
+ costs.set(runId, run.cost);
89
+ }
90
+ for (const [runId, cost] of trackedCosts) costs.set(runId, cost);
91
+ let total = 0;
92
+ for (const cost of costs.values()) total += cost;
93
+ const text = total > 0 ? String(Number(total.toFixed(COST_DECIMALS))) : void 0;
94
+ if (!force && text === previousText) return previousText;
95
+ if (attached) client.setStatus(TEAM_COST_STATUS, text);
96
+ return text;
97
+ }
81
98
  const root = defineRoot(({ context, host: serverHost, agent: host }) => {
82
99
  if (!host) throw new Error("Team session requires the headless host.");
83
100
  if (serverHost.context.environment === void 0) throw new Error("Team headless facet requires an admitted session environment.");
@@ -100,6 +117,26 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
100
117
  tracker: runtime.asyncJobTracker,
101
118
  loadConfig: () => loadConfig().config
102
119
  });
120
+ const nativeRuns = /* @__PURE__ */ new Map();
121
+ const childCosts = /* @__PURE__ */ new Map();
122
+ let costText;
123
+ let activityAttached = false;
124
+ const publishRunSnapshot = () => {
125
+ if (activityAttached) publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);
126
+ };
127
+ const publishCost = (force = false) => {
128
+ costText = updateTeamCost(execution.client, jobs.list(), nativeRuns, childCosts, costText, activityAttached, force);
129
+ };
130
+ const unsubscribeCostTracker = runtime.asyncJobTracker.subscribe(execution.sessionId, () => {
131
+ publishCost();
132
+ publishRunSnapshot();
133
+ });
134
+ const unsubscribeCostNative = subscribeNativeRunProjection(execution.sessionId, (runs) => {
135
+ nativeRuns.clear();
136
+ for (const run of runs) nativeRuns.set(run.runId, run);
137
+ publishCost();
138
+ publishRunSnapshot();
139
+ });
103
140
  let deliveries = Promise.resolve();
104
141
  runtime.completionNotifier.attachHost({ sendMessage: (message, options) => {
105
142
  const details = message.details ?? [];
@@ -160,13 +197,8 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
160
197
  activities: [{
161
198
  name: "@agimon-ai/doompi-team",
162
199
  async start(activityContext) {
163
- const nativeRuns = /* @__PURE__ */ new Map();
164
- const publishRunSnapshot = () => publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);
165
- const cleanups = [runtime.asyncJobTracker.subscribe(execution.sessionId, publishRunSnapshot), subscribeNativeRunProjection(execution.sessionId, (runs) => {
166
- nativeRuns.clear();
167
- for (const run of runs) nativeRuns.set(run.runId, run);
168
- publishRunSnapshot();
169
- })];
200
+ activityAttached = true;
201
+ publishCost(true);
170
202
  publishRunSnapshot();
171
203
  try {
172
204
  const discovered = runtime.discovery.discover(execution.cwd, "both").agents;
@@ -191,11 +223,14 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
191
223
  });
192
224
  channel.bindMainSession(activityContext.sessionId);
193
225
  return () => {
194
- for (const cleanup of cleanups.reverse()) cleanup();
226
+ if (!activityAttached) return;
227
+ activityAttached = false;
195
228
  channel.dispose();
229
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
196
230
  };
197
231
  } catch (error) {
198
- for (const cleanup of cleanups.reverse()) cleanup();
232
+ activityAttached = false;
233
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
199
234
  throw error;
200
235
  }
201
236
  }
@@ -204,6 +239,8 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
204
239
  runtime.pollScheduler.start();
205
240
  },
206
241
  async onDispose() {
242
+ activityAttached = false;
243
+ execution.client.setStatus(TEAM_COST_STATUS, void 0);
207
244
  channel.dispose();
208
245
  runtime.completionNotifier.dispose();
209
246
  const suspension = suspendScopeRuns({
@@ -215,6 +252,11 @@ const root = defineRoot(({ context, host: serverHost, agent: host }) => {
215
252
  const nativeShutdown = runtime.nativeRuns.close().finally(() => nativeRunProjection.dispose(execution.sessionId));
216
253
  const outcomes = await Promise.allSettled([suspension, nativeShutdown]);
217
254
  for (const outcome of outcomes) if (outcome.status === "rejected") process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);
255
+ unsubscribeCostTracker();
256
+ unsubscribeCostNative();
257
+ nativeRuns.clear();
258
+ childCosts.clear();
259
+ costText = void 0;
218
260
  bridge.abandonAll();
219
261
  runtime.dispose();
220
262
  runtime.asyncJobTracker.stop();
@@ -1 +1 @@
1
- {"version":3,"file":"root.server.mjs","names":[],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts"],"sourcesContent":["import { DOOM_BACKGROUND_WORK_SERVICE } from '@agimon-ai/doompi-core/background-work';\nimport { readDoomChildSessionService } from '@agimon-ai/doompi-core/child';\nimport { DOOM_DELEGATION_SERVICE } from '@agimon-ai/doompi-core/delegation';\nimport { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomHeadlessActivity } from '@agimon-ai/doompi-core/headless';\nimport type { DoomDirectEventBus } from '@agimon-ai/doompi-core/hub-channel';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/server-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { DOOM_SUBAGENT_POLICY_SERVICE } from '../../../../../schemas/subagentPolicy';\nimport { resolveActiveTeamModelSpecs } from '../../../../../services/agentDiscovery';\nimport type { NativeAsyncJobProjection, TrackedAsyncJob } from '../../../../../services/asyncJobTracker';\nimport { createBackgroundWorkService } from '../../../../../services/backgroundWorkService';\nimport { loadConfig } from '../../../../../services/config';\nimport { createDelegationBridge } from '../../../../../services/delegationBridge';\nimport { registerDirectRunBackgroundWork } from '../../../../../services/directRunBackgroundWork';\nimport { toModelInfo } from '../../../../../services/modelInfo';\nimport { nativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport { subscribeNativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport type { NativeTeamTransport } from '../../../../../services/nativeTeamChannel';\nimport { formatCompletionHeadline, SUBAGENT_NOTIFY_MESSAGE_TYPE } from '../../../../../services/notify';\nimport { openScopeAsync, suspendScopeRuns } from '../../../../../services/sessionLifecycle';\nimport { createSessionScope } from '../../../../../services/sessionPaths';\nimport { createSubagentPolicyService } from '../../../../../services/subagentPolicyService';\nimport { formatSuspendedRuns } from '../../../../../services/suspendedRuns';\nimport { createTeamExtensionRuntime } from '../../../../../services/teamRuntime';\nimport { catalogModels, presentCatalog } from '../../../../../services/webSubagentCatalog';\nimport {\n SUBAGENT_CATALOG_TYPE,\n SUBAGENT_RUNS_TYPE,\n type SubagentRun,\n type SubagentRunState,\n} from '../../../../../types/webSubagents';\n\ninterface SessionRunProjection extends SubagentRun {\n /** Host-private child transcript path consumed by the hub, not the browser. */\n sessionFile?: string;\n}\n\nconst TERMINAL_RUN_STATES = new Set(['completed', 'complete', 'failed', 'stopped', 'paused']);\n\nfunction runState(status: string): SubagentRunState {\n if (status === 'completed' || status === 'complete') return 'done';\n if (status === 'failed') return 'failed';\n if (status === 'stopped' || status === 'paused') return 'stopped';\n if (status === 'queued') return 'queued';\n return 'running';\n}\n\nfunction presentTrackedRun(run: TrackedAsyncJob): SessionRunProjection | undefined {\n if (!run.agent || !run.status || run.startedAt === undefined) return undefined;\n const lastUpdate = run.updatedAt ?? run.startedAt;\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task ?? '',\n cwd: run.cwd ?? '',\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: lastUpdate } : {}),\n lastUpdate,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction presentNativeRun(run: NativeAsyncJobProjection): SessionRunProjection {\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task,\n cwd: run.cwd,\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: run.updatedAt } : {}),\n lastUpdate: run.updatedAt,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction publishRuns(\n directEvents: DoomDirectEventBus,\n sessionId: string,\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n): void {\n const runs = jobs\n .filter((job) => !job.native)\n .map(presentTrackedRun)\n .filter((run): run is SessionRunProjection => run !== undefined);\n runs.push(...[...nativeRuns.values()].map(presentNativeRun));\n directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });\n}\n\nconst root = defineRoot(({ context, host: serverHost, agent: host }: DoomServerPluginContext) => {\n if (!host) throw new Error('Team session requires the headless host.');\n if (serverHost.context.environment === undefined) {\n throw new Error('Team headless facet requires an admitted session environment.');\n }\n const directEvents = serverHost.context.directEvents;\n if (directEvents === undefined) {\n throw new Error('Team headless facet requires host-owned direct events.');\n }\n const execution = host.context;\n const runtime = createTeamExtensionRuntime(undefined, {\n environment: serverHost.context.environment,\n childSessions: { get: () => readDoomChildSessionService(context) },\n nativeRunProjection,\n });\n const scope = createSessionScope(execution.sessionId);\n runtime.management.bindSessionScope(scope);\n const jobs = runtime.asyncJobTracker.forSession(execution.sessionId, scope);\n const bridge = createDelegationBridge({\n planner: runtime.spawnPlanner,\n management: runtime.management,\n waiter: runtime.subagentWaiter,\n scheduler: runtime.pollScheduler,\n tracker: runtime.asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n\n // Three jobs the Pi facet gets from one `ExtensionAPI.sendMessage`, which a\n // headless session has to do by hand:\n //\n // toast `client.notify` - operator-facing only. It writes a\n // `doom-notification` custom entry whose body is flattened to a\n // single line and capped at 4096 characters, and the model never\n // sees custom entries. So it gets a headline, not the result.\n // transcript `session.appendCustomEntry` - the durable record, under the\n // same custom type the TUI renderer uses, with the full\n // multi-line content and the structured details.\n // wake `session.admitPrompt(content, 'steer')` - the only call that\n // puts the completion in front of the model. 'steer' means \"how\n // to deliver if a turn is running\"; an idle agent is woken\n // either way. `session.prompt(_, 'steer')` would NOT do this: it\n // is enqueue-only and parks the message on an idle lane.\n //\n // Serialised through one tail promise. Failures bypass the notifier's batcher\n // (`services/notify`), so two runs failing in the same tick produce two\n // deliveries; sequencing them keeps each one observing settled lane state.\n let deliveries: Promise<void> = Promise.resolve();\n runtime.completionNotifier.attachHost({\n sendMessage: (message, options) => {\n const details = message.details ?? [];\n const headline = details.length > 0 ? formatCompletionHeadline(details) : message.content;\n const failed = details.some((detail) => detail.status !== 'completed');\n const delivery = deliveries.then(async () => {\n await execution.client.notify({ body: headline, level: failed ? 'warning' : 'info' });\n await execution.session.appendCustomEntry(SUBAGENT_NOTIFY_MESSAGE_TYPE, {\n content: message.content,\n details,\n });\n // `triggerTurn: false` has no faithful headless mapping: there is no\n // way to put text in model context without starting or joining a turn\n // (`appendCustomEntry` is journal-only). Keep today's behaviour - the\n // operator sees it, the model does not wake - rather than guessing.\n if (options?.triggerTurn === false) return;\n // Matching the voice facet, which also refuses rather than degrading:\n // `session.prompt(_, 'steer')` is NOT a fallback here, because it is\n // enqueue-only and would park the completion on an idle lane - the\n // original bug. Refusing surfaces as non-delivery, which keeps\n // `ResultWatcher`'s claim alive for a retry.\n if (!execution.session.admitPrompt) throw new Error('The session cannot admit a completion prompt.');\n await execution.session.admitPrompt(message.content, 'steer');\n });\n // The notifier reports non-delivery on a rejection, which keeps\n // `ResultWatcher`'s claim alive for a retry. The tail must not inherit\n // that rejection, or one failed completion would poison every later one.\n deliveries = delivery.catch(() => undefined);\n return delivery;\n },\n });\n const transport: NativeTeamTransport = {\n sendMessage: ((message) =>\n void execution.client.notify({\n body:\n typeof message.content === 'string'\n ? message.content\n : message.content.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n level: 'info',\n })) as NativeTeamTransport['sendMessage'],\n sendUserMessage: ((message) =>\n void execution.session.prompt(\n typeof message === 'string'\n ? message\n : message.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n 'steer',\n )) as NativeTeamTransport['sendUserMessage'],\n };\n const channel = runtime.teamChannel.createHeadlessRuntime(transport);\n const serverService = (ctx: Context) => {\n ctx.plugin((providerContext) => {\n const availableModels = execution.model ? [toModelInfo(execution.model)] : [];\n providerContext.provide(\n DOOM_DELEGATION_SERVICE,\n bridge.createService(providerContext, {\n sessionId: execution.sessionId,\n sessionScope: createSessionScope(execution.sessionId),\n availableModels,\n ...(execution.model ? { parentModel: execution.model } : {}),\n // Read fresh per request, exactly as the Pi facet does at root.cli.ts.\n // Omitting this is what made every headless `context: 'fork'`\n // delegation report that the parent had no capturable branch.\n captureForkSource: () => execution.session?.forkSource?.(),\n }),\n );\n const backgroundWork = createBackgroundWorkService(providerContext);\n providerContext.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n registerDirectRunBackgroundWork(providerContext, backgroundWork, execution.sessionId, runtime.asyncJobTracker);\n providerContext.provide(DOOM_SUBAGENT_POLICY_SERVICE, createSubagentPolicyService(runtime.capabilityPolicies));\n });\n };\n\n const environment = serverHost.context.environment;\n const activity: DoomHeadlessActivity = {\n name: '@agimon-ai/doompi-team',\n async start(activityContext) {\n const nativeRuns = new Map<string, NativeAsyncJobProjection>();\n const publishRunSnapshot = (): void => publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);\n const cleanups = [\n runtime.asyncJobTracker.subscribe(execution.sessionId, publishRunSnapshot),\n subscribeNativeRunProjection(execution.sessionId, (runs) => {\n nativeRuns.clear();\n for (const run of runs) nativeRuns.set(run.runId, run);\n publishRunSnapshot();\n }),\n ];\n publishRunSnapshot();\n try {\n const discovered = runtime.discovery.discover(execution.cwd, 'both').agents;\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: presentCatalog(discovered),\n models: catalogModels(discovered, resolveActiveTeamModelSpecs(environment) ?? []),\n });\n } catch (error) {\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: [],\n models: [],\n warning: error instanceof Error ? error.message : String(error),\n });\n }\n try {\n const opened = await openScopeAsync(scope);\n if (opened.suspended.length)\n await activityContext.client.notify({ body: formatSuspendedRuns(opened.suspended), level: 'info' });\n channel.bindMainSession(activityContext.sessionId);\n return () => {\n for (const cleanup of cleanups.reverse()) cleanup();\n channel.dispose();\n };\n } catch (error) {\n for (const cleanup of cleanups.reverse()) cleanup();\n throw error;\n }\n },\n };\n\n return {\n value: {\n runtime,\n execution,\n scope,\n jobs,\n bridge,\n channel,\n directEvents,\n environment: serverHost.context.environment,\n },\n services: [serverService],\n activities: [activity],\n // Mirrors `root.cli.ts`, which starts the scheduler in its own `onStart`.\n // Without this the delegation bridge's progress subscription never ticks\n // and `scheduler.wake()` is a no-op, so the pre-timeout nudge a long\n // delegation gets in the TUI never fires headless. `onDispose` below\n // already stops it; this is the missing half of that pair.\n onStart() {\n runtime.pollScheduler.start();\n },\n async onDispose() {\n channel.dispose();\n runtime.completionNotifier.dispose();\n const suspension = suspendScopeRuns({\n scope,\n reason: 'headless session ended',\n jobs: jobs.list(),\n stop: (runId, reason) => runtime.management.stop(runId, reason),\n });\n const nativeShutdown = runtime.nativeRuns.close().finally(() => nativeRunProjection.dispose(execution.sessionId));\n const outcomes = await Promise.allSettled([suspension, nativeShutdown]);\n for (const outcome of outcomes) {\n if (outcome.status === 'rejected')\n process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);\n }\n bridge.abandonAll();\n runtime.dispose();\n runtime.asyncJobTracker.stop();\n runtime.pollScheduler.stop();\n },\n };\n});\n\nexport type TeamServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n"],"mappings":"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"}
1
+ {"version":3,"file":"root.server.mjs","names":[],"sources":["../../../../../../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts"],"sourcesContent":["import { DOOM_BACKGROUND_WORK_SERVICE } from '@agimon-ai/doompi-core/background-work';\nimport { readDoomChildSessionService } from '@agimon-ai/doompi-core/child';\nimport { DOOM_DELEGATION_SERVICE } from '@agimon-ai/doompi-core/delegation';\nimport { defineRoot } from '@agimon-ai/doompi-core/extension-file';\nimport type { DoomHeadlessActivity } from '@agimon-ai/doompi-core/headless';\nimport type { DoomDirectEventBus } from '@agimon-ai/doompi-core/hub-channel';\nimport type { DoomServerPluginContext } from '@agimon-ai/doompi-core/server-facet';\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { DOOM_SUBAGENT_POLICY_SERVICE } from '../../../../../schemas/subagentPolicy';\nimport { resolveActiveTeamModelSpecs } from '../../../../../services/agentDiscovery';\nimport type { NativeAsyncJobProjection, TrackedAsyncJob } from '../../../../../services/asyncJobTracker';\nimport { createBackgroundWorkService } from '../../../../../services/backgroundWorkService';\nimport { loadConfig } from '../../../../../services/config';\nimport { createDelegationBridge } from '../../../../../services/delegationBridge';\nimport { registerDirectRunBackgroundWork } from '../../../../../services/directRunBackgroundWork';\nimport { toModelInfo } from '../../../../../services/modelInfo';\nimport { nativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport { subscribeNativeRunProjection } from '../../../../../services/nativeRunProjection';\nimport type { NativeTeamTransport } from '../../../../../services/nativeTeamChannel';\nimport { formatCompletionHeadline, SUBAGENT_NOTIFY_MESSAGE_TYPE } from '../../../../../services/notify';\nimport { openScopeAsync, suspendScopeRuns } from '../../../../../services/sessionLifecycle';\nimport { createSessionScope } from '../../../../../services/sessionPaths';\nimport { createSubagentPolicyService } from '../../../../../services/subagentPolicyService';\nimport { formatSuspendedRuns } from '../../../../../services/suspendedRuns';\nimport { createTeamExtensionRuntime } from '../../../../../services/teamRuntime';\nimport { catalogModels, presentCatalog } from '../../../../../services/webSubagentCatalog';\nimport {\n SUBAGENT_CATALOG_TYPE,\n SUBAGENT_RUNS_TYPE,\n type SubagentRun,\n type SubagentRunState,\n} from '../../../../../types/webSubagents';\n\ninterface SessionRunProjection extends SubagentRun {\n /** Host-private child transcript path consumed by the hub, not the browser. */\n sessionFile?: string;\n}\n\nconst TERMINAL_RUN_STATES = new Set(['completed', 'complete', 'failed', 'stopped', 'paused']);\n\nfunction runState(status: string): SubagentRunState {\n if (status === 'completed' || status === 'complete') return 'done';\n if (status === 'failed') return 'failed';\n if (status === 'stopped' || status === 'paused') return 'stopped';\n if (status === 'queued') return 'queued';\n return 'running';\n}\n\nfunction presentTrackedRun(run: TrackedAsyncJob): SessionRunProjection | undefined {\n if (!run.agent || !run.status || run.startedAt === undefined) return undefined;\n const lastUpdate = run.updatedAt ?? run.startedAt;\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task ?? '',\n cwd: run.cwd ?? '',\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: lastUpdate } : {}),\n lastUpdate,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction presentNativeRun(run: NativeAsyncJobProjection): SessionRunProjection {\n return {\n runId: run.runId,\n agent: run.agent,\n ...(run.identity === undefined ? {} : { identity: run.identity }),\n ...(run.inline === undefined ? {} : { inline: run.inline }),\n state: runState(run.status),\n rawState: run.status,\n task: run.task,\n cwd: run.cwd,\n startedAt: run.startedAt,\n ...(TERMINAL_RUN_STATES.has(run.status) ? { endedAt: run.updatedAt } : {}),\n lastUpdate: run.updatedAt,\n tail: [],\n ...(run.sessionFile === undefined ? {} : { sessionFile: run.sessionFile }),\n ...(run.summary === undefined ? {} : { summary: run.summary }),\n ...(run.error === undefined ? {} : { error: run.error }),\n };\n}\n\nfunction publishRuns(\n directEvents: DoomDirectEventBus,\n sessionId: string,\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n): void {\n const runs = jobs\n .filter((job) => !job.native)\n .map(presentTrackedRun)\n .filter((run): run is SessionRunProjection => run !== undefined);\n runs.push(...[...nativeRuns.values()].map(presentNativeRun));\n directEvents.publish(SUBAGENT_RUNS_TYPE, sessionId, { runs });\n}\n\nconst TEAM_COST_STATUS = 'doom-team-cost';\nconst COST_DECIMALS = 6;\n\nfunction updateTeamCost(\n client: { setStatus(source: string, text: string | undefined): void },\n jobs: readonly TrackedAsyncJob[],\n nativeRuns: ReadonlyMap<string, NativeAsyncJobProjection>,\n costs: Map<string, number>,\n previousText: string | undefined,\n attached: boolean,\n force = false,\n): string | undefined {\n const trackedCosts = new Map<string, number>();\n for (const job of jobs) {\n if (job.cost !== undefined && Number.isFinite(job.cost)) trackedCosts.set(job.runId, job.cost);\n }\n for (const [runId, run] of nativeRuns) {\n if (trackedCosts.has(runId) || run.cost === undefined || !Number.isFinite(run.cost)) continue;\n costs.set(runId, run.cost);\n }\n for (const [runId, cost] of trackedCosts) costs.set(runId, cost);\n\n let total = 0;\n for (const cost of costs.values()) total += cost;\n const text = total > 0 ? String(Number(total.toFixed(COST_DECIMALS))) : undefined;\n if (!force && text === previousText) return previousText;\n if (attached) client.setStatus(TEAM_COST_STATUS, text);\n return text;\n}\n\nconst root = defineRoot(({ context, host: serverHost, agent: host }: DoomServerPluginContext) => {\n if (!host) throw new Error('Team session requires the headless host.');\n if (serverHost.context.environment === undefined) {\n throw new Error('Team headless facet requires an admitted session environment.');\n }\n const directEvents = serverHost.context.directEvents;\n if (directEvents === undefined) {\n throw new Error('Team headless facet requires host-owned direct events.');\n }\n const execution = host.context;\n const runtime = createTeamExtensionRuntime(undefined, {\n environment: serverHost.context.environment,\n childSessions: { get: () => readDoomChildSessionService(context) },\n nativeRunProjection,\n });\n const scope = createSessionScope(execution.sessionId);\n runtime.management.bindSessionScope(scope);\n const jobs = runtime.asyncJobTracker.forSession(execution.sessionId, scope);\n const bridge = createDelegationBridge({\n planner: runtime.spawnPlanner,\n management: runtime.management,\n waiter: runtime.subagentWaiter,\n scheduler: runtime.pollScheduler,\n tracker: runtime.asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const nativeRuns = new Map<string, NativeAsyncJobProjection>();\n const childCosts = new Map<string, number>();\n let costText: string | undefined;\n let activityAttached = false;\n const publishRunSnapshot = (): void => {\n if (activityAttached) publishRuns(directEvents, execution.sessionId, jobs.list(), nativeRuns);\n };\n const publishCost = (force = false): void => {\n costText = updateTeamCost(execution.client, jobs.list(), nativeRuns, childCosts, costText, activityAttached, force);\n };\n const unsubscribeCostTracker = runtime.asyncJobTracker.subscribe(execution.sessionId, () => {\n publishCost();\n publishRunSnapshot();\n });\n const unsubscribeCostNative = subscribeNativeRunProjection(execution.sessionId, (runs) => {\n nativeRuns.clear();\n for (const run of runs) nativeRuns.set(run.runId, run);\n publishCost();\n publishRunSnapshot();\n });\n\n // Three jobs the Pi facet gets from one `ExtensionAPI.sendMessage`, which a\n // headless session has to do by hand:\n //\n // toast `client.notify` - operator-facing only. It writes a\n // `doom-notification` custom entry whose body is flattened to a\n // single line and capped at 4096 characters, and the model never\n // sees custom entries. So it gets a headline, not the result.\n // transcript `session.appendCustomEntry` - the durable record, under the\n // same custom type the TUI renderer uses, with the full\n // multi-line content and the structured details.\n // wake `session.admitPrompt(content, 'steer')` - the only call that\n // puts the completion in front of the model. 'steer' means \"how\n // to deliver if a turn is running\"; an idle agent is woken\n // either way. `session.prompt(_, 'steer')` would NOT do this: it\n // is enqueue-only and parks the message on an idle lane.\n //\n // Serialised through one tail promise. Failures bypass the notifier's batcher\n // (`services/notify`), so two runs failing in the same tick produce two\n // deliveries; sequencing them keeps each one observing settled lane state.\n let deliveries: Promise<void> = Promise.resolve();\n runtime.completionNotifier.attachHost({\n sendMessage: (message, options) => {\n const details = message.details ?? [];\n const headline = details.length > 0 ? formatCompletionHeadline(details) : message.content;\n const failed = details.some((detail) => detail.status !== 'completed');\n const delivery = deliveries.then(async () => {\n await execution.client.notify({ body: headline, level: failed ? 'warning' : 'info' });\n await execution.session.appendCustomEntry(SUBAGENT_NOTIFY_MESSAGE_TYPE, {\n content: message.content,\n details,\n });\n // `triggerTurn: false` has no faithful headless mapping: there is no\n // way to put text in model context without starting or joining a turn\n // (`appendCustomEntry` is journal-only). Keep today's behaviour - the\n // operator sees it, the model does not wake - rather than guessing.\n if (options?.triggerTurn === false) return;\n // Matching the voice facet, which also refuses rather than degrading:\n // `session.prompt(_, 'steer')` is NOT a fallback here, because it is\n // enqueue-only and would park the completion on an idle lane - the\n // original bug. Refusing surfaces as non-delivery, which keeps\n // `ResultWatcher`'s claim alive for a retry.\n if (!execution.session.admitPrompt) throw new Error('The session cannot admit a completion prompt.');\n await execution.session.admitPrompt(message.content, 'steer');\n });\n // The notifier reports non-delivery on a rejection, which keeps\n // `ResultWatcher`'s claim alive for a retry. The tail must not inherit\n // that rejection, or one failed completion would poison every later one.\n deliveries = delivery.catch(() => undefined);\n return delivery;\n },\n });\n const transport: NativeTeamTransport = {\n sendMessage: ((message) =>\n void execution.client.notify({\n body:\n typeof message.content === 'string'\n ? message.content\n : message.content.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n level: 'info',\n })) as NativeTeamTransport['sendMessage'],\n sendUserMessage: ((message) =>\n void execution.session.prompt(\n typeof message === 'string'\n ? message\n : message.map((part) => ('text' in part ? part.text : '[image]')).join('\\n'),\n 'steer',\n )) as NativeTeamTransport['sendUserMessage'],\n };\n const channel = runtime.teamChannel.createHeadlessRuntime(transport);\n const serverService = (ctx: Context) => {\n ctx.plugin((providerContext) => {\n const availableModels = execution.model ? [toModelInfo(execution.model)] : [];\n providerContext.provide(\n DOOM_DELEGATION_SERVICE,\n bridge.createService(providerContext, {\n sessionId: execution.sessionId,\n sessionScope: createSessionScope(execution.sessionId),\n availableModels,\n ...(execution.model ? { parentModel: execution.model } : {}),\n // Read fresh per request, exactly as the Pi facet does at root.cli.ts.\n // Omitting this is what made every headless `context: 'fork'`\n // delegation report that the parent had no capturable branch.\n captureForkSource: () => execution.session?.forkSource?.(),\n }),\n );\n const backgroundWork = createBackgroundWorkService(providerContext);\n providerContext.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n registerDirectRunBackgroundWork(providerContext, backgroundWork, execution.sessionId, runtime.asyncJobTracker);\n providerContext.provide(DOOM_SUBAGENT_POLICY_SERVICE, createSubagentPolicyService(runtime.capabilityPolicies));\n });\n };\n\n const environment = serverHost.context.environment;\n const activity: DoomHeadlessActivity = {\n name: '@agimon-ai/doompi-team',\n async start(activityContext) {\n activityAttached = true;\n publishCost(true);\n publishRunSnapshot();\n try {\n const discovered = runtime.discovery.discover(execution.cwd, 'both').agents;\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: presentCatalog(discovered),\n models: catalogModels(discovered, resolveActiveTeamModelSpecs(environment) ?? []),\n });\n } catch (error) {\n directEvents.publish(SUBAGENT_CATALOG_TYPE, execution.sessionId, {\n cwd: execution.cwd,\n agents: [],\n models: [],\n warning: error instanceof Error ? error.message : String(error),\n });\n }\n try {\n const opened = await openScopeAsync(scope);\n if (opened.suspended.length)\n await activityContext.client.notify({ body: formatSuspendedRuns(opened.suspended), level: 'info' });\n channel.bindMainSession(activityContext.sessionId);\n return () => {\n if (!activityAttached) return;\n activityAttached = false;\n channel.dispose();\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n };\n } catch (error) {\n activityAttached = false;\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n throw error;\n }\n },\n };\n\n return {\n value: {\n runtime,\n execution,\n scope,\n jobs,\n bridge,\n channel,\n directEvents,\n environment: serverHost.context.environment,\n },\n services: [serverService],\n activities: [activity],\n // Mirrors `root.cli.ts`, which starts the scheduler in its own `onStart`.\n // Without this the delegation bridge's progress subscription never ticks\n // and `scheduler.wake()` is a no-op, so the pre-timeout nudge a long\n // delegation gets in the TUI never fires headless. `onDispose` below\n // already stops it; this is the missing half of that pair.\n onStart() {\n runtime.pollScheduler.start();\n },\n async onDispose() {\n activityAttached = false;\n execution.client.setStatus(TEAM_COST_STATUS, undefined);\n channel.dispose();\n runtime.completionNotifier.dispose();\n const suspension = suspendScopeRuns({\n scope,\n reason: 'headless session ended',\n jobs: jobs.list(),\n stop: (runId, reason) => runtime.management.stop(runId, reason),\n });\n const nativeShutdown = runtime.nativeRuns.close().finally(() => nativeRunProjection.dispose(execution.sessionId));\n const outcomes = await Promise.allSettled([suspension, nativeShutdown]);\n for (const outcome of outcomes) {\n if (outcome.status === 'rejected')\n process.emitWarning(`Could not shut down headless Team runs: ${String(outcome.reason)}`);\n }\n unsubscribeCostTracker();\n unsubscribeCostNative();\n nativeRuns.clear();\n childCosts.clear();\n costText = undefined;\n bridge.abandonAll();\n runtime.dispose();\n runtime.asyncJobTracker.stop();\n runtime.pollScheduler.stop();\n },\n };\n});\n\nexport type TeamServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/asyncJobTracker/index.ts"],"sourcesContent":["import type { ActivityState } from '../../types';\nimport type { ExternalRunProjection } from '../externalProcessIpc';\nimport type { SessionScope } from '../sessionPaths';\n\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\nconst DEFAULT_RETENTION_MS = 10 * 60_000;\n\ntype ExternalResult = Record<string, unknown>;\n\nexport interface TrackedAsyncJob {\n runId: string;\n agent?: string;\n /** Generated addressable identity, for example `alan-reviewer-3`. */\n identity?: string;\n /** True when this run came from a one-shot inline agent definition. */\n inline?: boolean;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n task?: string;\n cwd?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n native?: boolean;\n tokens?: number;\n cost?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface NativeAsyncJobProjection {\n runId: string;\n agent: string;\n identity?: string;\n inline?: boolean;\n task: string;\n cwd: string;\n runtime: string;\n status: string;\n startedAt: number;\n updatedAt: number;\n error?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n}\n\nexport type TrackedAsyncJobsContract = {\n track(runId: string, identity?: { identity: string; inline: boolean }): void;\n untrack(runId: string): void;\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = {\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract;\n stop(): void;\n};\n\n/**\n * Resolve a caller-supplied id to a tracked run id.\n *\n * Order matters. An exact run id wins, then a unique run-id prefix, and only\n * then a generated identity. A name is checked last so that a friendly name\n * which happens to look like a hex prefix can never shadow a real run.\n */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const runs = jobs.list();\n const matches = runs.map((job) => job.runId).filter((runId) => runId === id || runId.startsWith(id));\n const exact = matches.find((runId) => runId === id);\n if (exact) return exact;\n if (matches.length === 1) return matches[0]!;\n if (matches.length === 0) {\n const wanted = id.trim().toLowerCase();\n const named = runs.filter((job) => job.identity?.toLowerCase() === wanted);\n if (named.length === 1) return named[0]!.runId;\n if (named.length > 1) throw new Error(`Multiple current-session runs are named '${id}'.`);\n throw new Error(`No current-session run found for '${id}'.`);\n }\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n scope: SessionScope;\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;\n}\n\nfunction isTerminal(status: string | undefined): boolean {\n return status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(status);\n}\n\nfunction changed(previous: TrackedAsyncJob | undefined, next: TrackedAsyncJob): boolean {\n if (!previous) return true;\n return JSON.stringify(previous) !== JSON.stringify(next);\n}\n\nconst ACTIVITY_STATES: ReadonlySet<string> = new Set([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction activityState(value: string | undefined): ActivityState | undefined {\n return value !== undefined && ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n protected readonly retentionMs = DEFAULT_RETENTION_MS;\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n\n private session(sessionId: string, scope: SessionScope): SessionJobs {\n const existing = this.sessions.get(sessionId);\n if (existing) {\n if (existing.scope.scopeKey !== scope.scopeKey)\n throw new Error(`Session '${sessionId}' is already bound to another scope.`);\n return existing;\n }\n const created: SessionJobs = {\n scope,\n jobs: new Map(),\n terminalAt: new Map(),\n handedOff: new Set(),\n cleanupTimers: new Map(),\n };\n this.sessions.set(sessionId, created);\n return created;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n this.session(sessionId, scope);\n return {\n track: (runId, identity) => this.trackInSession(sessionId, runId, identity),\n untrack: (runId) => this.untrackInSession(sessionId, runId),\n list: () => this.listInSession(sessionId),\n get: (runId) => this.getInSession(sessionId, runId),\n reset: () => this.resetSession(sessionId),\n };\n }\n\n upsertNative(sessionId: string, scope: SessionScope, projection: NativeAsyncJobProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n // `projection` replaces the record wholesale, so anything the tracker knows\n // and the projection does not carry has to be restated here or it is lost\n // on the first child event.\n const next: TrackedAsyncJob = {\n ...projection,\n ...((projection.identity ?? previous?.identity) ? { identity: projection.identity ?? previous?.identity } : {}),\n ...((projection.inline ?? previous?.inline) ? { inline: projection.inline ?? previous?.inline } : {}),\n native: true,\n };\n this.install(sessionId, session, next, previous);\n }\n\n getNative(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n const job = this.sessions.get(sessionId)?.jobs.get(runId);\n return job?.native ? (job as NativeAsyncJobProjection) : undefined;\n }\n\n upsertExternal(sessionId: string, scope: SessionScope, projection: ExternalRunProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n const next: TrackedAsyncJob = {\n runId: projection.runId,\n agent: projection.agent,\n // An external runner writes status.json and has never heard of the\n // identity, so it can only come from what the parent already tracked.\n ...(previous?.identity === undefined ? {} : { identity: previous.identity }),\n ...(previous?.inline === undefined ? {} : { inline: previous.inline }),\n task: projection.task,\n cwd: projection.cwd,\n runtime: projection.runtime,\n status: projection.state,\n startedAt: projection.startedAt,\n updatedAt: projection.updatedAt,\n ...(projection.error === undefined ? {} : { error: projection.error }),\n ...(projection.summary === undefined ? {} : { summary: projection.summary }),\n ...(projection.tokens === undefined ? {} : { tokens: projection.tokens }),\n ...(projection.cost === undefined ? {} : { cost: projection.cost }),\n ...(projection.currentTool === undefined ? {} : { currentTool: projection.currentTool }),\n ...(projection.toolCount === undefined ? {} : { toolCount: projection.toolCount }),\n ...(activityState(projection.activityState) === undefined\n ? {}\n : { activityState: activityState(projection.activityState) }),\n ...(projection.attentionReason === undefined ? {} : { attentionReason: projection.attentionReason }),\n ...(projection.sessionFile === undefined ? {} : { sessionFile: projection.sessionFile }),\n ...(projection.transcriptPath === undefined ? {} : { transcriptPath: projection.transcriptPath }),\n };\n this.install(sessionId, session, next, previous);\n }\n\n markExternalFailed(sessionId: string, scope: SessionScope, runId: string, error: string): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n const now = Date.now();\n const next: TrackedAsyncJob = {\n ...(previous ?? { runId }),\n runId,\n status: 'failed',\n error,\n updatedAt: now,\n };\n this.install(sessionId, session, next, previous);\n }\n\n acceptExternalResult(sessionId: string, scope: SessionScope, runId: string, result: ExternalResult): boolean {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n if (!previous) return false;\n const success = result.success;\n const state = typeof result.state === 'string' ? result.state : success === false ? 'failed' : 'completed';\n const summary = typeof result.summary === 'string' ? result.summary : previous.summary;\n const next: TrackedAsyncJob = {\n ...previous,\n status: state,\n updatedAt: typeof result.updatedAt === 'number' ? result.updatedAt : Date.now(),\n ...(summary === undefined ? {} : { summary }),\n ...(typeof result.error === 'string' ? { error: result.error } : {}),\n };\n this.install(sessionId, session, next, previous);\n return true;\n }\n\n private install(\n sessionId: string,\n session: SessionJobs,\n next: TrackedAsyncJob,\n previous: TrackedAsyncJob | undefined,\n ): void {\n session.jobs.set(next.runId, next);\n const terminal = isTerminal(next.status);\n if (terminal && !isTerminal(previous?.status)) session.terminalAt.set(next.runId, Date.now());\n if (!terminal) {\n session.terminalAt.delete(next.runId);\n session.handedOff.delete(next.runId);\n this.clearCleanupTimer(session, next.runId);\n }\n if (changed(previous, next)) this.invalidate(sessionId);\n }\n\n acknowledgeHandoff(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !isTerminal(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n const timer = setTimeout(() => {\n this.removeIfRetained(sessionId, runId);\n }, this.retentionMs);\n timer.unref?.();\n session.cleanupTimers.set(runId, timer);\n }\n\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter((job) => !isTerminal(job.status) || !session.handedOff.has(job.runId));\n }\n\n subscribe(sessionId: string, listener: () => void): () => void {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to subscribe to subagent runs.');\n const listeners = this.listeners.get(sessionId) ?? new Set<() => void>();\n listeners.add(listener);\n this.listeners.set(sessionId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) this.listeners.delete(sessionId);\n };\n }\n\n private trackInSession(sessionId: string, runId: string, identity?: { identity: string; inline: boolean }): void {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session '${sessionId}' must be bound to an explicit scope before tracking runs.`);\n if (!session.jobs.has(runId)) {\n session.jobs.set(runId, {\n runId,\n status: undefined,\n ...(identity ? { identity: identity.identity, inline: identity.inline } : {}),\n });\n this.invalidate(sessionId);\n }\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n this.clearCleanupTimer(session, runId);\n const deleted = session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n if (session.jobs.size === 0) this.sessions.delete(sessionId);\n if (deleted) this.invalidate(sessionId);\n }\n\n private removeIfRetained(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session || !session.handedOff.has(runId)) return;\n session.cleanupTimers.delete(runId);\n this.untrackInSession(sessionId, runId);\n }\n\n private clearCleanupTimer(session: SessionJobs, runId: string): void {\n const timer = session.cleanupTimers.get(runId);\n if (timer !== undefined) {\n clearTimeout(timer);\n session.cleanupTimers.delete(runId);\n }\n }\n\n private resetSession(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n const hadJobs = session.jobs.size > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n private listInSession(sessionId: string): TrackedAsyncJob[] {\n return [...(this.sessions.get(sessionId)?.jobs.values() ?? [])];\n }\n\n private getInSession(sessionId: string, runId: string): TrackedAsyncJob | undefined {\n return this.sessions.get(sessionId)?.jobs.get(runId);\n }\n\n stop(): void {\n for (const session of this.sessions.values()) {\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n }\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/asyncJobTracker/index.ts"],"sourcesContent":["import type { ActivityState } from '../../types';\nimport type { ExternalRunProjection } from '../externalProcessIpc';\nimport type { SessionScope } from '../sessionPaths';\n\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\nconst DEFAULT_RETENTION_MS = 10 * 60_000;\n\ntype ExternalResult = Record<string, unknown>;\n\nexport interface TrackedAsyncJob {\n runId: string;\n agent?: string;\n /** Generated addressable identity, for example `alan-reviewer-3`. */\n identity?: string;\n /** True when this run came from a one-shot inline agent definition. */\n inline?: boolean;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n task?: string;\n cwd?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n native?: boolean;\n tokens?: number;\n cost?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface NativeAsyncJobProjection {\n runId: string;\n agent: string;\n identity?: string;\n inline?: boolean;\n task: string;\n cwd: string;\n runtime: string;\n status: string;\n startedAt: number;\n updatedAt: number;\n error?: string;\n sessionFile?: string;\n transcriptPath?: string;\n cost?: number;\n summary?: string;\n}\n\nexport type TrackedAsyncJobsContract = {\n track(runId: string, identity?: { identity: string; inline: boolean }): void;\n untrack(runId: string): void;\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = {\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract;\n stop(): void;\n};\n\n/**\n * Resolve a caller-supplied id to a tracked run id.\n *\n * Order matters. An exact run id wins, then a unique run-id prefix, and only\n * then a generated identity. A name is checked last so that a friendly name\n * which happens to look like a hex prefix can never shadow a real run.\n */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const runs = jobs.list();\n const matches = runs.map((job) => job.runId).filter((runId) => runId === id || runId.startsWith(id));\n const exact = matches.find((runId) => runId === id);\n if (exact) return exact;\n if (matches.length === 1) return matches[0]!;\n if (matches.length === 0) {\n const wanted = id.trim().toLowerCase();\n const named = runs.filter((job) => job.identity?.toLowerCase() === wanted);\n if (named.length === 1) return named[0]!.runId;\n if (named.length > 1) throw new Error(`Multiple current-session runs are named '${id}'.`);\n throw new Error(`No current-session run found for '${id}'.`);\n }\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n scope: SessionScope;\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;\n}\n\nfunction isTerminal(status: string | undefined): boolean {\n return status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(status);\n}\n\nfunction changed(previous: TrackedAsyncJob | undefined, next: TrackedAsyncJob): boolean {\n if (!previous) return true;\n return JSON.stringify(previous) !== JSON.stringify(next);\n}\n\nconst ACTIVITY_STATES: ReadonlySet<string> = new Set([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction activityState(value: string | undefined): ActivityState | undefined {\n return value !== undefined && ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n protected readonly retentionMs = DEFAULT_RETENTION_MS;\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n\n private session(sessionId: string, scope: SessionScope): SessionJobs {\n const existing = this.sessions.get(sessionId);\n if (existing) {\n if (existing.scope.scopeKey !== scope.scopeKey)\n throw new Error(`Session '${sessionId}' is already bound to another scope.`);\n return existing;\n }\n const created: SessionJobs = {\n scope,\n jobs: new Map(),\n terminalAt: new Map(),\n handedOff: new Set(),\n cleanupTimers: new Map(),\n };\n this.sessions.set(sessionId, created);\n return created;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n this.session(sessionId, scope);\n return {\n track: (runId, identity) => this.trackInSession(sessionId, runId, identity),\n untrack: (runId) => this.untrackInSession(sessionId, runId),\n list: () => this.listInSession(sessionId),\n get: (runId) => this.getInSession(sessionId, runId),\n reset: () => this.resetSession(sessionId),\n };\n }\n\n upsertNative(sessionId: string, scope: SessionScope, projection: NativeAsyncJobProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n // `projection` replaces the record wholesale, so anything the tracker knows\n // and the projection does not carry has to be restated here or it is lost\n // on the first child event.\n const next: TrackedAsyncJob = {\n ...projection,\n ...((projection.identity ?? previous?.identity) ? { identity: projection.identity ?? previous?.identity } : {}),\n ...((projection.inline ?? previous?.inline) ? { inline: projection.inline ?? previous?.inline } : {}),\n native: true,\n };\n this.install(sessionId, session, next, previous);\n }\n\n getNative(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n const job = this.sessions.get(sessionId)?.jobs.get(runId);\n return job?.native ? (job as NativeAsyncJobProjection) : undefined;\n }\n\n upsertExternal(sessionId: string, scope: SessionScope, projection: ExternalRunProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n const next: TrackedAsyncJob = {\n runId: projection.runId,\n agent: projection.agent,\n // An external runner writes status.json and has never heard of the\n // identity, so it can only come from what the parent already tracked.\n ...(previous?.identity === undefined ? {} : { identity: previous.identity }),\n ...(previous?.inline === undefined ? {} : { inline: previous.inline }),\n task: projection.task,\n cwd: projection.cwd,\n runtime: projection.runtime,\n status: projection.state,\n startedAt: projection.startedAt,\n updatedAt: projection.updatedAt,\n ...(projection.error === undefined ? {} : { error: projection.error }),\n ...(projection.summary === undefined ? {} : { summary: projection.summary }),\n ...(projection.tokens === undefined ? {} : { tokens: projection.tokens }),\n ...(projection.cost === undefined ? {} : { cost: projection.cost }),\n ...(projection.currentTool === undefined ? {} : { currentTool: projection.currentTool }),\n ...(projection.toolCount === undefined ? {} : { toolCount: projection.toolCount }),\n ...(activityState(projection.activityState) === undefined\n ? {}\n : { activityState: activityState(projection.activityState) }),\n ...(projection.attentionReason === undefined ? {} : { attentionReason: projection.attentionReason }),\n ...(projection.sessionFile === undefined ? {} : { sessionFile: projection.sessionFile }),\n ...(projection.transcriptPath === undefined ? {} : { transcriptPath: projection.transcriptPath }),\n };\n this.install(sessionId, session, next, previous);\n }\n\n markExternalFailed(sessionId: string, scope: SessionScope, runId: string, error: string): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n const now = Date.now();\n const next: TrackedAsyncJob = {\n ...(previous ?? { runId }),\n runId,\n status: 'failed',\n error,\n updatedAt: now,\n };\n this.install(sessionId, session, next, previous);\n }\n\n acceptExternalResult(sessionId: string, scope: SessionScope, runId: string, result: ExternalResult): boolean {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n if (!previous) return false;\n const success = result.success;\n const state = typeof result.state === 'string' ? result.state : success === false ? 'failed' : 'completed';\n const summary = typeof result.summary === 'string' ? result.summary : previous.summary;\n const next: TrackedAsyncJob = {\n ...previous,\n status: state,\n updatedAt: typeof result.updatedAt === 'number' ? result.updatedAt : Date.now(),\n ...(summary === undefined ? {} : { summary }),\n ...(typeof result.error === 'string' ? { error: result.error } : {}),\n };\n this.install(sessionId, session, next, previous);\n return true;\n }\n\n private install(\n sessionId: string,\n session: SessionJobs,\n next: TrackedAsyncJob,\n previous: TrackedAsyncJob | undefined,\n ): void {\n session.jobs.set(next.runId, next);\n const terminal = isTerminal(next.status);\n if (terminal && !isTerminal(previous?.status)) session.terminalAt.set(next.runId, Date.now());\n if (!terminal) {\n session.terminalAt.delete(next.runId);\n session.handedOff.delete(next.runId);\n this.clearCleanupTimer(session, next.runId);\n }\n if (changed(previous, next)) this.invalidate(sessionId);\n }\n\n acknowledgeHandoff(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !isTerminal(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n const timer = setTimeout(() => {\n this.removeIfRetained(sessionId, runId);\n }, this.retentionMs);\n timer.unref?.();\n session.cleanupTimers.set(runId, timer);\n }\n\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter((job) => !isTerminal(job.status) || !session.handedOff.has(job.runId));\n }\n\n subscribe(sessionId: string, listener: () => void): () => void {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to subscribe to subagent runs.');\n const listeners = this.listeners.get(sessionId) ?? new Set<() => void>();\n listeners.add(listener);\n this.listeners.set(sessionId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) this.listeners.delete(sessionId);\n };\n }\n\n private trackInSession(sessionId: string, runId: string, identity?: { identity: string; inline: boolean }): void {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session '${sessionId}' must be bound to an explicit scope before tracking runs.`);\n if (!session.jobs.has(runId)) {\n session.jobs.set(runId, {\n runId,\n status: undefined,\n ...(identity ? { identity: identity.identity, inline: identity.inline } : {}),\n });\n this.invalidate(sessionId);\n }\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n this.clearCleanupTimer(session, runId);\n const deleted = session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n if (session.jobs.size === 0) this.sessions.delete(sessionId);\n if (deleted) this.invalidate(sessionId);\n }\n\n private removeIfRetained(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session || !session.handedOff.has(runId)) return;\n session.cleanupTimers.delete(runId);\n this.untrackInSession(sessionId, runId);\n }\n\n private clearCleanupTimer(session: SessionJobs, runId: string): void {\n const timer = session.cleanupTimers.get(runId);\n if (timer !== undefined) {\n clearTimeout(timer);\n session.cleanupTimers.delete(runId);\n }\n }\n\n private resetSession(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n const hadJobs = session.jobs.size > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n private listInSession(sessionId: string): TrackedAsyncJob[] {\n return [...(this.sessions.get(sessionId)?.jobs.values() ?? [])];\n }\n\n private getInSession(sessionId: string, runId: string): TrackedAsyncJob | undefined {\n return this.sessions.get(sessionId)?.jobs.get(runId);\n }\n\n stop(): void {\n for (const session of this.sessions.values()) {\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n }\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/asyncJobTracker/index.ts"],"sourcesContent":["import type { ActivityState } from '../../types';\nimport type { ExternalRunProjection } from '../externalProcessIpc';\nimport type { SessionScope } from '../sessionPaths';\n\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\nconst DEFAULT_RETENTION_MS = 10 * 60_000;\n\ntype ExternalResult = Record<string, unknown>;\n\nexport interface TrackedAsyncJob {\n runId: string;\n agent?: string;\n /** Generated addressable identity, for example `alan-reviewer-3`. */\n identity?: string;\n /** True when this run came from a one-shot inline agent definition. */\n inline?: boolean;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n task?: string;\n cwd?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n native?: boolean;\n tokens?: number;\n cost?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface NativeAsyncJobProjection {\n runId: string;\n agent: string;\n identity?: string;\n inline?: boolean;\n task: string;\n cwd: string;\n runtime: string;\n status: string;\n startedAt: number;\n updatedAt: number;\n error?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n}\n\nexport type TrackedAsyncJobsContract = {\n track(runId: string, identity?: { identity: string; inline: boolean }): void;\n untrack(runId: string): void;\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = {\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract;\n stop(): void;\n};\n\n/**\n * Resolve a caller-supplied id to a tracked run id.\n *\n * Order matters. An exact run id wins, then a unique run-id prefix, and only\n * then a generated identity. A name is checked last so that a friendly name\n * which happens to look like a hex prefix can never shadow a real run.\n */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const runs = jobs.list();\n const matches = runs.map((job) => job.runId).filter((runId) => runId === id || runId.startsWith(id));\n const exact = matches.find((runId) => runId === id);\n if (exact) return exact;\n if (matches.length === 1) return matches[0]!;\n if (matches.length === 0) {\n const wanted = id.trim().toLowerCase();\n const named = runs.filter((job) => job.identity?.toLowerCase() === wanted);\n if (named.length === 1) return named[0]!.runId;\n if (named.length > 1) throw new Error(`Multiple current-session runs are named '${id}'.`);\n throw new Error(`No current-session run found for '${id}'.`);\n }\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n scope: SessionScope;\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;\n}\n\nfunction isTerminal(status: string | undefined): boolean {\n return status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(status);\n}\n\nfunction changed(previous: TrackedAsyncJob | undefined, next: TrackedAsyncJob): boolean {\n if (!previous) return true;\n return JSON.stringify(previous) !== JSON.stringify(next);\n}\n\nconst ACTIVITY_STATES: ReadonlySet<string> = new Set([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction activityState(value: string | undefined): ActivityState | undefined {\n return value !== undefined && ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n protected readonly retentionMs = DEFAULT_RETENTION_MS;\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n\n private session(sessionId: string, scope: SessionScope): SessionJobs {\n const existing = this.sessions.get(sessionId);\n if (existing) {\n if (existing.scope.scopeKey !== scope.scopeKey)\n throw new Error(`Session '${sessionId}' is already bound to another scope.`);\n return existing;\n }\n const created: SessionJobs = {\n scope,\n jobs: new Map(),\n terminalAt: new Map(),\n handedOff: new Set(),\n cleanupTimers: new Map(),\n };\n this.sessions.set(sessionId, created);\n return created;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n this.session(sessionId, scope);\n return {\n track: (runId, identity) => this.trackInSession(sessionId, runId, identity),\n untrack: (runId) => this.untrackInSession(sessionId, runId),\n list: () => this.listInSession(sessionId),\n get: (runId) => this.getInSession(sessionId, runId),\n reset: () => this.resetSession(sessionId),\n };\n }\n\n upsertNative(sessionId: string, scope: SessionScope, projection: NativeAsyncJobProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n // `projection` replaces the record wholesale, so anything the tracker knows\n // and the projection does not carry has to be restated here or it is lost\n // on the first child event.\n const next: TrackedAsyncJob = {\n ...projection,\n ...((projection.identity ?? previous?.identity) ? { identity: projection.identity ?? previous?.identity } : {}),\n ...((projection.inline ?? previous?.inline) ? { inline: projection.inline ?? previous?.inline } : {}),\n native: true,\n };\n this.install(sessionId, session, next, previous);\n }\n\n getNative(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n const job = this.sessions.get(sessionId)?.jobs.get(runId);\n return job?.native ? (job as NativeAsyncJobProjection) : undefined;\n }\n\n upsertExternal(sessionId: string, scope: SessionScope, projection: ExternalRunProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n const next: TrackedAsyncJob = {\n runId: projection.runId,\n agent: projection.agent,\n // An external runner writes status.json and has never heard of the\n // identity, so it can only come from what the parent already tracked.\n ...(previous?.identity === undefined ? {} : { identity: previous.identity }),\n ...(previous?.inline === undefined ? {} : { inline: previous.inline }),\n task: projection.task,\n cwd: projection.cwd,\n runtime: projection.runtime,\n status: projection.state,\n startedAt: projection.startedAt,\n updatedAt: projection.updatedAt,\n ...(projection.error === undefined ? {} : { error: projection.error }),\n ...(projection.summary === undefined ? {} : { summary: projection.summary }),\n ...(projection.tokens === undefined ? {} : { tokens: projection.tokens }),\n ...(projection.cost === undefined ? {} : { cost: projection.cost }),\n ...(projection.currentTool === undefined ? {} : { currentTool: projection.currentTool }),\n ...(projection.toolCount === undefined ? {} : { toolCount: projection.toolCount }),\n ...(activityState(projection.activityState) === undefined\n ? {}\n : { activityState: activityState(projection.activityState) }),\n ...(projection.attentionReason === undefined ? {} : { attentionReason: projection.attentionReason }),\n ...(projection.sessionFile === undefined ? {} : { sessionFile: projection.sessionFile }),\n ...(projection.transcriptPath === undefined ? {} : { transcriptPath: projection.transcriptPath }),\n };\n this.install(sessionId, session, next, previous);\n }\n\n markExternalFailed(sessionId: string, scope: SessionScope, runId: string, error: string): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n const now = Date.now();\n const next: TrackedAsyncJob = {\n ...(previous ?? { runId }),\n runId,\n status: 'failed',\n error,\n updatedAt: now,\n };\n this.install(sessionId, session, next, previous);\n }\n\n acceptExternalResult(sessionId: string, scope: SessionScope, runId: string, result: ExternalResult): boolean {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n if (!previous) return false;\n const success = result.success;\n const state = typeof result.state === 'string' ? result.state : success === false ? 'failed' : 'completed';\n const summary = typeof result.summary === 'string' ? result.summary : previous.summary;\n const next: TrackedAsyncJob = {\n ...previous,\n status: state,\n updatedAt: typeof result.updatedAt === 'number' ? result.updatedAt : Date.now(),\n ...(summary === undefined ? {} : { summary }),\n ...(typeof result.error === 'string' ? { error: result.error } : {}),\n };\n this.install(sessionId, session, next, previous);\n return true;\n }\n\n private install(\n sessionId: string,\n session: SessionJobs,\n next: TrackedAsyncJob,\n previous: TrackedAsyncJob | undefined,\n ): void {\n session.jobs.set(next.runId, next);\n const terminal = isTerminal(next.status);\n if (terminal && !isTerminal(previous?.status)) session.terminalAt.set(next.runId, Date.now());\n if (!terminal) {\n session.terminalAt.delete(next.runId);\n session.handedOff.delete(next.runId);\n this.clearCleanupTimer(session, next.runId);\n }\n if (changed(previous, next)) this.invalidate(sessionId);\n }\n\n acknowledgeHandoff(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !isTerminal(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n const timer = setTimeout(() => {\n this.removeIfRetained(sessionId, runId);\n }, this.retentionMs);\n timer.unref?.();\n session.cleanupTimers.set(runId, timer);\n }\n\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter((job) => !isTerminal(job.status) || !session.handedOff.has(job.runId));\n }\n\n subscribe(sessionId: string, listener: () => void): () => void {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to subscribe to subagent runs.');\n const listeners = this.listeners.get(sessionId) ?? new Set<() => void>();\n listeners.add(listener);\n this.listeners.set(sessionId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) this.listeners.delete(sessionId);\n };\n }\n\n private trackInSession(sessionId: string, runId: string, identity?: { identity: string; inline: boolean }): void {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session '${sessionId}' must be bound to an explicit scope before tracking runs.`);\n if (!session.jobs.has(runId)) {\n session.jobs.set(runId, {\n runId,\n status: undefined,\n ...(identity ? { identity: identity.identity, inline: identity.inline } : {}),\n });\n this.invalidate(sessionId);\n }\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n this.clearCleanupTimer(session, runId);\n const deleted = session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n if (session.jobs.size === 0) this.sessions.delete(sessionId);\n if (deleted) this.invalidate(sessionId);\n }\n\n private removeIfRetained(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session || !session.handedOff.has(runId)) return;\n session.cleanupTimers.delete(runId);\n this.untrackInSession(sessionId, runId);\n }\n\n private clearCleanupTimer(session: SessionJobs, runId: string): void {\n const timer = session.cleanupTimers.get(runId);\n if (timer !== undefined) {\n clearTimeout(timer);\n session.cleanupTimers.delete(runId);\n }\n }\n\n private resetSession(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n const hadJobs = session.jobs.size > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n private listInSession(sessionId: string): TrackedAsyncJob[] {\n return [...(this.sessions.get(sessionId)?.jobs.values() ?? [])];\n }\n\n private getInSession(sessionId: string, runId: string): TrackedAsyncJob | undefined {\n return this.sessions.get(sessionId)?.jobs.get(runId);\n }\n\n stop(): void {\n for (const session of this.sessions.values()) {\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n }\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":";AAIA,MAAa,4CAA4B,IAAI,IAAI;CAAC;CAAY;CAAa;CAAU;CAAU;AAAS,CAAC;AACzG,MAAM,uBAAuB;;;;;;;;AAmE7B,SAAgB,oBAAoB,MAAgC,IAAoB;CACtF,MAAM,OAAO,KAAK,KAAK;CACvB,MAAM,UAAU,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,QAAQ,UAAU,UAAU,MAAM,MAAM,WAAW,EAAE,CAAC;CACnG,MAAM,QAAQ,QAAQ,MAAM,UAAU,UAAU,EAAE;CAClD,IAAI,OAAO,OAAO;CAClB,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACzC,IAAI,QAAQ,WAAW,GAAG;EACxB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,YAAY;EACrC,MAAM,QAAQ,KAAK,QAAQ,QAAQ,IAAI,UAAU,YAAY,MAAM,MAAM;EACzE,IAAI,MAAM,WAAW,GAAG,OAAO,MAAM,EAAE,CAAE;EACzC,IAAI,MAAM,SAAS,GAAG,MAAM,IAAI,MAAM,4CAA4C,GAAG,GAAG;EACxF,MAAM,IAAI,MAAM,qCAAqC,GAAG,GAAG;CAC7D;CACA,MAAM,IAAI,MAAM,wCAAwC,GAAG,oBAAoB;AACjF;AAUA,SAAS,WAAW,QAAqC;CACvD,OAAO,WAAW,KAAA,KAAa,0BAA0B,IAAI,MAAM;AACrE;AAEA,SAAS,QAAQ,UAAuC,MAAgC;CACtF,IAAI,CAAC,UAAU,OAAO;CACtB,OAAO,KAAK,UAAU,QAAQ,MAAM,KAAK,UAAU,IAAI;AACzD;AAEA,MAAM,kCAAuC,IAAI,IAAI;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,cAAc,OAAsD;CAC3E,OAAO,UAAU,KAAA,KAAa,gBAAgB,IAAI,KAAK,IAAK,QAA0B,KAAA;AACxF;AAEA,IAAa,kBAAb,MAAgE;CAC9D,cAAiC;CACjC,2BAA4B,IAAI,IAAyB;CACzD,4BAA6B,IAAI,IAA6B;CAE9D,QAAgB,WAAmB,OAAkC;EACnE,MAAM,WAAW,KAAK,SAAS,IAAI,SAAS;EAC5C,IAAI,UAAU;GACZ,IAAI,SAAS,MAAM,aAAa,MAAM,UACpC,MAAM,IAAI,MAAM,YAAY,UAAU,qCAAqC;GAC7E,OAAO;EACT;EACA,MAAM,UAAuB;GAC3B;GACA,sBAAM,IAAI,IAAI;GACd,4BAAY,IAAI,IAAI;GACpB,2BAAW,IAAI,IAAI;GACnB,+BAAe,IAAI,IAAI;EACzB;EACA,KAAK,SAAS,IAAI,WAAW,OAAO;EACpC,OAAO;CACT;CAEA,WAAmB,WAAyB;EAC1C,KAAK,MAAM,YAAY,KAAK,UAAU,IAAI,SAAS,KAAK,CAAC,GAAG,SAAS;CACvE;CAEA,WAAW,WAAmB,OAA+C;EAC3E,IAAI,CAAC,UAAU,KAAK,GAAG,MAAM,IAAI,MAAM,yDAAyD;EAChG,KAAK,QAAQ,WAAW,KAAK;EAC7B,OAAO;GACL,QAAQ,OAAO,aAAa,KAAK,eAAe,WAAW,OAAO,QAAQ;GAC1E,UAAU,UAAU,KAAK,iBAAiB,WAAW,KAAK;GAC1D,YAAY,KAAK,cAAc,SAAS;GACxC,MAAM,UAAU,KAAK,aAAa,WAAW,KAAK;GAClD,aAAa,KAAK,aAAa,SAAS;EAC1C;CACF;CAEA,aAAa,WAAmB,OAAqB,YAA4C;EAC/F,MAAM,UAAU,KAAK,QAAQ,WAAW,KAAK;EAC7C,MAAM,WAAW,QAAQ,KAAK,IAAI,WAAW,KAAK;EAIlD,MAAM,OAAwB;GAC5B,GAAG;GACH,GAAK,WAAW,YAAY,UAAU,WAAY,EAAE,UAAU,WAAW,YAAY,UAAU,SAAS,IAAI,CAAC;GAC7G,GAAK,WAAW,UAAU,UAAU,SAAU,EAAE,QAAQ,WAAW,UAAU,UAAU,OAAO,IAAI,CAAC;GACnG,QAAQ;EACV;EACA,KAAK,QAAQ,WAAW,SAAS,MAAM,QAAQ;CACjD;CAEA,UAAU,WAAmB,OAAqD;EAChF,MAAM,MAAM,KAAK,SAAS,IAAI,SAAS,CAAC,EAAE,KAAK,IAAI,KAAK;EACxD,OAAO,KAAK,SAAU,MAAmC,KAAA;CAC3D;CAEA,eAAe,WAAmB,OAAqB,YAAyC;EAC9F,MAAM,UAAU,KAAK,QAAQ,WAAW,KAAK;EAC7C,MAAM,WAAW,QAAQ,KAAK,IAAI,WAAW,KAAK;EAClD,MAAM,OAAwB;GAC5B,OAAO,WAAW;GAClB,OAAO,WAAW;GAGlB,GAAI,UAAU,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,SAAS,SAAS;GAC1E,GAAI,UAAU,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,SAAS,OAAO;GACpE,MAAM,WAAW;GACjB,KAAK,WAAW;GAChB,SAAS,WAAW;GACpB,QAAQ,WAAW;GACnB,WAAW,WAAW;GACtB,WAAW,WAAW;GACtB,GAAI,WAAW,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,OAAO,WAAW,MAAM;GACpE,GAAI,WAAW,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,SAAS,WAAW,QAAQ;GAC1E,GAAI,WAAW,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,WAAW,OAAO;GACvE,GAAI,WAAW,SAAS,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM,WAAW,KAAK;GACjE,GAAI,WAAW,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa,WAAW,YAAY;GACtF,GAAI,WAAW,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,WAAW,UAAU;GAChF,GAAI,cAAc,WAAW,aAAa,MAAM,KAAA,IAC5C,CAAC,IACD,EAAE,eAAe,cAAc,WAAW,aAAa,EAAE;GAC7D,GAAI,WAAW,oBAAoB,KAAA,IAAY,CAAC,IAAI,EAAE,iBAAiB,WAAW,gBAAgB;GAClG,GAAI,WAAW,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa,WAAW,YAAY;GACtF,GAAI,WAAW,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAE,gBAAgB,WAAW,eAAe;EACjG;EACA,KAAK,QAAQ,WAAW,SAAS,MAAM,QAAQ;CACjD;CAEA,mBAAmB,WAAmB,OAAqB,OAAe,OAAqB;EAC7F,MAAM,UAAU,KAAK,QAAQ,WAAW,KAAK;EAC7C,MAAM,WAAW,QAAQ,KAAK,IAAI,KAAK;EACvC,MAAM,MAAM,KAAK,IAAI;EACrB,MAAM,OAAwB;GAC5B,GAAI,YAAY,EAAE,MAAM;GACxB;GACA,QAAQ;GACR;GACA,WAAW;EACb;EACA,KAAK,QAAQ,WAAW,SAAS,MAAM,QAAQ;CACjD;CAEA,qBAAqB,WAAmB,OAAqB,OAAe,QAAiC;EAC3G,MAAM,UAAU,KAAK,QAAQ,WAAW,KAAK;EAC7C,MAAM,WAAW,QAAQ,KAAK,IAAI,KAAK;EACvC,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,UAAU,OAAO;EACvB,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,QAAQ,YAAY,QAAQ,WAAW;EAC/F,MAAM,UAAU,OAAO,OAAO,YAAY,WAAW,OAAO,UAAU,SAAS;EAC/E,MAAM,OAAwB;GAC5B,GAAG;GACH,QAAQ;GACR,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY,KAAK,IAAI;GAC9E,GAAI,YAAY,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ;GAC3C,GAAI,OAAO,OAAO,UAAU,WAAW,EAAE,OAAO,OAAO,MAAM,IAAI,CAAC;EACpE;EACA,KAAK,QAAQ,WAAW,SAAS,MAAM,QAAQ;EAC/C,OAAO;CACT;CAEA,QACE,WACA,SACA,MACA,UACM;EACN,QAAQ,KAAK,IAAI,KAAK,OAAO,IAAI;EACjC,MAAM,WAAW,WAAW,KAAK,MAAM;EACvC,IAAI,YAAY,CAAC,WAAW,UAAU,MAAM,GAAG,QAAQ,WAAW,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC;EAC5F,IAAI,CAAC,UAAU;GACb,QAAQ,WAAW,OAAO,KAAK,KAAK;GACpC,QAAQ,UAAU,OAAO,KAAK,KAAK;GACnC,KAAK,kBAAkB,SAAS,KAAK,KAAK;EAC5C;EACA,IAAI,QAAQ,UAAU,IAAI,GAAG,KAAK,WAAW,SAAS;CACxD;CAEA,mBAAmB,WAAmB,OAAqB;EACzD,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,MAAM,SAAS,SAAS,KAAK,IAAI,KAAK,CAAC,EAAE;EACzC,IAAI,CAAC,WAAW,CAAC,WAAW,MAAM,KAAK,QAAQ,UAAU,IAAI,KAAK,GAAG;EACrE,QAAQ,UAAU,IAAI,KAAK;EAC3B,KAAK,WAAW,SAAS;EACzB,MAAM,QAAQ,iBAAiB;GAC7B,KAAK,iBAAiB,WAAW,KAAK;EACxC,GAAG,KAAK,WAAW;EACnB,MAAM,QAAQ;EACd,QAAQ,cAAc,IAAI,OAAO,KAAK;CACxC;CAEA,mBAAmB,WAAsC;EACvD,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,SAAS,OAAO,CAAC;EACtB,OAAO,CAAC,GAAG,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,CAAC,WAAW,IAAI,MAAM,KAAK,CAAC,QAAQ,UAAU,IAAI,IAAI,KAAK,CAAC;CAChH;CAEA,UAAU,WAAmB,UAAkC;EAC7D,IAAI,CAAC,UAAU,KAAK,GAAG,MAAM,IAAI,MAAM,gEAAgE;EACvG,MAAM,YAAY,KAAK,UAAU,IAAI,SAAS,qBAAK,IAAI,IAAgB;EACvE,UAAU,IAAI,QAAQ;EACtB,KAAK,UAAU,IAAI,WAAW,SAAS;EACvC,aAAa;GACX,UAAU,OAAO,QAAQ;GACzB,IAAI,UAAU,SAAS,GAAG,KAAK,UAAU,OAAO,SAAS;EAC3D;CACF;CAEA,eAAuB,WAAmB,OAAe,UAAwD;EAC/G,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,YAAY,UAAU,2DAA2D;EAC/G,IAAI,CAAC,QAAQ,KAAK,IAAI,KAAK,GAAG;GAC5B,QAAQ,KAAK,IAAI,OAAO;IACtB;IACA,QAAQ,KAAA;IACR,GAAI,WAAW;KAAE,UAAU,SAAS;KAAU,QAAQ,SAAS;IAAO,IAAI,CAAC;GAC7E,CAAC;GACD,KAAK,WAAW,SAAS;EAC3B;CACF;CAEA,iBAAyB,WAAmB,OAAqB;EAC/D,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,SAAS;EACd,KAAK,kBAAkB,SAAS,KAAK;EACrC,MAAM,UAAU,QAAQ,KAAK,OAAO,KAAK;EACzC,QAAQ,WAAW,OAAO,KAAK;EAC/B,QAAQ,UAAU,OAAO,KAAK;EAC9B,IAAI,QAAQ,KAAK,SAAS,GAAG,KAAK,SAAS,OAAO,SAAS;EAC3D,IAAI,SAAS,KAAK,WAAW,SAAS;CACxC;CAEA,iBAAyB,WAAmB,OAAqB;EAC/D,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,WAAW,CAAC,QAAQ,UAAU,IAAI,KAAK,GAAG;EAC/C,QAAQ,cAAc,OAAO,KAAK;EAClC,KAAK,iBAAiB,WAAW,KAAK;CACxC;CAEA,kBAA0B,SAAsB,OAAqB;EACnE,MAAM,QAAQ,QAAQ,cAAc,IAAI,KAAK;EAC7C,IAAI,UAAU,KAAA,GAAW;GACvB,aAAa,KAAK;GAClB,QAAQ,cAAc,OAAO,KAAK;EACpC;CACF;CAEA,aAAqB,WAAyB;EAC5C,MAAM,UAAU,KAAK,SAAS,IAAI,SAAS;EAC3C,IAAI,CAAC,SAAS;EACd,KAAK,MAAM,SAAS,QAAQ,cAAc,OAAO,GAAG,aAAa,KAAK;EACtE,MAAM,UAAU,QAAQ,KAAK,OAAO;EACpC,KAAK,SAAS,OAAO,SAAS;EAC9B,IAAI,SAAS,KAAK,WAAW,SAAS;CACxC;CAEA,cAAsB,WAAsC;EAC1D,OAAO,CAAC,GAAI,KAAK,SAAS,IAAI,SAAS,CAAC,EAAE,KAAK,OAAO,KAAK,CAAC,CAAE;CAChE;CAEA,aAAqB,WAAmB,OAA4C;EAClF,OAAO,KAAK,SAAS,IAAI,SAAS,CAAC,EAAE,KAAK,IAAI,KAAK;CACrD;CAEA,OAAa;EACX,KAAK,MAAM,WAAW,KAAK,SAAS,OAAO,GACzC,KAAK,MAAM,SAAS,QAAQ,cAAc,OAAO,GAAG,aAAa,KAAK;EAExE,KAAK,SAAS,MAAM;EACpB,KAAK,UAAU,MAAM;CACvB;AACF"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/asyncJobTracker/index.ts"],"sourcesContent":["import type { ActivityState } from '../../types';\nimport type { ExternalRunProjection } from '../externalProcessIpc';\nimport type { SessionScope } from '../sessionPaths';\n\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\nconst DEFAULT_RETENTION_MS = 10 * 60_000;\n\ntype ExternalResult = Record<string, unknown>;\n\nexport interface TrackedAsyncJob {\n runId: string;\n agent?: string;\n /** Generated addressable identity, for example `alan-reviewer-3`. */\n identity?: string;\n /** True when this run came from a one-shot inline agent definition. */\n inline?: boolean;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n task?: string;\n cwd?: string;\n sessionFile?: string;\n transcriptPath?: string;\n summary?: string;\n native?: boolean;\n tokens?: number;\n cost?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport interface NativeAsyncJobProjection {\n runId: string;\n agent: string;\n identity?: string;\n inline?: boolean;\n task: string;\n cwd: string;\n runtime: string;\n status: string;\n startedAt: number;\n updatedAt: number;\n error?: string;\n sessionFile?: string;\n transcriptPath?: string;\n cost?: number;\n summary?: string;\n}\n\nexport type TrackedAsyncJobsContract = {\n track(runId: string, identity?: { identity: string; inline: boolean }): void;\n untrack(runId: string): void;\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = {\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract;\n stop(): void;\n};\n\n/**\n * Resolve a caller-supplied id to a tracked run id.\n *\n * Order matters. An exact run id wins, then a unique run-id prefix, and only\n * then a generated identity. A name is checked last so that a friendly name\n * which happens to look like a hex prefix can never shadow a real run.\n */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const runs = jobs.list();\n const matches = runs.map((job) => job.runId).filter((runId) => runId === id || runId.startsWith(id));\n const exact = matches.find((runId) => runId === id);\n if (exact) return exact;\n if (matches.length === 1) return matches[0]!;\n if (matches.length === 0) {\n const wanted = id.trim().toLowerCase();\n const named = runs.filter((job) => job.identity?.toLowerCase() === wanted);\n if (named.length === 1) return named[0]!.runId;\n if (named.length > 1) throw new Error(`Multiple current-session runs are named '${id}'.`);\n throw new Error(`No current-session run found for '${id}'.`);\n }\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n scope: SessionScope;\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n cleanupTimers: Map<string, ReturnType<typeof setTimeout>>;\n}\n\nfunction isTerminal(status: string | undefined): boolean {\n return status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(status);\n}\n\nfunction changed(previous: TrackedAsyncJob | undefined, next: TrackedAsyncJob): boolean {\n if (!previous) return true;\n return JSON.stringify(previous) !== JSON.stringify(next);\n}\n\nconst ACTIVITY_STATES: ReadonlySet<string> = new Set([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction activityState(value: string | undefined): ActivityState | undefined {\n return value !== undefined && ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n protected readonly retentionMs = DEFAULT_RETENTION_MS;\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n\n private session(sessionId: string, scope: SessionScope): SessionJobs {\n const existing = this.sessions.get(sessionId);\n if (existing) {\n if (existing.scope.scopeKey !== scope.scopeKey)\n throw new Error(`Session '${sessionId}' is already bound to another scope.`);\n return existing;\n }\n const created: SessionJobs = {\n scope,\n jobs: new Map(),\n terminalAt: new Map(),\n handedOff: new Set(),\n cleanupTimers: new Map(),\n };\n this.sessions.set(sessionId, created);\n return created;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string, scope: SessionScope): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n this.session(sessionId, scope);\n return {\n track: (runId, identity) => this.trackInSession(sessionId, runId, identity),\n untrack: (runId) => this.untrackInSession(sessionId, runId),\n list: () => this.listInSession(sessionId),\n get: (runId) => this.getInSession(sessionId, runId),\n reset: () => this.resetSession(sessionId),\n };\n }\n\n upsertNative(sessionId: string, scope: SessionScope, projection: NativeAsyncJobProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n // `projection` replaces the record wholesale, so anything the tracker knows\n // and the projection does not carry has to be restated here or it is lost\n // on the first child event.\n const next: TrackedAsyncJob = {\n ...projection,\n ...((projection.identity ?? previous?.identity) ? { identity: projection.identity ?? previous?.identity } : {}),\n ...((projection.inline ?? previous?.inline) ? { inline: projection.inline ?? previous?.inline } : {}),\n native: true,\n };\n this.install(sessionId, session, next, previous);\n }\n\n getNative(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n const job = this.sessions.get(sessionId)?.jobs.get(runId);\n return job?.native ? (job as NativeAsyncJobProjection) : undefined;\n }\n\n upsertExternal(sessionId: string, scope: SessionScope, projection: ExternalRunProjection): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(projection.runId);\n const next: TrackedAsyncJob = {\n runId: projection.runId,\n agent: projection.agent,\n // An external runner writes status.json and has never heard of the\n // identity, so it can only come from what the parent already tracked.\n ...(previous?.identity === undefined ? {} : { identity: previous.identity }),\n ...(previous?.inline === undefined ? {} : { inline: previous.inline }),\n task: projection.task,\n cwd: projection.cwd,\n runtime: projection.runtime,\n status: projection.state,\n startedAt: projection.startedAt,\n updatedAt: projection.updatedAt,\n ...(projection.error === undefined ? {} : { error: projection.error }),\n ...(projection.summary === undefined ? {} : { summary: projection.summary }),\n ...(projection.tokens === undefined ? {} : { tokens: projection.tokens }),\n ...(projection.cost === undefined ? {} : { cost: projection.cost }),\n ...(projection.currentTool === undefined ? {} : { currentTool: projection.currentTool }),\n ...(projection.toolCount === undefined ? {} : { toolCount: projection.toolCount }),\n ...(activityState(projection.activityState) === undefined\n ? {}\n : { activityState: activityState(projection.activityState) }),\n ...(projection.attentionReason === undefined ? {} : { attentionReason: projection.attentionReason }),\n ...(projection.sessionFile === undefined ? {} : { sessionFile: projection.sessionFile }),\n ...(projection.transcriptPath === undefined ? {} : { transcriptPath: projection.transcriptPath }),\n };\n this.install(sessionId, session, next, previous);\n }\n\n markExternalFailed(sessionId: string, scope: SessionScope, runId: string, error: string): void {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n const now = Date.now();\n const next: TrackedAsyncJob = {\n ...(previous ?? { runId }),\n runId,\n status: 'failed',\n error,\n updatedAt: now,\n };\n this.install(sessionId, session, next, previous);\n }\n\n acceptExternalResult(sessionId: string, scope: SessionScope, runId: string, result: ExternalResult): boolean {\n const session = this.session(sessionId, scope);\n const previous = session.jobs.get(runId);\n if (!previous) return false;\n const success = result.success;\n const state = typeof result.state === 'string' ? result.state : success === false ? 'failed' : 'completed';\n const summary = typeof result.summary === 'string' ? result.summary : previous.summary;\n const next: TrackedAsyncJob = {\n ...previous,\n status: state,\n updatedAt: typeof result.updatedAt === 'number' ? result.updatedAt : Date.now(),\n ...(summary === undefined ? {} : { summary }),\n ...(typeof result.error === 'string' ? { error: result.error } : {}),\n };\n this.install(sessionId, session, next, previous);\n return true;\n }\n\n private install(\n sessionId: string,\n session: SessionJobs,\n next: TrackedAsyncJob,\n previous: TrackedAsyncJob | undefined,\n ): void {\n session.jobs.set(next.runId, next);\n const terminal = isTerminal(next.status);\n if (terminal && !isTerminal(previous?.status)) session.terminalAt.set(next.runId, Date.now());\n if (!terminal) {\n session.terminalAt.delete(next.runId);\n session.handedOff.delete(next.runId);\n this.clearCleanupTimer(session, next.runId);\n }\n if (changed(previous, next)) this.invalidate(sessionId);\n }\n\n acknowledgeHandoff(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !isTerminal(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n const timer = setTimeout(() => {\n this.removeIfRetained(sessionId, runId);\n }, this.retentionMs);\n timer.unref?.();\n session.cleanupTimers.set(runId, timer);\n }\n\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter((job) => !isTerminal(job.status) || !session.handedOff.has(job.runId));\n }\n\n subscribe(sessionId: string, listener: () => void): () => void {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to subscribe to subagent runs.');\n const listeners = this.listeners.get(sessionId) ?? new Set<() => void>();\n listeners.add(listener);\n this.listeners.set(sessionId, listeners);\n return () => {\n listeners.delete(listener);\n if (listeners.size === 0) this.listeners.delete(sessionId);\n };\n }\n\n private trackInSession(sessionId: string, runId: string, identity?: { identity: string; inline: boolean }): void {\n const session = this.sessions.get(sessionId);\n if (!session) throw new Error(`Session '${sessionId}' must be bound to an explicit scope before tracking runs.`);\n if (!session.jobs.has(runId)) {\n session.jobs.set(runId, {\n runId,\n status: undefined,\n ...(identity ? { identity: identity.identity, inline: identity.inline } : {}),\n });\n this.invalidate(sessionId);\n }\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n this.clearCleanupTimer(session, runId);\n const deleted = session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n if (session.jobs.size === 0) this.sessions.delete(sessionId);\n if (deleted) this.invalidate(sessionId);\n }\n\n private removeIfRetained(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session || !session.handedOff.has(runId)) return;\n session.cleanupTimers.delete(runId);\n this.untrackInSession(sessionId, runId);\n }\n\n private clearCleanupTimer(session: SessionJobs, runId: string): void {\n const timer = session.cleanupTimers.get(runId);\n if (timer !== undefined) {\n clearTimeout(timer);\n session.cleanupTimers.delete(runId);\n }\n }\n\n private resetSession(sessionId: string): void {\n const session = this.sessions.get(sessionId);\n if (!session) return;\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n const hadJobs = session.jobs.size > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n private listInSession(sessionId: string): TrackedAsyncJob[] {\n return [...(this.sessions.get(sessionId)?.jobs.values() ?? [])];\n }\n\n private getInSession(sessionId: string, runId: string): TrackedAsyncJob | undefined {\n return this.sessions.get(sessionId)?.jobs.get(runId);\n }\n\n stop(): void {\n for (const session of this.sessions.values()) {\n for (const timer of session.cleanupTimers.values()) clearTimeout(timer);\n }\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":"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"}
@@ -96,11 +96,12 @@ var NativeRunCoordinator = class {
96
96
  status: event.state === "starting" ? "queued" : event.state === "running" ? "running" : event.state,
97
97
  startedAt: entry.startedAt,
98
98
  updatedAt: event.timestamp,
99
+ ...event.cost === void 0 ? {} : { cost: event.cost },
100
+ ...event.sessionFile ? { sessionFile: event.sessionFile } : {},
99
101
  ...event.message ? {
100
102
  error: status === "failed" || status === "stopped" ? event.message : void 0,
101
103
  summary: event.message
102
- } : {},
103
- ...event.sessionFile ? { sessionFile: event.sessionFile } : {}
104
+ } : {}
104
105
  };
105
106
  this.tracker.upsertNative(entry.sessionId, entry.scope, projection);
106
107
  this.projection?.publish(entry.sessionId, projection);
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/nativeRunCoordinator/index.ts"],"sourcesContent":["import type {\n DoomChildSessionEvent,\n DoomChildSessionHandle,\n DoomChildSessionRequest,\n DoomChildSessionServiceProvider,\n} from '@agimon-ai/doompi-core/child';\n\nimport type { AsyncJobTracker, NativeAsyncJobProjection } from '../asyncJobTracker';\nimport type { NativeRunProjectionSink } from '../nativeRunProjection';\nimport type { CompletionNotifierContract } from '../notify';\nimport type { SessionScope } from '../sessionPaths';\n\n/** Team-owned run identity, carried beside the shared child request rather than inside it. */\nexport interface NativeRunIdentity {\n readonly identity: string;\n readonly inline: boolean;\n}\n\nexport interface NativeRunCoordinatorContract {\n start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle>;\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined;\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined;\n steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void>;\n stop(sessionId: string, runId: string, reason?: string): Promise<void>;\n close(): Promise<void>;\n}\n\ninterface NativeRunEntry {\n readonly sessionId: string;\n readonly scope: SessionScope;\n readonly request: DoomChildSessionRequest;\n readonly identity?: NativeRunIdentity;\n readonly handle: DoomChildSessionHandle;\n unsubscribe?: () => void;\n startedAt?: number;\n terminal: boolean;\n}\n\nfunction terminalState(event: DoomChildSessionEvent): 'completed' | 'failed' | 'stopped' | undefined {\n return event.state === 'completed' || event.state === 'failed' || event.state === 'stopped' ? event.state : undefined;\n}\n\nfunction entryKey(sessionId: string, runId: string): string {\n return JSON.stringify([sessionId, runId]);\n}\n\nexport class NativeRunCoordinator implements NativeRunCoordinatorContract {\n private readonly entries = new Map<string, NativeRunEntry>();\n private closed = false;\n private closing: Promise<void> | undefined;\n\n constructor(\n private readonly childSessions: DoomChildSessionServiceProvider | undefined,\n private readonly tracker: AsyncJobTracker,\n private readonly notifier?: CompletionNotifierContract,\n private readonly projection?: NativeRunProjectionSink,\n ) {}\n\n async start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle> {\n if (this.closed) throw new Error('The native Team run coordinator is closed.');\n if (!sessionId.trim()) throw new Error('Native child runs require a session identity.');\n const service = this.childSessions?.get();\n if (!service) throw new Error('The in-process Pi child-session service is unavailable.');\n const handle = await service.start(request);\n if (this.closed) {\n await handle.dispose();\n throw new Error('The native Team run coordinator closed during child startup.');\n }\n const entry: NativeRunEntry = {\n sessionId,\n scope: request.scope,\n request,\n ...(identity ? { identity } : {}),\n handle,\n terminal: false,\n };\n const key = entryKey(sessionId, request.runId);\n this.entries.set(key, entry);\n try {\n const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));\n if (entry.terminal) unsubscribe();\n else entry.unsubscribe = unsubscribe;\n return handle;\n } catch (error) {\n this.entries.delete(key);\n await handle.dispose().catch(() => undefined);\n throw error;\n }\n }\n\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined {\n return this.entries.get(entryKey(sessionId, runId))?.handle;\n }\n\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n return this.tracker.getNative(sessionId, runId);\n }\n\n async steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.steer(message, signal);\n }\n\n async stop(sessionId: string, runId: string, reason?: string): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.stop(reason);\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.closing ??= (async () => {\n const active = [...this.entries.values()];\n const outcomes = await Promise.allSettled(active.map((entry) => entry.handle.dispose()));\n for (const entry of active) {\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n }\n const failures = outcomes\n .filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n .map((outcome) => outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Native Team run shutdown failed.');\n })();\n return this.closing;\n }\n\n private onEvent(entry: NativeRunEntry, event: DoomChildSessionEvent): void {\n entry.startedAt ??= event.timestamp;\n const status = terminalState(event);\n const projection: NativeAsyncJobProjection = {\n runId: event.runId,\n agent: entry.request.agent,\n // Carried on the projection, not merged in by the tracker, because\n // `upsertNative` replaces the record wholesale on every event.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n status: event.state === 'starting' ? 'queued' : event.state === 'running' ? 'running' : event.state,\n startedAt: entry.startedAt,\n updatedAt: event.timestamp,\n ...(event.message\n ? { error: status === 'failed' || status === 'stopped' ? event.message : undefined, summary: event.message }\n : {}),\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n this.tracker.upsertNative(entry.sessionId, entry.scope, projection);\n this.projection?.publish(entry.sessionId, projection);\n if (!status || entry.terminal) return;\n entry.terminal = true;\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n const result = {\n runId: entry.request.runId,\n agent: entry.request.agent,\n // The completion message is the only thing the model sees when a run\n // ends, so it names the agent the same way every other surface does.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n state: status,\n success: status === 'completed',\n summary:\n event.message ?? (status === 'completed' ? 'Native child run completed.' : `Native child run ${status}.`),\n durationMs: Math.max(\n 0,\n event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp),\n ),\n sessionId: entry.sessionId,\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n if (this.notifier) {\n void this.notifier\n .deliver(result)\n .then((delivered) => {\n if (delivered) this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);\n })\n .catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../../src/services/nativeRunCoordinator/index.ts"],"sourcesContent":["import type {\n DoomChildSessionEvent,\n DoomChildSessionHandle,\n DoomChildSessionRequest,\n DoomChildSessionServiceProvider,\n} from '@agimon-ai/doompi-core/child';\n\nimport type { AsyncJobTracker, NativeAsyncJobProjection } from '../asyncJobTracker';\nimport type { NativeRunProjectionSink } from '../nativeRunProjection';\nimport type { CompletionNotifierContract } from '../notify';\nimport type { SessionScope } from '../sessionPaths';\n\n/** Team-owned run identity, carried beside the shared child request rather than inside it. */\nexport interface NativeRunIdentity {\n readonly identity: string;\n readonly inline: boolean;\n}\n\nexport interface NativeRunCoordinatorContract {\n start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle>;\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined;\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined;\n steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void>;\n stop(sessionId: string, runId: string, reason?: string): Promise<void>;\n close(): Promise<void>;\n}\n\ninterface NativeRunEntry {\n readonly sessionId: string;\n readonly scope: SessionScope;\n readonly request: DoomChildSessionRequest;\n readonly identity?: NativeRunIdentity;\n readonly handle: DoomChildSessionHandle;\n unsubscribe?: () => void;\n startedAt?: number;\n terminal: boolean;\n}\n\nfunction terminalState(event: DoomChildSessionEvent): 'completed' | 'failed' | 'stopped' | undefined {\n return event.state === 'completed' || event.state === 'failed' || event.state === 'stopped' ? event.state : undefined;\n}\n\nfunction entryKey(sessionId: string, runId: string): string {\n return JSON.stringify([sessionId, runId]);\n}\n\nexport class NativeRunCoordinator implements NativeRunCoordinatorContract {\n private readonly entries = new Map<string, NativeRunEntry>();\n private closed = false;\n private closing: Promise<void> | undefined;\n\n constructor(\n private readonly childSessions: DoomChildSessionServiceProvider | undefined,\n private readonly tracker: AsyncJobTracker,\n private readonly notifier?: CompletionNotifierContract,\n private readonly projection?: NativeRunProjectionSink,\n ) {}\n\n async start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle> {\n if (this.closed) throw new Error('The native Team run coordinator is closed.');\n if (!sessionId.trim()) throw new Error('Native child runs require a session identity.');\n const service = this.childSessions?.get();\n if (!service) throw new Error('The in-process Pi child-session service is unavailable.');\n const handle = await service.start(request);\n if (this.closed) {\n await handle.dispose();\n throw new Error('The native Team run coordinator closed during child startup.');\n }\n const entry: NativeRunEntry = {\n sessionId,\n scope: request.scope,\n request,\n ...(identity ? { identity } : {}),\n handle,\n terminal: false,\n };\n const key = entryKey(sessionId, request.runId);\n this.entries.set(key, entry);\n try {\n const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));\n if (entry.terminal) unsubscribe();\n else entry.unsubscribe = unsubscribe;\n return handle;\n } catch (error) {\n this.entries.delete(key);\n await handle.dispose().catch(() => undefined);\n throw error;\n }\n }\n\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined {\n return this.entries.get(entryKey(sessionId, runId))?.handle;\n }\n\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n return this.tracker.getNative(sessionId, runId);\n }\n\n async steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.steer(message, signal);\n }\n\n async stop(sessionId: string, runId: string, reason?: string): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.stop(reason);\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.closing ??= (async () => {\n const active = [...this.entries.values()];\n const outcomes = await Promise.allSettled(active.map((entry) => entry.handle.dispose()));\n for (const entry of active) {\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n }\n const failures = outcomes\n .filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n .map((outcome) => outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Native Team run shutdown failed.');\n })();\n return this.closing;\n }\n\n private onEvent(entry: NativeRunEntry, event: DoomChildSessionEvent): void {\n entry.startedAt ??= event.timestamp;\n const status = terminalState(event);\n const projection: NativeAsyncJobProjection = {\n runId: event.runId,\n agent: entry.request.agent,\n // Carried on the projection, not merged in by the tracker, because\n // `upsertNative` replaces the record wholesale on every event.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n status: event.state === 'starting' ? 'queued' : event.state === 'running' ? 'running' : event.state,\n startedAt: entry.startedAt,\n updatedAt: event.timestamp,\n ...(event.cost === undefined ? {} : { cost: event.cost }),\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n ...(event.message\n ? { error: status === 'failed' || status === 'stopped' ? event.message : undefined, summary: event.message }\n : {}),\n };\n this.tracker.upsertNative(entry.sessionId, entry.scope, projection);\n this.projection?.publish(entry.sessionId, projection);\n if (!status || entry.terminal) return;\n entry.terminal = true;\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n const result = {\n runId: entry.request.runId,\n agent: entry.request.agent,\n // The completion message is the only thing the model sees when a run\n // ends, so it names the agent the same way every other surface does.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n state: status,\n success: status === 'completed',\n summary:\n event.message ?? (status === 'completed' ? 'Native child run completed.' : `Native child run ${status}.`),\n durationMs: Math.max(\n 0,\n event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp),\n ),\n sessionId: entry.sessionId,\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n if (this.notifier) {\n void this.notifier\n .deliver(result)\n .then((delivered) => {\n if (delivered) this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);\n })\n .catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
@@ -96,11 +96,12 @@ var NativeRunCoordinator = class {
96
96
  status: event.state === "starting" ? "queued" : event.state === "running" ? "running" : event.state,
97
97
  startedAt: entry.startedAt,
98
98
  updatedAt: event.timestamp,
99
+ ...event.cost === void 0 ? {} : { cost: event.cost },
100
+ ...event.sessionFile ? { sessionFile: event.sessionFile } : {},
99
101
  ...event.message ? {
100
102
  error: status === "failed" || status === "stopped" ? event.message : void 0,
101
103
  summary: event.message
102
- } : {},
103
- ...event.sessionFile ? { sessionFile: event.sessionFile } : {}
104
+ } : {}
104
105
  };
105
106
  this.tracker.upsertNative(entry.sessionId, entry.scope, projection);
106
107
  this.projection?.publish(entry.sessionId, projection);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/nativeRunCoordinator/index.ts"],"sourcesContent":["import type {\n DoomChildSessionEvent,\n DoomChildSessionHandle,\n DoomChildSessionRequest,\n DoomChildSessionServiceProvider,\n} from '@agimon-ai/doompi-core/child';\n\nimport type { AsyncJobTracker, NativeAsyncJobProjection } from '../asyncJobTracker';\nimport type { NativeRunProjectionSink } from '../nativeRunProjection';\nimport type { CompletionNotifierContract } from '../notify';\nimport type { SessionScope } from '../sessionPaths';\n\n/** Team-owned run identity, carried beside the shared child request rather than inside it. */\nexport interface NativeRunIdentity {\n readonly identity: string;\n readonly inline: boolean;\n}\n\nexport interface NativeRunCoordinatorContract {\n start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle>;\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined;\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined;\n steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void>;\n stop(sessionId: string, runId: string, reason?: string): Promise<void>;\n close(): Promise<void>;\n}\n\ninterface NativeRunEntry {\n readonly sessionId: string;\n readonly scope: SessionScope;\n readonly request: DoomChildSessionRequest;\n readonly identity?: NativeRunIdentity;\n readonly handle: DoomChildSessionHandle;\n unsubscribe?: () => void;\n startedAt?: number;\n terminal: boolean;\n}\n\nfunction terminalState(event: DoomChildSessionEvent): 'completed' | 'failed' | 'stopped' | undefined {\n return event.state === 'completed' || event.state === 'failed' || event.state === 'stopped' ? event.state : undefined;\n}\n\nfunction entryKey(sessionId: string, runId: string): string {\n return JSON.stringify([sessionId, runId]);\n}\n\nexport class NativeRunCoordinator implements NativeRunCoordinatorContract {\n private readonly entries = new Map<string, NativeRunEntry>();\n private closed = false;\n private closing: Promise<void> | undefined;\n\n constructor(\n private readonly childSessions: DoomChildSessionServiceProvider | undefined,\n private readonly tracker: AsyncJobTracker,\n private readonly notifier?: CompletionNotifierContract,\n private readonly projection?: NativeRunProjectionSink,\n ) {}\n\n async start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle> {\n if (this.closed) throw new Error('The native Team run coordinator is closed.');\n if (!sessionId.trim()) throw new Error('Native child runs require a session identity.');\n const service = this.childSessions?.get();\n if (!service) throw new Error('The in-process Pi child-session service is unavailable.');\n const handle = await service.start(request);\n if (this.closed) {\n await handle.dispose();\n throw new Error('The native Team run coordinator closed during child startup.');\n }\n const entry: NativeRunEntry = {\n sessionId,\n scope: request.scope,\n request,\n ...(identity ? { identity } : {}),\n handle,\n terminal: false,\n };\n const key = entryKey(sessionId, request.runId);\n this.entries.set(key, entry);\n try {\n const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));\n if (entry.terminal) unsubscribe();\n else entry.unsubscribe = unsubscribe;\n return handle;\n } catch (error) {\n this.entries.delete(key);\n await handle.dispose().catch(() => undefined);\n throw error;\n }\n }\n\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined {\n return this.entries.get(entryKey(sessionId, runId))?.handle;\n }\n\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n return this.tracker.getNative(sessionId, runId);\n }\n\n async steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.steer(message, signal);\n }\n\n async stop(sessionId: string, runId: string, reason?: string): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.stop(reason);\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.closing ??= (async () => {\n const active = [...this.entries.values()];\n const outcomes = await Promise.allSettled(active.map((entry) => entry.handle.dispose()));\n for (const entry of active) {\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n }\n const failures = outcomes\n .filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n .map((outcome) => outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Native Team run shutdown failed.');\n })();\n return this.closing;\n }\n\n private onEvent(entry: NativeRunEntry, event: DoomChildSessionEvent): void {\n entry.startedAt ??= event.timestamp;\n const status = terminalState(event);\n const projection: NativeAsyncJobProjection = {\n runId: event.runId,\n agent: entry.request.agent,\n // Carried on the projection, not merged in by the tracker, because\n // `upsertNative` replaces the record wholesale on every event.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n status: event.state === 'starting' ? 'queued' : event.state === 'running' ? 'running' : event.state,\n startedAt: entry.startedAt,\n updatedAt: event.timestamp,\n ...(event.message\n ? { error: status === 'failed' || status === 'stopped' ? event.message : undefined, summary: event.message }\n : {}),\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n this.tracker.upsertNative(entry.sessionId, entry.scope, projection);\n this.projection?.publish(entry.sessionId, projection);\n if (!status || entry.terminal) return;\n entry.terminal = true;\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n const result = {\n runId: entry.request.runId,\n agent: entry.request.agent,\n // The completion message is the only thing the model sees when a run\n // ends, so it names the agent the same way every other surface does.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n state: status,\n success: status === 'completed',\n summary:\n event.message ?? (status === 'completed' ? 'Native child run completed.' : `Native child run ${status}.`),\n durationMs: Math.max(\n 0,\n event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp),\n ),\n sessionId: entry.sessionId,\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n if (this.notifier) {\n void this.notifier\n .deliver(result)\n .then((delivered) => {\n if (delivered) this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);\n })\n .catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/nativeRunCoordinator/index.ts"],"sourcesContent":["import type {\n DoomChildSessionEvent,\n DoomChildSessionHandle,\n DoomChildSessionRequest,\n DoomChildSessionServiceProvider,\n} from '@agimon-ai/doompi-core/child';\n\nimport type { AsyncJobTracker, NativeAsyncJobProjection } from '../asyncJobTracker';\nimport type { NativeRunProjectionSink } from '../nativeRunProjection';\nimport type { CompletionNotifierContract } from '../notify';\nimport type { SessionScope } from '../sessionPaths';\n\n/** Team-owned run identity, carried beside the shared child request rather than inside it. */\nexport interface NativeRunIdentity {\n readonly identity: string;\n readonly inline: boolean;\n}\n\nexport interface NativeRunCoordinatorContract {\n start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle>;\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined;\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined;\n steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void>;\n stop(sessionId: string, runId: string, reason?: string): Promise<void>;\n close(): Promise<void>;\n}\n\ninterface NativeRunEntry {\n readonly sessionId: string;\n readonly scope: SessionScope;\n readonly request: DoomChildSessionRequest;\n readonly identity?: NativeRunIdentity;\n readonly handle: DoomChildSessionHandle;\n unsubscribe?: () => void;\n startedAt?: number;\n terminal: boolean;\n}\n\nfunction terminalState(event: DoomChildSessionEvent): 'completed' | 'failed' | 'stopped' | undefined {\n return event.state === 'completed' || event.state === 'failed' || event.state === 'stopped' ? event.state : undefined;\n}\n\nfunction entryKey(sessionId: string, runId: string): string {\n return JSON.stringify([sessionId, runId]);\n}\n\nexport class NativeRunCoordinator implements NativeRunCoordinatorContract {\n private readonly entries = new Map<string, NativeRunEntry>();\n private closed = false;\n private closing: Promise<void> | undefined;\n\n constructor(\n private readonly childSessions: DoomChildSessionServiceProvider | undefined,\n private readonly tracker: AsyncJobTracker,\n private readonly notifier?: CompletionNotifierContract,\n private readonly projection?: NativeRunProjectionSink,\n ) {}\n\n async start(\n sessionId: string,\n request: DoomChildSessionRequest,\n identity?: NativeRunIdentity,\n ): Promise<DoomChildSessionHandle> {\n if (this.closed) throw new Error('The native Team run coordinator is closed.');\n if (!sessionId.trim()) throw new Error('Native child runs require a session identity.');\n const service = this.childSessions?.get();\n if (!service) throw new Error('The in-process Pi child-session service is unavailable.');\n const handle = await service.start(request);\n if (this.closed) {\n await handle.dispose();\n throw new Error('The native Team run coordinator closed during child startup.');\n }\n const entry: NativeRunEntry = {\n sessionId,\n scope: request.scope,\n request,\n ...(identity ? { identity } : {}),\n handle,\n terminal: false,\n };\n const key = entryKey(sessionId, request.runId);\n this.entries.set(key, entry);\n try {\n const unsubscribe = handle.subscribe((event) => this.onEvent(entry, event));\n if (entry.terminal) unsubscribe();\n else entry.unsubscribe = unsubscribe;\n return handle;\n } catch (error) {\n this.entries.delete(key);\n await handle.dispose().catch(() => undefined);\n throw error;\n }\n }\n\n get(sessionId: string, runId: string): DoomChildSessionHandle | undefined {\n return this.entries.get(entryKey(sessionId, runId))?.handle;\n }\n\n status(sessionId: string, runId: string): NativeAsyncJobProjection | undefined {\n return this.tracker.getNative(sessionId, runId);\n }\n\n async steer(sessionId: string, runId: string, message: string, signal?: AbortSignal): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.steer(message, signal);\n }\n\n async stop(sessionId: string, runId: string, reason?: string): Promise<void> {\n const handle = this.get(sessionId, runId);\n if (!handle) throw new Error(`No active native run matches '${runId}'.`);\n await handle.stop(reason);\n }\n\n async close(): Promise<void> {\n this.closed = true;\n this.closing ??= (async () => {\n const active = [...this.entries.values()];\n const outcomes = await Promise.allSettled(active.map((entry) => entry.handle.dispose()));\n for (const entry of active) {\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n }\n const failures = outcomes\n .filter((outcome): outcome is PromiseRejectedResult => outcome.status === 'rejected')\n .map((outcome) => outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Native Team run shutdown failed.');\n })();\n return this.closing;\n }\n\n private onEvent(entry: NativeRunEntry, event: DoomChildSessionEvent): void {\n entry.startedAt ??= event.timestamp;\n const status = terminalState(event);\n const projection: NativeAsyncJobProjection = {\n runId: event.runId,\n agent: entry.request.agent,\n // Carried on the projection, not merged in by the tracker, because\n // `upsertNative` replaces the record wholesale on every event.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n status: event.state === 'starting' ? 'queued' : event.state === 'running' ? 'running' : event.state,\n startedAt: entry.startedAt,\n updatedAt: event.timestamp,\n ...(event.cost === undefined ? {} : { cost: event.cost }),\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n ...(event.message\n ? { error: status === 'failed' || status === 'stopped' ? event.message : undefined, summary: event.message }\n : {}),\n };\n this.tracker.upsertNative(entry.sessionId, entry.scope, projection);\n this.projection?.publish(entry.sessionId, projection);\n if (!status || entry.terminal) return;\n entry.terminal = true;\n entry.unsubscribe?.();\n this.entries.delete(entryKey(entry.sessionId, entry.request.runId));\n const result = {\n runId: entry.request.runId,\n agent: entry.request.agent,\n // The completion message is the only thing the model sees when a run\n // ends, so it names the agent the same way every other surface does.\n ...(entry.identity ? { identity: entry.identity.identity, inline: entry.identity.inline } : {}),\n task: entry.request.task,\n cwd: entry.request.cwd,\n runtime: 'pi',\n state: status,\n success: status === 'completed',\n summary:\n event.message ?? (status === 'completed' ? 'Native child run completed.' : `Native child run ${status}.`),\n durationMs: Math.max(\n 0,\n event.timestamp - (this.tracker.getNative(entry.sessionId, entry.request.runId)?.startedAt ?? event.timestamp),\n ),\n sessionId: entry.sessionId,\n ...(event.sessionFile ? { sessionFile: event.sessionFile } : {}),\n };\n if (this.notifier) {\n void this.notifier\n .deliver(result)\n .then((delivered) => {\n if (delivered) this.tracker.acknowledgeHandoff(entry.sessionId, entry.request.runId);\n })\n .catch(() => undefined);\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-team",
3
- "version": "0.0.1-alpha.73",
3
+ "version": "0.0.1-alpha.76",
4
4
  "description": "Asynchronous named subagents, team runs, intercom, and model policy for Pi coding agents.",
5
5
  "keywords": [
6
6
  "agent-team",
@@ -124,19 +124,19 @@
124
124
  "registry": "https://registry.npmjs.org/"
125
125
  },
126
126
  "dependencies": {
127
- "@agimon-ai/doompi-cache": "0.0.1-alpha.39",
128
- "@agimon-ai/doompi-config": "0.0.1-alpha.72",
129
- "@agimon-ai/doompi-core": "0.0.1-alpha.73",
130
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.70",
131
- "@agimon-ai/doompi-ui": "0.0.1-alpha.73",
132
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.31",
133
- "@agimon-ai/doompi-web-security": "0.0.1-alpha.33",
127
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.42",
128
+ "@agimon-ai/doompi-config": "0.0.1-alpha.75",
129
+ "@agimon-ai/doompi-core": "0.0.1-alpha.76",
130
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.73",
131
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.76",
132
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.34",
133
+ "@agimon-ai/doompi-web-security": "0.0.1-alpha.36",
134
134
  "@deepseek-ai/cordis": "4.0.2",
135
135
  "typebox": "1.3.30",
136
136
  "yaml": "2.9.0"
137
137
  },
138
138
  "devDependencies": {
139
- "@agimon-ai/doompi-build": "0.0.1-alpha.2",
139
+ "@agimon-ai/doompi-build": "0.0.1-alpha.5",
140
140
  "@earendil-works/pi-agent-core": "0.85.1",
141
141
  "@earendil-works/pi-ai": "0.85.1",
142
142
  "@earendil-works/pi-coding-agent": "0.85.1",