@agimon-ai/doompi-team 0.0.1-alpha.53 → 0.0.1-alpha.55

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.
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./filesystem/paths.cjs"),n=require("./runs/background/statusWriter.cjs"),r=require("./statusReader.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);const o=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]);function s(e,t){let n=e.list().map(e=>e.runId).filter(e=>e===t||e.startsWith(t)),r=n.find(e=>e===t);if(r)return r;if(n.length===1)return n[0];throw n.length===0?Error(`No current-session run found for '${t}'.`):Error(`Multiple current-session runs match '${t}'. Use a longer id.`)}const c=`__unscoped__`;function l(e){return a.join(t.currentRunsDir(),e,n.STATUS_FILE_NAME)}function u(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function d(e){return u(e)===`ENOENT`}const f=new Set([`starting`,`working`,`tool`,`waiting_for_reply`,`needs_attention`,`finalizing`,`active_long_running`]);function p(e){return typeof e==`string`&&f.has(e)?e:void 0}var m=class{scheduler;constructor(e){this.scheduler=e}pollIntervalMs=250;retentionMs=1e4;now(){return Date.now()}readFile(e){try{return i.readFileSync(e,`utf-8`)}catch(e){if(d(e))return;throw e}}async readFileAsync(e){try{return await i.promises.readFile(e,`utf-8`)}catch(e){if(d(e))return;throw e}}sessions=new Map;unregisterPoll;session(e){let t=this.sessions.get(e);return t||(t={jobs:new Map,terminalAt:new Map},this.sessions.set(e,t)),t}forSession(e){if(!e.trim())throw Error(`Pi session identity is required to track subagent runs.`);return{track:t=>this.trackInSession(e,t),untrack:t=>this.untrackInSession(e,t),list:()=>this.listInSession(e),get:t=>this.getInSession(e,t),reset:()=>this.resetSession(e)}}parseStatus(e,t){if(t===void 0)return;let n=r.parseAsyncRunStatus(t,l(e));if(n.kind!==`ok`)return;let i=n.status;return{agent:i.agent,status:i.state,startedAt:i.startedAt,updatedAt:i.lastUpdate,error:i.error,activityState:p(i.activityState),attentionReason:i.attentionReason,runtime:i.runtime,tokens:i.tokens,currentTool:i.currentTool,toolCount:i.toolCount}}readStatus(e){return this.parseStatus(e,this.readFile(l(e)))}async readStatusAsync(e){return this.parseStatus(e,await this.readFileAsync(l(e)))}applyStatus(e,t,n){let r=this.sessions.get(e);if(!r)return!1;let i=r.jobs.get(t);if(n===void 0)return i||r.jobs.set(t,{runId:t,status:void 0}),!1;let a={runId:t,...n};r.jobs.set(t,a);let s=i?.status!==void 0&&o.has(i.status),c=a.status!==void 0&&o.has(a.status);return c&&!s&&r.terminalAt.set(t,this.now()),c||r.terminalAt.delete(t),!i||i.status!==a.status||i.updatedAt!==a.updatedAt||i.tokens!==a.tokens||i.currentTool!==a.currentTool||i.toolCount!==a.toolCount}refresh(e,t){return this.applyStatus(e,t,this.readStatus(t))}async refreshAsync(e,t){return this.applyStatus(e,t,await this.readStatusAsync(t))}async run(){let e=!1,t=this.now();for(let[n,r]of this.sessions){for(let t of r.jobs.keys())await this.refreshAsync(n,t)&&(e=!0);for(let[n,i]of r.terminalAt)t-i<=this.retentionMs||(r.jobs.delete(n),r.terminalAt.delete(n),e=!0);r.jobs.size===0&&this.sessions.get(n)===r&&this.sessions.delete(n)}return e}track(e){this.trackInSession(c,e)}untrack(e){this.untrackInSession(c,e)}list(){return this.listInSession(c)}get(e){return this.getInSession(c,e)}reset(){this.resetSession(c)}trackInSession(e,t){this.session(e),this.refresh(e,t)}untrackInSession(e,t){let n=this.sessions.get(e);n?.jobs.delete(t),n?.terminalAt.delete(t),n?.jobs.size===0&&this.sessions.delete(e)}listInSession(e){return[...this.sessions.get(e)?.jobs.values()??[]]}getInSession(e,t){return this.sessions.get(e)?.jobs.get(t)}resetSession(e){this.sessions.delete(e)}start(){this.stop(),this.unregisterPoll=this.scheduler.register({id:`async-job-tracker`,intervalMs:this.pollIntervalMs,run:()=>this.run()})}stop(){this.unregisterPoll?.(),this.unregisterPoll=void 0,this.sessions.clear()}};exports.AsyncJobTracker=m,exports.TERMINAL_ASYNC_JOB_STATES=o,exports.resolveTrackedRunId=s;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./filesystem/paths.cjs"),n=require("./runs/background/statusWriter.cjs"),r=require("./statusReader.cjs");let i=require("node:fs");i=e.__toESM(i,1);let a=require("node:path");a=e.__toESM(a,1);const o=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]);function s(e,t){let n=e.list().map(e=>e.runId).filter(e=>e===t||e.startsWith(t)),r=n.find(e=>e===t);if(r)return r;if(n.length===1)return n[0];throw n.length===0?Error(`No current-session run found for '${t}'.`):Error(`Multiple current-session runs match '${t}'. Use a longer id.`)}const c=`__unscoped__`;function l(e){return a.join(t.currentRunsDir(),e,n.STATUS_FILE_NAME)}function u(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function d(e){return u(e)===`ENOENT`}const f=new Set([`starting`,`working`,`tool`,`waiting_for_reply`,`needs_attention`,`finalizing`,`active_long_running`]);function p(e){return typeof e==`string`&&f.has(e)?e:void 0}var m=class{scheduler;constructor(e){this.scheduler=e}pollIntervalMs=250;retentionMs=1e4;now(){return Date.now()}readFile(e){try{return i.readFileSync(e,`utf-8`)}catch(e){if(d(e))return;throw e}}async readFileAsync(e){try{return await i.promises.readFile(e,`utf-8`)}catch(e){if(d(e))return;throw e}}sessions=new Map;listeners=new Map;unregisterPoll;session(e){let t=this.sessions.get(e);return t||(t={jobs:new Map,terminalAt:new Map,handedOff:new Set},this.sessions.set(e,t)),t}invalidate(e){for(let t of this.listeners.get(e)??[])t()}forSession(e){if(!e.trim())throw Error(`Pi session identity is required to track subagent runs.`);return{track:t=>this.trackInSession(e,t),untrack:t=>this.untrackInSession(e,t),list:()=>this.listInSession(e),get:t=>this.getInSession(e,t),reset:()=>this.resetSession(e)}}parseStatus(e,t){if(t===void 0)return;let n=r.parseAsyncRunStatus(t,l(e));if(n.kind!==`ok`)return;let i=n.status;return{agent:i.agent,status:i.state,startedAt:i.startedAt,updatedAt:i.lastUpdate,error:i.error,activityState:p(i.activityState),attentionReason:i.attentionReason,runtime:i.runtime,tokens:i.tokens,currentTool:i.currentTool,toolCount:i.toolCount}}readStatus(e){return this.parseStatus(e,this.readFile(l(e)))}async readStatusAsync(e){return this.parseStatus(e,await this.readFileAsync(l(e)))}applyStatus(e,t,n){let r=this.sessions.get(e);if(!r)return!1;let i=r.jobs.get(t);if(n===void 0)return!i&&(r.jobs.set(t,{runId:t,status:void 0}),!0);let a={runId:t,...n};r.jobs.set(t,a);let s=i?.status!==void 0&&o.has(i.status),c=a.status!==void 0&&o.has(a.status);return c&&!s&&r.terminalAt.set(t,this.now()),c||(r.terminalAt.delete(t),r.handedOff.delete(t)),!i||i.status!==a.status||i.updatedAt!==a.updatedAt||i.tokens!==a.tokens||i.currentTool!==a.currentTool||i.toolCount!==a.toolCount}refresh(e,t){let n=this.applyStatus(e,t,this.readStatus(t));return n&&this.invalidate(e),n}async refreshAsync(e,t){let n=this.applyStatus(e,t,await this.readStatusAsync(t));return n&&this.invalidate(e),n}async run(){let e=!1,t=this.now();for(let[n,r]of this.sessions){for(let t of r.jobs.keys())await this.refreshAsync(n,t)&&(e=!0);for(let[i,a]of r.terminalAt)!r.handedOff.has(i)||t-a<=this.retentionMs||(r.jobs.delete(i),r.terminalAt.delete(i),r.handedOff.delete(i),this.invalidate(n),e=!0);r.jobs.size===0&&this.sessions.get(n)===r&&this.sessions.delete(n)}return e}track(e){this.trackInSession(c,e)}untrack(e){this.untrackInSession(c,e)}acknowledgeHandoff(e,t){this.acknowledgeHandoffInSession(e,t)}listBackgroundWork(e){let t=this.sessions.get(e);return t?[...t.jobs.values()].filter(e=>e.status===void 0||!o.has(e.status)||!t.handedOff.has(e.runId)):[]}subscribe(e,t){if(!e.trim())throw Error(`Pi session identity is required to subscribe to subagent runs.`);return this.subscribeInSession(e,t)}list(){return this.listInSession(c)}get(e){return this.getInSession(c,e)}reset(){this.resetSession(c)}trackInSession(e,t){this.session(e),this.refresh(e,t)}untrackInSession(e,t){let n=this.sessions.get(e),r=n?.jobs.delete(t)??!1;n?.terminalAt.delete(t),n?.handedOff.delete(t),n?.jobs.size===0&&this.sessions.delete(e),r&&this.invalidate(e)}acknowledgeHandoffInSession(e,t){let n=this.sessions.get(e),r=n?.jobs.get(t)?.status;!n||!r||!o.has(r)||n.handedOff.has(t)||(n.handedOff.add(t),this.invalidate(e))}subscribeInSession(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>{n.delete(t),n.size===0&&this.listeners.delete(e)}}listInSession(e){return[...this.sessions.get(e)?.jobs.values()??[]]}getInSession(e,t){return this.sessions.get(e)?.jobs.get(t)}resetSession(e){let t=(this.sessions.get(e)?.jobs.size??0)>0;this.sessions.delete(e),t&&this.invalidate(e)}start(){this.stop(),this.unregisterPoll=this.scheduler.register({id:`async-job-tracker`,intervalMs:this.pollIntervalMs,run:()=>this.run()})}stop(){this.unregisterPoll?.(),this.unregisterPoll=void 0,this.sessions.clear(),this.listeners.clear()}};exports.AsyncJobTracker=m,exports.TERMINAL_ASYNC_JOB_STATES=o,exports.resolveTrackedRunId=s;
2
2
  //# sourceMappingURL=asyncJobTracker.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncJobTracker.cjs","names":["path","currentRunsDir","STATUS_FILE_NAME","fs","parseAsyncRunStatus"],"sources":["../../../src/adapters/asyncJobTracker.ts"],"sourcesContent":["/**\n * Live, in-memory registry of the async runs this session currently cares\n * about, refreshed from `status.json` on a `PollScheduler` tick.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/asyncJobTracker.ts` is 590 lines\n * doing four different jobs: this registry, TUI widget rendering\n * (`renderWidget`/`widgetRenderKey`/`ctx.ui.requestRender`), forwarding\n * control events and steering notices by byte-range-tailing each run's\n * `events.jsonl`, and projecting nested/parallel-group run trees. Only the\n * registry is ported. The other three have no consumer in this package yet:\n * TUI rendering is `G8` (slash commands + TUI), and nested/chain/parallel run\n * modes are `G7`+ (this package is currently single-run async-only - see\n * `spawnHandshake.ts`'s header). Porting rendering or event-tailing logic\n * with nothing to render to or forward to would be untested, unreachable\n * code; it is deferred to whichever later port actually needs it, not\n * dropped silently - this note is where to look for it.\n *\n * DESIGN PATTERNS:\n * - The in-memory map is a pure CACHE, never authoritative on its own:\n * `status.json` (owned by `CoalescedStatusWriter` elsewhere) is the only\n * source of truth. `track()` reads it synchronously immediately, so a\n * caller that already knows a run's id sees *something* right away rather\n * than waiting out the first poll\n * - Restart survivability follows from the point above rather than a second\n * persistence layer: this package has not built session-scoped run\n * ownership yet (which ids \"belong\" to a restarted session is not\n * something this module can determine on its own - see the AVOID entry),\n * so recovery is the caller re-`track()`-ing whatever ids it still cares\n * about from ITS OWN durable record. The very next poll tick repopulates\n * that job's status straight from disk. There is no separate store to keep\n * in sync, and therefore no window where memory could diverge from disk\n * that a restart would not immediately close\n * - Registered with `PollScheduler`, not a private timer: this polls\n * potentially many jobs' status files on an unbounded, periodic cadence\n * with no low-latency requirement - the exact shape `PollScheduler` exists\n * for. Contrast `completionBatcher.ts`/`spawnHandshake.ts`, which own\n * their own timers because they are short-lived, single-shot, and need\n * sub-200ms responsiveness `PollScheduler`'s floor cannot give them; this\n * registry needs neither\n * - Retention-then-eviction happens on the SAME poll tick as the status\n * refresh, rather than the predecessor's per-job `setTimeout`\n * (`scheduleCleanup`): a terminal job aged past `retentionMs` is simply\n * dropped the next time `run()` walks the map. One clock, not one timer per\n * job\n *\n * AVOID:\n * - Scanning `currentRunsDir()` to guess which run ids \"belong\" to this session on\n * startup. This package has no session-scoped run ownership concept yet;\n * inventing one here to answer \"what should I restore\" would be a guess,\n * not a fact this module has standing to assert. Restoration is the\n * caller's job (see the design note above)\n * - Growing `terminalStates` independently of `staleRunReconciler.ts`'s own\n * (unexported) copy; both must treat the same state strings as terminal,\n * or a job could be evicted here while the reconciler still thinks it is\n * in flight, or vice versa\n *\n * `activityState`/`attentionReason` (added for `subagentWait.ts`):\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and a `reason` (e.g. `'missing-deliverable'`), but returns it rather than\n * writing it anywhere - it has no standing to touch `status.json` itself (see\n * that module's header). Something upstream of this tracker - the run's own\n * runner main loop, not yet built - is responsible for folding that result\n * into `status.json` through `CoalescedStatusWriter`. This tracker's job is\n * only to read whatever ends up there, the same as every other field: it does\n * not compute attention, detect it, or decide when a run needs it.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseAsyncRunStatus } from './statusReader';\nimport { STATUS_FILE_NAME } from './runs/background/statusWriter';\nimport { currentRunsDir } from './filesystem/paths';\nimport type { PollSchedulerContract } from './pollScheduler';\nimport type { ActivityState } from '../types';\n\n/**\n * States that mean \"already finished\"; matches `staleRunReconciler.ts`'s\n * own (unexported) set. Exported so a caller outside this module (the slash\n * layer's poll-until-terminal helper) can recognize the same terminal set\n * `TrackedAsyncJob.status` uses, rather than declaring a third copy of this\n * literal.\n */\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\n/** How long a terminal job stays queryable before eviction. */\nconst DEFAULT_RETENTION_MS = 10_000;\n/** How often tracked jobs' status is refreshed from disk. */\nconst DEFAULT_POLL_INTERVAL_MS = 250;\n\nconst POLL_SUBSCRIBER_ID = 'async-job-tracker';\n\n/**\n * The minimum shape this module reads. A run's actual `status.json` carries\n * far more (this package has no shared status type yet - see\n * `staleRunReconciler.ts`'s `ReconcilableStatus` for the same caveat);\n * everything else is simply not read here.\n */\nexport interface TrackedAsyncJob {\n runId: string;\n /** Named agent executing this run. Absent until the first readable status. */\n agent?: string;\n /** `status.json`'s `state` field. `undefined` until the first successful read. */\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n /** `status.json`'s `activityState` field, e.g. `'needs_attention'`. See the module doc. */\n activityState?: ActivityState;\n /** `status.json`'s `reason` field, present alongside `activityState` (e.g. `'missing-deliverable'`). */\n attentionReason?: string;\n /** Which runtime is executing this run. Absent means the default `pi` path. */\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport type TrackedAsyncJobsContract = {\n /**\n * Start tracking `runId`: reads its current status synchronously (so a\n * caller sees something immediately) and adds it to the poll rotation.\n * Calling this again for an id already tracked simply re-reads it now,\n * the same idempotent-refresh idiom as `CoalescedStatusWriter.open()`.\n */\n track(runId: string): void;\n /** Stop tracking `runId` immediately, without waiting out its retention window. */\n untrack(runId: string): void;\n /** Every currently tracked job (in-flight, or terminal within its retention window). */\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n /** Drop every tracked job. */\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = TrackedAsyncJobsContract & {\n /** Return the isolated run collection owned by one Pi session context. */\n forSession(sessionId: string): TrackedAsyncJobsContract;\n /** Register with `PollScheduler`. Call once; a second call is a reset, same as `ResultWatcher.start()`. */\n start(): void;\n /** Unregister from `PollScheduler` and drop every tracked job. */\n stop(): void;\n};\n\n/** Resolve an exact id or unique prefix only inside one Pi session's run collection. */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const matches = jobs\n .list()\n .map((job) => job.runId)\n .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) throw new Error(`No current-session run found for '${id}'.`);\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n}\n\nconst UNSCOPED_SESSION = '__unscoped__';\n\nfunction statusPathFor(runId: string): string {\n return path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\ninterface ReadStatusResult {\n agent: string;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nconst VALID_ACTIVITY_STATES: ReadonlySet<string> = new Set<ActivityState>([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction asActivityState(value: unknown): ActivityState | undefined {\n return typeof value === 'string' && VALID_ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n constructor(private readonly scheduler: PollSchedulerContract) {}\n\n /**\n * Runtime tuning seams for tests, kept out of the dependency constructor.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly retentionMs: number = DEFAULT_RETENTION_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n protected async readFileAsync(filePath: string): Promise<string | undefined> {\n try {\n return await fs.promises.readFile(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n private readonly sessions = new Map<string, SessionJobs>();\n private unregisterPoll: (() => void) | undefined;\n\n private session(sessionId: string): SessionJobs {\n let session = this.sessions.get(sessionId);\n if (!session) {\n session = { jobs: new Map(), terminalAt: new Map() };\n this.sessions.set(sessionId, session);\n }\n return session;\n }\n\n forSession(sessionId: string): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n return {\n track: (runId) => this.trackInSession(sessionId, runId),\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 private parseStatus(runId: string, raw: string | undefined): ReadStatusResult | undefined {\n if (raw === undefined) return undefined;\n const result = parseAsyncRunStatus(raw, statusPathFor(runId));\n if (result.kind !== 'ok') return undefined;\n const status = result.status;\n return {\n agent: status.agent,\n status: status.state,\n startedAt: status.startedAt,\n updatedAt: status.lastUpdate,\n error: status.error,\n activityState: asActivityState(status.activityState),\n attentionReason: status.attentionReason,\n runtime: status.runtime,\n tokens: status.tokens,\n currentTool: status.currentTool,\n toolCount: status.toolCount,\n };\n }\n\n private readStatus(runId: string): ReadStatusResult | undefined {\n return this.parseStatus(runId, this.readFile(statusPathFor(runId)));\n }\n\n private async readStatusAsync(runId: string): Promise<ReadStatusResult | undefined> {\n return this.parseStatus(runId, await this.readFileAsync(statusPathFor(runId)));\n }\n\n private applyStatus(sessionId: string, runId: string, read: ReadStatusResult | undefined): boolean {\n // Teardown (`stop()`, `reset()`) can land between two of the tick's awaits.\n // A late refresh must observe that and drop its result, never recreate the\n // session it belonged to: resurrecting it here is what made `stop()` not stop.\n const session = this.sessions.get(sessionId);\n if (!session) return false;\n const existing = session.jobs.get(runId);\n if (read === undefined) {\n // A run may not have created its file yet, and a torn read must not blank\n // a previously known status for one polling pass.\n if (existing) return false;\n session.jobs.set(runId, { runId, status: undefined });\n return false;\n }\n\n const job: TrackedAsyncJob = { runId, ...read };\n session.jobs.set(runId, job);\n const wasTerminal = existing?.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(existing.status);\n const isTerminal = job.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(job.status);\n if (isTerminal && !wasTerminal) session.terminalAt.set(runId, this.now());\n if (!isTerminal) session.terminalAt.delete(runId);\n return (\n !existing ||\n existing.status !== job.status ||\n existing.updatedAt !== job.updatedAt ||\n existing.tokens !== job.tokens ||\n existing.currentTool !== job.currentTool ||\n existing.toolCount !== job.toolCount\n );\n }\n\n private refresh(sessionId: string, runId: string): boolean {\n return this.applyStatus(sessionId, runId, this.readStatus(runId));\n }\n\n private async refreshAsync(sessionId: string, runId: string): Promise<boolean> {\n return this.applyStatus(sessionId, runId, await this.readStatusAsync(runId));\n }\n\n /** One scheduler tick: asynchronously refresh every tracked status, then evict expired terminal jobs. */\n protected async run(): Promise<boolean> {\n let changed = false;\n const now = this.now();\n for (const [sessionId, session] of this.sessions) {\n for (const runId of session.jobs.keys()) {\n if (await this.refreshAsync(sessionId, runId)) changed = true;\n }\n for (const [runId, terminatedAt] of session.terminalAt) {\n if (now - terminatedAt <= this.retentionMs) continue;\n session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n changed = true;\n }\n // Only drop the entry this pass actually walked: a `track()` that landed\n // during an await above may have installed a fresh one under the same id.\n if (session.jobs.size === 0 && this.sessions.get(sessionId) === session) this.sessions.delete(sessionId);\n }\n\n return changed;\n }\n\n track(runId: string): void {\n this.trackInSession(UNSCOPED_SESSION, runId);\n }\n\n untrack(runId: string): void {\n this.untrackInSession(UNSCOPED_SESSION, runId);\n }\n\n list(): TrackedAsyncJob[] {\n return this.listInSession(UNSCOPED_SESSION);\n }\n\n get(runId: string): TrackedAsyncJob | undefined {\n return this.getInSession(UNSCOPED_SESSION, runId);\n }\n\n reset(): void {\n this.resetSession(UNSCOPED_SESSION);\n }\n\n private trackInSession(sessionId: string, runId: string): void {\n // `applyStatus` no longer creates sessions (see its comment), so tracking is\n // the one path that does.\n this.session(sessionId);\n this.refresh(sessionId, runId);\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n session?.jobs.delete(runId);\n session?.terminalAt.delete(runId);\n if (session?.jobs.size === 0) this.sessions.delete(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 private resetSession(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n\n start(): void {\n this.stop();\n this.unregisterPoll = this.scheduler.register({\n id: POLL_SUBSCRIBER_ID,\n intervalMs: this.pollIntervalMs,\n run: () => this.run(),\n });\n }\n\n stop(): void {\n this.unregisterPoll?.();\n this.unregisterPoll = undefined;\n this.sessions.clear();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"asyncJobTracker.cjs","names":["path","currentRunsDir","STATUS_FILE_NAME","fs","parseAsyncRunStatus"],"sources":["../../../src/adapters/asyncJobTracker.ts"],"sourcesContent":["/**\n * Live, in-memory registry of the async runs this session currently cares\n * about, refreshed from `status.json` on a `PollScheduler` tick.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/asyncJobTracker.ts` is 590 lines\n * doing four different jobs: this registry, TUI widget rendering\n * (`renderWidget`/`widgetRenderKey`/`ctx.ui.requestRender`), forwarding\n * control events and steering notices by byte-range-tailing each run's\n * `events.jsonl`, and projecting nested/parallel-group run trees. Only the\n * registry is ported. The other three have no consumer in this package yet:\n * TUI rendering is `G8` (slash commands + TUI), and nested/chain/parallel run\n * modes are `G7`+ (this package is currently single-run async-only - see\n * `spawnHandshake.ts`'s header). Porting rendering or event-tailing logic\n * with nothing to render to or forward to would be untested, unreachable\n * code; it is deferred to whichever later port actually needs it, not\n * dropped silently - this note is where to look for it.\n *\n * DESIGN PATTERNS:\n * - The in-memory map is a pure CACHE, never authoritative on its own:\n * `status.json` (owned by `CoalescedStatusWriter` elsewhere) is the only\n * source of truth. `track()` reads it synchronously immediately, so a\n * caller that already knows a run's id sees *something* right away rather\n * than waiting out the first poll\n * - Restart survivability follows from the point above rather than a second\n * persistence layer: this package has not built session-scoped run\n * ownership yet (which ids \"belong\" to a restarted session is not\n * something this module can determine on its own - see the AVOID entry),\n * so recovery is the caller re-`track()`-ing whatever ids it still cares\n * about from ITS OWN durable record. The very next poll tick repopulates\n * that job's status straight from disk. There is no separate store to keep\n * in sync, and therefore no window where memory could diverge from disk\n * that a restart would not immediately close\n * - Registered with `PollScheduler`, not a private timer: this polls\n * potentially many jobs' status files on an unbounded, periodic cadence\n * with no low-latency requirement - the exact shape `PollScheduler` exists\n * for. Contrast `completionBatcher.ts`/`spawnHandshake.ts`, which own\n * their own timers because they are short-lived, single-shot, and need\n * sub-200ms responsiveness `PollScheduler`'s floor cannot give them; this\n * registry needs neither\n * - Retention-then-eviction happens on the SAME poll tick as the status\n * refresh, rather than the predecessor's per-job `setTimeout`\n * (`scheduleCleanup`): a terminal job is dropped only after its result was\n * safely handed off and `retentionMs` elapsed. Failed delivery therefore\n * keeps the tracker entry available for `ResultWatcher`'s later retry\n *\n * AVOID:\n * - Scanning `currentRunsDir()` to guess which run ids \"belong\" to this session on\n * startup. This package has no session-scoped run ownership concept yet;\n * inventing one here to answer \"what should I restore\" would be a guess,\n * not a fact this module has standing to assert. Restoration is the\n * caller's job (see the design note above)\n * - Growing `terminalStates` independently of `staleRunReconciler.ts`'s own\n * (unexported) copy; both must treat the same state strings as terminal,\n * or a job could be evicted here while the reconciler still thinks it is\n * in flight, or vice versa\n *\n * `activityState`/`attentionReason` (added for `subagentWait.ts`):\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and a `reason` (e.g. `'missing-deliverable'`), but returns it rather than\n * writing it anywhere - it has no standing to touch `status.json` itself (see\n * that module's header). Something upstream of this tracker - the run's own\n * runner main loop, not yet built - is responsible for folding that result\n * into `status.json` through `CoalescedStatusWriter`. This tracker's job is\n * only to read whatever ends up there, the same as every other field: it does\n * not compute attention, detect it, or decide when a run needs it.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseAsyncRunStatus } from './statusReader';\nimport { STATUS_FILE_NAME } from './runs/background/statusWriter';\nimport { currentRunsDir } from './filesystem/paths';\nimport type { PollSchedulerContract } from './pollScheduler';\nimport type { ActivityState } from '../types';\n\n/**\n * States that mean \"already finished\"; matches `staleRunReconciler.ts`'s\n * own (unexported) set. Exported so a caller outside this module (the slash\n * layer's poll-until-terminal helper) can recognize the same terminal set\n * `TrackedAsyncJob.status` uses, rather than declaring a third copy of this\n * literal.\n */\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\n/** Minimum time a handed-off terminal job stays queryable before eviction. */\nconst DEFAULT_RETENTION_MS = 10_000;\n/** How often tracked jobs' status is refreshed from disk. */\nconst DEFAULT_POLL_INTERVAL_MS = 250;\n\nconst POLL_SUBSCRIBER_ID = 'async-job-tracker';\n\n/**\n * The minimum shape this module reads. A run's actual `status.json` carries\n * far more (this package has no shared status type yet - see\n * `staleRunReconciler.ts`'s `ReconcilableStatus` for the same caveat);\n * everything else is simply not read here.\n */\nexport interface TrackedAsyncJob {\n runId: string;\n /** Named agent executing this run. Absent until the first readable status. */\n agent?: string;\n /** `status.json`'s `state` field. `undefined` until the first successful read. */\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n /** `status.json`'s `activityState` field, e.g. `'needs_attention'`. See the module doc. */\n activityState?: ActivityState;\n /** `status.json`'s `reason` field, present alongside `activityState` (e.g. `'missing-deliverable'`). */\n attentionReason?: string;\n /** Which runtime is executing this run. Absent means the default `pi` path. */\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport type TrackedAsyncJobsContract = {\n /**\n * Start tracking `runId`: reads its current status synchronously (so a\n * caller sees something immediately) and adds it to the poll rotation.\n * Calling this again for an id already tracked simply re-reads it now,\n * the same idempotent-refresh idiom as `CoalescedStatusWriter.open()`.\n */\n track(runId: string): void;\n /** Stop tracking `runId` immediately, without waiting out its retention window. */\n untrack(runId: string): void;\n /** Every tracked job, including terminal jobs still awaiting a safe handoff. */\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n /** Drop every tracked job. */\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = TrackedAsyncJobsContract & {\n /** Return the isolated run collection owned by one Pi session context. */\n forSession(sessionId: string): TrackedAsyncJobsContract;\n /** Register with `PollScheduler`. Call once; a second call is a reset, same as `ResultWatcher.start()`. */\n start(): void;\n /** Unregister from `PollScheduler` and drop every tracked job. */\n stop(): void;\n};\n\n/** Resolve an exact id or unique prefix only inside one Pi session's run collection. */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const matches = jobs\n .list()\n .map((job) => job.runId)\n .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) throw new Error(`No current-session run found for '${id}'.`);\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n}\n\nconst UNSCOPED_SESSION = '__unscoped__';\n\nfunction statusPathFor(runId: string): string {\n return path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\ninterface ReadStatusResult {\n agent: string;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nconst VALID_ACTIVITY_STATES: ReadonlySet<string> = new Set<ActivityState>([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction asActivityState(value: unknown): ActivityState | undefined {\n return typeof value === 'string' && VALID_ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n constructor(private readonly scheduler: PollSchedulerContract) {}\n\n /**\n * Runtime tuning seams for tests, kept out of the dependency constructor.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly retentionMs: number = DEFAULT_RETENTION_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n protected async readFileAsync(filePath: string): Promise<string | undefined> {\n try {\n return await fs.promises.readFile(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n private unregisterPoll: (() => void) | undefined;\n\n private session(sessionId: string): SessionJobs {\n let session = this.sessions.get(sessionId);\n if (!session) {\n session = { jobs: new Map(), terminalAt: new Map(), handedOff: new Set() };\n this.sessions.set(sessionId, session);\n }\n return session;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n return {\n track: (runId) => this.trackInSession(sessionId, runId),\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 private parseStatus(runId: string, raw: string | undefined): ReadStatusResult | undefined {\n if (raw === undefined) return undefined;\n const result = parseAsyncRunStatus(raw, statusPathFor(runId));\n if (result.kind !== 'ok') return undefined;\n const status = result.status;\n return {\n agent: status.agent,\n status: status.state,\n startedAt: status.startedAt,\n updatedAt: status.lastUpdate,\n error: status.error,\n activityState: asActivityState(status.activityState),\n attentionReason: status.attentionReason,\n runtime: status.runtime,\n tokens: status.tokens,\n currentTool: status.currentTool,\n toolCount: status.toolCount,\n };\n }\n\n private readStatus(runId: string): ReadStatusResult | undefined {\n return this.parseStatus(runId, this.readFile(statusPathFor(runId)));\n }\n\n private async readStatusAsync(runId: string): Promise<ReadStatusResult | undefined> {\n return this.parseStatus(runId, await this.readFileAsync(statusPathFor(runId)));\n }\n\n private applyStatus(sessionId: string, runId: string, read: ReadStatusResult | undefined): boolean {\n // Teardown (`stop()`, `reset()`) can land between two of the tick's awaits.\n // A late refresh must observe that and drop its result, never recreate the\n // session it belonged to: resurrecting it here is what made `stop()` not stop.\n const session = this.sessions.get(sessionId);\n if (!session) return false;\n const existing = session.jobs.get(runId);\n if (read === undefined) {\n // A run may not have created its file yet, and a torn read must not blank\n // a previously known status for one polling pass.\n if (existing) return false;\n session.jobs.set(runId, { runId, status: undefined });\n return true;\n }\n\n const job: TrackedAsyncJob = { runId, ...read };\n session.jobs.set(runId, job);\n const wasTerminal = existing?.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(existing.status);\n const isTerminal = job.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(job.status);\n if (isTerminal && !wasTerminal) session.terminalAt.set(runId, this.now());\n if (!isTerminal) {\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n }\n return (\n !existing ||\n existing.status !== job.status ||\n existing.updatedAt !== job.updatedAt ||\n existing.tokens !== job.tokens ||\n existing.currentTool !== job.currentTool ||\n existing.toolCount !== job.toolCount\n );\n }\n\n private refresh(sessionId: string, runId: string): boolean {\n const changed = this.applyStatus(sessionId, runId, this.readStatus(runId));\n if (changed) this.invalidate(sessionId);\n return changed;\n }\n\n private async refreshAsync(sessionId: string, runId: string): Promise<boolean> {\n const changed = this.applyStatus(sessionId, runId, await this.readStatusAsync(runId));\n if (changed) this.invalidate(sessionId);\n return changed;\n }\n\n /** One scheduler tick: asynchronously refresh every tracked status, then evict expired terminal jobs. */\n protected async run(): Promise<boolean> {\n let changed = false;\n const now = this.now();\n for (const [sessionId, session] of this.sessions) {\n for (const runId of session.jobs.keys()) {\n if (await this.refreshAsync(sessionId, runId)) changed = true;\n }\n for (const [runId, terminatedAt] of session.terminalAt) {\n if (!session.handedOff.has(runId) || now - terminatedAt <= this.retentionMs) continue;\n session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n this.invalidate(sessionId);\n changed = true;\n }\n // Only drop the entry this pass actually walked: a `track()` that landed\n // during an await above may have installed a fresh one under the same id.\n if (session.jobs.size === 0 && this.sessions.get(sessionId) === session) this.sessions.delete(sessionId);\n }\n\n return changed;\n }\n\n track(runId: string): void {\n this.trackInSession(UNSCOPED_SESSION, runId);\n }\n\n untrack(runId: string): void {\n this.untrackInSession(UNSCOPED_SESSION, runId);\n }\n\n /** Mark one session's terminal result as safely handed off. */\n acknowledgeHandoff(sessionId: string, runId: string): void {\n this.acknowledgeHandoffInSession(sessionId, runId);\n }\n\n /** Work that still suppresses same-session idle continuation. */\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter(\n (job) =>\n job.status === undefined || !TERMINAL_ASYNC_JOB_STATES.has(job.status) || !session.handedOff.has(job.runId),\n );\n }\n\n /** Subscribe to invalidations for one exact Pi session. */\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 return this.subscribeInSession(sessionId, listener);\n }\n\n list(): TrackedAsyncJob[] {\n return this.listInSession(UNSCOPED_SESSION);\n }\n\n get(runId: string): TrackedAsyncJob | undefined {\n return this.getInSession(UNSCOPED_SESSION, runId);\n }\n\n reset(): void {\n this.resetSession(UNSCOPED_SESSION);\n }\n\n private trackInSession(sessionId: string, runId: string): void {\n // `applyStatus` no longer creates sessions (see its comment), so tracking is\n // the one path that does.\n this.session(sessionId);\n this.refresh(sessionId, runId);\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const deleted = session?.jobs.delete(runId) ?? false;\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 acknowledgeHandoffInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !status || !TERMINAL_ASYNC_JOB_STATES.has(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n }\n\n private subscribeInSession(sessionId: string, listener: () => void): () => void {\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 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 private resetSession(sessionId: string): void {\n const hadJobs = (this.sessions.get(sessionId)?.jobs.size ?? 0) > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n start(): void {\n this.stop();\n this.unregisterPoll = this.scheduler.register({\n id: POLL_SUBSCRIBER_ID,\n intervalMs: this.pollIntervalMs,\n run: () => this.run(),\n });\n }\n\n stop(): void {\n this.unregisterPoll?.();\n this.unregisterPoll = undefined;\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{currentRunsDir as t,init_paths as n}from"./filesystem/paths.mjs";import{STATUS_FILE_NAME as r,init_statusWriter as i}from"./runs/background/statusWriter.mjs";import{init_statusReader as a,parseAsyncRunStatus as o}from"./statusReader.mjs";import*as s from"node:fs";import*as c from"node:path";function l(e,t){let n=e.list().map(e=>e.runId).filter(e=>e===t||e.startsWith(t)),r=n.find(e=>e===t);if(r)return r;if(n.length===1)return n[0];throw n.length===0?Error(`No current-session run found for '${t}'.`):Error(`Multiple current-session runs match '${t}'. Use a longer id.`)}function u(e){return c.join(t(),e,r)}function d(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function f(e){return d(e)===`ENOENT`}function p(e){return typeof e==`string`&&y.has(e)?e:void 0}var m,h,g,_,v,y,b,x=e((()=>{a(),i(),n(),m=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]),h=1e4,g=250,_=`async-job-tracker`,v=`__unscoped__`,y=new Set([`starting`,`working`,`tool`,`waiting_for_reply`,`needs_attention`,`finalizing`,`active_long_running`]),b=class{scheduler;constructor(e){this.scheduler=e}pollIntervalMs=g;retentionMs=h;now(){return Date.now()}readFile(e){try{return s.readFileSync(e,`utf-8`)}catch(e){if(f(e))return;throw e}}async readFileAsync(e){try{return await s.promises.readFile(e,`utf-8`)}catch(e){if(f(e))return;throw e}}sessions=new Map;unregisterPoll;session(e){let t=this.sessions.get(e);return t||(t={jobs:new Map,terminalAt:new Map},this.sessions.set(e,t)),t}forSession(e){if(!e.trim())throw Error(`Pi session identity is required to track subagent runs.`);return{track:t=>this.trackInSession(e,t),untrack:t=>this.untrackInSession(e,t),list:()=>this.listInSession(e),get:t=>this.getInSession(e,t),reset:()=>this.resetSession(e)}}parseStatus(e,t){if(t===void 0)return;let n=o(t,u(e));if(n.kind!==`ok`)return;let r=n.status;return{agent:r.agent,status:r.state,startedAt:r.startedAt,updatedAt:r.lastUpdate,error:r.error,activityState:p(r.activityState),attentionReason:r.attentionReason,runtime:r.runtime,tokens:r.tokens,currentTool:r.currentTool,toolCount:r.toolCount}}readStatus(e){return this.parseStatus(e,this.readFile(u(e)))}async readStatusAsync(e){return this.parseStatus(e,await this.readFileAsync(u(e)))}applyStatus(e,t,n){let r=this.sessions.get(e);if(!r)return!1;let i=r.jobs.get(t);if(n===void 0)return i||r.jobs.set(t,{runId:t,status:void 0}),!1;let a={runId:t,...n};r.jobs.set(t,a);let o=i?.status!==void 0&&m.has(i.status),s=a.status!==void 0&&m.has(a.status);return s&&!o&&r.terminalAt.set(t,this.now()),s||r.terminalAt.delete(t),!i||i.status!==a.status||i.updatedAt!==a.updatedAt||i.tokens!==a.tokens||i.currentTool!==a.currentTool||i.toolCount!==a.toolCount}refresh(e,t){return this.applyStatus(e,t,this.readStatus(t))}async refreshAsync(e,t){return this.applyStatus(e,t,await this.readStatusAsync(t))}async run(){let e=!1,t=this.now();for(let[n,r]of this.sessions){for(let t of r.jobs.keys())await this.refreshAsync(n,t)&&(e=!0);for(let[n,i]of r.terminalAt)t-i<=this.retentionMs||(r.jobs.delete(n),r.terminalAt.delete(n),e=!0);r.jobs.size===0&&this.sessions.get(n)===r&&this.sessions.delete(n)}return e}track(e){this.trackInSession(v,e)}untrack(e){this.untrackInSession(v,e)}list(){return this.listInSession(v)}get(e){return this.getInSession(v,e)}reset(){this.resetSession(v)}trackInSession(e,t){this.session(e),this.refresh(e,t)}untrackInSession(e,t){let n=this.sessions.get(e);n?.jobs.delete(t),n?.terminalAt.delete(t),n?.jobs.size===0&&this.sessions.delete(e)}listInSession(e){return[...this.sessions.get(e)?.jobs.values()??[]]}getInSession(e,t){return this.sessions.get(e)?.jobs.get(t)}resetSession(e){this.sessions.delete(e)}start(){this.stop(),this.unregisterPoll=this.scheduler.register({id:_,intervalMs:this.pollIntervalMs,run:()=>this.run()})}stop(){this.unregisterPoll?.(),this.unregisterPoll=void 0,this.sessions.clear()}}}));x();export{b as AsyncJobTracker,m as TERMINAL_ASYNC_JOB_STATES,x as init_asyncJobTracker,l as resolveTrackedRunId};
1
+ import{__esmMin as e}from"../../_virtual/_rolldown/runtime.mjs";import{currentRunsDir as t,init_paths as n}from"./filesystem/paths.mjs";import{STATUS_FILE_NAME as r,init_statusWriter as i}from"./runs/background/statusWriter.mjs";import{init_statusReader as a,parseAsyncRunStatus as o}from"./statusReader.mjs";import*as s from"node:fs";import*as c from"node:path";function l(e,t){let n=e.list().map(e=>e.runId).filter(e=>e===t||e.startsWith(t)),r=n.find(e=>e===t);if(r)return r;if(n.length===1)return n[0];throw n.length===0?Error(`No current-session run found for '${t}'.`):Error(`Multiple current-session runs match '${t}'. Use a longer id.`)}function u(e){return c.join(t(),e,r)}function d(e){return typeof e==`object`&&e&&`code`in e?e.code:void 0}function f(e){return d(e)===`ENOENT`}function p(e){return typeof e==`string`&&y.has(e)?e:void 0}var m,h,g,_,v,y,b,x=e((()=>{a(),i(),n(),m=new Set([`complete`,`completed`,`failed`,`paused`,`stopped`]),h=1e4,g=250,_=`async-job-tracker`,v=`__unscoped__`,y=new Set([`starting`,`working`,`tool`,`waiting_for_reply`,`needs_attention`,`finalizing`,`active_long_running`]),b=class{scheduler;constructor(e){this.scheduler=e}pollIntervalMs=g;retentionMs=h;now(){return Date.now()}readFile(e){try{return s.readFileSync(e,`utf-8`)}catch(e){if(f(e))return;throw e}}async readFileAsync(e){try{return await s.promises.readFile(e,`utf-8`)}catch(e){if(f(e))return;throw e}}sessions=new Map;listeners=new Map;unregisterPoll;session(e){let t=this.sessions.get(e);return t||(t={jobs:new Map,terminalAt:new Map,handedOff:new Set},this.sessions.set(e,t)),t}invalidate(e){for(let t of this.listeners.get(e)??[])t()}forSession(e){if(!e.trim())throw Error(`Pi session identity is required to track subagent runs.`);return{track:t=>this.trackInSession(e,t),untrack:t=>this.untrackInSession(e,t),list:()=>this.listInSession(e),get:t=>this.getInSession(e,t),reset:()=>this.resetSession(e)}}parseStatus(e,t){if(t===void 0)return;let n=o(t,u(e));if(n.kind!==`ok`)return;let r=n.status;return{agent:r.agent,status:r.state,startedAt:r.startedAt,updatedAt:r.lastUpdate,error:r.error,activityState:p(r.activityState),attentionReason:r.attentionReason,runtime:r.runtime,tokens:r.tokens,currentTool:r.currentTool,toolCount:r.toolCount}}readStatus(e){return this.parseStatus(e,this.readFile(u(e)))}async readStatusAsync(e){return this.parseStatus(e,await this.readFileAsync(u(e)))}applyStatus(e,t,n){let r=this.sessions.get(e);if(!r)return!1;let i=r.jobs.get(t);if(n===void 0)return!i&&(r.jobs.set(t,{runId:t,status:void 0}),!0);let a={runId:t,...n};r.jobs.set(t,a);let o=i?.status!==void 0&&m.has(i.status),s=a.status!==void 0&&m.has(a.status);return s&&!o&&r.terminalAt.set(t,this.now()),s||(r.terminalAt.delete(t),r.handedOff.delete(t)),!i||i.status!==a.status||i.updatedAt!==a.updatedAt||i.tokens!==a.tokens||i.currentTool!==a.currentTool||i.toolCount!==a.toolCount}refresh(e,t){let n=this.applyStatus(e,t,this.readStatus(t));return n&&this.invalidate(e),n}async refreshAsync(e,t){let n=this.applyStatus(e,t,await this.readStatusAsync(t));return n&&this.invalidate(e),n}async run(){let e=!1,t=this.now();for(let[n,r]of this.sessions){for(let t of r.jobs.keys())await this.refreshAsync(n,t)&&(e=!0);for(let[i,a]of r.terminalAt)!r.handedOff.has(i)||t-a<=this.retentionMs||(r.jobs.delete(i),r.terminalAt.delete(i),r.handedOff.delete(i),this.invalidate(n),e=!0);r.jobs.size===0&&this.sessions.get(n)===r&&this.sessions.delete(n)}return e}track(e){this.trackInSession(v,e)}untrack(e){this.untrackInSession(v,e)}acknowledgeHandoff(e,t){this.acknowledgeHandoffInSession(e,t)}listBackgroundWork(e){let t=this.sessions.get(e);return t?[...t.jobs.values()].filter(e=>e.status===void 0||!m.has(e.status)||!t.handedOff.has(e.runId)):[]}subscribe(e,t){if(!e.trim())throw Error(`Pi session identity is required to subscribe to subagent runs.`);return this.subscribeInSession(e,t)}list(){return this.listInSession(v)}get(e){return this.getInSession(v,e)}reset(){this.resetSession(v)}trackInSession(e,t){this.session(e),this.refresh(e,t)}untrackInSession(e,t){let n=this.sessions.get(e),r=n?.jobs.delete(t)??!1;n?.terminalAt.delete(t),n?.handedOff.delete(t),n?.jobs.size===0&&this.sessions.delete(e),r&&this.invalidate(e)}acknowledgeHandoffInSession(e,t){let n=this.sessions.get(e),r=n?.jobs.get(t)?.status;!n||!r||!m.has(r)||n.handedOff.has(t)||(n.handedOff.add(t),this.invalidate(e))}subscribeInSession(e,t){let n=this.listeners.get(e)??new Set;return n.add(t),this.listeners.set(e,n),()=>{n.delete(t),n.size===0&&this.listeners.delete(e)}}listInSession(e){return[...this.sessions.get(e)?.jobs.values()??[]]}getInSession(e,t){return this.sessions.get(e)?.jobs.get(t)}resetSession(e){let t=(this.sessions.get(e)?.jobs.size??0)>0;this.sessions.delete(e),t&&this.invalidate(e)}start(){this.stop(),this.unregisterPoll=this.scheduler.register({id:_,intervalMs:this.pollIntervalMs,run:()=>this.run()})}stop(){this.unregisterPoll?.(),this.unregisterPoll=void 0,this.sessions.clear(),this.listeners.clear()}}}));x();export{b as AsyncJobTracker,m as TERMINAL_ASYNC_JOB_STATES,x as init_asyncJobTracker,l as resolveTrackedRunId};
2
2
  //# sourceMappingURL=asyncJobTracker.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"asyncJobTracker.mjs","names":["path","fs"],"sources":["../../../src/adapters/asyncJobTracker.ts"],"sourcesContent":["/**\n * Live, in-memory registry of the async runs this session currently cares\n * about, refreshed from `status.json` on a `PollScheduler` tick.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/asyncJobTracker.ts` is 590 lines\n * doing four different jobs: this registry, TUI widget rendering\n * (`renderWidget`/`widgetRenderKey`/`ctx.ui.requestRender`), forwarding\n * control events and steering notices by byte-range-tailing each run's\n * `events.jsonl`, and projecting nested/parallel-group run trees. Only the\n * registry is ported. The other three have no consumer in this package yet:\n * TUI rendering is `G8` (slash commands + TUI), and nested/chain/parallel run\n * modes are `G7`+ (this package is currently single-run async-only - see\n * `spawnHandshake.ts`'s header). Porting rendering or event-tailing logic\n * with nothing to render to or forward to would be untested, unreachable\n * code; it is deferred to whichever later port actually needs it, not\n * dropped silently - this note is where to look for it.\n *\n * DESIGN PATTERNS:\n * - The in-memory map is a pure CACHE, never authoritative on its own:\n * `status.json` (owned by `CoalescedStatusWriter` elsewhere) is the only\n * source of truth. `track()` reads it synchronously immediately, so a\n * caller that already knows a run's id sees *something* right away rather\n * than waiting out the first poll\n * - Restart survivability follows from the point above rather than a second\n * persistence layer: this package has not built session-scoped run\n * ownership yet (which ids \"belong\" to a restarted session is not\n * something this module can determine on its own - see the AVOID entry),\n * so recovery is the caller re-`track()`-ing whatever ids it still cares\n * about from ITS OWN durable record. The very next poll tick repopulates\n * that job's status straight from disk. There is no separate store to keep\n * in sync, and therefore no window where memory could diverge from disk\n * that a restart would not immediately close\n * - Registered with `PollScheduler`, not a private timer: this polls\n * potentially many jobs' status files on an unbounded, periodic cadence\n * with no low-latency requirement - the exact shape `PollScheduler` exists\n * for. Contrast `completionBatcher.ts`/`spawnHandshake.ts`, which own\n * their own timers because they are short-lived, single-shot, and need\n * sub-200ms responsiveness `PollScheduler`'s floor cannot give them; this\n * registry needs neither\n * - Retention-then-eviction happens on the SAME poll tick as the status\n * refresh, rather than the predecessor's per-job `setTimeout`\n * (`scheduleCleanup`): a terminal job aged past `retentionMs` is simply\n * dropped the next time `run()` walks the map. One clock, not one timer per\n * job\n *\n * AVOID:\n * - Scanning `currentRunsDir()` to guess which run ids \"belong\" to this session on\n * startup. This package has no session-scoped run ownership concept yet;\n * inventing one here to answer \"what should I restore\" would be a guess,\n * not a fact this module has standing to assert. Restoration is the\n * caller's job (see the design note above)\n * - Growing `terminalStates` independently of `staleRunReconciler.ts`'s own\n * (unexported) copy; both must treat the same state strings as terminal,\n * or a job could be evicted here while the reconciler still thinks it is\n * in flight, or vice versa\n *\n * `activityState`/`attentionReason` (added for `subagentWait.ts`):\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and a `reason` (e.g. `'missing-deliverable'`), but returns it rather than\n * writing it anywhere - it has no standing to touch `status.json` itself (see\n * that module's header). Something upstream of this tracker - the run's own\n * runner main loop, not yet built - is responsible for folding that result\n * into `status.json` through `CoalescedStatusWriter`. This tracker's job is\n * only to read whatever ends up there, the same as every other field: it does\n * not compute attention, detect it, or decide when a run needs it.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseAsyncRunStatus } from './statusReader';\nimport { STATUS_FILE_NAME } from './runs/background/statusWriter';\nimport { currentRunsDir } from './filesystem/paths';\nimport type { PollSchedulerContract } from './pollScheduler';\nimport type { ActivityState } from '../types';\n\n/**\n * States that mean \"already finished\"; matches `staleRunReconciler.ts`'s\n * own (unexported) set. Exported so a caller outside this module (the slash\n * layer's poll-until-terminal helper) can recognize the same terminal set\n * `TrackedAsyncJob.status` uses, rather than declaring a third copy of this\n * literal.\n */\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\n/** How long a terminal job stays queryable before eviction. */\nconst DEFAULT_RETENTION_MS = 10_000;\n/** How often tracked jobs' status is refreshed from disk. */\nconst DEFAULT_POLL_INTERVAL_MS = 250;\n\nconst POLL_SUBSCRIBER_ID = 'async-job-tracker';\n\n/**\n * The minimum shape this module reads. A run's actual `status.json` carries\n * far more (this package has no shared status type yet - see\n * `staleRunReconciler.ts`'s `ReconcilableStatus` for the same caveat);\n * everything else is simply not read here.\n */\nexport interface TrackedAsyncJob {\n runId: string;\n /** Named agent executing this run. Absent until the first readable status. */\n agent?: string;\n /** `status.json`'s `state` field. `undefined` until the first successful read. */\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n /** `status.json`'s `activityState` field, e.g. `'needs_attention'`. See the module doc. */\n activityState?: ActivityState;\n /** `status.json`'s `reason` field, present alongside `activityState` (e.g. `'missing-deliverable'`). */\n attentionReason?: string;\n /** Which runtime is executing this run. Absent means the default `pi` path. */\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport type TrackedAsyncJobsContract = {\n /**\n * Start tracking `runId`: reads its current status synchronously (so a\n * caller sees something immediately) and adds it to the poll rotation.\n * Calling this again for an id already tracked simply re-reads it now,\n * the same idempotent-refresh idiom as `CoalescedStatusWriter.open()`.\n */\n track(runId: string): void;\n /** Stop tracking `runId` immediately, without waiting out its retention window. */\n untrack(runId: string): void;\n /** Every currently tracked job (in-flight, or terminal within its retention window). */\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n /** Drop every tracked job. */\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = TrackedAsyncJobsContract & {\n /** Return the isolated run collection owned by one Pi session context. */\n forSession(sessionId: string): TrackedAsyncJobsContract;\n /** Register with `PollScheduler`. Call once; a second call is a reset, same as `ResultWatcher.start()`. */\n start(): void;\n /** Unregister from `PollScheduler` and drop every tracked job. */\n stop(): void;\n};\n\n/** Resolve an exact id or unique prefix only inside one Pi session's run collection. */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const matches = jobs\n .list()\n .map((job) => job.runId)\n .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) throw new Error(`No current-session run found for '${id}'.`);\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n}\n\nconst UNSCOPED_SESSION = '__unscoped__';\n\nfunction statusPathFor(runId: string): string {\n return path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\ninterface ReadStatusResult {\n agent: string;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nconst VALID_ACTIVITY_STATES: ReadonlySet<string> = new Set<ActivityState>([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction asActivityState(value: unknown): ActivityState | undefined {\n return typeof value === 'string' && VALID_ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n constructor(private readonly scheduler: PollSchedulerContract) {}\n\n /**\n * Runtime tuning seams for tests, kept out of the dependency constructor.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly retentionMs: number = DEFAULT_RETENTION_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n protected async readFileAsync(filePath: string): Promise<string | undefined> {\n try {\n return await fs.promises.readFile(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n private readonly sessions = new Map<string, SessionJobs>();\n private unregisterPoll: (() => void) | undefined;\n\n private session(sessionId: string): SessionJobs {\n let session = this.sessions.get(sessionId);\n if (!session) {\n session = { jobs: new Map(), terminalAt: new Map() };\n this.sessions.set(sessionId, session);\n }\n return session;\n }\n\n forSession(sessionId: string): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n return {\n track: (runId) => this.trackInSession(sessionId, runId),\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 private parseStatus(runId: string, raw: string | undefined): ReadStatusResult | undefined {\n if (raw === undefined) return undefined;\n const result = parseAsyncRunStatus(raw, statusPathFor(runId));\n if (result.kind !== 'ok') return undefined;\n const status = result.status;\n return {\n agent: status.agent,\n status: status.state,\n startedAt: status.startedAt,\n updatedAt: status.lastUpdate,\n error: status.error,\n activityState: asActivityState(status.activityState),\n attentionReason: status.attentionReason,\n runtime: status.runtime,\n tokens: status.tokens,\n currentTool: status.currentTool,\n toolCount: status.toolCount,\n };\n }\n\n private readStatus(runId: string): ReadStatusResult | undefined {\n return this.parseStatus(runId, this.readFile(statusPathFor(runId)));\n }\n\n private async readStatusAsync(runId: string): Promise<ReadStatusResult | undefined> {\n return this.parseStatus(runId, await this.readFileAsync(statusPathFor(runId)));\n }\n\n private applyStatus(sessionId: string, runId: string, read: ReadStatusResult | undefined): boolean {\n // Teardown (`stop()`, `reset()`) can land between two of the tick's awaits.\n // A late refresh must observe that and drop its result, never recreate the\n // session it belonged to: resurrecting it here is what made `stop()` not stop.\n const session = this.sessions.get(sessionId);\n if (!session) return false;\n const existing = session.jobs.get(runId);\n if (read === undefined) {\n // A run may not have created its file yet, and a torn read must not blank\n // a previously known status for one polling pass.\n if (existing) return false;\n session.jobs.set(runId, { runId, status: undefined });\n return false;\n }\n\n const job: TrackedAsyncJob = { runId, ...read };\n session.jobs.set(runId, job);\n const wasTerminal = existing?.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(existing.status);\n const isTerminal = job.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(job.status);\n if (isTerminal && !wasTerminal) session.terminalAt.set(runId, this.now());\n if (!isTerminal) session.terminalAt.delete(runId);\n return (\n !existing ||\n existing.status !== job.status ||\n existing.updatedAt !== job.updatedAt ||\n existing.tokens !== job.tokens ||\n existing.currentTool !== job.currentTool ||\n existing.toolCount !== job.toolCount\n );\n }\n\n private refresh(sessionId: string, runId: string): boolean {\n return this.applyStatus(sessionId, runId, this.readStatus(runId));\n }\n\n private async refreshAsync(sessionId: string, runId: string): Promise<boolean> {\n return this.applyStatus(sessionId, runId, await this.readStatusAsync(runId));\n }\n\n /** One scheduler tick: asynchronously refresh every tracked status, then evict expired terminal jobs. */\n protected async run(): Promise<boolean> {\n let changed = false;\n const now = this.now();\n for (const [sessionId, session] of this.sessions) {\n for (const runId of session.jobs.keys()) {\n if (await this.refreshAsync(sessionId, runId)) changed = true;\n }\n for (const [runId, terminatedAt] of session.terminalAt) {\n if (now - terminatedAt <= this.retentionMs) continue;\n session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n changed = true;\n }\n // Only drop the entry this pass actually walked: a `track()` that landed\n // during an await above may have installed a fresh one under the same id.\n if (session.jobs.size === 0 && this.sessions.get(sessionId) === session) this.sessions.delete(sessionId);\n }\n\n return changed;\n }\n\n track(runId: string): void {\n this.trackInSession(UNSCOPED_SESSION, runId);\n }\n\n untrack(runId: string): void {\n this.untrackInSession(UNSCOPED_SESSION, runId);\n }\n\n list(): TrackedAsyncJob[] {\n return this.listInSession(UNSCOPED_SESSION);\n }\n\n get(runId: string): TrackedAsyncJob | undefined {\n return this.getInSession(UNSCOPED_SESSION, runId);\n }\n\n reset(): void {\n this.resetSession(UNSCOPED_SESSION);\n }\n\n private trackInSession(sessionId: string, runId: string): void {\n // `applyStatus` no longer creates sessions (see its comment), so tracking is\n // the one path that does.\n this.session(sessionId);\n this.refresh(sessionId, runId);\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n session?.jobs.delete(runId);\n session?.terminalAt.delete(runId);\n if (session?.jobs.size === 0) this.sessions.delete(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 private resetSession(sessionId: string): void {\n this.sessions.delete(sessionId);\n }\n\n start(): void {\n this.stop();\n this.unregisterPoll = this.scheduler.register({\n id: POLL_SUBSCRIBER_ID,\n intervalMs: this.pollIntervalMs,\n run: () => this.run(),\n });\n }\n\n stop(): void {\n this.unregisterPoll?.();\n this.unregisterPoll = undefined;\n this.sessions.clear();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"asyncJobTracker.mjs","names":["path","fs"],"sources":["../../../src/adapters/asyncJobTracker.ts"],"sourcesContent":["/**\n * Live, in-memory registry of the async runs this session currently cares\n * about, refreshed from `status.json` on a `PollScheduler` tick.\n *\n * WHY THIS IS SMALLER THAN THE PREDECESSOR:\n * `doom-pi-subagents/src/runs/background/asyncJobTracker.ts` is 590 lines\n * doing four different jobs: this registry, TUI widget rendering\n * (`renderWidget`/`widgetRenderKey`/`ctx.ui.requestRender`), forwarding\n * control events and steering notices by byte-range-tailing each run's\n * `events.jsonl`, and projecting nested/parallel-group run trees. Only the\n * registry is ported. The other three have no consumer in this package yet:\n * TUI rendering is `G8` (slash commands + TUI), and nested/chain/parallel run\n * modes are `G7`+ (this package is currently single-run async-only - see\n * `spawnHandshake.ts`'s header). Porting rendering or event-tailing logic\n * with nothing to render to or forward to would be untested, unreachable\n * code; it is deferred to whichever later port actually needs it, not\n * dropped silently - this note is where to look for it.\n *\n * DESIGN PATTERNS:\n * - The in-memory map is a pure CACHE, never authoritative on its own:\n * `status.json` (owned by `CoalescedStatusWriter` elsewhere) is the only\n * source of truth. `track()` reads it synchronously immediately, so a\n * caller that already knows a run's id sees *something* right away rather\n * than waiting out the first poll\n * - Restart survivability follows from the point above rather than a second\n * persistence layer: this package has not built session-scoped run\n * ownership yet (which ids \"belong\" to a restarted session is not\n * something this module can determine on its own - see the AVOID entry),\n * so recovery is the caller re-`track()`-ing whatever ids it still cares\n * about from ITS OWN durable record. The very next poll tick repopulates\n * that job's status straight from disk. There is no separate store to keep\n * in sync, and therefore no window where memory could diverge from disk\n * that a restart would not immediately close\n * - Registered with `PollScheduler`, not a private timer: this polls\n * potentially many jobs' status files on an unbounded, periodic cadence\n * with no low-latency requirement - the exact shape `PollScheduler` exists\n * for. Contrast `completionBatcher.ts`/`spawnHandshake.ts`, which own\n * their own timers because they are short-lived, single-shot, and need\n * sub-200ms responsiveness `PollScheduler`'s floor cannot give them; this\n * registry needs neither\n * - Retention-then-eviction happens on the SAME poll tick as the status\n * refresh, rather than the predecessor's per-job `setTimeout`\n * (`scheduleCleanup`): a terminal job is dropped only after its result was\n * safely handed off and `retentionMs` elapsed. Failed delivery therefore\n * keeps the tracker entry available for `ResultWatcher`'s later retry\n *\n * AVOID:\n * - Scanning `currentRunsDir()` to guess which run ids \"belong\" to this session on\n * startup. This package has no session-scoped run ownership concept yet;\n * inventing one here to answer \"what should I restore\" would be a guess,\n * not a fact this module has standing to assert. Restoration is the\n * caller's job (see the design note above)\n * - Growing `terminalStates` independently of `staleRunReconciler.ts`'s own\n * (unexported) copy; both must treat the same state strings as terminal,\n * or a job could be evicted here while the reconciler still thinks it is\n * in flight, or vice versa\n *\n * `activityState`/`attentionReason` (added for `subagentWait.ts`):\n * `DeliverableGuard.evaluate()` computes `activityState: 'needs_attention'`\n * and a `reason` (e.g. `'missing-deliverable'`), but returns it rather than\n * writing it anywhere - it has no standing to touch `status.json` itself (see\n * that module's header). Something upstream of this tracker - the run's own\n * runner main loop, not yet built - is responsible for folding that result\n * into `status.json` through `CoalescedStatusWriter`. This tracker's job is\n * only to read whatever ends up there, the same as every other field: it does\n * not compute attention, detect it, or decide when a run needs it.\n */\n\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport { parseAsyncRunStatus } from './statusReader';\nimport { STATUS_FILE_NAME } from './runs/background/statusWriter';\nimport { currentRunsDir } from './filesystem/paths';\nimport type { PollSchedulerContract } from './pollScheduler';\nimport type { ActivityState } from '../types';\n\n/**\n * States that mean \"already finished\"; matches `staleRunReconciler.ts`'s\n * own (unexported) set. Exported so a caller outside this module (the slash\n * layer's poll-until-terminal helper) can recognize the same terminal set\n * `TrackedAsyncJob.status` uses, rather than declaring a third copy of this\n * literal.\n */\nexport const TERMINAL_ASYNC_JOB_STATES = new Set(['complete', 'completed', 'failed', 'paused', 'stopped']);\n\n/** Minimum time a handed-off terminal job stays queryable before eviction. */\nconst DEFAULT_RETENTION_MS = 10_000;\n/** How often tracked jobs' status is refreshed from disk. */\nconst DEFAULT_POLL_INTERVAL_MS = 250;\n\nconst POLL_SUBSCRIBER_ID = 'async-job-tracker';\n\n/**\n * The minimum shape this module reads. A run's actual `status.json` carries\n * far more (this package has no shared status type yet - see\n * `staleRunReconciler.ts`'s `ReconcilableStatus` for the same caveat);\n * everything else is simply not read here.\n */\nexport interface TrackedAsyncJob {\n runId: string;\n /** Named agent executing this run. Absent until the first readable status. */\n agent?: string;\n /** `status.json`'s `state` field. `undefined` until the first successful read. */\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n /** `status.json`'s `activityState` field, e.g. `'needs_attention'`. See the module doc. */\n activityState?: ActivityState;\n /** `status.json`'s `reason` field, present alongside `activityState` (e.g. `'missing-deliverable'`). */\n attentionReason?: string;\n /** Which runtime is executing this run. Absent means the default `pi` path. */\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nexport type TrackedAsyncJobsContract = {\n /**\n * Start tracking `runId`: reads its current status synchronously (so a\n * caller sees something immediately) and adds it to the poll rotation.\n * Calling this again for an id already tracked simply re-reads it now,\n * the same idempotent-refresh idiom as `CoalescedStatusWriter.open()`.\n */\n track(runId: string): void;\n /** Stop tracking `runId` immediately, without waiting out its retention window. */\n untrack(runId: string): void;\n /** Every tracked job, including terminal jobs still awaiting a safe handoff. */\n list(): TrackedAsyncJob[];\n get(runId: string): TrackedAsyncJob | undefined;\n /** Drop every tracked job. */\n reset(): void;\n};\n\nexport type AsyncJobTrackerContract = TrackedAsyncJobsContract & {\n /** Return the isolated run collection owned by one Pi session context. */\n forSession(sessionId: string): TrackedAsyncJobsContract;\n /** Register with `PollScheduler`. Call once; a second call is a reset, same as `ResultWatcher.start()`. */\n start(): void;\n /** Unregister from `PollScheduler` and drop every tracked job. */\n stop(): void;\n};\n\n/** Resolve an exact id or unique prefix only inside one Pi session's run collection. */\nexport function resolveTrackedRunId(jobs: TrackedAsyncJobsContract, id: string): string {\n const matches = jobs\n .list()\n .map((job) => job.runId)\n .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) throw new Error(`No current-session run found for '${id}'.`);\n throw new Error(`Multiple current-session runs match '${id}'. Use a longer id.`);\n}\n\ninterface SessionJobs {\n jobs: Map<string, TrackedAsyncJob>;\n terminalAt: Map<string, number>;\n handedOff: Set<string>;\n}\n\nconst UNSCOPED_SESSION = '__unscoped__';\n\nfunction statusPathFor(runId: string): string {\n return path.join(currentRunsDir(), runId, STATUS_FILE_NAME);\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === 'object' && error !== null && 'code' in error\n ? (error as NodeJS.ErrnoException).code\n : undefined;\n}\n\nfunction isNotFound(error: unknown): boolean {\n return errorCode(error) === 'ENOENT';\n}\n\ninterface ReadStatusResult {\n agent: string;\n status: string | undefined;\n startedAt?: number;\n updatedAt?: number;\n error?: string;\n activityState?: ActivityState;\n attentionReason?: string;\n runtime?: string;\n tokens?: number;\n currentTool?: string;\n toolCount?: number;\n}\n\nconst VALID_ACTIVITY_STATES: ReadonlySet<string> = new Set<ActivityState>([\n 'starting',\n 'working',\n 'tool',\n 'waiting_for_reply',\n 'needs_attention',\n 'finalizing',\n 'active_long_running',\n]);\n\nfunction asActivityState(value: unknown): ActivityState | undefined {\n return typeof value === 'string' && VALID_ACTIVITY_STATES.has(value) ? (value as ActivityState) : undefined;\n}\n\nexport class AsyncJobTracker implements AsyncJobTrackerContract {\n constructor(private readonly scheduler: PollSchedulerContract) {}\n\n /**\n * Runtime tuning seams for tests, kept out of the dependency constructor.\n */\n protected readonly pollIntervalMs: number = DEFAULT_POLL_INTERVAL_MS;\n protected readonly retentionMs: number = DEFAULT_RETENTION_MS;\n\n protected now(): number {\n return Date.now();\n }\n\n protected readFile(filePath: string): string | undefined {\n try {\n return fs.readFileSync(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n protected async readFileAsync(filePath: string): Promise<string | undefined> {\n try {\n return await fs.promises.readFile(filePath, 'utf-8');\n } catch (error) {\n if (isNotFound(error)) return undefined;\n throw error;\n }\n }\n\n private readonly sessions = new Map<string, SessionJobs>();\n private readonly listeners = new Map<string, Set<() => void>>();\n private unregisterPoll: (() => void) | undefined;\n\n private session(sessionId: string): SessionJobs {\n let session = this.sessions.get(sessionId);\n if (!session) {\n session = { jobs: new Map(), terminalAt: new Map(), handedOff: new Set() };\n this.sessions.set(sessionId, session);\n }\n return session;\n }\n\n private invalidate(sessionId: string): void {\n for (const listener of this.listeners.get(sessionId) ?? []) listener();\n }\n\n forSession(sessionId: string): TrackedAsyncJobsContract {\n if (!sessionId.trim()) throw new Error('Pi session identity is required to track subagent runs.');\n return {\n track: (runId) => this.trackInSession(sessionId, runId),\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 private parseStatus(runId: string, raw: string | undefined): ReadStatusResult | undefined {\n if (raw === undefined) return undefined;\n const result = parseAsyncRunStatus(raw, statusPathFor(runId));\n if (result.kind !== 'ok') return undefined;\n const status = result.status;\n return {\n agent: status.agent,\n status: status.state,\n startedAt: status.startedAt,\n updatedAt: status.lastUpdate,\n error: status.error,\n activityState: asActivityState(status.activityState),\n attentionReason: status.attentionReason,\n runtime: status.runtime,\n tokens: status.tokens,\n currentTool: status.currentTool,\n toolCount: status.toolCount,\n };\n }\n\n private readStatus(runId: string): ReadStatusResult | undefined {\n return this.parseStatus(runId, this.readFile(statusPathFor(runId)));\n }\n\n private async readStatusAsync(runId: string): Promise<ReadStatusResult | undefined> {\n return this.parseStatus(runId, await this.readFileAsync(statusPathFor(runId)));\n }\n\n private applyStatus(sessionId: string, runId: string, read: ReadStatusResult | undefined): boolean {\n // Teardown (`stop()`, `reset()`) can land between two of the tick's awaits.\n // A late refresh must observe that and drop its result, never recreate the\n // session it belonged to: resurrecting it here is what made `stop()` not stop.\n const session = this.sessions.get(sessionId);\n if (!session) return false;\n const existing = session.jobs.get(runId);\n if (read === undefined) {\n // A run may not have created its file yet, and a torn read must not blank\n // a previously known status for one polling pass.\n if (existing) return false;\n session.jobs.set(runId, { runId, status: undefined });\n return true;\n }\n\n const job: TrackedAsyncJob = { runId, ...read };\n session.jobs.set(runId, job);\n const wasTerminal = existing?.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(existing.status);\n const isTerminal = job.status !== undefined && TERMINAL_ASYNC_JOB_STATES.has(job.status);\n if (isTerminal && !wasTerminal) session.terminalAt.set(runId, this.now());\n if (!isTerminal) {\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n }\n return (\n !existing ||\n existing.status !== job.status ||\n existing.updatedAt !== job.updatedAt ||\n existing.tokens !== job.tokens ||\n existing.currentTool !== job.currentTool ||\n existing.toolCount !== job.toolCount\n );\n }\n\n private refresh(sessionId: string, runId: string): boolean {\n const changed = this.applyStatus(sessionId, runId, this.readStatus(runId));\n if (changed) this.invalidate(sessionId);\n return changed;\n }\n\n private async refreshAsync(sessionId: string, runId: string): Promise<boolean> {\n const changed = this.applyStatus(sessionId, runId, await this.readStatusAsync(runId));\n if (changed) this.invalidate(sessionId);\n return changed;\n }\n\n /** One scheduler tick: asynchronously refresh every tracked status, then evict expired terminal jobs. */\n protected async run(): Promise<boolean> {\n let changed = false;\n const now = this.now();\n for (const [sessionId, session] of this.sessions) {\n for (const runId of session.jobs.keys()) {\n if (await this.refreshAsync(sessionId, runId)) changed = true;\n }\n for (const [runId, terminatedAt] of session.terminalAt) {\n if (!session.handedOff.has(runId) || now - terminatedAt <= this.retentionMs) continue;\n session.jobs.delete(runId);\n session.terminalAt.delete(runId);\n session.handedOff.delete(runId);\n this.invalidate(sessionId);\n changed = true;\n }\n // Only drop the entry this pass actually walked: a `track()` that landed\n // during an await above may have installed a fresh one under the same id.\n if (session.jobs.size === 0 && this.sessions.get(sessionId) === session) this.sessions.delete(sessionId);\n }\n\n return changed;\n }\n\n track(runId: string): void {\n this.trackInSession(UNSCOPED_SESSION, runId);\n }\n\n untrack(runId: string): void {\n this.untrackInSession(UNSCOPED_SESSION, runId);\n }\n\n /** Mark one session's terminal result as safely handed off. */\n acknowledgeHandoff(sessionId: string, runId: string): void {\n this.acknowledgeHandoffInSession(sessionId, runId);\n }\n\n /** Work that still suppresses same-session idle continuation. */\n listBackgroundWork(sessionId: string): TrackedAsyncJob[] {\n const session = this.sessions.get(sessionId);\n if (!session) return [];\n return [...session.jobs.values()].filter(\n (job) =>\n job.status === undefined || !TERMINAL_ASYNC_JOB_STATES.has(job.status) || !session.handedOff.has(job.runId),\n );\n }\n\n /** Subscribe to invalidations for one exact Pi session. */\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 return this.subscribeInSession(sessionId, listener);\n }\n\n list(): TrackedAsyncJob[] {\n return this.listInSession(UNSCOPED_SESSION);\n }\n\n get(runId: string): TrackedAsyncJob | undefined {\n return this.getInSession(UNSCOPED_SESSION, runId);\n }\n\n reset(): void {\n this.resetSession(UNSCOPED_SESSION);\n }\n\n private trackInSession(sessionId: string, runId: string): void {\n // `applyStatus` no longer creates sessions (see its comment), so tracking is\n // the one path that does.\n this.session(sessionId);\n this.refresh(sessionId, runId);\n }\n\n private untrackInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const deleted = session?.jobs.delete(runId) ?? false;\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 acknowledgeHandoffInSession(sessionId: string, runId: string): void {\n const session = this.sessions.get(sessionId);\n const status = session?.jobs.get(runId)?.status;\n if (!session || !status || !TERMINAL_ASYNC_JOB_STATES.has(status) || session.handedOff.has(runId)) return;\n session.handedOff.add(runId);\n this.invalidate(sessionId);\n }\n\n private subscribeInSession(sessionId: string, listener: () => void): () => void {\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 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 private resetSession(sessionId: string): void {\n const hadJobs = (this.sessions.get(sessionId)?.jobs.size ?? 0) > 0;\n this.sessions.delete(sessionId);\n if (hadJobs) this.invalidate(sessionId);\n }\n\n start(): void {\n this.stop();\n this.unregisterPoll = this.scheduler.register({\n id: POLL_SUBSCRIBER_ID,\n intervalMs: this.pollIntervalMs,\n run: () => this.run(),\n });\n }\n\n stop(): void {\n this.unregisterPoll?.();\n this.unregisterPoll = undefined;\n this.sessions.clear();\n this.listeners.clear();\n }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../services/backgroundWorkService.cjs"),t=require("../../services/subagentPolicyService.cjs");let n=require("@agimon-ai/doompi-extension-contracts/delegation"),r=require("@agimon-ai/doompi-extension-contracts/fable-plan"),i=require("@agimon-ai/doompi-extension-contracts/background-work"),a=require("@agimon-ai/doompi-extension-contracts/subagent-policy");function o(o,s){let c=e.createBackgroundWorkService(o),l=t.createSubagentPolicyService(s.policies),u=s.delegation.createService(o,s.session),d=s.fablePlan.createService({sessionId:s.session.sessionId,cwd:s.session.cwd});o.provide(i.DOOM_BACKGROUND_WORK_SERVICE,c),o.provide(a.DOOM_SUBAGENT_POLICY_SERVICE,l),o.provide(n.DOOM_DELEGATION_SERVICE,u),o.provide(r.DOOM_FABLE_PLAN_SERVICE,d),s.observeDelegation&&(o.on(n.DOOM_DELEGATION_REQUESTED_EVENT,e=>s.observeDelegation?.({kind:`requested`,event:e})),o.on(n.DOOM_DELEGATION_STARTED_EVENT,e=>s.observeDelegation?.({kind:`started`,event:e})),o.on(n.DOOM_DELEGATION_UPDATED_EVENT,e=>s.observeDelegation?.({kind:`updated`,event:e})),o.on(n.DOOM_DELEGATION_FINISHED_EVENT,e=>s.observeDelegation?.({kind:`finished`,event:e})),o.on(n.DOOM_DELEGATION_CANCELLED_EVENT,e=>s.observeDelegation?.({kind:`cancelled`,event:e}))),o.effect(()=>()=>{s.delegation.abandonAll(),s.fablePlan.abandonAll(),s.policies.clear()},`@agimon-ai/doompi-team/collaboration`)}exports.teamCollaborationPlugin=o;
1
+ const e=require("./extensions/directRunBackgroundWork.cjs"),t=require("../../services/backgroundWorkService.cjs"),n=require("../../services/subagentPolicyService.cjs");let r=require("@agimon-ai/doompi-extension-contracts/delegation"),i=require("@agimon-ai/doompi-extension-contracts/fable-plan"),a=require("@agimon-ai/doompi-extension-contracts/background-work"),o=require("@agimon-ai/doompi-extension-contracts/subagent-policy");function s(s,c){let l=t.createBackgroundWorkService(s),u=n.createSubagentPolicyService(c.policies),d=c.delegation.createService(s,c.session),f=c.fablePlan.createService({sessionId:c.session.sessionId,cwd:c.session.cwd});s.provide(a.DOOM_BACKGROUND_WORK_SERVICE,l),s.provide(o.DOOM_SUBAGENT_POLICY_SERVICE,u),s.provide(r.DOOM_DELEGATION_SERVICE,d),s.provide(i.DOOM_FABLE_PLAN_SERVICE,f),e.registerDirectRunBackgroundWork(s,l,c.session.sessionId,c.directRunTracker),c.observeDelegation&&(s.on(r.DOOM_DELEGATION_REQUESTED_EVENT,e=>c.observeDelegation?.({kind:`requested`,event:e})),s.on(r.DOOM_DELEGATION_STARTED_EVENT,e=>c.observeDelegation?.({kind:`started`,event:e})),s.on(r.DOOM_DELEGATION_UPDATED_EVENT,e=>c.observeDelegation?.({kind:`updated`,event:e})),s.on(r.DOOM_DELEGATION_FINISHED_EVENT,e=>c.observeDelegation?.({kind:`finished`,event:e})),s.on(r.DOOM_DELEGATION_CANCELLED_EVENT,e=>c.observeDelegation?.({kind:`cancelled`,event:e}))),s.effect(()=>()=>{c.delegation.abandonAll(),c.fablePlan.abandonAll(),c.policies.clear()},`@agimon-ai/doompi-team/collaboration`)}exports.teamCollaborationPlugin=s;
2
2
  //# sourceMappingURL=collaboration.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"collaboration.cjs","names":["createBackgroundWorkService","createSubagentPolicyService","DOOM_BACKGROUND_WORK_SERVICE","DOOM_SUBAGENT_POLICY_SERVICE","DOOM_DELEGATION_SERVICE","DOOM_FABLE_PLAN_SERVICE","DOOM_DELEGATION_REQUESTED_EVENT","DOOM_DELEGATION_STARTED_EVENT","DOOM_DELEGATION_UPDATED_EVENT","DOOM_DELEGATION_FINISHED_EVENT","DOOM_DELEGATION_CANCELLED_EVENT"],"sources":["../../../../src/adapters/pi/collaboration.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport {\n type DelegationCancel,\n type DelegationRequest,\n type DelegationResult,\n type DelegationStarted,\n type DelegationUpdate,\n DOOM_DELEGATION_CANCELLED_EVENT,\n DOOM_DELEGATION_FINISHED_EVENT,\n DOOM_DELEGATION_REQUESTED_EVENT,\n DOOM_DELEGATION_SERVICE,\n DOOM_DELEGATION_STARTED_EVENT,\n DOOM_DELEGATION_UPDATED_EVENT,\n type DoomDelegationService,\n} from '@agimon-ai/doompi-extension-contracts/delegation';\nimport { DOOM_FABLE_PLAN_SERVICE, type DoomFablePlanService } from '@agimon-ai/doompi-extension-contracts/fable-plan';\nimport {\n DOOM_SUBAGENT_POLICY_SERVICE,\n type DoomSubagentPolicyService,\n} from '@agimon-ai/doompi-extension-contracts/subagent-policy';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { DelegationBridge, DelegationSessionContext } from './extensions/delegationBridge';\nimport type { FablePlanBridge } from './extensions/fablePlanBridge';\nimport type { SubagentCapabilityPolicyStore } from '../../schemas/team/capabilityCeiling';\nimport { createBackgroundWorkService } from '../../services/backgroundWorkService';\nimport { createSubagentPolicyService } from '../../services/subagentPolicyService';\n\nexport interface TeamCollaborationPluginConfig {\n readonly session: DelegationSessionContext & { readonly cwd: string };\n readonly delegation: DelegationBridge;\n readonly fablePlan: FablePlanBridge;\n readonly policies: SubagentCapabilityPolicyStore;\n readonly observeDelegation?: (observation: TeamDelegationObservation) => void;\n}\n\nexport type TeamDelegationObservation =\n | { readonly kind: 'requested'; readonly event: DelegationRequest }\n | { readonly kind: 'started'; readonly event: DelegationStarted }\n | { readonly kind: 'updated'; readonly event: DelegationUpdate }\n | { readonly kind: 'finished'; readonly event: DelegationResult }\n | { readonly kind: 'cancelled'; readonly event: DelegationCancel };\n\n/** Mount every Team-owned collaboration capability in one session fiber. */\nexport function teamCollaborationPlugin(ctx: Context, config: TeamCollaborationPluginConfig): void {\n const backgroundWork: DoomBackgroundWorkService = createBackgroundWorkService(ctx);\n const subagentPolicy: DoomSubagentPolicyService = createSubagentPolicyService(config.policies);\n const delegation: DoomDelegationService = config.delegation.createService(ctx, config.session);\n const fablePlan: DoomFablePlanService = config.fablePlan.createService({\n sessionId: config.session.sessionId,\n cwd: config.session.cwd,\n });\n\n ctx.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n ctx.provide(DOOM_SUBAGENT_POLICY_SERVICE, subagentPolicy);\n ctx.provide(DOOM_DELEGATION_SERVICE, delegation);\n ctx.provide(DOOM_FABLE_PLAN_SERVICE, fablePlan);\n\n if (config.observeDelegation) {\n ctx.on(DOOM_DELEGATION_REQUESTED_EVENT, (event) => config.observeDelegation?.({ kind: 'requested', event }));\n ctx.on(DOOM_DELEGATION_STARTED_EVENT, (event) => config.observeDelegation?.({ kind: 'started', event }));\n ctx.on(DOOM_DELEGATION_UPDATED_EVENT, (event) => config.observeDelegation?.({ kind: 'updated', event }));\n ctx.on(DOOM_DELEGATION_FINISHED_EVENT, (event) => config.observeDelegation?.({ kind: 'finished', event }));\n ctx.on(DOOM_DELEGATION_CANCELLED_EVENT, (event) => config.observeDelegation?.({ kind: 'cancelled', event }));\n }\n\n ctx.effect(\n () => () => {\n config.delegation.abandonAll();\n config.fablePlan.abandonAll();\n config.policies.clear();\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n}\n"],"mappings":"wXA8CA,SAAgB,EAAwB,EAAc,EAA6C,CACjG,IAAM,EAA4CA,EAAAA,4BAA4B,CAAG,EAC3E,EAA4CC,EAAAA,4BAA4B,EAAO,QAAQ,EACvF,EAAoC,EAAO,WAAW,cAAc,EAAK,EAAO,OAAO,EACvF,EAAkC,EAAO,UAAU,cAAc,CACrE,UAAW,EAAO,QAAQ,UAC1B,IAAK,EAAO,QAAQ,GACtB,CAAC,EAED,EAAI,QAAQC,EAAAA,6BAA8B,CAAc,EACxD,EAAI,QAAQC,EAAAA,6BAA8B,CAAc,EACxD,EAAI,QAAQC,EAAAA,wBAAyB,CAAU,EAC/C,EAAI,QAAQC,EAAAA,wBAAyB,CAAS,EAE1C,EAAO,oBACT,EAAI,GAAGC,EAAAA,gCAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,EAC3G,EAAI,GAAGC,EAAAA,8BAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAGC,EAAAA,8BAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAGC,EAAAA,+BAAiC,GAAU,EAAO,oBAAoB,CAAE,KAAM,WAAY,OAAM,CAAC,CAAC,EACzG,EAAI,GAAGC,EAAAA,gCAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,GAG7G,EAAI,eACU,CACV,EAAO,WAAW,WAAW,EAC7B,EAAO,UAAU,WAAW,EAC5B,EAAO,SAAS,MAAM,CACxB,EACA,sCACF,CACF"}
1
+ {"version":3,"file":"collaboration.cjs","names":["createBackgroundWorkService","createSubagentPolicyService","DOOM_BACKGROUND_WORK_SERVICE","DOOM_SUBAGENT_POLICY_SERVICE","DOOM_DELEGATION_SERVICE","DOOM_FABLE_PLAN_SERVICE","DOOM_DELEGATION_REQUESTED_EVENT","DOOM_DELEGATION_STARTED_EVENT","DOOM_DELEGATION_UPDATED_EVENT","DOOM_DELEGATION_FINISHED_EVENT","DOOM_DELEGATION_CANCELLED_EVENT"],"sources":["../../../../src/adapters/pi/collaboration.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport {\n type DelegationCancel,\n type DelegationRequest,\n type DelegationResult,\n type DelegationStarted,\n type DelegationUpdate,\n DOOM_DELEGATION_CANCELLED_EVENT,\n DOOM_DELEGATION_FINISHED_EVENT,\n DOOM_DELEGATION_REQUESTED_EVENT,\n DOOM_DELEGATION_SERVICE,\n DOOM_DELEGATION_STARTED_EVENT,\n DOOM_DELEGATION_UPDATED_EVENT,\n type DoomDelegationService,\n} from '@agimon-ai/doompi-extension-contracts/delegation';\nimport { DOOM_FABLE_PLAN_SERVICE, type DoomFablePlanService } from '@agimon-ai/doompi-extension-contracts/fable-plan';\nimport {\n DOOM_SUBAGENT_POLICY_SERVICE,\n type DoomSubagentPolicyService,\n} from '@agimon-ai/doompi-extension-contracts/subagent-policy';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { AsyncJobTracker } from '../asyncJobTracker';\nimport type { DelegationBridge, DelegationSessionContext } from './extensions/delegationBridge';\nimport { registerDirectRunBackgroundWork } from './extensions/directRunBackgroundWork';\nimport type { FablePlanBridge } from './extensions/fablePlanBridge';\nimport type { SubagentCapabilityPolicyStore } from '../../schemas/team/capabilityCeiling';\nimport { createBackgroundWorkService } from '../../services/backgroundWorkService';\nimport { createSubagentPolicyService } from '../../services/subagentPolicyService';\n\nexport interface TeamCollaborationPluginConfig {\n readonly session: DelegationSessionContext & { readonly cwd: string };\n readonly directRunTracker: AsyncJobTracker;\n readonly delegation: DelegationBridge;\n readonly fablePlan: FablePlanBridge;\n readonly policies: SubagentCapabilityPolicyStore;\n readonly observeDelegation?: (observation: TeamDelegationObservation) => void;\n}\n\nexport type TeamDelegationObservation =\n | { readonly kind: 'requested'; readonly event: DelegationRequest }\n | { readonly kind: 'started'; readonly event: DelegationStarted }\n | { readonly kind: 'updated'; readonly event: DelegationUpdate }\n | { readonly kind: 'finished'; readonly event: DelegationResult }\n | { readonly kind: 'cancelled'; readonly event: DelegationCancel };\n\n/** Mount every Team-owned collaboration capability in one session fiber. */\nexport function teamCollaborationPlugin(ctx: Context, config: TeamCollaborationPluginConfig): void {\n const backgroundWork: DoomBackgroundWorkService = createBackgroundWorkService(ctx);\n const subagentPolicy: DoomSubagentPolicyService = createSubagentPolicyService(config.policies);\n const delegation: DoomDelegationService = config.delegation.createService(ctx, config.session);\n const fablePlan: DoomFablePlanService = config.fablePlan.createService({\n sessionId: config.session.sessionId,\n cwd: config.session.cwd,\n });\n\n ctx.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n ctx.provide(DOOM_SUBAGENT_POLICY_SERVICE, subagentPolicy);\n ctx.provide(DOOM_DELEGATION_SERVICE, delegation);\n ctx.provide(DOOM_FABLE_PLAN_SERVICE, fablePlan);\n registerDirectRunBackgroundWork(ctx, backgroundWork, config.session.sessionId, config.directRunTracker);\n\n if (config.observeDelegation) {\n ctx.on(DOOM_DELEGATION_REQUESTED_EVENT, (event) => config.observeDelegation?.({ kind: 'requested', event }));\n ctx.on(DOOM_DELEGATION_STARTED_EVENT, (event) => config.observeDelegation?.({ kind: 'started', event }));\n ctx.on(DOOM_DELEGATION_UPDATED_EVENT, (event) => config.observeDelegation?.({ kind: 'updated', event }));\n ctx.on(DOOM_DELEGATION_FINISHED_EVENT, (event) => config.observeDelegation?.({ kind: 'finished', event }));\n ctx.on(DOOM_DELEGATION_CANCELLED_EVENT, (event) => config.observeDelegation?.({ kind: 'cancelled', event }));\n }\n\n ctx.effect(\n () => () => {\n config.delegation.abandonAll();\n config.fablePlan.abandonAll();\n config.policies.clear();\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n}\n"],"mappings":"8aAiDA,SAAgB,EAAwB,EAAc,EAA6C,CACjG,IAAM,EAA4CA,EAAAA,4BAA4B,CAAG,EAC3E,EAA4CC,EAAAA,4BAA4B,EAAO,QAAQ,EACvF,EAAoC,EAAO,WAAW,cAAc,EAAK,EAAO,OAAO,EACvF,EAAkC,EAAO,UAAU,cAAc,CACrE,UAAW,EAAO,QAAQ,UAC1B,IAAK,EAAO,QAAQ,GACtB,CAAC,EAED,EAAI,QAAQC,EAAAA,6BAA8B,CAAc,EACxD,EAAI,QAAQC,EAAAA,6BAA8B,CAAc,EACxD,EAAI,QAAQC,EAAAA,wBAAyB,CAAU,EAC/C,EAAI,QAAQC,EAAAA,wBAAyB,CAAS,EAC9C,EAAA,gCAAgC,EAAK,EAAgB,EAAO,QAAQ,UAAW,EAAO,gBAAgB,EAElG,EAAO,oBACT,EAAI,GAAGC,EAAAA,gCAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,EAC3G,EAAI,GAAGC,EAAAA,8BAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAGC,EAAAA,8BAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAGC,EAAAA,+BAAiC,GAAU,EAAO,oBAAoB,CAAE,KAAM,WAAY,OAAM,CAAC,CAAC,EACzG,EAAI,GAAGC,EAAAA,gCAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,GAG7G,EAAI,eACU,CACV,EAAO,WAAW,WAAW,EAC7B,EAAO,UAAU,WAAW,EAC5B,EAAO,SAAS,MAAM,CACxB,EACA,sCACF,CACF"}
@@ -1,2 +1,2 @@
1
- import{createBackgroundWorkService as e}from"../../services/backgroundWorkService.mjs";import{createSubagentPolicyService as t}from"../../services/subagentPolicyService.mjs";import{DOOM_DELEGATION_CANCELLED_EVENT as n,DOOM_DELEGATION_FINISHED_EVENT as r,DOOM_DELEGATION_REQUESTED_EVENT as i,DOOM_DELEGATION_SERVICE as a,DOOM_DELEGATION_STARTED_EVENT as o,DOOM_DELEGATION_UPDATED_EVENT as s}from"@agimon-ai/doompi-extension-contracts/delegation";import{DOOM_FABLE_PLAN_SERVICE as c}from"@agimon-ai/doompi-extension-contracts/fable-plan";import{DOOM_BACKGROUND_WORK_SERVICE as l}from"@agimon-ai/doompi-extension-contracts/background-work";import{DOOM_SUBAGENT_POLICY_SERVICE as u}from"@agimon-ai/doompi-extension-contracts/subagent-policy";function d(d,f){let p=e(d),m=t(f.policies),h=f.delegation.createService(d,f.session),g=f.fablePlan.createService({sessionId:f.session.sessionId,cwd:f.session.cwd});d.provide(l,p),d.provide(u,m),d.provide(a,h),d.provide(c,g),f.observeDelegation&&(d.on(i,e=>f.observeDelegation?.({kind:`requested`,event:e})),d.on(o,e=>f.observeDelegation?.({kind:`started`,event:e})),d.on(s,e=>f.observeDelegation?.({kind:`updated`,event:e})),d.on(r,e=>f.observeDelegation?.({kind:`finished`,event:e})),d.on(n,e=>f.observeDelegation?.({kind:`cancelled`,event:e}))),d.effect(()=>()=>{f.delegation.abandonAll(),f.fablePlan.abandonAll(),f.policies.clear()},`@agimon-ai/doompi-team/collaboration`)}export{d as teamCollaborationPlugin};
1
+ import{registerDirectRunBackgroundWork as e}from"./extensions/directRunBackgroundWork.mjs";import{createBackgroundWorkService as t}from"../../services/backgroundWorkService.mjs";import{createSubagentPolicyService as n}from"../../services/subagentPolicyService.mjs";import{DOOM_DELEGATION_CANCELLED_EVENT as r,DOOM_DELEGATION_FINISHED_EVENT as i,DOOM_DELEGATION_REQUESTED_EVENT as a,DOOM_DELEGATION_SERVICE as o,DOOM_DELEGATION_STARTED_EVENT as s,DOOM_DELEGATION_UPDATED_EVENT as c}from"@agimon-ai/doompi-extension-contracts/delegation";import{DOOM_FABLE_PLAN_SERVICE as l}from"@agimon-ai/doompi-extension-contracts/fable-plan";import{DOOM_BACKGROUND_WORK_SERVICE as u}from"@agimon-ai/doompi-extension-contracts/background-work";import{DOOM_SUBAGENT_POLICY_SERVICE as d}from"@agimon-ai/doompi-extension-contracts/subagent-policy";function f(f,p){let m=t(f),h=n(p.policies),g=p.delegation.createService(f,p.session),_=p.fablePlan.createService({sessionId:p.session.sessionId,cwd:p.session.cwd});f.provide(u,m),f.provide(d,h),f.provide(o,g),f.provide(l,_),e(f,m,p.session.sessionId,p.directRunTracker),p.observeDelegation&&(f.on(a,e=>p.observeDelegation?.({kind:`requested`,event:e})),f.on(s,e=>p.observeDelegation?.({kind:`started`,event:e})),f.on(c,e=>p.observeDelegation?.({kind:`updated`,event:e})),f.on(i,e=>p.observeDelegation?.({kind:`finished`,event:e})),f.on(r,e=>p.observeDelegation?.({kind:`cancelled`,event:e}))),f.effect(()=>()=>{p.delegation.abandonAll(),p.fablePlan.abandonAll(),p.policies.clear()},`@agimon-ai/doompi-team/collaboration`)}export{f as teamCollaborationPlugin};
2
2
  //# sourceMappingURL=collaboration.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"collaboration.mjs","names":[],"sources":["../../../../src/adapters/pi/collaboration.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport {\n type DelegationCancel,\n type DelegationRequest,\n type DelegationResult,\n type DelegationStarted,\n type DelegationUpdate,\n DOOM_DELEGATION_CANCELLED_EVENT,\n DOOM_DELEGATION_FINISHED_EVENT,\n DOOM_DELEGATION_REQUESTED_EVENT,\n DOOM_DELEGATION_SERVICE,\n DOOM_DELEGATION_STARTED_EVENT,\n DOOM_DELEGATION_UPDATED_EVENT,\n type DoomDelegationService,\n} from '@agimon-ai/doompi-extension-contracts/delegation';\nimport { DOOM_FABLE_PLAN_SERVICE, type DoomFablePlanService } from '@agimon-ai/doompi-extension-contracts/fable-plan';\nimport {\n DOOM_SUBAGENT_POLICY_SERVICE,\n type DoomSubagentPolicyService,\n} from '@agimon-ai/doompi-extension-contracts/subagent-policy';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { DelegationBridge, DelegationSessionContext } from './extensions/delegationBridge';\nimport type { FablePlanBridge } from './extensions/fablePlanBridge';\nimport type { SubagentCapabilityPolicyStore } from '../../schemas/team/capabilityCeiling';\nimport { createBackgroundWorkService } from '../../services/backgroundWorkService';\nimport { createSubagentPolicyService } from '../../services/subagentPolicyService';\n\nexport interface TeamCollaborationPluginConfig {\n readonly session: DelegationSessionContext & { readonly cwd: string };\n readonly delegation: DelegationBridge;\n readonly fablePlan: FablePlanBridge;\n readonly policies: SubagentCapabilityPolicyStore;\n readonly observeDelegation?: (observation: TeamDelegationObservation) => void;\n}\n\nexport type TeamDelegationObservation =\n | { readonly kind: 'requested'; readonly event: DelegationRequest }\n | { readonly kind: 'started'; readonly event: DelegationStarted }\n | { readonly kind: 'updated'; readonly event: DelegationUpdate }\n | { readonly kind: 'finished'; readonly event: DelegationResult }\n | { readonly kind: 'cancelled'; readonly event: DelegationCancel };\n\n/** Mount every Team-owned collaboration capability in one session fiber. */\nexport function teamCollaborationPlugin(ctx: Context, config: TeamCollaborationPluginConfig): void {\n const backgroundWork: DoomBackgroundWorkService = createBackgroundWorkService(ctx);\n const subagentPolicy: DoomSubagentPolicyService = createSubagentPolicyService(config.policies);\n const delegation: DoomDelegationService = config.delegation.createService(ctx, config.session);\n const fablePlan: DoomFablePlanService = config.fablePlan.createService({\n sessionId: config.session.sessionId,\n cwd: config.session.cwd,\n });\n\n ctx.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n ctx.provide(DOOM_SUBAGENT_POLICY_SERVICE, subagentPolicy);\n ctx.provide(DOOM_DELEGATION_SERVICE, delegation);\n ctx.provide(DOOM_FABLE_PLAN_SERVICE, fablePlan);\n\n if (config.observeDelegation) {\n ctx.on(DOOM_DELEGATION_REQUESTED_EVENT, (event) => config.observeDelegation?.({ kind: 'requested', event }));\n ctx.on(DOOM_DELEGATION_STARTED_EVENT, (event) => config.observeDelegation?.({ kind: 'started', event }));\n ctx.on(DOOM_DELEGATION_UPDATED_EVENT, (event) => config.observeDelegation?.({ kind: 'updated', event }));\n ctx.on(DOOM_DELEGATION_FINISHED_EVENT, (event) => config.observeDelegation?.({ kind: 'finished', event }));\n ctx.on(DOOM_DELEGATION_CANCELLED_EVENT, (event) => config.observeDelegation?.({ kind: 'cancelled', event }));\n }\n\n ctx.effect(\n () => () => {\n config.delegation.abandonAll();\n config.fablePlan.abandonAll();\n config.policies.clear();\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n}\n"],"mappings":"kuBA8CA,SAAgB,EAAwB,EAAc,EAA6C,CACjG,IAAM,EAA4C,EAA4B,CAAG,EAC3E,EAA4C,EAA4B,EAAO,QAAQ,EACvF,EAAoC,EAAO,WAAW,cAAc,EAAK,EAAO,OAAO,EACvF,EAAkC,EAAO,UAAU,cAAc,CACrE,UAAW,EAAO,QAAQ,UAC1B,IAAK,EAAO,QAAQ,GACtB,CAAC,EAED,EAAI,QAAQ,EAA8B,CAAc,EACxD,EAAI,QAAQ,EAA8B,CAAc,EACxD,EAAI,QAAQ,EAAyB,CAAU,EAC/C,EAAI,QAAQ,EAAyB,CAAS,EAE1C,EAAO,oBACT,EAAI,GAAG,EAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,EAC3G,EAAI,GAAG,EAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAG,EAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAG,EAAiC,GAAU,EAAO,oBAAoB,CAAE,KAAM,WAAY,OAAM,CAAC,CAAC,EACzG,EAAI,GAAG,EAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,GAG7G,EAAI,eACU,CACV,EAAO,WAAW,WAAW,EAC7B,EAAO,UAAU,WAAW,EAC5B,EAAO,SAAS,MAAM,CACxB,EACA,sCACF,CACF"}
1
+ {"version":3,"file":"collaboration.mjs","names":[],"sources":["../../../../src/adapters/pi/collaboration.ts"],"sourcesContent":["import {\n DOOM_BACKGROUND_WORK_SERVICE,\n type DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport {\n type DelegationCancel,\n type DelegationRequest,\n type DelegationResult,\n type DelegationStarted,\n type DelegationUpdate,\n DOOM_DELEGATION_CANCELLED_EVENT,\n DOOM_DELEGATION_FINISHED_EVENT,\n DOOM_DELEGATION_REQUESTED_EVENT,\n DOOM_DELEGATION_SERVICE,\n DOOM_DELEGATION_STARTED_EVENT,\n DOOM_DELEGATION_UPDATED_EVENT,\n type DoomDelegationService,\n} from '@agimon-ai/doompi-extension-contracts/delegation';\nimport { DOOM_FABLE_PLAN_SERVICE, type DoomFablePlanService } from '@agimon-ai/doompi-extension-contracts/fable-plan';\nimport {\n DOOM_SUBAGENT_POLICY_SERVICE,\n type DoomSubagentPolicyService,\n} from '@agimon-ai/doompi-extension-contracts/subagent-policy';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { AsyncJobTracker } from '../asyncJobTracker';\nimport type { DelegationBridge, DelegationSessionContext } from './extensions/delegationBridge';\nimport { registerDirectRunBackgroundWork } from './extensions/directRunBackgroundWork';\nimport type { FablePlanBridge } from './extensions/fablePlanBridge';\nimport type { SubagentCapabilityPolicyStore } from '../../schemas/team/capabilityCeiling';\nimport { createBackgroundWorkService } from '../../services/backgroundWorkService';\nimport { createSubagentPolicyService } from '../../services/subagentPolicyService';\n\nexport interface TeamCollaborationPluginConfig {\n readonly session: DelegationSessionContext & { readonly cwd: string };\n readonly directRunTracker: AsyncJobTracker;\n readonly delegation: DelegationBridge;\n readonly fablePlan: FablePlanBridge;\n readonly policies: SubagentCapabilityPolicyStore;\n readonly observeDelegation?: (observation: TeamDelegationObservation) => void;\n}\n\nexport type TeamDelegationObservation =\n | { readonly kind: 'requested'; readonly event: DelegationRequest }\n | { readonly kind: 'started'; readonly event: DelegationStarted }\n | { readonly kind: 'updated'; readonly event: DelegationUpdate }\n | { readonly kind: 'finished'; readonly event: DelegationResult }\n | { readonly kind: 'cancelled'; readonly event: DelegationCancel };\n\n/** Mount every Team-owned collaboration capability in one session fiber. */\nexport function teamCollaborationPlugin(ctx: Context, config: TeamCollaborationPluginConfig): void {\n const backgroundWork: DoomBackgroundWorkService = createBackgroundWorkService(ctx);\n const subagentPolicy: DoomSubagentPolicyService = createSubagentPolicyService(config.policies);\n const delegation: DoomDelegationService = config.delegation.createService(ctx, config.session);\n const fablePlan: DoomFablePlanService = config.fablePlan.createService({\n sessionId: config.session.sessionId,\n cwd: config.session.cwd,\n });\n\n ctx.provide(DOOM_BACKGROUND_WORK_SERVICE, backgroundWork);\n ctx.provide(DOOM_SUBAGENT_POLICY_SERVICE, subagentPolicy);\n ctx.provide(DOOM_DELEGATION_SERVICE, delegation);\n ctx.provide(DOOM_FABLE_PLAN_SERVICE, fablePlan);\n registerDirectRunBackgroundWork(ctx, backgroundWork, config.session.sessionId, config.directRunTracker);\n\n if (config.observeDelegation) {\n ctx.on(DOOM_DELEGATION_REQUESTED_EVENT, (event) => config.observeDelegation?.({ kind: 'requested', event }));\n ctx.on(DOOM_DELEGATION_STARTED_EVENT, (event) => config.observeDelegation?.({ kind: 'started', event }));\n ctx.on(DOOM_DELEGATION_UPDATED_EVENT, (event) => config.observeDelegation?.({ kind: 'updated', event }));\n ctx.on(DOOM_DELEGATION_FINISHED_EVENT, (event) => config.observeDelegation?.({ kind: 'finished', event }));\n ctx.on(DOOM_DELEGATION_CANCELLED_EVENT, (event) => config.observeDelegation?.({ kind: 'cancelled', event }));\n }\n\n ctx.effect(\n () => () => {\n config.delegation.abandonAll();\n config.fablePlan.abandonAll();\n config.policies.clear();\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n}\n"],"mappings":"6zBAiDA,SAAgB,EAAwB,EAAc,EAA6C,CACjG,IAAM,EAA4C,EAA4B,CAAG,EAC3E,EAA4C,EAA4B,EAAO,QAAQ,EACvF,EAAoC,EAAO,WAAW,cAAc,EAAK,EAAO,OAAO,EACvF,EAAkC,EAAO,UAAU,cAAc,CACrE,UAAW,EAAO,QAAQ,UAC1B,IAAK,EAAO,QAAQ,GACtB,CAAC,EAED,EAAI,QAAQ,EAA8B,CAAc,EACxD,EAAI,QAAQ,EAA8B,CAAc,EACxD,EAAI,QAAQ,EAAyB,CAAU,EAC/C,EAAI,QAAQ,EAAyB,CAAS,EAC9C,EAAgC,EAAK,EAAgB,EAAO,QAAQ,UAAW,EAAO,gBAAgB,EAElG,EAAO,oBACT,EAAI,GAAG,EAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,EAC3G,EAAI,GAAG,EAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAG,EAAgC,GAAU,EAAO,oBAAoB,CAAE,KAAM,UAAW,OAAM,CAAC,CAAC,EACvG,EAAI,GAAG,EAAiC,GAAU,EAAO,oBAAoB,CAAE,KAAM,WAAY,OAAM,CAAC,CAAC,EACzG,EAAI,GAAG,EAAkC,GAAU,EAAO,oBAAoB,CAAE,KAAM,YAAa,OAAM,CAAC,CAAC,GAG7G,EAAI,eACU,CACV,EAAO,WAAW,WAAW,EAC7B,EAAO,UAAU,WAAW,EAC5B,EAAO,SAAS,MAAM,CACxB,EACA,sCACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../agents/discovery.cjs"),t=require("../../services/webSubagentCatalog.cjs"),n=require("../sessionCatalogSnapshot.cjs"),r=require("../filesystem/paths.cjs"),i=require("../api/teamSnapshot.cjs"),a=require("../../services/models/modelResolution.cjs"),o=require("../runs/shared/modelFallback.cjs"),ee=require("../scopeOwner.cjs"),te=require("./extensions/spawnPlan.cjs"),s=require("../asyncJobTracker.cjs"),c=require("./commands/slash/slashCommands.cjs"),l=require("./extensions/config.cjs"),u=require("./extensions/delegationBridge.cjs"),d=require("./extensions/fablePlanBridge.cjs"),f=require("./extensions/orchestratorPrompt.cjs"),ne=require("../suspendedRuns.cjs"),p=require("../runs/registry/sessionLifecycle.cjs"),m=require("./tui/completionNotice.cjs"),re=require("./tui/slashRunNotice.cjs"),h=require("./tui/register.cjs"),ie=require("./collaboration.cjs"),ae=require("./teamRuntime.cjs");let g=require("@agimon-ai/doompi-extension-contracts/child-process"),_=require("@agimon-ai/doompi-extension-contracts/cordis-host"),v=require("@agimon-ai/doompi-extension-contracts/context-contributions"),y=require("@agimon-ai/doompi-extension-contracts/mcp-tool-resolver"),oe=require("@agimon-ai/doompi-extension-contracts/readiness"),b=require("@agimon-ai/doompi-extension-contracts/ui-hub"),se=require("@agimon-ai/doompi-telemetry");const x=`unknown`,S=`@agimon-ai/doompi-team`;function ce(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function le(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`;return`${e.packageId} initialization ${e.state}: ${n}`}function ue(e,t,n,r,i,a){return{spawnPlanner:n,tracker:r,scheduler:i,discovery:e,skills:t,management:a,loadConfig:()=>l.loadConfig().config}}function de(e,t){if(t.kind===`requested`){let{event:n}=t;e.recordEvent(`doom_team.delegation_requested`,{"agent.name":n.agent,"task.id":n.taskId,mode:n.runMode??`foreground`})}else if(t.kind===`started`){let{event:n}=t;e.recordEvent(`doom_team.child_started`,{"run.id":n.runId,outcome:`started`})}else if(t.kind===`updated`){let{event:n}=t;e.recordEvent(`doom_team.delegation_updated`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,"team.token_count":n.tokens??0,outcome:n.status??`updated`})}else if(t.kind===`finished`){let{event:n}=t;e.recordEvent(`doom_team.delegation_finished`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,outcome:n.status})}else e.recordEvent(`doom_team.delegation_cancelled`,{outcome:`cancelled`})}function C(C,w){let T,fe=e=>(T??=(0,se.createDoomTelemetry)({serviceName:`doom-team`,packageName:`@agimon-ai/doompi-team`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),T),pe=(e,t)=>{T?.recordEvent(e,t)};C.effect(()=>async()=>{await T?.shutdown(),T=void 0},`@agimon-ai/doompi-team/telemetry`);let E=ae.createTeamExtensionRuntime(pe),{subagentTool:me,teamChannel:D,pollScheduler:O,asyncJobTracker:k,subagentWaiter:A,asyncSubagentSpawner:he,discovery:j,skills:M,spawnPlanner:N,completionNotifier:P,resultWatcher:F,staleRunReconciler:I,management:L,capabilityPolicies:R,mcpToolResolver:ge}=E,z=!0,B=0,V,H,U,W,G,K;C.inject([_.DOOM_CORDIS_SESSION_SERVICE],e=>{let t={cordis:e,service:e.get(_.DOOM_CORDIS_SESSION_SERVICE)};K=t,e.inject([y.DOOM_MCP_TOOL_RESOLVER_SERVICE],e=>ge.bind((0,y.requireDoomMcpToolResolver)(e))),e.inject([v.DOOM_CONTEXT_CONTRIBUTIONS_SERVICE],e=>{let t=(0,v.requireDoomContextContributions)(e).register({source:S,id:`runtime`,label:`Team`,order:200,snapshot:()=>i.formatTeamContextSnapshot(i.readActiveTeamSnapshot())});return()=>t.dispose()}),e.effect(()=>()=>{K===t&&(K=void 0)},`${S}/cordis-session`)});let _e=e=>{let t=K,n=e.sessionManager.getSessionId();return(t?.service.context.sessionManager===e.sessionManager&&t.service.sessionId===n?(0,oe.readDoomReadinessCoordinator)(t.cordis):void 0)||(W??=(0,oe.createDoomReadinessCoordinator)({notify:e=>{let t=le(e);G?.hasUI?G.ui.notify(t,`warning`):process.emitWarning(t)}}),W)},q=async(e,t)=>{t?.throwIfAborted();let n=H;if(!n)throw Error(`Doom Team session initialization has not started.`);if(U!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale Pi session.`);let r=await n;if(t?.throwIfAborted(),await r.wait(t?{signal:t}:void 0),!z||n!==H||U!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale extension generation.`)},J=new Proxy(w,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await q(a,r),t.execute(e,n,r,i,a))})}:t===`registerCommand`?(t,n)=>{e.registerCommand(t,{...n,handler:async(e,t)=>(await q(t),n.handler(e,t))})}:t===`registerShortcut`?(t,n)=>{e.registerShortcut(t,{...n,handler:async e=>(await q(e),n.handler(e))})}:ce(e,t)}});me.registerTool(J),C.inject([b.DOOM_UI_HUB_SERVICE],e=>h.registerSubagentLeaderContribution((0,b.requireDoomUiHub)(e)));let Y=D.createRuntime(J);C.effect(()=>()=>Y.dispose(),`@agimon-ai/doompi-team/channel`),D.registerClient(J),P.attachHost(w),C.effect(()=>()=>P.dispose(),`@agimon-ai/doompi-team/completions`),O.start(),C.effect(()=>()=>O.stop(),`@agimon-ai/doompi-team/polling`),k.start(),C.effect(()=>()=>k.stop(),`@agimon-ai/doompi-team/jobs`);let X,Z,Q,ve=u.createDelegationBridge({planner:N,management:L,waiter:A,scheduler:O,tracker:k,loadConfig:()=>l.loadConfig().config}),ye=d.createFablePlanBridge({spawner:he,waiter:A,management:L,policies:R,report:(e,t)=>{T?.recordEvent(e,t)}});C.effect(()=>async()=>{await X?.dispose(),X=void 0},`@agimon-ai/doompi-team/collaboration`),C.effect(()=>()=>{F.stop(),Q=void 0},`@agimon-ai/doompi-team/result-watcher`);let be=O.register({id:`stale-run-reconciler`,intervalMs:2e3,run:async()=>{let e=!1,t=!1;for(let n of Z?.list()??[]){if(n.status&&s.TERMINAL_ASYNC_JOB_STATES.has(n.status))continue;t=!0;let{reconcile:r,sweep:i}=I.reconcileAndSweepAsync?await I.reconcileAndSweepAsync(n.runId):I.reconcileAndSweep(n.runId);r.repaired&&(Z?.track(n.runId),e=!0),i.recovered.length>0&&(e=!0)}return e||t}});C.effect(()=>be,`@agimon-ai/doompi-team/stale-runs`),w.on(`before_agent_start`,e=>{if(!(!z||!f.shouldInjectOrchestratorPrompt(l.loadConfig().config)))return{systemPrompt:f.appendOrchestratorPrompt(e.systemPrompt)}});let $={baseCwd:void 0},xe=(r,i)=>{if((0,g.resolveRootSessionId)(i)===i)try{n.writeSessionCatalogSnapshot(i,{cwd:r,agents:t.presentCatalog(j.discover(r,`both`).agents),models:e.resolveActiveTeamModelSpecs()??[]})}catch{}};w.on(`before_agent_start`,(e,t)=>{z&&xe(t.cwd,t.sessionManager.getSessionId())});let Se=ue(j,M,N,k,O,L);c.registerSlashCommands(J,$,Se),h.registerAgentListCommand(J,{discovery:j,skills:M,policies:R,launchAgent:(e,t)=>c.startSingleAgentRun(J,e,$,Se,{agent:t.agent,task:t.task,fork:t.context===`fork`})}),h.registerFleetCommand(J,{scheduler:O,tracker:k,management:L}),C.inject([b.DOOM_UI_HUB_SERVICE],e=>h.registerAgentStatus(w,(0,b.requireDoomUiHub)(e),{scheduler:O,tracker:k})),m.registerCompletionRenderer(w),re.registerSlashRunRenderer(w);let Ce=async()=>{let e=[],t=X;X=void 0;try{await t?.dispose()}catch(t){e.push(t)}for(let t of[()=>R.clear(),()=>ve.abandonAll(),()=>ye.abandonAll(),()=>Y.dispose()])try{t()}catch(t){e.push(t)}if(Z=void 0,e.length!==0)return e.length===1&&e[0]instanceof Error?e[0]:AggregateError(e,`Team session cleanup failed.`)};w.on(`session_start`,(e,t)=>{if(!z)return;let n=++B,i=H;V?.abort(Error(`Doom Team session initialization was superseded.`));let s=new AbortController;V=s,U=t.sessionManager,G=t;let c=fe(t);c.recordEvent(`doom_team.session_started`,{"team.root_session":(0,g.resolveRootSessionId)(t.sessionManager.getSessionId())===t.sessionManager.getSessionId()});let l=t.sessionManager.getSessionId(),u=r.createSessionScope((0,g.resolveRootSessionId)(l));r.setCurrentSessionScope(u),$.baseCwd=t.cwd,xe(t.cwd,l),Q!==void 0&&Q!==u.scopeKey&&(F.stop(),Q=void 0);let d=Ce(),f=_e(t),m=()=>z&&n===B&&U===t.sessionManager&&!s.signal.aborted;H=(async()=>{if(i&&await Promise.allSettled([i.then(e=>e.wait())]),!m())throw Error(`Doom Team session initialization was superseded.`);return f.start(S,`${l}:${n}`,async e=>{e.throwIfAborted();let n=await d;if(n)throw n;if(!m()||(await ee.writeScopeOwnerAsync(u),e.throwIfAborted(),!m()))return{value:void 0};let r=await p.openScopeAsync(u,{readStatusAsync:async e=>L.statusAsync?(await L.statusAsync(e)).status:L.status(e).status});if(e.throwIfAborted(),!m())return{value:void 0};if(r.suspended.length>0&&t.hasUI){let n=await ne.formatSuspendedRunsAsync(r.suspended);if(e.throwIfAborted(),!m())return{value:void 0};t.ui.notify(n,`info`)}c.recordEvent(`doom_team.runs_restored`,{"team.suspended_count":r.suspended.length,"team.pruned_count":r.pruned.length,"team.reaped_count":r.reaped,outcome:`reported`}),Z=k.forSession(l),Q!==u.scopeKey&&(Q=u.scopeKey,F.start(e=>Z?.get(e.runId)?P.deliver(e):!1));let i=o.normalizeParentModel(t.model),s=K,f=(s?.service.context.sessionManager===t.sessionManager?s.cordis:C).plugin(ie.teamCollaborationPlugin,{session:{sessionId:l,cwd:t.cwd,availableModels:a.authenticatedModelInfos(t.modelRegistry),...i?{parentModel:i}:{},captureForkSource:()=>te.captureSessionForkSource(t.sessionManager,`tool`)},delegation:ve,fablePlan:ye,policies:R,observeDelegation:e=>de(c,e)});return X=f,await f.await(),m()?(Y.bindMainSession(l),{value:void 0}):(await f.dispose(),X===f&&(X=void 0),{value:void 0})})})(),Promise.allSettled([H.then(e=>e.wait())])}),C.effect(()=>async()=>{z=!1,B+=1;let e=H;H=void 0,U=void 0,V?.abort(Error(`Doom Team runtime was disposed.`)),V=void 0;let t=W;W=void 0,t?await t.dispose():e&&await Promise.allSettled([e.then(e=>e.wait())]),G=void 0},`@agimon-ai/doompi-team/session-fence`);let we,Te=async e=>{z=!1,B+=1;let t=H;H=void 0,U=void 0,V?.abort(Error(`Doom Team session ended.`)),V=void 0;let n=W;W=void 0,await Promise.allSettled([...t?[t.then(e=>e.wait())]:[],...n?[n.dispose()]:[]]);let i=r.tryCurrentSessionScope(),a=i?p.suspendScopeRuns({scope:i,reason:e.reason??x,readStatus:e=>L.status(e).status}):Promise.resolve(void 0),o;try{o=await a,await T?.recordEvent(`doom_team.session_finished`,{reason:e.reason??x,"team.suspended_count":o?.suspended.length??0,"team.unstoppable_count":o?.unstoppable.length??0,outcome:o?.unstoppable.length?`degraded`:`suspended`})}catch(t){await T?.recordError(`doom_team.session_shutdown_failed`,t,{reason:e.reason??x})}finally{await T?.shutdown(),T=void 0}};return w.on(`session_shutdown`,e=>(we??=Te(e),we)),E}exports.installTeamRuntime=C;
1
+ const e=require("../agents/discovery.cjs"),t=require("../../services/webSubagentCatalog.cjs"),n=require("../sessionCatalogSnapshot.cjs"),r=require("../filesystem/paths.cjs"),i=require("../api/teamSnapshot.cjs"),a=require("../../services/models/modelResolution.cjs"),o=require("../runs/shared/modelFallback.cjs"),ee=require("../scopeOwner.cjs"),te=require("./extensions/spawnPlan.cjs"),s=require("../asyncJobTracker.cjs"),c=require("./commands/slash/slashCommands.cjs"),l=require("./extensions/config.cjs"),u=require("./extensions/delegationBridge.cjs"),d=require("./extensions/fablePlanBridge.cjs"),f=require("./extensions/orchestratorPrompt.cjs"),ne=require("../suspendedRuns.cjs"),p=require("../runs/registry/sessionLifecycle.cjs"),m=require("./tui/completionNotice.cjs"),re=require("./tui/slashRunNotice.cjs"),h=require("./tui/register.cjs"),ie=require("./collaboration.cjs"),ae=require("./teamRuntime.cjs");let g=require("@agimon-ai/doompi-extension-contracts/child-process"),_=require("@agimon-ai/doompi-extension-contracts/cordis-host"),v=require("@agimon-ai/doompi-extension-contracts/context-contributions"),y=require("@agimon-ai/doompi-extension-contracts/mcp-tool-resolver"),oe=require("@agimon-ai/doompi-extension-contracts/readiness"),b=require("@agimon-ai/doompi-extension-contracts/ui-hub"),se=require("@agimon-ai/doompi-telemetry");const x=`unknown`,S=`@agimon-ai/doompi-team`;function ce(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function le(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`;return`${e.packageId} initialization ${e.state}: ${n}`}function ue(e,t,n,r,i,a){return{spawnPlanner:n,tracker:r,scheduler:i,discovery:e,skills:t,management:a,loadConfig:()=>l.loadConfig().config}}function de(e,t){if(t.kind===`requested`){let{event:n}=t;e.recordEvent(`doom_team.delegation_requested`,{"agent.name":n.agent,"task.id":n.taskId,mode:n.runMode??`foreground`})}else if(t.kind===`started`){let{event:n}=t;e.recordEvent(`doom_team.child_started`,{"run.id":n.runId,outcome:`started`})}else if(t.kind===`updated`){let{event:n}=t;e.recordEvent(`doom_team.delegation_updated`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,"team.token_count":n.tokens??0,outcome:n.status??`updated`})}else if(t.kind===`finished`){let{event:n}=t;e.recordEvent(`doom_team.delegation_finished`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,outcome:n.status})}else e.recordEvent(`doom_team.delegation_cancelled`,{outcome:`cancelled`})}function C(C,w){let T,fe=e=>(T??=(0,se.createDoomTelemetry)({serviceName:`doom-team`,packageName:`@agimon-ai/doompi-team`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),T),pe=(e,t)=>{T?.recordEvent(e,t)};C.effect(()=>async()=>{await T?.shutdown(),T=void 0},`@agimon-ai/doompi-team/telemetry`);let E=ae.createTeamExtensionRuntime(pe),{subagentTool:me,teamChannel:D,pollScheduler:O,asyncJobTracker:k,subagentWaiter:A,asyncSubagentSpawner:he,discovery:j,skills:M,spawnPlanner:N,completionNotifier:P,resultWatcher:F,staleRunReconciler:I,management:L,capabilityPolicies:R,mcpToolResolver:ge}=E,z=!0,B=0,V,H,U,W,G,K;C.inject([_.DOOM_CORDIS_SESSION_SERVICE],e=>{let t={cordis:e,service:e.get(_.DOOM_CORDIS_SESSION_SERVICE)};K=t,e.inject([y.DOOM_MCP_TOOL_RESOLVER_SERVICE],e=>ge.bind((0,y.requireDoomMcpToolResolver)(e))),e.inject([v.DOOM_CONTEXT_CONTRIBUTIONS_SERVICE],e=>{let t=(0,v.requireDoomContextContributions)(e).register({source:S,id:`runtime`,label:`Team`,order:200,snapshot:()=>i.formatTeamContextSnapshot(i.readActiveTeamSnapshot())});return()=>t.dispose()}),e.effect(()=>()=>{K===t&&(K=void 0)},`${S}/cordis-session`)});let _e=e=>{let t=K,n=e.sessionManager.getSessionId();return(t?.service.context.sessionManager===e.sessionManager&&t.service.sessionId===n?(0,oe.readDoomReadinessCoordinator)(t.cordis):void 0)||(W??=(0,oe.createDoomReadinessCoordinator)({notify:e=>{let t=le(e);G?.hasUI?G.ui.notify(t,`warning`):process.emitWarning(t)}}),W)},q=async(e,t)=>{t?.throwIfAborted();let n=H;if(!n)throw Error(`Doom Team session initialization has not started.`);if(U!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale Pi session.`);let r=await n;if(t?.throwIfAborted(),await r.wait(t?{signal:t}:void 0),!z||n!==H||U!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale extension generation.`)},J=new Proxy(w,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await q(a,r),t.execute(e,n,r,i,a))})}:t===`registerCommand`?(t,n)=>{e.registerCommand(t,{...n,handler:async(e,t)=>(await q(t),n.handler(e,t))})}:t===`registerShortcut`?(t,n)=>{e.registerShortcut(t,{...n,handler:async e=>(await q(e),n.handler(e))})}:ce(e,t)}});me.registerTool(J),C.inject([b.DOOM_UI_HUB_SERVICE],e=>h.registerSubagentLeaderContribution((0,b.requireDoomUiHub)(e)));let Y=D.createRuntime(J);C.effect(()=>()=>Y.dispose(),`@agimon-ai/doompi-team/channel`),D.registerClient(J),P.attachHost(w),C.effect(()=>()=>P.dispose(),`@agimon-ai/doompi-team/completions`),O.start(),C.effect(()=>()=>O.stop(),`@agimon-ai/doompi-team/polling`),k.start(),C.effect(()=>()=>k.stop(),`@agimon-ai/doompi-team/jobs`);let X,Z,Q,ve=u.createDelegationBridge({planner:N,management:L,waiter:A,scheduler:O,tracker:k,loadConfig:()=>l.loadConfig().config}),ye=d.createFablePlanBridge({spawner:he,waiter:A,management:L,policies:R,report:(e,t)=>{T?.recordEvent(e,t)}});C.effect(()=>async()=>{await X?.dispose(),X=void 0},`@agimon-ai/doompi-team/collaboration`),C.effect(()=>()=>{F.stop(),Q=void 0},`@agimon-ai/doompi-team/result-watcher`);let be=O.register({id:`stale-run-reconciler`,intervalMs:2e3,run:async()=>{let e=!1,t=!1;for(let n of Z?.list()??[]){if(n.status&&s.TERMINAL_ASYNC_JOB_STATES.has(n.status))continue;t=!0;let{reconcile:r,sweep:i}=I.reconcileAndSweepAsync?await I.reconcileAndSweepAsync(n.runId):I.reconcileAndSweep(n.runId);r.repaired&&(Z?.track(n.runId),e=!0),i.recovered.length>0&&(e=!0)}return e||t}});C.effect(()=>be,`@agimon-ai/doompi-team/stale-runs`),w.on(`before_agent_start`,e=>{if(!(!z||!f.shouldInjectOrchestratorPrompt(l.loadConfig().config)))return{systemPrompt:f.appendOrchestratorPrompt(e.systemPrompt)}});let $={baseCwd:void 0},xe=(r,i)=>{if((0,g.resolveRootSessionId)(i)===i)try{n.writeSessionCatalogSnapshot(i,{cwd:r,agents:t.presentCatalog(j.discover(r,`both`).agents),models:e.resolveActiveTeamModelSpecs()??[]})}catch{}};w.on(`before_agent_start`,(e,t)=>{z&&xe(t.cwd,t.sessionManager.getSessionId())});let Se=ue(j,M,N,k,O,L);c.registerSlashCommands(J,$,Se),h.registerAgentListCommand(J,{discovery:j,skills:M,policies:R,launchAgent:(e,t)=>c.startSingleAgentRun(J,e,$,Se,{agent:t.agent,task:t.task,fork:t.context===`fork`})}),h.registerFleetCommand(J,{scheduler:O,tracker:k,management:L}),C.inject([b.DOOM_UI_HUB_SERVICE],e=>h.registerAgentStatus(w,(0,b.requireDoomUiHub)(e),{scheduler:O,tracker:k})),m.registerCompletionRenderer(w),re.registerSlashRunRenderer(w);let Ce=async()=>{let e=[],t=X;X=void 0;try{await t?.dispose()}catch(t){e.push(t)}for(let t of[()=>R.clear(),()=>ve.abandonAll(),()=>ye.abandonAll(),()=>Y.dispose()])try{t()}catch(t){e.push(t)}if(Z=void 0,e.length!==0)return e.length===1&&e[0]instanceof Error?e[0]:AggregateError(e,`Team session cleanup failed.`)};w.on(`session_start`,(e,t)=>{if(!z)return;let n=++B,i=H;V?.abort(Error(`Doom Team session initialization was superseded.`));let s=new AbortController;V=s,U=t.sessionManager,G=t;let c=fe(t);c.recordEvent(`doom_team.session_started`,{"team.root_session":(0,g.resolveRootSessionId)(t.sessionManager.getSessionId())===t.sessionManager.getSessionId()});let l=t.sessionManager.getSessionId(),u=r.createSessionScope((0,g.resolveRootSessionId)(l));r.setCurrentSessionScope(u),$.baseCwd=t.cwd,xe(t.cwd,l),Q!==void 0&&Q!==u.scopeKey&&(F.stop(),Q=void 0);let d=Ce(),f=_e(t),m=()=>z&&n===B&&U===t.sessionManager&&!s.signal.aborted;H=(async()=>{if(i&&await Promise.allSettled([i.then(e=>e.wait())]),!m())throw Error(`Doom Team session initialization was superseded.`);return f.start(S,`${l}:${n}`,async e=>{e.throwIfAborted();let n=await d;if(n)throw n;if(!m()||(await ee.writeScopeOwnerAsync(u),e.throwIfAborted(),!m()))return{value:void 0};let r=await p.openScopeAsync(u,{readStatusAsync:async e=>L.statusAsync?(await L.statusAsync(e)).status:L.status(e).status});if(e.throwIfAborted(),!m())return{value:void 0};if(r.suspended.length>0&&t.hasUI){let n=await ne.formatSuspendedRunsAsync(r.suspended);if(e.throwIfAborted(),!m())return{value:void 0};t.ui.notify(n,`info`)}c.recordEvent(`doom_team.runs_restored`,{"team.suspended_count":r.suspended.length,"team.pruned_count":r.pruned.length,"team.reaped_count":r.reaped,outcome:`reported`}),Z=k.forSession(l),Q!==u.scopeKey&&(Q=u.scopeKey,F.start(async e=>{if(!Z?.get(e.runId))return!1;let t=await P.deliver(e);return t&&k.acknowledgeHandoff(l,e.runId),t}));let i=o.normalizeParentModel(t.model),s=K,f=(s?.service.context.sessionManager===t.sessionManager?s.cordis:C).plugin(ie.teamCollaborationPlugin,{session:{sessionId:l,cwd:t.cwd,availableModels:a.authenticatedModelInfos(t.modelRegistry),...i?{parentModel:i}:{},captureForkSource:()=>te.captureSessionForkSource(t.sessionManager,`tool`)},directRunTracker:k,delegation:ve,fablePlan:ye,policies:R,observeDelegation:e=>de(c,e)});return X=f,await f.await(),m()?(Y.bindMainSession(l),{value:void 0}):(await f.dispose(),X===f&&(X=void 0),{value:void 0})})})(),Promise.allSettled([H.then(e=>e.wait())])}),C.effect(()=>async()=>{z=!1,B+=1;let e=H;H=void 0,U=void 0,V?.abort(Error(`Doom Team runtime was disposed.`)),V=void 0;let t=W;W=void 0,t?await t.dispose():e&&await Promise.allSettled([e.then(e=>e.wait())]),G=void 0},`@agimon-ai/doompi-team/session-fence`);let we,Te=async e=>{z=!1,B+=1;let t=H;H=void 0,U=void 0,V?.abort(Error(`Doom Team session ended.`)),V=void 0;let n=W;W=void 0,await Promise.allSettled([...t?[t.then(e=>e.wait())]:[],...n?[n.dispose()]:[]]);let i=r.tryCurrentSessionScope(),a=i?p.suspendScopeRuns({scope:i,reason:e.reason??x,readStatus:e=>L.status(e).status}):Promise.resolve(void 0),o;try{o=await a,await T?.recordEvent(`doom_team.session_finished`,{reason:e.reason??x,"team.suspended_count":o?.suspended.length??0,"team.unstoppable_count":o?.unstoppable.length??0,outcome:o?.unstoppable.length?`degraded`:`suspended`})}catch(t){await T?.recordError(`doom_team.session_shutdown_failed`,t,{reason:e.reason??x})}finally{await T?.shutdown(),T=void 0}};return w.on(`session_shutdown`,e=>(we??=Te(e),we)),E}exports.installTeamRuntime=C;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["loadConfig","createDoomTelemetry","createTeamExtensionRuntime","DOOM_CORDIS_SESSION_SERVICE","DOOM_MCP_TOOL_RESOLVER_SERVICE","requireDoomMcpToolResolver","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","requireDoomContextContributions","formatTeamContextSnapshot","readActiveTeamSnapshot","readDoomReadinessCoordinator","createDoomReadinessCoordinator","DOOM_UI_HUB_SERVICE","registerSubagentLeaderContribution","requireDoomUiHub","createDelegationBridge","createFablePlanBridge","TERMINAL_ASYNC_JOB_STATES","shouldInjectOrchestratorPrompt","appendOrchestratorPrompt","resolveRootSessionId","presentCatalog","resolveActiveTeamModelSpecs","startSingleAgentRun","registerAgentStatus","createSessionScope","writeScopeOwnerAsync","openScopeAsync","formatSuspendedRunsAsync","normalizeParentModel","teamCollaborationPlugin","authenticatedModelInfos","captureSessionForkSource","tryCurrentSessionScope","suspendScopeRuns"],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["/**\n * Install the Team feature into one package-local Cordis root.\n *\n * The standard Pi factory owns that root and disposes it when Pi emits\n * `session_shutdown`. Each factory invocation therefore creates fresh session\n * state; no process-global container or replacement-root handshake participates\n * in reload. Long-lived services are registered as Cordis effects, and stale\n * asynchronous session-start continuations are fenced by a generation token.\n */\n\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport {\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_MCP_TOOL_RESOLVER_SERVICE,\n requireDoomMcpToolResolver,\n} from '@agimon-ai/doompi-extension-contracts/mcp-tool-resolver';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context, Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n registerSlashCommands,\n startSingleAgentRun,\n type SlashCommandDeps,\n type SlashCommandState,\n} from './commands/slash/slashCommands';\nimport { loadConfig } from './extensions/config';\nimport { createDelegationBridge } from './extensions/delegationBridge';\nimport { createFablePlanBridge } from './extensions/fablePlanBridge';\nimport type { ManagementActionsContract } from './extensions/managementActions';\nimport { appendOrchestratorPrompt, shouldInjectOrchestratorPrompt } from './extensions/orchestratorPrompt';\nimport { captureSessionForkSource, type SpawnPlannerContract } from './extensions/spawnPlan';\nimport type { SkillDiscoveryContract } from '../agents/skills';\nimport type { AgentDiscoveryContract } from '../agents/types';\nimport { formatTeamContextSnapshot, readActiveTeamSnapshot } from '../api/teamSnapshot';\nimport {\n type AsyncJobTrackerContract,\n type TrackedAsyncJobsContract,\n TERMINAL_ASYNC_JOB_STATES,\n} from '../asyncJobTracker';\nimport { openScopeAsync, suspendScopeRuns } from '../runs/registry/sessionLifecycle';\nimport { formatSuspendedRunsAsync } from '../suspendedRuns';\nimport { normalizeParentModel } from '../runs/shared/modelFallback';\nimport { authenticatedModelInfos } from '../../services/models/modelResolution';\nimport { createSessionScope, setCurrentSessionScope, tryCurrentSessionScope } from '../filesystem/paths';\nimport type { PollSchedulerContract } from '../pollScheduler';\nimport { writeScopeOwnerAsync } from '../scopeOwner';\nimport { resolveActiveTeamModelSpecs } from '../agents/discovery';\nimport { writeSessionCatalogSnapshot } from '../sessionCatalogSnapshot';\nimport { presentCatalog } from '../../services/webSubagentCatalog';\nimport { registerCompletionRenderer } from './tui/completionNotice';\nimport { registerSlashRunRenderer } from './tui/slashRunNotice';\nimport {\n registerAgentListCommand,\n registerAgentStatus,\n registerFleetCommand,\n registerSubagentLeaderContribution,\n} from './tui/register';\nimport { teamCollaborationPlugin, type TeamDelegationObservation } from './collaboration';\nimport { createTeamExtensionRuntime, type TeamExtensionRuntime } from './teamRuntime';\n\n/** See the subscriber's own comment for why crash detection does not need the 250ms floor. */\nconst STALE_RUN_RECONCILE_INTERVAL_MS = 2_000;\nconst SESSION_SHUTDOWN_REASON_FALLBACK = 'unknown';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-team';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\n\nfunction boundPiValue(target: ExtensionAPI, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction readinessNotificationMessage(notification: DoomReadinessNotification): string {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n return `${notification.packageId} initialization ${notification.state}: ${detail}`;\n}\n\nfunction buildSlashCommandDeps(\n discovery: AgentDiscoveryContract,\n skills: SkillDiscoveryContract,\n planner: SpawnPlannerContract,\n tracker: AsyncJobTrackerContract,\n scheduler: PollSchedulerContract,\n management: ManagementActionsContract,\n): SlashCommandDeps {\n return {\n spawnPlanner: planner,\n tracker,\n scheduler,\n discovery,\n skills,\n management,\n loadConfig: () => loadConfig().config,\n };\n}\n\nfunction recordDelegationObservation(telemetry: DoomTelemetry, observation: TeamDelegationObservation): void {\n if (observation.kind === 'requested') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_requested', {\n 'agent.name': event.agent,\n 'task.id': event.taskId,\n mode: event.runMode ?? 'foreground',\n });\n } else if (observation.kind === 'started') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.child_started', { 'run.id': event.runId, outcome: 'started' });\n } else if (observation.kind === 'updated') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_updated', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n 'team.token_count': event.tokens ?? 0,\n outcome: event.status ?? 'updated',\n });\n } else if (observation.kind === 'finished') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_finished', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n outcome: event.status,\n });\n } else {\n void telemetry.recordEvent('doom_team.delegation_cancelled', { outcome: 'cancelled' });\n }\n}\n\n/**\n * The extension factory pi's loader calls, exported as the module default\n * (see the override this file carries in `vibe-lint.config.yaml`). This is an\n * ESM `.ts` module, so it CANNOT use the `export =` form that\n * `subagentPromptRuntimeEntry.cts` is required to use - that file is `.cts`\n * and is resolved by raw path, this one is resolved as a package subpath and\n * matches the `export default` convention every sibling extension in this repo\n * already uses (`doom-task`, `doom-file-edit`, `doom-pi-ui`).\n */\nexport function installTeamRuntime(cordis: Context, pi: ExtensionAPI): TeamExtensionRuntime {\n let telemetry: DoomTelemetry | undefined;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-team',\n packageName: '@agimon-ai/doompi-team',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const reportConcurrencyEvent = (event: string, attributes: Record<string, unknown>): void => {\n void telemetry?.recordEvent(event, attributes);\n };\n cordis.effect(\n () => async () => {\n await telemetry?.shutdown();\n telemetry = undefined;\n },\n '@agimon-ai/doompi-team/telemetry',\n );\n const runtime = createTeamExtensionRuntime(reportConcurrencyEvent);\n const {\n subagentTool,\n teamChannel,\n pollScheduler,\n asyncJobTracker,\n subagentWaiter,\n asyncSubagentSpawner,\n discovery,\n skills,\n spawnPlanner,\n completionNotifier,\n resultWatcher,\n staleRunReconciler,\n management,\n capabilityPolicies,\n mcpToolResolver,\n } = runtime;\n\n let active = true;\n let sessionGeneration = 0;\n let readinessAbort: AbortController | undefined;\n let readinessHandle: Promise<DoomReadinessHandle<void>> | undefined;\n let readinessSessionManager: object | undefined;\n let standaloneReadiness: DoomReadinessCoordinator | undefined;\n let readinessNotificationContext: ExtensionContext | undefined;\n let activeCordisSession:\n | {\n readonly cordis: Context;\n readonly service: DoomCordisSessionService;\n }\n | undefined;\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const service = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const binding = { cordis: sessionContext, service };\n activeCordisSession = binding;\n sessionContext.inject([DOOM_MCP_TOOL_RESOLVER_SERVICE], (resolverContext) =>\n mcpToolResolver.bind(requireDoomMcpToolResolver(resolverContext)),\n );\n sessionContext.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contributionContext) => {\n const registration = requireDoomContextContributions(contributionContext).register({\n source: PACKAGE_SOURCE,\n id: 'runtime',\n label: 'Team',\n order: 200,\n snapshot: () => formatTeamContextSnapshot(readActiveTeamSnapshot()),\n });\n return () => registration.dispose();\n });\n sessionContext.effect(\n () => () => {\n if (activeCordisSession === binding) activeCordisSession = undefined;\n },\n `${PACKAGE_SOURCE}/cordis-session`,\n );\n });\n const coordinatorFor = (ctx: ExtensionContext): DoomReadinessCoordinator => {\n const cordisSession = activeCordisSession;\n const sessionId = ctx.sessionManager.getSessionId();\n const activeSessionMatches =\n cordisSession?.service.context.sessionManager === ctx.sessionManager &&\n cordisSession.service.sessionId === sessionId;\n const shared = activeSessionMatches ? readDoomReadinessCoordinator(cordisSession.cordis) : undefined;\n if (shared) return shared;\n standaloneReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n const message = readinessNotificationMessage(notification);\n if (readinessNotificationContext?.hasUI) readinessNotificationContext.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n return standaloneReadiness;\n };\n const waitForSessionReadiness = async (ctx: ExtensionContext, signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n const pending = readinessHandle;\n if (!pending) throw new Error('Doom Team session initialization has not started.');\n if (readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale Pi session.');\n }\n const handle = await pending;\n signal?.throwIfAborted();\n await handle.wait(signal ? { signal } : undefined);\n if (!active || pending !== readinessHandle || readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale extension generation.');\n }\n };\n const readinessPi = new Proxy(pi, {\n get(target, property) {\n if (property === 'registerTool') {\n return (tool: RegisteredTool): void => {\n target.registerTool({\n ...tool,\n execute: async (toolCallId, params, signal, onUpdate, ctx) => {\n await waitForSessionReadiness(ctx, signal);\n return tool.execute(toolCallId, params, signal, onUpdate, ctx);\n },\n });\n };\n }\n if (property === 'registerCommand') {\n return (name: string, options: CommandOptions): void => {\n target.registerCommand(name, {\n ...options,\n handler: async (args, ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(args, ctx);\n },\n });\n };\n }\n if (property === 'registerShortcut') {\n return (shortcut: Parameters<ExtensionAPI['registerShortcut']>[0], options: ShortcutOptions): void => {\n target.registerShortcut(shortcut, {\n ...options,\n handler: async (ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(ctx);\n },\n });\n };\n }\n return boundPiValue(target, property);\n },\n });\n\n subagentTool.registerTool(readinessPi);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => registerSubagentLeaderContribution(requireDoomUiHub(uiContext)));\n\n const teamRuntime = teamChannel.createRuntime(readinessPi);\n cordis.effect(() => () => teamRuntime.dispose(), '@agimon-ai/doompi-team/channel');\n teamChannel.registerClient(readinessPi);\n\n // Attach before `ResultWatcher.start()`, which `session_start` drives.\n completionNotifier.attachHost(pi);\n cordis.effect(() => () => completionNotifier.dispose(), '@agimon-ai/doompi-team/completions');\n\n pollScheduler.start();\n cordis.effect(() => () => pollScheduler.stop(), '@agimon-ai/doompi-team/polling');\n\n asyncJobTracker.start();\n cordis.effect(() => () => asyncJobTracker.stop(), '@agimon-ai/doompi-team/jobs');\n\n let collaborationFiber: Fiber | undefined;\n let currentSessionJobs: TrackedAsyncJobsContract | undefined;\n // The scope `ResultWatcher` is currently watching. See its start site in\n // the `session_start` handler for why the watcher is latched on this\n // rather than started once.\n let watchedScopeKey: string | undefined;\n const delegationBridge = createDelegationBridge({\n planner: spawnPlanner,\n management,\n waiter: subagentWaiter,\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const fablePlanBridge = createFablePlanBridge({\n spawner: asyncSubagentSpawner,\n waiter: subagentWaiter,\n management,\n policies: capabilityPolicies,\n report: (event, attributes) => {\n void telemetry?.recordEvent(event, attributes);\n },\n });\n cordis.effect(\n () => async () => {\n await collaborationFiber?.dispose();\n collaborationFiber = undefined;\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n cordis.effect(\n () => () => {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n },\n '@agimon-ai/doompi-team/result-watcher',\n );\n\n const unregisterStaleRunReconciler = pollScheduler.register({\n id: 'stale-run-reconciler',\n // Crash detection, not status freshness - `AsyncJobTracker` owns that and\n // polls on its own 250ms. This subscriber only ever acts on a run whose\n // process is provably dead, so noticing that 2s later is invisible, while\n // running it 4x/second per active run was not: each pass is a status read,\n // a run-id resolve and a pid probe that a healthy child always discards.\n intervalMs: STALE_RUN_RECONCILE_INTERVAL_MS,\n run: async () => {\n let repaired = false;\n let hasActiveRun = false;\n for (const job of currentSessionJobs?.list() ?? []) {\n if (job.status && TERMINAL_ASYNC_JOB_STATES.has(job.status)) continue;\n hasActiveRun = true;\n // One pass, one `inspect()`. Calling `reconcile` and\n // `sweepOrphanedClaims` separately probed the same pid twice for the\n // same answer - see `StaleRunReconcilerContract.reconcileAndSweep`.\n const { reconcile: outcome, sweep } = staleRunReconciler.reconcileAndSweepAsync\n ? await staleRunReconciler.reconcileAndSweepAsync(job.runId)\n : staleRunReconciler.reconcileAndSweep(job.runId);\n if (outcome.repaired) {\n currentSessionJobs?.track(job.runId);\n repaired = true;\n }\n if (sweep.recovered.length > 0) repaired = true;\n }\n // An active runner is useful work for scheduling purposes: keeping\n // the shared scheduler at its floor prevents crash detection from\n // inheriting the idle five-second backoff ceiling.\n return repaired || hasActiveRun;\n },\n });\n cordis.effect(() => unregisterStaleRunReconciler, '@agimon-ai/doompi-team/stale-runs');\n // The orchestration addendum. Registered here rather than inside the\n // `subagent` tool's description because it is behavioural guidance, and it\n // has to land before the model decides whether to reach for the tool at\n // all - see `orchestratorPrompt.ts`. Read config per turn so toggling it\n // does not need a session restart; `loadConfig` is memoized on the file's\n // own mtime, so this is a stat, not a read.\n pi.on('before_agent_start', (event: { systemPrompt?: string }) => {\n if (!active || !shouldInjectOrchestratorPrompt(loadConfig().config)) return undefined;\n return { systemPrompt: appendOrchestratorPrompt(event.systemPrompt) };\n });\n\n const state: SlashCommandState = { baseCwd: undefined };\n\n /**\n * Publish what this session can launch, for the cockpit hub to read.\n *\n * The hub and the session API run in `doompi-server`, which never receives\n * the domain projection's agent directories or the harness state behind the\n * Team model policy, so discovery run there misses every domain-provided\n * agent. This process has both. Republished on each turn so an agent file\n * added mid-session shows up without a restart.\n */\n const publishCatalogSnapshot = (cwd: string, sessionId: string): void => {\n // A spawned child shares its parent's scope directory; only the root\n // session may write the catalog the user is looking at.\n if (resolveRootSessionId(sessionId) !== sessionId) return;\n try {\n writeSessionCatalogSnapshot(sessionId, {\n cwd,\n agents: presentCatalog(discovery.discover(cwd, 'both').agents),\n models: resolveActiveTeamModelSpecs() ?? [],\n });\n } catch {\n // Best effort: the hub falls back to its own discovery, and every launch\n // path reports discovery failures on its own.\n }\n };\n\n pi.on('before_agent_start', (_event: { systemPrompt?: string }, ctx: ExtensionContext) => {\n if (!active) return undefined;\n publishCatalogSnapshot(ctx.cwd, ctx.sessionManager.getSessionId());\n return undefined;\n });\n const slashCommandDeps = buildSlashCommandDeps(\n discovery,\n skills,\n spawnPlanner,\n asyncJobTracker,\n pollScheduler,\n management,\n );\n registerSlashCommands(readinessPi, state, slashCommandDeps);\n registerAgentListCommand(readinessPi, {\n discovery,\n skills,\n policies: capabilityPolicies,\n // The catalog's r/R keys spawn through the same path as `/run`, so a run\n // started from the overlay is tracked and reported like any other.\n launchAgent: (ctx, request) =>\n startSingleAgentRun(readinessPi, ctx, state, slashCommandDeps, {\n agent: request.agent,\n task: request.task,\n fork: request.context === 'fork',\n }),\n });\n registerFleetCommand(readinessPi, {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n management,\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n return registerAgentStatus(pi, requireDoomUiHub(uiContext), {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n });\n });\n registerCompletionRenderer(pi);\n registerSlashRunRenderer(pi);\n\n const retireSessionBindings = async (): Promise<Error | undefined> => {\n const errors: unknown[] = [];\n const previousCollaboration = collaborationFiber;\n collaborationFiber = undefined;\n try {\n await previousCollaboration?.dispose();\n } catch (error) {\n errors.push(error);\n }\n for (const cleanup of [\n () => capabilityPolicies.clear(),\n () => delegationBridge.abandonAll(),\n () => fablePlanBridge.abandonAll(),\n () => teamRuntime.dispose(),\n ]) {\n try {\n cleanup();\n } catch (error) {\n errors.push(error);\n }\n }\n currentSessionJobs = undefined;\n if (errors.length === 0) return undefined;\n if (errors.length === 1 && errors[0] instanceof Error) return errors[0];\n return new AggregateError(errors, 'Team session cleanup failed.');\n };\n\n // Bind the root session directly into the one intercom transport. Child\n // processes bind from the team environment written during spawn.\n pi.on('session_start', (_event, ctx: ExtensionContext) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n const previousReadiness = readinessHandle;\n readinessAbort?.abort(new Error('Doom Team session initialization was superseded.'));\n const ownReadinessAbort = new AbortController();\n readinessAbort = ownReadinessAbort;\n readinessSessionManager = ctx.sessionManager;\n readinessNotificationContext = ctx;\n const activeTelemetry = getTelemetry(ctx);\n void activeTelemetry.recordEvent('doom_team.session_started', {\n 'team.root_session':\n resolveRootSessionId(ctx.sessionManager.getSessionId()) === ctx.sessionManager.getSessionId(),\n });\n // Before anything else: every scoped path helper reads this. A child\n // keeps the root inherited from its spawn environment, while a top-level\n // session falls back to its own Pi session id.\n const sessionId = ctx.sessionManager.getSessionId();\n const scope = createSessionScope(resolveRootSessionId(sessionId));\n setCurrentSessionScope(scope);\n state.baseCwd = ctx.cwd;\n publishCatalogSnapshot(ctx.cwd, sessionId);\n if (watchedScopeKey !== undefined && watchedScopeKey !== scope.scopeKey) {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n }\n const retirement = retireSessionBindings();\n const coordinator = coordinatorFor(ctx);\n const ownsSession = (): boolean =>\n active &&\n ownGeneration === sessionGeneration &&\n readinessSessionManager === ctx.sessionManager &&\n !ownReadinessAbort.signal.aborted;\n\n readinessHandle = (async (): Promise<DoomReadinessHandle<void>> => {\n if (previousReadiness) {\n await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);\n }\n if (!ownsSession()) throw new Error('Doom Team session initialization was superseded.');\n return coordinator.start(PACKAGE_SOURCE, `${sessionId}:${ownGeneration}`, async (signal) => {\n signal.throwIfAborted();\n const retirementError = await retirement;\n if (retirementError) throw retirementError;\n if (!ownsSession()) return { value: undefined };\n // Claim the scope for this process, so a later sweep can tell an\n // abandoned tree from a live one. Rewritten on every session_start\n // because /resume legitimately re-adopts a scope under a new process.\n await writeScopeOwnerAsync(scope);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n // Prune records whose process is gone, reap sibling scopes holding\n // nothing alive, and REPORT what is suspended. Nothing is restarted:\n // reopening a session must not silently spend tokens on stale work.\n const opened = await openScopeAsync(scope, {\n readStatusAsync: async (runId) =>\n management.statusAsync ? (await management.statusAsync(runId)).status : management.status(runId).status,\n });\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n if (opened.suspended.length > 0 && ctx.hasUI) {\n const message = await formatSuspendedRunsAsync(opened.suspended);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n ctx.ui.notify(message, 'info');\n }\n void activeTelemetry.recordEvent('doom_team.runs_restored', {\n 'team.suspended_count': opened.suspended.length,\n 'team.pruned_count': opened.pruned.length,\n 'team.reaped_count': opened.reaped,\n outcome: 'reported',\n });\n currentSessionJobs = asyncJobTracker.forSession(sessionId);\n // ResultWatcher needs the session scope, but a repeated start for the\n // same scope must not reset its in-flight claims.\n if (watchedScopeKey !== scope.scopeKey) {\n watchedScopeKey = scope.scopeKey;\n resultWatcher.start((result) =>\n currentSessionJobs?.get(result.runId) ? completionNotifier.deliver(result) : false,\n );\n }\n const parentModel = normalizeParentModel(ctx.model);\n const activeSession = activeCordisSession;\n const collaborationParent =\n activeSession?.service.context.sessionManager === ctx.sessionManager ? activeSession.cordis : cordis;\n const nextCollaboration = collaborationParent.plugin(teamCollaborationPlugin, {\n session: {\n sessionId,\n cwd: ctx.cwd,\n availableModels: authenticatedModelInfos(ctx.modelRegistry),\n ...(parentModel ? { parentModel } : {}),\n captureForkSource: () => captureSessionForkSource(ctx.sessionManager, 'tool'),\n },\n delegation: delegationBridge,\n fablePlan: fablePlanBridge,\n policies: capabilityPolicies,\n observeDelegation: (observation) => recordDelegationObservation(activeTelemetry, observation),\n });\n collaborationFiber = nextCollaboration;\n await nextCollaboration.await();\n if (!ownsSession()) {\n await nextCollaboration.dispose();\n if (collaborationFiber === nextCollaboration) collaborationFiber = undefined;\n return { value: undefined };\n }\n teamRuntime.bindMainSession(sessionId);\n return { value: undefined };\n });\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void Promise.allSettled([readinessHandle.then((handle) => handle.wait())]);\n return undefined;\n });\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team runtime was disposed.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n if (ownedReadiness) await ownedReadiness.dispose();\n else if (pendingReadiness) await Promise.allSettled([pendingReadiness.then((handle) => handle.wait())]);\n readinessNotificationContext = undefined;\n },\n '@agimon-ai/doompi-team/session-fence',\n );\n\n /**\n * Every reason suspends, with no exception for `reload`.\n *\n * Pi emits this for `quit`, `reload`, `new`, `resume` and `fork`. Treating\n * `reload` differently - keeping children alive because `/domains`, `/major-mode`\n * and `/profile` only reload the extension - would be the one case someone\n * has to remember, and the one that silently misbehaves if Pi adds a sixth\n * reason. Suspending uniformly costs a respawn on reload; restore continues\n * the child's own transcript rather than restarting its task.\n */\n let shutdownPromise: Promise<void> | undefined;\n const shutdownSession = async (event: { reason?: string }): Promise<void> => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team session ended.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n await Promise.allSettled([\n ...(pendingReadiness ? [pendingReadiness.then((handle) => handle.wait())] : []),\n ...(ownedReadiness ? [ownedReadiness.dispose()] : []),\n ]);\n const scope = tryCurrentSessionScope();\n const suspensionPromise = scope\n ? suspendScopeRuns({\n scope,\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n readStatus: (runId) => management.status(runId).status,\n })\n : Promise.resolve(undefined);\n let suspension: Awaited<ReturnType<typeof suspendScopeRuns>> | undefined;\n try {\n suspension = await suspensionPromise;\n await telemetry?.recordEvent('doom_team.session_finished', {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n 'team.suspended_count': suspension?.suspended.length ?? 0,\n 'team.unstoppable_count': suspension?.unstoppable.length ?? 0,\n outcome: suspension?.unstoppable.length ? 'degraded' : 'suspended',\n });\n } catch (error) {\n await telemetry?.recordError('doom_team.session_shutdown_failed', error, {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n });\n } finally {\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n };\n pi.on('session_shutdown', (event: { reason?: string }) => {\n shutdownPromise ??= shutdownSession(event);\n return shutdownPromise;\n });\n\n return runtime;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["loadConfig","createDoomTelemetry","createTeamExtensionRuntime","DOOM_CORDIS_SESSION_SERVICE","DOOM_MCP_TOOL_RESOLVER_SERVICE","requireDoomMcpToolResolver","DOOM_CONTEXT_CONTRIBUTIONS_SERVICE","requireDoomContextContributions","formatTeamContextSnapshot","readActiveTeamSnapshot","readDoomReadinessCoordinator","createDoomReadinessCoordinator","DOOM_UI_HUB_SERVICE","registerSubagentLeaderContribution","requireDoomUiHub","createDelegationBridge","createFablePlanBridge","TERMINAL_ASYNC_JOB_STATES","shouldInjectOrchestratorPrompt","appendOrchestratorPrompt","resolveRootSessionId","presentCatalog","resolveActiveTeamModelSpecs","startSingleAgentRun","registerAgentStatus","createSessionScope","writeScopeOwnerAsync","openScopeAsync","formatSuspendedRunsAsync","jobs","normalizeParentModel","teamCollaborationPlugin","authenticatedModelInfos","captureSessionForkSource","tryCurrentSessionScope","suspendScopeRuns"],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["/**\n * Install the Team feature into one package-local Cordis root.\n *\n * The standard Pi factory owns that root and disposes it when Pi emits\n * `session_shutdown`. Each factory invocation therefore creates fresh session\n * state; no process-global container or replacement-root handshake participates\n * in reload. Long-lived services are registered as Cordis effects, and stale\n * asynchronous session-start continuations are fenced by a generation token.\n */\n\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport {\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_MCP_TOOL_RESOLVER_SERVICE,\n requireDoomMcpToolResolver,\n} from '@agimon-ai/doompi-extension-contracts/mcp-tool-resolver';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context, Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n registerSlashCommands,\n startSingleAgentRun,\n type SlashCommandDeps,\n type SlashCommandState,\n} from './commands/slash/slashCommands';\nimport { loadConfig } from './extensions/config';\nimport { createDelegationBridge } from './extensions/delegationBridge';\nimport { createFablePlanBridge } from './extensions/fablePlanBridge';\nimport type { ManagementActionsContract } from './extensions/managementActions';\nimport { appendOrchestratorPrompt, shouldInjectOrchestratorPrompt } from './extensions/orchestratorPrompt';\nimport { captureSessionForkSource, type SpawnPlannerContract } from './extensions/spawnPlan';\nimport type { SkillDiscoveryContract } from '../agents/skills';\nimport type { AgentDiscoveryContract } from '../agents/types';\nimport { formatTeamContextSnapshot, readActiveTeamSnapshot } from '../api/teamSnapshot';\nimport {\n type AsyncJobTrackerContract,\n type TrackedAsyncJobsContract,\n TERMINAL_ASYNC_JOB_STATES,\n} from '../asyncJobTracker';\nimport { openScopeAsync, suspendScopeRuns } from '../runs/registry/sessionLifecycle';\nimport { formatSuspendedRunsAsync } from '../suspendedRuns';\nimport { normalizeParentModel } from '../runs/shared/modelFallback';\nimport { authenticatedModelInfos } from '../../services/models/modelResolution';\nimport { createSessionScope, setCurrentSessionScope, tryCurrentSessionScope } from '../filesystem/paths';\nimport type { PollSchedulerContract } from '../pollScheduler';\nimport { writeScopeOwnerAsync } from '../scopeOwner';\nimport { resolveActiveTeamModelSpecs } from '../agents/discovery';\nimport { writeSessionCatalogSnapshot } from '../sessionCatalogSnapshot';\nimport { presentCatalog } from '../../services/webSubagentCatalog';\nimport { registerCompletionRenderer } from './tui/completionNotice';\nimport { registerSlashRunRenderer } from './tui/slashRunNotice';\nimport {\n registerAgentListCommand,\n registerAgentStatus,\n registerFleetCommand,\n registerSubagentLeaderContribution,\n} from './tui/register';\nimport { teamCollaborationPlugin, type TeamDelegationObservation } from './collaboration';\nimport { createTeamExtensionRuntime, type TeamExtensionRuntime } from './teamRuntime';\n\n/** See the subscriber's own comment for why crash detection does not need the 250ms floor. */\nconst STALE_RUN_RECONCILE_INTERVAL_MS = 2_000;\nconst SESSION_SHUTDOWN_REASON_FALLBACK = 'unknown';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-team';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\n\nfunction boundPiValue(target: ExtensionAPI, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction readinessNotificationMessage(notification: DoomReadinessNotification): string {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n return `${notification.packageId} initialization ${notification.state}: ${detail}`;\n}\n\nfunction buildSlashCommandDeps(\n discovery: AgentDiscoveryContract,\n skills: SkillDiscoveryContract,\n planner: SpawnPlannerContract,\n tracker: AsyncJobTrackerContract,\n scheduler: PollSchedulerContract,\n management: ManagementActionsContract,\n): SlashCommandDeps {\n return {\n spawnPlanner: planner,\n tracker,\n scheduler,\n discovery,\n skills,\n management,\n loadConfig: () => loadConfig().config,\n };\n}\n\nfunction recordDelegationObservation(telemetry: DoomTelemetry, observation: TeamDelegationObservation): void {\n if (observation.kind === 'requested') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_requested', {\n 'agent.name': event.agent,\n 'task.id': event.taskId,\n mode: event.runMode ?? 'foreground',\n });\n } else if (observation.kind === 'started') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.child_started', { 'run.id': event.runId, outcome: 'started' });\n } else if (observation.kind === 'updated') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_updated', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n 'team.token_count': event.tokens ?? 0,\n outcome: event.status ?? 'updated',\n });\n } else if (observation.kind === 'finished') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_finished', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n outcome: event.status,\n });\n } else {\n void telemetry.recordEvent('doom_team.delegation_cancelled', { outcome: 'cancelled' });\n }\n}\n\n/**\n * The extension factory pi's loader calls, exported as the module default\n * (see the override this file carries in `vibe-lint.config.yaml`). This is an\n * ESM `.ts` module, so it CANNOT use the `export =` form that\n * `subagentPromptRuntimeEntry.cts` is required to use - that file is `.cts`\n * and is resolved by raw path, this one is resolved as a package subpath and\n * matches the `export default` convention every sibling extension in this repo\n * already uses (`doom-task`, `doom-file-edit`, `doom-pi-ui`).\n */\nexport function installTeamRuntime(cordis: Context, pi: ExtensionAPI): TeamExtensionRuntime {\n let telemetry: DoomTelemetry | undefined;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-team',\n packageName: '@agimon-ai/doompi-team',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const reportConcurrencyEvent = (event: string, attributes: Record<string, unknown>): void => {\n void telemetry?.recordEvent(event, attributes);\n };\n cordis.effect(\n () => async () => {\n await telemetry?.shutdown();\n telemetry = undefined;\n },\n '@agimon-ai/doompi-team/telemetry',\n );\n const runtime = createTeamExtensionRuntime(reportConcurrencyEvent);\n const {\n subagentTool,\n teamChannel,\n pollScheduler,\n asyncJobTracker,\n subagentWaiter,\n asyncSubagentSpawner,\n discovery,\n skills,\n spawnPlanner,\n completionNotifier,\n resultWatcher,\n staleRunReconciler,\n management,\n capabilityPolicies,\n mcpToolResolver,\n } = runtime;\n\n let active = true;\n let sessionGeneration = 0;\n let readinessAbort: AbortController | undefined;\n let readinessHandle: Promise<DoomReadinessHandle<void>> | undefined;\n let readinessSessionManager: object | undefined;\n let standaloneReadiness: DoomReadinessCoordinator | undefined;\n let readinessNotificationContext: ExtensionContext | undefined;\n let activeCordisSession:\n | {\n readonly cordis: Context;\n readonly service: DoomCordisSessionService;\n }\n | undefined;\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const service = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const binding = { cordis: sessionContext, service };\n activeCordisSession = binding;\n sessionContext.inject([DOOM_MCP_TOOL_RESOLVER_SERVICE], (resolverContext) =>\n mcpToolResolver.bind(requireDoomMcpToolResolver(resolverContext)),\n );\n sessionContext.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contributionContext) => {\n const registration = requireDoomContextContributions(contributionContext).register({\n source: PACKAGE_SOURCE,\n id: 'runtime',\n label: 'Team',\n order: 200,\n snapshot: () => formatTeamContextSnapshot(readActiveTeamSnapshot()),\n });\n return () => registration.dispose();\n });\n sessionContext.effect(\n () => () => {\n if (activeCordisSession === binding) activeCordisSession = undefined;\n },\n `${PACKAGE_SOURCE}/cordis-session`,\n );\n });\n const coordinatorFor = (ctx: ExtensionContext): DoomReadinessCoordinator => {\n const cordisSession = activeCordisSession;\n const sessionId = ctx.sessionManager.getSessionId();\n const activeSessionMatches =\n cordisSession?.service.context.sessionManager === ctx.sessionManager &&\n cordisSession.service.sessionId === sessionId;\n const shared = activeSessionMatches ? readDoomReadinessCoordinator(cordisSession.cordis) : undefined;\n if (shared) return shared;\n standaloneReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n const message = readinessNotificationMessage(notification);\n if (readinessNotificationContext?.hasUI) readinessNotificationContext.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n return standaloneReadiness;\n };\n const waitForSessionReadiness = async (ctx: ExtensionContext, signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n const pending = readinessHandle;\n if (!pending) throw new Error('Doom Team session initialization has not started.');\n if (readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale Pi session.');\n }\n const handle = await pending;\n signal?.throwIfAborted();\n await handle.wait(signal ? { signal } : undefined);\n if (!active || pending !== readinessHandle || readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale extension generation.');\n }\n };\n const readinessPi = new Proxy(pi, {\n get(target, property) {\n if (property === 'registerTool') {\n return (tool: RegisteredTool): void => {\n target.registerTool({\n ...tool,\n execute: async (toolCallId, params, signal, onUpdate, ctx) => {\n await waitForSessionReadiness(ctx, signal);\n return tool.execute(toolCallId, params, signal, onUpdate, ctx);\n },\n });\n };\n }\n if (property === 'registerCommand') {\n return (name: string, options: CommandOptions): void => {\n target.registerCommand(name, {\n ...options,\n handler: async (args, ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(args, ctx);\n },\n });\n };\n }\n if (property === 'registerShortcut') {\n return (shortcut: Parameters<ExtensionAPI['registerShortcut']>[0], options: ShortcutOptions): void => {\n target.registerShortcut(shortcut, {\n ...options,\n handler: async (ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(ctx);\n },\n });\n };\n }\n return boundPiValue(target, property);\n },\n });\n\n subagentTool.registerTool(readinessPi);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => registerSubagentLeaderContribution(requireDoomUiHub(uiContext)));\n\n const teamRuntime = teamChannel.createRuntime(readinessPi);\n cordis.effect(() => () => teamRuntime.dispose(), '@agimon-ai/doompi-team/channel');\n teamChannel.registerClient(readinessPi);\n\n // Attach before `ResultWatcher.start()`, which `session_start` drives.\n completionNotifier.attachHost(pi);\n cordis.effect(() => () => completionNotifier.dispose(), '@agimon-ai/doompi-team/completions');\n\n pollScheduler.start();\n cordis.effect(() => () => pollScheduler.stop(), '@agimon-ai/doompi-team/polling');\n\n asyncJobTracker.start();\n cordis.effect(() => () => asyncJobTracker.stop(), '@agimon-ai/doompi-team/jobs');\n\n let collaborationFiber: Fiber | undefined;\n let currentSessionJobs: TrackedAsyncJobsContract | undefined;\n // The scope `ResultWatcher` is currently watching. See its start site in\n // the `session_start` handler for why the watcher is latched on this\n // rather than started once.\n let watchedScopeKey: string | undefined;\n const delegationBridge = createDelegationBridge({\n planner: spawnPlanner,\n management,\n waiter: subagentWaiter,\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const fablePlanBridge = createFablePlanBridge({\n spawner: asyncSubagentSpawner,\n waiter: subagentWaiter,\n management,\n policies: capabilityPolicies,\n report: (event, attributes) => {\n void telemetry?.recordEvent(event, attributes);\n },\n });\n cordis.effect(\n () => async () => {\n await collaborationFiber?.dispose();\n collaborationFiber = undefined;\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n cordis.effect(\n () => () => {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n },\n '@agimon-ai/doompi-team/result-watcher',\n );\n\n const unregisterStaleRunReconciler = pollScheduler.register({\n id: 'stale-run-reconciler',\n // Crash detection, not status freshness - `AsyncJobTracker` owns that and\n // polls on its own 250ms. This subscriber only ever acts on a run whose\n // process is provably dead, so noticing that 2s later is invisible, while\n // running it 4x/second per active run was not: each pass is a status read,\n // a run-id resolve and a pid probe that a healthy child always discards.\n intervalMs: STALE_RUN_RECONCILE_INTERVAL_MS,\n run: async () => {\n let repaired = false;\n let hasActiveRun = false;\n for (const job of currentSessionJobs?.list() ?? []) {\n if (job.status && TERMINAL_ASYNC_JOB_STATES.has(job.status)) continue;\n hasActiveRun = true;\n // One pass, one `inspect()`. Calling `reconcile` and\n // `sweepOrphanedClaims` separately probed the same pid twice for the\n // same answer - see `StaleRunReconcilerContract.reconcileAndSweep`.\n const { reconcile: outcome, sweep } = staleRunReconciler.reconcileAndSweepAsync\n ? await staleRunReconciler.reconcileAndSweepAsync(job.runId)\n : staleRunReconciler.reconcileAndSweep(job.runId);\n if (outcome.repaired) {\n currentSessionJobs?.track(job.runId);\n repaired = true;\n }\n if (sweep.recovered.length > 0) repaired = true;\n }\n // An active runner is useful work for scheduling purposes: keeping\n // the shared scheduler at its floor prevents crash detection from\n // inheriting the idle five-second backoff ceiling.\n return repaired || hasActiveRun;\n },\n });\n cordis.effect(() => unregisterStaleRunReconciler, '@agimon-ai/doompi-team/stale-runs');\n // The orchestration addendum. Registered here rather than inside the\n // `subagent` tool's description because it is behavioural guidance, and it\n // has to land before the model decides whether to reach for the tool at\n // all - see `orchestratorPrompt.ts`. Read config per turn so toggling it\n // does not need a session restart; `loadConfig` is memoized on the file's\n // own mtime, so this is a stat, not a read.\n pi.on('before_agent_start', (event: { systemPrompt?: string }) => {\n if (!active || !shouldInjectOrchestratorPrompt(loadConfig().config)) return undefined;\n return { systemPrompt: appendOrchestratorPrompt(event.systemPrompt) };\n });\n\n const state: SlashCommandState = { baseCwd: undefined };\n\n /**\n * Publish what this session can launch, for the cockpit hub to read.\n *\n * The hub and the session API run in `doompi-server`, which never receives\n * the domain projection's agent directories or the harness state behind the\n * Team model policy, so discovery run there misses every domain-provided\n * agent. This process has both. Republished on each turn so an agent file\n * added mid-session shows up without a restart.\n */\n const publishCatalogSnapshot = (cwd: string, sessionId: string): void => {\n // A spawned child shares its parent's scope directory; only the root\n // session may write the catalog the user is looking at.\n if (resolveRootSessionId(sessionId) !== sessionId) return;\n try {\n writeSessionCatalogSnapshot(sessionId, {\n cwd,\n agents: presentCatalog(discovery.discover(cwd, 'both').agents),\n models: resolveActiveTeamModelSpecs() ?? [],\n });\n } catch {\n // Best effort: the hub falls back to its own discovery, and every launch\n // path reports discovery failures on its own.\n }\n };\n\n pi.on('before_agent_start', (_event: { systemPrompt?: string }, ctx: ExtensionContext) => {\n if (!active) return undefined;\n publishCatalogSnapshot(ctx.cwd, ctx.sessionManager.getSessionId());\n return undefined;\n });\n const slashCommandDeps = buildSlashCommandDeps(\n discovery,\n skills,\n spawnPlanner,\n asyncJobTracker,\n pollScheduler,\n management,\n );\n registerSlashCommands(readinessPi, state, slashCommandDeps);\n registerAgentListCommand(readinessPi, {\n discovery,\n skills,\n policies: capabilityPolicies,\n // The catalog's r/R keys spawn through the same path as `/run`, so a run\n // started from the overlay is tracked and reported like any other.\n launchAgent: (ctx, request) =>\n startSingleAgentRun(readinessPi, ctx, state, slashCommandDeps, {\n agent: request.agent,\n task: request.task,\n fork: request.context === 'fork',\n }),\n });\n registerFleetCommand(readinessPi, {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n management,\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n return registerAgentStatus(pi, requireDoomUiHub(uiContext), {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n });\n });\n registerCompletionRenderer(pi);\n registerSlashRunRenderer(pi);\n\n const retireSessionBindings = async (): Promise<Error | undefined> => {\n const errors: unknown[] = [];\n const previousCollaboration = collaborationFiber;\n collaborationFiber = undefined;\n try {\n await previousCollaboration?.dispose();\n } catch (error) {\n errors.push(error);\n }\n for (const cleanup of [\n () => capabilityPolicies.clear(),\n () => delegationBridge.abandonAll(),\n () => fablePlanBridge.abandonAll(),\n () => teamRuntime.dispose(),\n ]) {\n try {\n cleanup();\n } catch (error) {\n errors.push(error);\n }\n }\n currentSessionJobs = undefined;\n if (errors.length === 0) return undefined;\n if (errors.length === 1 && errors[0] instanceof Error) return errors[0];\n return new AggregateError(errors, 'Team session cleanup failed.');\n };\n\n // Bind the root session directly into the one intercom transport. Child\n // processes bind from the team environment written during spawn.\n pi.on('session_start', (_event, ctx: ExtensionContext) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n const previousReadiness = readinessHandle;\n readinessAbort?.abort(new Error('Doom Team session initialization was superseded.'));\n const ownReadinessAbort = new AbortController();\n readinessAbort = ownReadinessAbort;\n readinessSessionManager = ctx.sessionManager;\n readinessNotificationContext = ctx;\n const activeTelemetry = getTelemetry(ctx);\n void activeTelemetry.recordEvent('doom_team.session_started', {\n 'team.root_session':\n resolveRootSessionId(ctx.sessionManager.getSessionId()) === ctx.sessionManager.getSessionId(),\n });\n // Before anything else: every scoped path helper reads this. A child\n // keeps the root inherited from its spawn environment, while a top-level\n // session falls back to its own Pi session id.\n const sessionId = ctx.sessionManager.getSessionId();\n const scope = createSessionScope(resolveRootSessionId(sessionId));\n setCurrentSessionScope(scope);\n state.baseCwd = ctx.cwd;\n publishCatalogSnapshot(ctx.cwd, sessionId);\n if (watchedScopeKey !== undefined && watchedScopeKey !== scope.scopeKey) {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n }\n const retirement = retireSessionBindings();\n const coordinator = coordinatorFor(ctx);\n const ownsSession = (): boolean =>\n active &&\n ownGeneration === sessionGeneration &&\n readinessSessionManager === ctx.sessionManager &&\n !ownReadinessAbort.signal.aborted;\n\n readinessHandle = (async (): Promise<DoomReadinessHandle<void>> => {\n if (previousReadiness) {\n await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);\n }\n if (!ownsSession()) throw new Error('Doom Team session initialization was superseded.');\n return coordinator.start(PACKAGE_SOURCE, `${sessionId}:${ownGeneration}`, async (signal) => {\n signal.throwIfAborted();\n const retirementError = await retirement;\n if (retirementError) throw retirementError;\n if (!ownsSession()) return { value: undefined };\n // Claim the scope for this process, so a later sweep can tell an\n // abandoned tree from a live one. Rewritten on every session_start\n // because /resume legitimately re-adopts a scope under a new process.\n await writeScopeOwnerAsync(scope);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n // Prune records whose process is gone, reap sibling scopes holding\n // nothing alive, and REPORT what is suspended. Nothing is restarted:\n // reopening a session must not silently spend tokens on stale work.\n const opened = await openScopeAsync(scope, {\n readStatusAsync: async (runId) =>\n management.statusAsync ? (await management.statusAsync(runId)).status : management.status(runId).status,\n });\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n if (opened.suspended.length > 0 && ctx.hasUI) {\n const message = await formatSuspendedRunsAsync(opened.suspended);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n ctx.ui.notify(message, 'info');\n }\n void activeTelemetry.recordEvent('doom_team.runs_restored', {\n 'team.suspended_count': opened.suspended.length,\n 'team.pruned_count': opened.pruned.length,\n 'team.reaped_count': opened.reaped,\n outcome: 'reported',\n });\n currentSessionJobs = asyncJobTracker.forSession(sessionId);\n // ResultWatcher needs the session scope, but a repeated start for the\n // same scope must not reset its in-flight claims.\n if (watchedScopeKey !== scope.scopeKey) {\n watchedScopeKey = scope.scopeKey;\n resultWatcher.start(async (result) => {\n const jobs = currentSessionJobs;\n if (!jobs?.get(result.runId)) return false;\n const accepted = await completionNotifier.deliver(result);\n if (accepted) asyncJobTracker.acknowledgeHandoff(sessionId, result.runId);\n return accepted;\n });\n }\n const parentModel = normalizeParentModel(ctx.model);\n const activeSession = activeCordisSession;\n const collaborationParent =\n activeSession?.service.context.sessionManager === ctx.sessionManager ? activeSession.cordis : cordis;\n const nextCollaboration = collaborationParent.plugin(teamCollaborationPlugin, {\n session: {\n sessionId,\n cwd: ctx.cwd,\n availableModels: authenticatedModelInfos(ctx.modelRegistry),\n ...(parentModel ? { parentModel } : {}),\n captureForkSource: () => captureSessionForkSource(ctx.sessionManager, 'tool'),\n },\n directRunTracker: asyncJobTracker,\n delegation: delegationBridge,\n fablePlan: fablePlanBridge,\n policies: capabilityPolicies,\n observeDelegation: (observation) => recordDelegationObservation(activeTelemetry, observation),\n });\n collaborationFiber = nextCollaboration;\n await nextCollaboration.await();\n if (!ownsSession()) {\n await nextCollaboration.dispose();\n if (collaborationFiber === nextCollaboration) collaborationFiber = undefined;\n return { value: undefined };\n }\n teamRuntime.bindMainSession(sessionId);\n return { value: undefined };\n });\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void Promise.allSettled([readinessHandle.then((handle) => handle.wait())]);\n return undefined;\n });\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team runtime was disposed.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n if (ownedReadiness) await ownedReadiness.dispose();\n else if (pendingReadiness) await Promise.allSettled([pendingReadiness.then((handle) => handle.wait())]);\n readinessNotificationContext = undefined;\n },\n '@agimon-ai/doompi-team/session-fence',\n );\n\n /**\n * Every reason suspends, with no exception for `reload`.\n *\n * Pi emits this for `quit`, `reload`, `new`, `resume` and `fork`. Treating\n * `reload` differently - keeping children alive because `/domains`, `/major-mode`\n * and `/profile` only reload the extension - would be the one case someone\n * has to remember, and the one that silently misbehaves if Pi adds a sixth\n * reason. Suspending uniformly costs a respawn on reload; restore continues\n * the child's own transcript rather than restarting its task.\n */\n let shutdownPromise: Promise<void> | undefined;\n const shutdownSession = async (event: { reason?: string }): Promise<void> => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team session ended.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n await Promise.allSettled([\n ...(pendingReadiness ? [pendingReadiness.then((handle) => handle.wait())] : []),\n ...(ownedReadiness ? [ownedReadiness.dispose()] : []),\n ]);\n const scope = tryCurrentSessionScope();\n const suspensionPromise = scope\n ? suspendScopeRuns({\n scope,\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n readStatus: (runId) => management.status(runId).status,\n })\n : Promise.resolve(undefined);\n let suspension: Awaited<ReturnType<typeof suspendScopeRuns>> | undefined;\n try {\n suspension = await suspensionPromise;\n await telemetry?.recordEvent('doom_team.session_finished', {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n 'team.suspended_count': suspension?.suspended.length ?? 0,\n 'team.unstoppable_count': suspension?.unstoppable.length ?? 0,\n outcome: suspension?.unstoppable.length ? 'degraded' : 'suspended',\n });\n } catch (error) {\n await telemetry?.recordError('doom_team.session_shutdown_failed', error, {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n });\n } finally {\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n };\n pi.on('session_shutdown', (event: { reason?: string }) => {\n shutdownPromise ??= shutdownSession(event);\n return shutdownPromise;\n });\n\n return runtime;\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{resolveActiveTeamModelSpecs as e}from"../agents/discovery.mjs";import{presentCatalog as t}from"../../services/webSubagentCatalog.mjs";import{writeSessionCatalogSnapshot as n}from"../sessionCatalogSnapshot.mjs";import{createSessionScope as r,init_paths as i,setCurrentSessionScope as a,tryCurrentSessionScope as o}from"../filesystem/paths.mjs";import{formatTeamContextSnapshot as s,readActiveTeamSnapshot as c}from"../api/teamSnapshot.mjs";import{authenticatedModelInfos as ee}from"../../services/models/modelResolution.mjs";import{normalizeParentModel as te}from"../runs/shared/modelFallback.mjs";import{init_scopeOwner as l,writeScopeOwnerAsync as ne}from"../scopeOwner.mjs";import{captureSessionForkSource as re}from"./extensions/spawnPlan.mjs";import{TERMINAL_ASYNC_JOB_STATES as u,init_asyncJobTracker as d}from"../asyncJobTracker.mjs";import{registerSlashCommands as f,startSingleAgentRun as p}from"./commands/slash/slashCommands.mjs";import{loadConfig as m}from"./extensions/config.mjs";import{createDelegationBridge as ie}from"./extensions/delegationBridge.mjs";import{createFablePlanBridge as ae}from"./extensions/fablePlanBridge.mjs";import{appendOrchestratorPrompt as oe,shouldInjectOrchestratorPrompt as se}from"./extensions/orchestratorPrompt.mjs";import{formatSuspendedRunsAsync as ce}from"../suspendedRuns.mjs";import{openScopeAsync as le,suspendScopeRuns as ue}from"../runs/registry/sessionLifecycle.mjs";import{registerCompletionRenderer as de}from"./tui/completionNotice.mjs";import{registerSlashRunRenderer as fe}from"./tui/slashRunNotice.mjs";import{registerAgentListCommand as pe,registerAgentStatus as me,registerFleetCommand as he,registerSubagentLeaderContribution as ge}from"./tui/register.mjs";import{teamCollaborationPlugin as _e}from"./collaboration.mjs";import{createTeamExtensionRuntime as ve}from"./teamRuntime.mjs";import{resolveRootSessionId as h}from"@agimon-ai/doompi-extension-contracts/child-process";import{DOOM_CORDIS_SESSION_SERVICE as g}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_CONTEXT_CONTRIBUTIONS_SERVICE as ye,requireDoomContextContributions as be}from"@agimon-ai/doompi-extension-contracts/context-contributions";import{DOOM_MCP_TOOL_RESOLVER_SERVICE as xe,requireDoomMcpToolResolver as Se}from"@agimon-ai/doompi-extension-contracts/mcp-tool-resolver";import{createDoomReadinessCoordinator as Ce,readDoomReadinessCoordinator as we}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as _,requireDoomUiHub as v}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as Te}from"@agimon-ai/doompi-telemetry";d(),i(),l();const y=`unknown`,b=`@agimon-ai/doompi-team`;function Ee(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function De(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`;return`${e.packageId} initialization ${e.state}: ${n}`}function Oe(e,t,n,r,i,a){return{spawnPlanner:n,tracker:r,scheduler:i,discovery:e,skills:t,management:a,loadConfig:()=>m().config}}function ke(e,t){if(t.kind===`requested`){let{event:n}=t;e.recordEvent(`doom_team.delegation_requested`,{"agent.name":n.agent,"task.id":n.taskId,mode:n.runMode??`foreground`})}else if(t.kind===`started`){let{event:n}=t;e.recordEvent(`doom_team.child_started`,{"run.id":n.runId,outcome:`started`})}else if(t.kind===`updated`){let{event:n}=t;e.recordEvent(`doom_team.delegation_updated`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,"team.token_count":n.tokens??0,outcome:n.status??`updated`})}else if(t.kind===`finished`){let{event:n}=t;e.recordEvent(`doom_team.delegation_finished`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,outcome:n.status})}else e.recordEvent(`doom_team.delegation_cancelled`,{outcome:`cancelled`})}function x(i,l){let d,x=e=>(d??=Te({serviceName:`doom-team`,packageName:`@agimon-ai/doompi-team`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),d),Ae=(e,t)=>{d?.recordEvent(e,t)};i.effect(()=>async()=>{await d?.shutdown(),d=void 0},`@agimon-ai/doompi-team/telemetry`);let S=ve(Ae),{subagentTool:je,teamChannel:C,pollScheduler:w,asyncJobTracker:T,subagentWaiter:E,asyncSubagentSpawner:Me,discovery:D,skills:O,spawnPlanner:k,completionNotifier:A,resultWatcher:j,staleRunReconciler:M,management:N,capabilityPolicies:P,mcpToolResolver:Ne}=S,F=!0,I=0,L,R,z,B,V,H;i.inject([g],e=>{let t={cordis:e,service:e.get(g)};H=t,e.inject([xe],e=>Ne.bind(Se(e))),e.inject([ye],e=>{let t=be(e).register({source:b,id:`runtime`,label:`Team`,order:200,snapshot:()=>s(c())});return()=>t.dispose()}),e.effect(()=>()=>{H===t&&(H=void 0)},`${b}/cordis-session`)});let Pe=e=>{let t=H,n=e.sessionManager.getSessionId();return(t?.service.context.sessionManager===e.sessionManager&&t.service.sessionId===n?we(t.cordis):void 0)||(B??=Ce({notify:e=>{let t=De(e);V?.hasUI?V.ui.notify(t,`warning`):process.emitWarning(t)}}),B)},U=async(e,t)=>{t?.throwIfAborted();let n=R;if(!n)throw Error(`Doom Team session initialization has not started.`);if(z!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale Pi session.`);let r=await n;if(t?.throwIfAborted(),await r.wait(t?{signal:t}:void 0),!F||n!==R||z!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale extension generation.`)},W=new Proxy(l,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await U(a,r),t.execute(e,n,r,i,a))})}:t===`registerCommand`?(t,n)=>{e.registerCommand(t,{...n,handler:async(e,t)=>(await U(t),n.handler(e,t))})}:t===`registerShortcut`?(t,n)=>{e.registerShortcut(t,{...n,handler:async e=>(await U(e),n.handler(e))})}:Ee(e,t)}});je.registerTool(W),i.inject([_],e=>ge(v(e)));let G=C.createRuntime(W);i.effect(()=>()=>G.dispose(),`@agimon-ai/doompi-team/channel`),C.registerClient(W),A.attachHost(l),i.effect(()=>()=>A.dispose(),`@agimon-ai/doompi-team/completions`),w.start(),i.effect(()=>()=>w.stop(),`@agimon-ai/doompi-team/polling`),T.start(),i.effect(()=>()=>T.stop(),`@agimon-ai/doompi-team/jobs`);let K,q,J,Y=ie({planner:k,management:N,waiter:E,scheduler:w,tracker:T,loadConfig:()=>m().config}),X=ae({spawner:Me,waiter:E,management:N,policies:P,report:(e,t)=>{d?.recordEvent(e,t)}});i.effect(()=>async()=>{await K?.dispose(),K=void 0},`@agimon-ai/doompi-team/collaboration`),i.effect(()=>()=>{j.stop(),J=void 0},`@agimon-ai/doompi-team/result-watcher`);let Fe=w.register({id:`stale-run-reconciler`,intervalMs:2e3,run:async()=>{let e=!1,t=!1;for(let n of q?.list()??[]){if(n.status&&u.has(n.status))continue;t=!0;let{reconcile:r,sweep:i}=M.reconcileAndSweepAsync?await M.reconcileAndSweepAsync(n.runId):M.reconcileAndSweep(n.runId);r.repaired&&(q?.track(n.runId),e=!0),i.recovered.length>0&&(e=!0)}return e||t}});i.effect(()=>Fe,`@agimon-ai/doompi-team/stale-runs`),l.on(`before_agent_start`,e=>{if(!(!F||!se(m().config)))return{systemPrompt:oe(e.systemPrompt)}});let Z={baseCwd:void 0},Q=(r,i)=>{if(h(i)===i)try{n(i,{cwd:r,agents:t(D.discover(r,`both`).agents),models:e()??[]})}catch{}};l.on(`before_agent_start`,(e,t)=>{F&&Q(t.cwd,t.sessionManager.getSessionId())});let $=Oe(D,O,k,T,w,N);f(W,Z,$),pe(W,{discovery:D,skills:O,policies:P,launchAgent:(e,t)=>p(W,e,Z,$,{agent:t.agent,task:t.task,fork:t.context===`fork`})}),he(W,{scheduler:w,tracker:T,management:N}),i.inject([_],e=>me(l,v(e),{scheduler:w,tracker:T})),de(l),fe(l);let Ie=async()=>{let e=[],t=K;K=void 0;try{await t?.dispose()}catch(t){e.push(t)}for(let t of[()=>P.clear(),()=>Y.abandonAll(),()=>X.abandonAll(),()=>G.dispose()])try{t()}catch(t){e.push(t)}if(q=void 0,e.length!==0)return e.length===1&&e[0]instanceof Error?e[0]:AggregateError(e,`Team session cleanup failed.`)};l.on(`session_start`,(e,t)=>{if(!F)return;let n=++I,o=R;L?.abort(Error(`Doom Team session initialization was superseded.`));let s=new AbortController;L=s,z=t.sessionManager,V=t;let c=x(t);c.recordEvent(`doom_team.session_started`,{"team.root_session":h(t.sessionManager.getSessionId())===t.sessionManager.getSessionId()});let l=t.sessionManager.getSessionId(),u=r(h(l));a(u),Z.baseCwd=t.cwd,Q(t.cwd,l),J!==void 0&&J!==u.scopeKey&&(j.stop(),J=void 0);let d=Ie(),f=Pe(t),p=()=>F&&n===I&&z===t.sessionManager&&!s.signal.aborted;R=(async()=>{if(o&&await Promise.allSettled([o.then(e=>e.wait())]),!p())throw Error(`Doom Team session initialization was superseded.`);return f.start(b,`${l}:${n}`,async e=>{e.throwIfAborted();let n=await d;if(n)throw n;if(!p()||(await ne(u),e.throwIfAborted(),!p()))return{value:void 0};let r=await le(u,{readStatusAsync:async e=>N.statusAsync?(await N.statusAsync(e)).status:N.status(e).status});if(e.throwIfAborted(),!p())return{value:void 0};if(r.suspended.length>0&&t.hasUI){let n=await ce(r.suspended);if(e.throwIfAborted(),!p())return{value:void 0};t.ui.notify(n,`info`)}c.recordEvent(`doom_team.runs_restored`,{"team.suspended_count":r.suspended.length,"team.pruned_count":r.pruned.length,"team.reaped_count":r.reaped,outcome:`reported`}),q=T.forSession(l),J!==u.scopeKey&&(J=u.scopeKey,j.start(e=>q?.get(e.runId)?A.deliver(e):!1));let a=te(t.model),o=H,s=(o?.service.context.sessionManager===t.sessionManager?o.cordis:i).plugin(_e,{session:{sessionId:l,cwd:t.cwd,availableModels:ee(t.modelRegistry),...a?{parentModel:a}:{},captureForkSource:()=>re(t.sessionManager,`tool`)},delegation:Y,fablePlan:X,policies:P,observeDelegation:e=>ke(c,e)});return K=s,await s.await(),p()?(G.bindMainSession(l),{value:void 0}):(await s.dispose(),K===s&&(K=void 0),{value:void 0})})})(),Promise.allSettled([R.then(e=>e.wait())])}),i.effect(()=>async()=>{F=!1,I+=1;let e=R;R=void 0,z=void 0,L?.abort(Error(`Doom Team runtime was disposed.`)),L=void 0;let t=B;B=void 0,t?await t.dispose():e&&await Promise.allSettled([e.then(e=>e.wait())]),V=void 0},`@agimon-ai/doompi-team/session-fence`);let Le,Re=async e=>{F=!1,I+=1;let t=R;R=void 0,z=void 0,L?.abort(Error(`Doom Team session ended.`)),L=void 0;let n=B;B=void 0,await Promise.allSettled([...t?[t.then(e=>e.wait())]:[],...n?[n.dispose()]:[]]);let r=o(),i=r?ue({scope:r,reason:e.reason??y,readStatus:e=>N.status(e).status}):Promise.resolve(void 0),a;try{a=await i,await d?.recordEvent(`doom_team.session_finished`,{reason:e.reason??y,"team.suspended_count":a?.suspended.length??0,"team.unstoppable_count":a?.unstoppable.length??0,outcome:a?.unstoppable.length?`degraded`:`suspended`})}catch(t){await d?.recordError(`doom_team.session_shutdown_failed`,t,{reason:e.reason??y})}finally{await d?.shutdown(),d=void 0}};return l.on(`session_shutdown`,e=>(Le??=Re(e),Le)),S}export{x as installTeamRuntime};
1
+ import{resolveActiveTeamModelSpecs as e}from"../agents/discovery.mjs";import{presentCatalog as t}from"../../services/webSubagentCatalog.mjs";import{writeSessionCatalogSnapshot as n}from"../sessionCatalogSnapshot.mjs";import{createSessionScope as r,init_paths as i,setCurrentSessionScope as a,tryCurrentSessionScope as o}from"../filesystem/paths.mjs";import{formatTeamContextSnapshot as s,readActiveTeamSnapshot as c}from"../api/teamSnapshot.mjs";import{authenticatedModelInfos as ee}from"../../services/models/modelResolution.mjs";import{normalizeParentModel as te}from"../runs/shared/modelFallback.mjs";import{init_scopeOwner as l,writeScopeOwnerAsync as ne}from"../scopeOwner.mjs";import{captureSessionForkSource as re}from"./extensions/spawnPlan.mjs";import{TERMINAL_ASYNC_JOB_STATES as u,init_asyncJobTracker as d}from"../asyncJobTracker.mjs";import{registerSlashCommands as f,startSingleAgentRun as p}from"./commands/slash/slashCommands.mjs";import{loadConfig as m}from"./extensions/config.mjs";import{createDelegationBridge as ie}from"./extensions/delegationBridge.mjs";import{createFablePlanBridge as ae}from"./extensions/fablePlanBridge.mjs";import{appendOrchestratorPrompt as oe,shouldInjectOrchestratorPrompt as se}from"./extensions/orchestratorPrompt.mjs";import{formatSuspendedRunsAsync as ce}from"../suspendedRuns.mjs";import{openScopeAsync as le,suspendScopeRuns as ue}from"../runs/registry/sessionLifecycle.mjs";import{registerCompletionRenderer as de}from"./tui/completionNotice.mjs";import{registerSlashRunRenderer as fe}from"./tui/slashRunNotice.mjs";import{registerAgentListCommand as pe,registerAgentStatus as me,registerFleetCommand as he,registerSubagentLeaderContribution as ge}from"./tui/register.mjs";import{teamCollaborationPlugin as _e}from"./collaboration.mjs";import{createTeamExtensionRuntime as ve}from"./teamRuntime.mjs";import{resolveRootSessionId as h}from"@agimon-ai/doompi-extension-contracts/child-process";import{DOOM_CORDIS_SESSION_SERVICE as g}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_CONTEXT_CONTRIBUTIONS_SERVICE as ye,requireDoomContextContributions as be}from"@agimon-ai/doompi-extension-contracts/context-contributions";import{DOOM_MCP_TOOL_RESOLVER_SERVICE as xe,requireDoomMcpToolResolver as Se}from"@agimon-ai/doompi-extension-contracts/mcp-tool-resolver";import{createDoomReadinessCoordinator as Ce,readDoomReadinessCoordinator as we}from"@agimon-ai/doompi-extension-contracts/readiness";import{DOOM_UI_HUB_SERVICE as _,requireDoomUiHub as v}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{createDoomTelemetry as Te}from"@agimon-ai/doompi-telemetry";d(),i(),l();const y=`unknown`,b=`@agimon-ai/doompi-team`;function Ee(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function De(e){let t=e.diagnostics.join(`; `),n=(e.error?.message??t)||`Initialization did not complete.`;return`${e.packageId} initialization ${e.state}: ${n}`}function Oe(e,t,n,r,i,a){return{spawnPlanner:n,tracker:r,scheduler:i,discovery:e,skills:t,management:a,loadConfig:()=>m().config}}function ke(e,t){if(t.kind===`requested`){let{event:n}=t;e.recordEvent(`doom_team.delegation_requested`,{"agent.name":n.agent,"task.id":n.taskId,mode:n.runMode??`foreground`})}else if(t.kind===`started`){let{event:n}=t;e.recordEvent(`doom_team.child_started`,{"run.id":n.runId,outcome:`started`})}else if(t.kind===`updated`){let{event:n}=t;e.recordEvent(`doom_team.delegation_updated`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,"team.token_count":n.tokens??0,outcome:n.status??`updated`})}else if(t.kind===`finished`){let{event:n}=t;e.recordEvent(`doom_team.delegation_finished`,{"run.id":n.runId,"team.tool_count":n.toolCount??0,"team.duration_ms":n.durationMs??0,outcome:n.status})}else e.recordEvent(`doom_team.delegation_cancelled`,{outcome:`cancelled`})}function x(i,l){let d,x=e=>(d??=Te({serviceName:`doom-team`,packageName:`@agimon-ai/doompi-team`,cwd:e.cwd,env:process.env,enableLogs:!0,enableTraces:!0}),d),Ae=(e,t)=>{d?.recordEvent(e,t)};i.effect(()=>async()=>{await d?.shutdown(),d=void 0},`@agimon-ai/doompi-team/telemetry`);let S=ve(Ae),{subagentTool:je,teamChannel:C,pollScheduler:w,asyncJobTracker:T,subagentWaiter:E,asyncSubagentSpawner:Me,discovery:D,skills:O,spawnPlanner:k,completionNotifier:A,resultWatcher:j,staleRunReconciler:M,management:N,capabilityPolicies:P,mcpToolResolver:Ne}=S,F=!0,I=0,L,R,z,B,V,H;i.inject([g],e=>{let t={cordis:e,service:e.get(g)};H=t,e.inject([xe],e=>Ne.bind(Se(e))),e.inject([ye],e=>{let t=be(e).register({source:b,id:`runtime`,label:`Team`,order:200,snapshot:()=>s(c())});return()=>t.dispose()}),e.effect(()=>()=>{H===t&&(H=void 0)},`${b}/cordis-session`)});let Pe=e=>{let t=H,n=e.sessionManager.getSessionId();return(t?.service.context.sessionManager===e.sessionManager&&t.service.sessionId===n?we(t.cordis):void 0)||(B??=Ce({notify:e=>{let t=De(e);V?.hasUI?V.ui.notify(t,`warning`):process.emitWarning(t)}}),B)},U=async(e,t)=>{t?.throwIfAborted();let n=R;if(!n)throw Error(`Doom Team session initialization has not started.`);if(z!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale Pi session.`);let r=await n;if(t?.throwIfAborted(),await r.wait(t?{signal:t}:void 0),!F||n!==R||z!==e.sessionManager)throw Error(`Doom Team readiness belongs to a stale extension generation.`)},W=new Proxy(l,{get(e,t){return t===`registerTool`?t=>{e.registerTool({...t,execute:async(e,n,r,i,a)=>(await U(a,r),t.execute(e,n,r,i,a))})}:t===`registerCommand`?(t,n)=>{e.registerCommand(t,{...n,handler:async(e,t)=>(await U(t),n.handler(e,t))})}:t===`registerShortcut`?(t,n)=>{e.registerShortcut(t,{...n,handler:async e=>(await U(e),n.handler(e))})}:Ee(e,t)}});je.registerTool(W),i.inject([_],e=>ge(v(e)));let G=C.createRuntime(W);i.effect(()=>()=>G.dispose(),`@agimon-ai/doompi-team/channel`),C.registerClient(W),A.attachHost(l),i.effect(()=>()=>A.dispose(),`@agimon-ai/doompi-team/completions`),w.start(),i.effect(()=>()=>w.stop(),`@agimon-ai/doompi-team/polling`),T.start(),i.effect(()=>()=>T.stop(),`@agimon-ai/doompi-team/jobs`);let K,q,J,Y=ie({planner:k,management:N,waiter:E,scheduler:w,tracker:T,loadConfig:()=>m().config}),X=ae({spawner:Me,waiter:E,management:N,policies:P,report:(e,t)=>{d?.recordEvent(e,t)}});i.effect(()=>async()=>{await K?.dispose(),K=void 0},`@agimon-ai/doompi-team/collaboration`),i.effect(()=>()=>{j.stop(),J=void 0},`@agimon-ai/doompi-team/result-watcher`);let Fe=w.register({id:`stale-run-reconciler`,intervalMs:2e3,run:async()=>{let e=!1,t=!1;for(let n of q?.list()??[]){if(n.status&&u.has(n.status))continue;t=!0;let{reconcile:r,sweep:i}=M.reconcileAndSweepAsync?await M.reconcileAndSweepAsync(n.runId):M.reconcileAndSweep(n.runId);r.repaired&&(q?.track(n.runId),e=!0),i.recovered.length>0&&(e=!0)}return e||t}});i.effect(()=>Fe,`@agimon-ai/doompi-team/stale-runs`),l.on(`before_agent_start`,e=>{if(!(!F||!se(m().config)))return{systemPrompt:oe(e.systemPrompt)}});let Z={baseCwd:void 0},Q=(r,i)=>{if(h(i)===i)try{n(i,{cwd:r,agents:t(D.discover(r,`both`).agents),models:e()??[]})}catch{}};l.on(`before_agent_start`,(e,t)=>{F&&Q(t.cwd,t.sessionManager.getSessionId())});let $=Oe(D,O,k,T,w,N);f(W,Z,$),pe(W,{discovery:D,skills:O,policies:P,launchAgent:(e,t)=>p(W,e,Z,$,{agent:t.agent,task:t.task,fork:t.context===`fork`})}),he(W,{scheduler:w,tracker:T,management:N}),i.inject([_],e=>me(l,v(e),{scheduler:w,tracker:T})),de(l),fe(l);let Ie=async()=>{let e=[],t=K;K=void 0;try{await t?.dispose()}catch(t){e.push(t)}for(let t of[()=>P.clear(),()=>Y.abandonAll(),()=>X.abandonAll(),()=>G.dispose()])try{t()}catch(t){e.push(t)}if(q=void 0,e.length!==0)return e.length===1&&e[0]instanceof Error?e[0]:AggregateError(e,`Team session cleanup failed.`)};l.on(`session_start`,(e,t)=>{if(!F)return;let n=++I,o=R;L?.abort(Error(`Doom Team session initialization was superseded.`));let s=new AbortController;L=s,z=t.sessionManager,V=t;let c=x(t);c.recordEvent(`doom_team.session_started`,{"team.root_session":h(t.sessionManager.getSessionId())===t.sessionManager.getSessionId()});let l=t.sessionManager.getSessionId(),u=r(h(l));a(u),Z.baseCwd=t.cwd,Q(t.cwd,l),J!==void 0&&J!==u.scopeKey&&(j.stop(),J=void 0);let d=Ie(),f=Pe(t),p=()=>F&&n===I&&z===t.sessionManager&&!s.signal.aborted;R=(async()=>{if(o&&await Promise.allSettled([o.then(e=>e.wait())]),!p())throw Error(`Doom Team session initialization was superseded.`);return f.start(b,`${l}:${n}`,async e=>{e.throwIfAborted();let n=await d;if(n)throw n;if(!p()||(await ne(u),e.throwIfAborted(),!p()))return{value:void 0};let r=await le(u,{readStatusAsync:async e=>N.statusAsync?(await N.statusAsync(e)).status:N.status(e).status});if(e.throwIfAborted(),!p())return{value:void 0};if(r.suspended.length>0&&t.hasUI){let n=await ce(r.suspended);if(e.throwIfAborted(),!p())return{value:void 0};t.ui.notify(n,`info`)}c.recordEvent(`doom_team.runs_restored`,{"team.suspended_count":r.suspended.length,"team.pruned_count":r.pruned.length,"team.reaped_count":r.reaped,outcome:`reported`}),q=T.forSession(l),J!==u.scopeKey&&(J=u.scopeKey,j.start(async e=>{if(!q?.get(e.runId))return!1;let t=await A.deliver(e);return t&&T.acknowledgeHandoff(l,e.runId),t}));let a=te(t.model),o=H,s=(o?.service.context.sessionManager===t.sessionManager?o.cordis:i).plugin(_e,{session:{sessionId:l,cwd:t.cwd,availableModels:ee(t.modelRegistry),...a?{parentModel:a}:{},captureForkSource:()=>re(t.sessionManager,`tool`)},directRunTracker:T,delegation:Y,fablePlan:X,policies:P,observeDelegation:e=>ke(c,e)});return K=s,await s.await(),p()?(G.bindMainSession(l),{value:void 0}):(await s.dispose(),K===s&&(K=void 0),{value:void 0})})})(),Promise.allSettled([R.then(e=>e.wait())])}),i.effect(()=>async()=>{F=!1,I+=1;let e=R;R=void 0,z=void 0,L?.abort(Error(`Doom Team runtime was disposed.`)),L=void 0;let t=B;B=void 0,t?await t.dispose():e&&await Promise.allSettled([e.then(e=>e.wait())]),V=void 0},`@agimon-ai/doompi-team/session-fence`);let Le,Re=async e=>{F=!1,I+=1;let t=R;R=void 0,z=void 0,L?.abort(Error(`Doom Team session ended.`)),L=void 0;let n=B;B=void 0,await Promise.allSettled([...t?[t.then(e=>e.wait())]:[],...n?[n.dispose()]:[]]);let r=o(),i=r?ue({scope:r,reason:e.reason??y,readStatus:e=>N.status(e).status}):Promise.resolve(void 0),a;try{a=await i,await d?.recordEvent(`doom_team.session_finished`,{reason:e.reason??y,"team.suspended_count":a?.suspended.length??0,"team.unstoppable_count":a?.unstoppable.length??0,outcome:a?.unstoppable.length?`degraded`:`suspended`})}catch(t){await d?.recordError(`doom_team.session_shutdown_failed`,t,{reason:e.reason??y})}finally{await d?.shutdown(),d=void 0}};return l.on(`session_shutdown`,e=>(Le??=Re(e),Le)),S}export{x as installTeamRuntime};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["/**\n * Install the Team feature into one package-local Cordis root.\n *\n * The standard Pi factory owns that root and disposes it when Pi emits\n * `session_shutdown`. Each factory invocation therefore creates fresh session\n * state; no process-global container or replacement-root handshake participates\n * in reload. Long-lived services are registered as Cordis effects, and stale\n * asynchronous session-start continuations are fenced by a generation token.\n */\n\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport {\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_MCP_TOOL_RESOLVER_SERVICE,\n requireDoomMcpToolResolver,\n} from '@agimon-ai/doompi-extension-contracts/mcp-tool-resolver';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context, Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n registerSlashCommands,\n startSingleAgentRun,\n type SlashCommandDeps,\n type SlashCommandState,\n} from './commands/slash/slashCommands';\nimport { loadConfig } from './extensions/config';\nimport { createDelegationBridge } from './extensions/delegationBridge';\nimport { createFablePlanBridge } from './extensions/fablePlanBridge';\nimport type { ManagementActionsContract } from './extensions/managementActions';\nimport { appendOrchestratorPrompt, shouldInjectOrchestratorPrompt } from './extensions/orchestratorPrompt';\nimport { captureSessionForkSource, type SpawnPlannerContract } from './extensions/spawnPlan';\nimport type { SkillDiscoveryContract } from '../agents/skills';\nimport type { AgentDiscoveryContract } from '../agents/types';\nimport { formatTeamContextSnapshot, readActiveTeamSnapshot } from '../api/teamSnapshot';\nimport {\n type AsyncJobTrackerContract,\n type TrackedAsyncJobsContract,\n TERMINAL_ASYNC_JOB_STATES,\n} from '../asyncJobTracker';\nimport { openScopeAsync, suspendScopeRuns } from '../runs/registry/sessionLifecycle';\nimport { formatSuspendedRunsAsync } from '../suspendedRuns';\nimport { normalizeParentModel } from '../runs/shared/modelFallback';\nimport { authenticatedModelInfos } from '../../services/models/modelResolution';\nimport { createSessionScope, setCurrentSessionScope, tryCurrentSessionScope } from '../filesystem/paths';\nimport type { PollSchedulerContract } from '../pollScheduler';\nimport { writeScopeOwnerAsync } from '../scopeOwner';\nimport { resolveActiveTeamModelSpecs } from '../agents/discovery';\nimport { writeSessionCatalogSnapshot } from '../sessionCatalogSnapshot';\nimport { presentCatalog } from '../../services/webSubagentCatalog';\nimport { registerCompletionRenderer } from './tui/completionNotice';\nimport { registerSlashRunRenderer } from './tui/slashRunNotice';\nimport {\n registerAgentListCommand,\n registerAgentStatus,\n registerFleetCommand,\n registerSubagentLeaderContribution,\n} from './tui/register';\nimport { teamCollaborationPlugin, type TeamDelegationObservation } from './collaboration';\nimport { createTeamExtensionRuntime, type TeamExtensionRuntime } from './teamRuntime';\n\n/** See the subscriber's own comment for why crash detection does not need the 250ms floor. */\nconst STALE_RUN_RECONCILE_INTERVAL_MS = 2_000;\nconst SESSION_SHUTDOWN_REASON_FALLBACK = 'unknown';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-team';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\n\nfunction boundPiValue(target: ExtensionAPI, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction readinessNotificationMessage(notification: DoomReadinessNotification): string {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n return `${notification.packageId} initialization ${notification.state}: ${detail}`;\n}\n\nfunction buildSlashCommandDeps(\n discovery: AgentDiscoveryContract,\n skills: SkillDiscoveryContract,\n planner: SpawnPlannerContract,\n tracker: AsyncJobTrackerContract,\n scheduler: PollSchedulerContract,\n management: ManagementActionsContract,\n): SlashCommandDeps {\n return {\n spawnPlanner: planner,\n tracker,\n scheduler,\n discovery,\n skills,\n management,\n loadConfig: () => loadConfig().config,\n };\n}\n\nfunction recordDelegationObservation(telemetry: DoomTelemetry, observation: TeamDelegationObservation): void {\n if (observation.kind === 'requested') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_requested', {\n 'agent.name': event.agent,\n 'task.id': event.taskId,\n mode: event.runMode ?? 'foreground',\n });\n } else if (observation.kind === 'started') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.child_started', { 'run.id': event.runId, outcome: 'started' });\n } else if (observation.kind === 'updated') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_updated', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n 'team.token_count': event.tokens ?? 0,\n outcome: event.status ?? 'updated',\n });\n } else if (observation.kind === 'finished') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_finished', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n outcome: event.status,\n });\n } else {\n void telemetry.recordEvent('doom_team.delegation_cancelled', { outcome: 'cancelled' });\n }\n}\n\n/**\n * The extension factory pi's loader calls, exported as the module default\n * (see the override this file carries in `vibe-lint.config.yaml`). This is an\n * ESM `.ts` module, so it CANNOT use the `export =` form that\n * `subagentPromptRuntimeEntry.cts` is required to use - that file is `.cts`\n * and is resolved by raw path, this one is resolved as a package subpath and\n * matches the `export default` convention every sibling extension in this repo\n * already uses (`doom-task`, `doom-file-edit`, `doom-pi-ui`).\n */\nexport function installTeamRuntime(cordis: Context, pi: ExtensionAPI): TeamExtensionRuntime {\n let telemetry: DoomTelemetry | undefined;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-team',\n packageName: '@agimon-ai/doompi-team',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const reportConcurrencyEvent = (event: string, attributes: Record<string, unknown>): void => {\n void telemetry?.recordEvent(event, attributes);\n };\n cordis.effect(\n () => async () => {\n await telemetry?.shutdown();\n telemetry = undefined;\n },\n '@agimon-ai/doompi-team/telemetry',\n );\n const runtime = createTeamExtensionRuntime(reportConcurrencyEvent);\n const {\n subagentTool,\n teamChannel,\n pollScheduler,\n asyncJobTracker,\n subagentWaiter,\n asyncSubagentSpawner,\n discovery,\n skills,\n spawnPlanner,\n completionNotifier,\n resultWatcher,\n staleRunReconciler,\n management,\n capabilityPolicies,\n mcpToolResolver,\n } = runtime;\n\n let active = true;\n let sessionGeneration = 0;\n let readinessAbort: AbortController | undefined;\n let readinessHandle: Promise<DoomReadinessHandle<void>> | undefined;\n let readinessSessionManager: object | undefined;\n let standaloneReadiness: DoomReadinessCoordinator | undefined;\n let readinessNotificationContext: ExtensionContext | undefined;\n let activeCordisSession:\n | {\n readonly cordis: Context;\n readonly service: DoomCordisSessionService;\n }\n | undefined;\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const service = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const binding = { cordis: sessionContext, service };\n activeCordisSession = binding;\n sessionContext.inject([DOOM_MCP_TOOL_RESOLVER_SERVICE], (resolverContext) =>\n mcpToolResolver.bind(requireDoomMcpToolResolver(resolverContext)),\n );\n sessionContext.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contributionContext) => {\n const registration = requireDoomContextContributions(contributionContext).register({\n source: PACKAGE_SOURCE,\n id: 'runtime',\n label: 'Team',\n order: 200,\n snapshot: () => formatTeamContextSnapshot(readActiveTeamSnapshot()),\n });\n return () => registration.dispose();\n });\n sessionContext.effect(\n () => () => {\n if (activeCordisSession === binding) activeCordisSession = undefined;\n },\n `${PACKAGE_SOURCE}/cordis-session`,\n );\n });\n const coordinatorFor = (ctx: ExtensionContext): DoomReadinessCoordinator => {\n const cordisSession = activeCordisSession;\n const sessionId = ctx.sessionManager.getSessionId();\n const activeSessionMatches =\n cordisSession?.service.context.sessionManager === ctx.sessionManager &&\n cordisSession.service.sessionId === sessionId;\n const shared = activeSessionMatches ? readDoomReadinessCoordinator(cordisSession.cordis) : undefined;\n if (shared) return shared;\n standaloneReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n const message = readinessNotificationMessage(notification);\n if (readinessNotificationContext?.hasUI) readinessNotificationContext.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n return standaloneReadiness;\n };\n const waitForSessionReadiness = async (ctx: ExtensionContext, signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n const pending = readinessHandle;\n if (!pending) throw new Error('Doom Team session initialization has not started.');\n if (readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale Pi session.');\n }\n const handle = await pending;\n signal?.throwIfAborted();\n await handle.wait(signal ? { signal } : undefined);\n if (!active || pending !== readinessHandle || readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale extension generation.');\n }\n };\n const readinessPi = new Proxy(pi, {\n get(target, property) {\n if (property === 'registerTool') {\n return (tool: RegisteredTool): void => {\n target.registerTool({\n ...tool,\n execute: async (toolCallId, params, signal, onUpdate, ctx) => {\n await waitForSessionReadiness(ctx, signal);\n return tool.execute(toolCallId, params, signal, onUpdate, ctx);\n },\n });\n };\n }\n if (property === 'registerCommand') {\n return (name: string, options: CommandOptions): void => {\n target.registerCommand(name, {\n ...options,\n handler: async (args, ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(args, ctx);\n },\n });\n };\n }\n if (property === 'registerShortcut') {\n return (shortcut: Parameters<ExtensionAPI['registerShortcut']>[0], options: ShortcutOptions): void => {\n target.registerShortcut(shortcut, {\n ...options,\n handler: async (ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(ctx);\n },\n });\n };\n }\n return boundPiValue(target, property);\n },\n });\n\n subagentTool.registerTool(readinessPi);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => registerSubagentLeaderContribution(requireDoomUiHub(uiContext)));\n\n const teamRuntime = teamChannel.createRuntime(readinessPi);\n cordis.effect(() => () => teamRuntime.dispose(), '@agimon-ai/doompi-team/channel');\n teamChannel.registerClient(readinessPi);\n\n // Attach before `ResultWatcher.start()`, which `session_start` drives.\n completionNotifier.attachHost(pi);\n cordis.effect(() => () => completionNotifier.dispose(), '@agimon-ai/doompi-team/completions');\n\n pollScheduler.start();\n cordis.effect(() => () => pollScheduler.stop(), '@agimon-ai/doompi-team/polling');\n\n asyncJobTracker.start();\n cordis.effect(() => () => asyncJobTracker.stop(), '@agimon-ai/doompi-team/jobs');\n\n let collaborationFiber: Fiber | undefined;\n let currentSessionJobs: TrackedAsyncJobsContract | undefined;\n // The scope `ResultWatcher` is currently watching. See its start site in\n // the `session_start` handler for why the watcher is latched on this\n // rather than started once.\n let watchedScopeKey: string | undefined;\n const delegationBridge = createDelegationBridge({\n planner: spawnPlanner,\n management,\n waiter: subagentWaiter,\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const fablePlanBridge = createFablePlanBridge({\n spawner: asyncSubagentSpawner,\n waiter: subagentWaiter,\n management,\n policies: capabilityPolicies,\n report: (event, attributes) => {\n void telemetry?.recordEvent(event, attributes);\n },\n });\n cordis.effect(\n () => async () => {\n await collaborationFiber?.dispose();\n collaborationFiber = undefined;\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n cordis.effect(\n () => () => {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n },\n '@agimon-ai/doompi-team/result-watcher',\n );\n\n const unregisterStaleRunReconciler = pollScheduler.register({\n id: 'stale-run-reconciler',\n // Crash detection, not status freshness - `AsyncJobTracker` owns that and\n // polls on its own 250ms. This subscriber only ever acts on a run whose\n // process is provably dead, so noticing that 2s later is invisible, while\n // running it 4x/second per active run was not: each pass is a status read,\n // a run-id resolve and a pid probe that a healthy child always discards.\n intervalMs: STALE_RUN_RECONCILE_INTERVAL_MS,\n run: async () => {\n let repaired = false;\n let hasActiveRun = false;\n for (const job of currentSessionJobs?.list() ?? []) {\n if (job.status && TERMINAL_ASYNC_JOB_STATES.has(job.status)) continue;\n hasActiveRun = true;\n // One pass, one `inspect()`. Calling `reconcile` and\n // `sweepOrphanedClaims` separately probed the same pid twice for the\n // same answer - see `StaleRunReconcilerContract.reconcileAndSweep`.\n const { reconcile: outcome, sweep } = staleRunReconciler.reconcileAndSweepAsync\n ? await staleRunReconciler.reconcileAndSweepAsync(job.runId)\n : staleRunReconciler.reconcileAndSweep(job.runId);\n if (outcome.repaired) {\n currentSessionJobs?.track(job.runId);\n repaired = true;\n }\n if (sweep.recovered.length > 0) repaired = true;\n }\n // An active runner is useful work for scheduling purposes: keeping\n // the shared scheduler at its floor prevents crash detection from\n // inheriting the idle five-second backoff ceiling.\n return repaired || hasActiveRun;\n },\n });\n cordis.effect(() => unregisterStaleRunReconciler, '@agimon-ai/doompi-team/stale-runs');\n // The orchestration addendum. Registered here rather than inside the\n // `subagent` tool's description because it is behavioural guidance, and it\n // has to land before the model decides whether to reach for the tool at\n // all - see `orchestratorPrompt.ts`. Read config per turn so toggling it\n // does not need a session restart; `loadConfig` is memoized on the file's\n // own mtime, so this is a stat, not a read.\n pi.on('before_agent_start', (event: { systemPrompt?: string }) => {\n if (!active || !shouldInjectOrchestratorPrompt(loadConfig().config)) return undefined;\n return { systemPrompt: appendOrchestratorPrompt(event.systemPrompt) };\n });\n\n const state: SlashCommandState = { baseCwd: undefined };\n\n /**\n * Publish what this session can launch, for the cockpit hub to read.\n *\n * The hub and the session API run in `doompi-server`, which never receives\n * the domain projection's agent directories or the harness state behind the\n * Team model policy, so discovery run there misses every domain-provided\n * agent. This process has both. Republished on each turn so an agent file\n * added mid-session shows up without a restart.\n */\n const publishCatalogSnapshot = (cwd: string, sessionId: string): void => {\n // A spawned child shares its parent's scope directory; only the root\n // session may write the catalog the user is looking at.\n if (resolveRootSessionId(sessionId) !== sessionId) return;\n try {\n writeSessionCatalogSnapshot(sessionId, {\n cwd,\n agents: presentCatalog(discovery.discover(cwd, 'both').agents),\n models: resolveActiveTeamModelSpecs() ?? [],\n });\n } catch {\n // Best effort: the hub falls back to its own discovery, and every launch\n // path reports discovery failures on its own.\n }\n };\n\n pi.on('before_agent_start', (_event: { systemPrompt?: string }, ctx: ExtensionContext) => {\n if (!active) return undefined;\n publishCatalogSnapshot(ctx.cwd, ctx.sessionManager.getSessionId());\n return undefined;\n });\n const slashCommandDeps = buildSlashCommandDeps(\n discovery,\n skills,\n spawnPlanner,\n asyncJobTracker,\n pollScheduler,\n management,\n );\n registerSlashCommands(readinessPi, state, slashCommandDeps);\n registerAgentListCommand(readinessPi, {\n discovery,\n skills,\n policies: capabilityPolicies,\n // The catalog's r/R keys spawn through the same path as `/run`, so a run\n // started from the overlay is tracked and reported like any other.\n launchAgent: (ctx, request) =>\n startSingleAgentRun(readinessPi, ctx, state, slashCommandDeps, {\n agent: request.agent,\n task: request.task,\n fork: request.context === 'fork',\n }),\n });\n registerFleetCommand(readinessPi, {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n management,\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n return registerAgentStatus(pi, requireDoomUiHub(uiContext), {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n });\n });\n registerCompletionRenderer(pi);\n registerSlashRunRenderer(pi);\n\n const retireSessionBindings = async (): Promise<Error | undefined> => {\n const errors: unknown[] = [];\n const previousCollaboration = collaborationFiber;\n collaborationFiber = undefined;\n try {\n await previousCollaboration?.dispose();\n } catch (error) {\n errors.push(error);\n }\n for (const cleanup of [\n () => capabilityPolicies.clear(),\n () => delegationBridge.abandonAll(),\n () => fablePlanBridge.abandonAll(),\n () => teamRuntime.dispose(),\n ]) {\n try {\n cleanup();\n } catch (error) {\n errors.push(error);\n }\n }\n currentSessionJobs = undefined;\n if (errors.length === 0) return undefined;\n if (errors.length === 1 && errors[0] instanceof Error) return errors[0];\n return new AggregateError(errors, 'Team session cleanup failed.');\n };\n\n // Bind the root session directly into the one intercom transport. Child\n // processes bind from the team environment written during spawn.\n pi.on('session_start', (_event, ctx: ExtensionContext) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n const previousReadiness = readinessHandle;\n readinessAbort?.abort(new Error('Doom Team session initialization was superseded.'));\n const ownReadinessAbort = new AbortController();\n readinessAbort = ownReadinessAbort;\n readinessSessionManager = ctx.sessionManager;\n readinessNotificationContext = ctx;\n const activeTelemetry = getTelemetry(ctx);\n void activeTelemetry.recordEvent('doom_team.session_started', {\n 'team.root_session':\n resolveRootSessionId(ctx.sessionManager.getSessionId()) === ctx.sessionManager.getSessionId(),\n });\n // Before anything else: every scoped path helper reads this. A child\n // keeps the root inherited from its spawn environment, while a top-level\n // session falls back to its own Pi session id.\n const sessionId = ctx.sessionManager.getSessionId();\n const scope = createSessionScope(resolveRootSessionId(sessionId));\n setCurrentSessionScope(scope);\n state.baseCwd = ctx.cwd;\n publishCatalogSnapshot(ctx.cwd, sessionId);\n if (watchedScopeKey !== undefined && watchedScopeKey !== scope.scopeKey) {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n }\n const retirement = retireSessionBindings();\n const coordinator = coordinatorFor(ctx);\n const ownsSession = (): boolean =>\n active &&\n ownGeneration === sessionGeneration &&\n readinessSessionManager === ctx.sessionManager &&\n !ownReadinessAbort.signal.aborted;\n\n readinessHandle = (async (): Promise<DoomReadinessHandle<void>> => {\n if (previousReadiness) {\n await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);\n }\n if (!ownsSession()) throw new Error('Doom Team session initialization was superseded.');\n return coordinator.start(PACKAGE_SOURCE, `${sessionId}:${ownGeneration}`, async (signal) => {\n signal.throwIfAborted();\n const retirementError = await retirement;\n if (retirementError) throw retirementError;\n if (!ownsSession()) return { value: undefined };\n // Claim the scope for this process, so a later sweep can tell an\n // abandoned tree from a live one. Rewritten on every session_start\n // because /resume legitimately re-adopts a scope under a new process.\n await writeScopeOwnerAsync(scope);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n // Prune records whose process is gone, reap sibling scopes holding\n // nothing alive, and REPORT what is suspended. Nothing is restarted:\n // reopening a session must not silently spend tokens on stale work.\n const opened = await openScopeAsync(scope, {\n readStatusAsync: async (runId) =>\n management.statusAsync ? (await management.statusAsync(runId)).status : management.status(runId).status,\n });\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n if (opened.suspended.length > 0 && ctx.hasUI) {\n const message = await formatSuspendedRunsAsync(opened.suspended);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n ctx.ui.notify(message, 'info');\n }\n void activeTelemetry.recordEvent('doom_team.runs_restored', {\n 'team.suspended_count': opened.suspended.length,\n 'team.pruned_count': opened.pruned.length,\n 'team.reaped_count': opened.reaped,\n outcome: 'reported',\n });\n currentSessionJobs = asyncJobTracker.forSession(sessionId);\n // ResultWatcher needs the session scope, but a repeated start for the\n // same scope must not reset its in-flight claims.\n if (watchedScopeKey !== scope.scopeKey) {\n watchedScopeKey = scope.scopeKey;\n resultWatcher.start((result) =>\n currentSessionJobs?.get(result.runId) ? completionNotifier.deliver(result) : false,\n );\n }\n const parentModel = normalizeParentModel(ctx.model);\n const activeSession = activeCordisSession;\n const collaborationParent =\n activeSession?.service.context.sessionManager === ctx.sessionManager ? activeSession.cordis : cordis;\n const nextCollaboration = collaborationParent.plugin(teamCollaborationPlugin, {\n session: {\n sessionId,\n cwd: ctx.cwd,\n availableModels: authenticatedModelInfos(ctx.modelRegistry),\n ...(parentModel ? { parentModel } : {}),\n captureForkSource: () => captureSessionForkSource(ctx.sessionManager, 'tool'),\n },\n delegation: delegationBridge,\n fablePlan: fablePlanBridge,\n policies: capabilityPolicies,\n observeDelegation: (observation) => recordDelegationObservation(activeTelemetry, observation),\n });\n collaborationFiber = nextCollaboration;\n await nextCollaboration.await();\n if (!ownsSession()) {\n await nextCollaboration.dispose();\n if (collaborationFiber === nextCollaboration) collaborationFiber = undefined;\n return { value: undefined };\n }\n teamRuntime.bindMainSession(sessionId);\n return { value: undefined };\n });\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void Promise.allSettled([readinessHandle.then((handle) => handle.wait())]);\n return undefined;\n });\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team runtime was disposed.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n if (ownedReadiness) await ownedReadiness.dispose();\n else if (pendingReadiness) await Promise.allSettled([pendingReadiness.then((handle) => handle.wait())]);\n readinessNotificationContext = undefined;\n },\n '@agimon-ai/doompi-team/session-fence',\n );\n\n /**\n * Every reason suspends, with no exception for `reload`.\n *\n * Pi emits this for `quit`, `reload`, `new`, `resume` and `fork`. Treating\n * `reload` differently - keeping children alive because `/domains`, `/major-mode`\n * and `/profile` only reload the extension - would be the one case someone\n * has to remember, and the one that silently misbehaves if Pi adds a sixth\n * reason. Suspending uniformly costs a respawn on reload; restore continues\n * the child's own transcript rather than restarting its task.\n */\n let shutdownPromise: Promise<void> | undefined;\n const shutdownSession = async (event: { reason?: string }): Promise<void> => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team session ended.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n await Promise.allSettled([\n ...(pendingReadiness ? [pendingReadiness.then((handle) => handle.wait())] : []),\n ...(ownedReadiness ? [ownedReadiness.dispose()] : []),\n ]);\n const scope = tryCurrentSessionScope();\n const suspensionPromise = scope\n ? suspendScopeRuns({\n scope,\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n readStatus: (runId) => management.status(runId).status,\n })\n : Promise.resolve(undefined);\n let suspension: Awaited<ReturnType<typeof suspendScopeRuns>> | undefined;\n try {\n suspension = await suspensionPromise;\n await telemetry?.recordEvent('doom_team.session_finished', {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n 'team.suspended_count': suspension?.suspended.length ?? 0,\n 'team.unstoppable_count': suspension?.unstoppable.length ?? 0,\n outcome: suspension?.unstoppable.length ? 'degraded' : 'suspended',\n });\n } catch (error) {\n await telemetry?.recordError('doom_team.session_shutdown_failed', error, {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n });\n } finally {\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n };\n pi.on('session_shutdown', (event: { reason?: string }) => {\n shutdownPromise ??= shutdownSession(event);\n return shutdownPromise;\n });\n\n return runtime;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":["jobs"],"sources":["../../../../src/adapters/pi/extension.ts"],"sourcesContent":["/**\n * Install the Team feature into one package-local Cordis root.\n *\n * The standard Pi factory owns that root and disposes it when Pi emits\n * `session_shutdown`. Each factory invocation therefore creates fresh session\n * state; no process-global container or replacement-root handshake participates\n * in reload. Long-lived services are registered as Cordis effects, and stale\n * asynchronous session-start continuations are fenced by a generation token.\n */\n\nimport { resolveRootSessionId } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n DOOM_CONTEXT_CONTRIBUTIONS_SERVICE,\n requireDoomContextContributions,\n} from '@agimon-ai/doompi-extension-contracts/context-contributions';\nimport {\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport {\n DOOM_MCP_TOOL_RESOLVER_SERVICE,\n requireDoomMcpToolResolver,\n} from '@agimon-ai/doompi-extension-contracts/mcp-tool-resolver';\nimport {\n createDoomReadinessCoordinator,\n type DoomReadinessCoordinator,\n type DoomReadinessHandle,\n type DoomReadinessNotification,\n readDoomReadinessCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/readiness';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport { createDoomTelemetry, type DoomTelemetry } from '@agimon-ai/doompi-telemetry';\nimport type { Context, Fiber } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n registerSlashCommands,\n startSingleAgentRun,\n type SlashCommandDeps,\n type SlashCommandState,\n} from './commands/slash/slashCommands';\nimport { loadConfig } from './extensions/config';\nimport { createDelegationBridge } from './extensions/delegationBridge';\nimport { createFablePlanBridge } from './extensions/fablePlanBridge';\nimport type { ManagementActionsContract } from './extensions/managementActions';\nimport { appendOrchestratorPrompt, shouldInjectOrchestratorPrompt } from './extensions/orchestratorPrompt';\nimport { captureSessionForkSource, type SpawnPlannerContract } from './extensions/spawnPlan';\nimport type { SkillDiscoveryContract } from '../agents/skills';\nimport type { AgentDiscoveryContract } from '../agents/types';\nimport { formatTeamContextSnapshot, readActiveTeamSnapshot } from '../api/teamSnapshot';\nimport {\n type AsyncJobTrackerContract,\n type TrackedAsyncJobsContract,\n TERMINAL_ASYNC_JOB_STATES,\n} from '../asyncJobTracker';\nimport { openScopeAsync, suspendScopeRuns } from '../runs/registry/sessionLifecycle';\nimport { formatSuspendedRunsAsync } from '../suspendedRuns';\nimport { normalizeParentModel } from '../runs/shared/modelFallback';\nimport { authenticatedModelInfos } from '../../services/models/modelResolution';\nimport { createSessionScope, setCurrentSessionScope, tryCurrentSessionScope } from '../filesystem/paths';\nimport type { PollSchedulerContract } from '../pollScheduler';\nimport { writeScopeOwnerAsync } from '../scopeOwner';\nimport { resolveActiveTeamModelSpecs } from '../agents/discovery';\nimport { writeSessionCatalogSnapshot } from '../sessionCatalogSnapshot';\nimport { presentCatalog } from '../../services/webSubagentCatalog';\nimport { registerCompletionRenderer } from './tui/completionNotice';\nimport { registerSlashRunRenderer } from './tui/slashRunNotice';\nimport {\n registerAgentListCommand,\n registerAgentStatus,\n registerFleetCommand,\n registerSubagentLeaderContribution,\n} from './tui/register';\nimport { teamCollaborationPlugin, type TeamDelegationObservation } from './collaboration';\nimport { createTeamExtensionRuntime, type TeamExtensionRuntime } from './teamRuntime';\n\n/** See the subscriber's own comment for why crash detection does not need the 250ms floor. */\nconst STALE_RUN_RECONCILE_INTERVAL_MS = 2_000;\nconst SESSION_SHUTDOWN_REASON_FALLBACK = 'unknown';\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-team';\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ntype CommandOptions = Parameters<ExtensionAPI['registerCommand']>[1];\ntype ShortcutOptions = Parameters<ExtensionAPI['registerShortcut']>[1];\n\nfunction boundPiValue(target: ExtensionAPI, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\nfunction readinessNotificationMessage(notification: DoomReadinessNotification): string {\n const diagnostics = notification.diagnostics.join('; ');\n const detail = (notification.error?.message ?? diagnostics) || 'Initialization did not complete.';\n return `${notification.packageId} initialization ${notification.state}: ${detail}`;\n}\n\nfunction buildSlashCommandDeps(\n discovery: AgentDiscoveryContract,\n skills: SkillDiscoveryContract,\n planner: SpawnPlannerContract,\n tracker: AsyncJobTrackerContract,\n scheduler: PollSchedulerContract,\n management: ManagementActionsContract,\n): SlashCommandDeps {\n return {\n spawnPlanner: planner,\n tracker,\n scheduler,\n discovery,\n skills,\n management,\n loadConfig: () => loadConfig().config,\n };\n}\n\nfunction recordDelegationObservation(telemetry: DoomTelemetry, observation: TeamDelegationObservation): void {\n if (observation.kind === 'requested') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_requested', {\n 'agent.name': event.agent,\n 'task.id': event.taskId,\n mode: event.runMode ?? 'foreground',\n });\n } else if (observation.kind === 'started') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.child_started', { 'run.id': event.runId, outcome: 'started' });\n } else if (observation.kind === 'updated') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_updated', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n 'team.token_count': event.tokens ?? 0,\n outcome: event.status ?? 'updated',\n });\n } else if (observation.kind === 'finished') {\n const { event } = observation;\n void telemetry.recordEvent('doom_team.delegation_finished', {\n 'run.id': event.runId,\n 'team.tool_count': event.toolCount ?? 0,\n 'team.duration_ms': event.durationMs ?? 0,\n outcome: event.status,\n });\n } else {\n void telemetry.recordEvent('doom_team.delegation_cancelled', { outcome: 'cancelled' });\n }\n}\n\n/**\n * The extension factory pi's loader calls, exported as the module default\n * (see the override this file carries in `vibe-lint.config.yaml`). This is an\n * ESM `.ts` module, so it CANNOT use the `export =` form that\n * `subagentPromptRuntimeEntry.cts` is required to use - that file is `.cts`\n * and is resolved by raw path, this one is resolved as a package subpath and\n * matches the `export default` convention every sibling extension in this repo\n * already uses (`doom-task`, `doom-file-edit`, `doom-pi-ui`).\n */\nexport function installTeamRuntime(cordis: Context, pi: ExtensionAPI): TeamExtensionRuntime {\n let telemetry: DoomTelemetry | undefined;\n const getTelemetry = (ctx: ExtensionContext): DoomTelemetry => {\n telemetry ??= createDoomTelemetry({\n serviceName: 'doom-team',\n packageName: '@agimon-ai/doompi-team',\n cwd: ctx.cwd,\n env: process.env,\n enableLogs: true,\n enableTraces: true,\n });\n return telemetry;\n };\n const reportConcurrencyEvent = (event: string, attributes: Record<string, unknown>): void => {\n void telemetry?.recordEvent(event, attributes);\n };\n cordis.effect(\n () => async () => {\n await telemetry?.shutdown();\n telemetry = undefined;\n },\n '@agimon-ai/doompi-team/telemetry',\n );\n const runtime = createTeamExtensionRuntime(reportConcurrencyEvent);\n const {\n subagentTool,\n teamChannel,\n pollScheduler,\n asyncJobTracker,\n subagentWaiter,\n asyncSubagentSpawner,\n discovery,\n skills,\n spawnPlanner,\n completionNotifier,\n resultWatcher,\n staleRunReconciler,\n management,\n capabilityPolicies,\n mcpToolResolver,\n } = runtime;\n\n let active = true;\n let sessionGeneration = 0;\n let readinessAbort: AbortController | undefined;\n let readinessHandle: Promise<DoomReadinessHandle<void>> | undefined;\n let readinessSessionManager: object | undefined;\n let standaloneReadiness: DoomReadinessCoordinator | undefined;\n let readinessNotificationContext: ExtensionContext | undefined;\n let activeCordisSession:\n | {\n readonly cordis: Context;\n readonly service: DoomCordisSessionService;\n }\n | undefined;\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], (sessionContext) => {\n const service = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const binding = { cordis: sessionContext, service };\n activeCordisSession = binding;\n sessionContext.inject([DOOM_MCP_TOOL_RESOLVER_SERVICE], (resolverContext) =>\n mcpToolResolver.bind(requireDoomMcpToolResolver(resolverContext)),\n );\n sessionContext.inject([DOOM_CONTEXT_CONTRIBUTIONS_SERVICE], (contributionContext) => {\n const registration = requireDoomContextContributions(contributionContext).register({\n source: PACKAGE_SOURCE,\n id: 'runtime',\n label: 'Team',\n order: 200,\n snapshot: () => formatTeamContextSnapshot(readActiveTeamSnapshot()),\n });\n return () => registration.dispose();\n });\n sessionContext.effect(\n () => () => {\n if (activeCordisSession === binding) activeCordisSession = undefined;\n },\n `${PACKAGE_SOURCE}/cordis-session`,\n );\n });\n const coordinatorFor = (ctx: ExtensionContext): DoomReadinessCoordinator => {\n const cordisSession = activeCordisSession;\n const sessionId = ctx.sessionManager.getSessionId();\n const activeSessionMatches =\n cordisSession?.service.context.sessionManager === ctx.sessionManager &&\n cordisSession.service.sessionId === sessionId;\n const shared = activeSessionMatches ? readDoomReadinessCoordinator(cordisSession.cordis) : undefined;\n if (shared) return shared;\n standaloneReadiness ??= createDoomReadinessCoordinator({\n notify: (notification) => {\n const message = readinessNotificationMessage(notification);\n if (readinessNotificationContext?.hasUI) readinessNotificationContext.ui.notify(message, 'warning');\n else process.emitWarning(message);\n },\n });\n return standaloneReadiness;\n };\n const waitForSessionReadiness = async (ctx: ExtensionContext, signal?: AbortSignal): Promise<void> => {\n signal?.throwIfAborted();\n const pending = readinessHandle;\n if (!pending) throw new Error('Doom Team session initialization has not started.');\n if (readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale Pi session.');\n }\n const handle = await pending;\n signal?.throwIfAborted();\n await handle.wait(signal ? { signal } : undefined);\n if (!active || pending !== readinessHandle || readinessSessionManager !== ctx.sessionManager) {\n throw new Error('Doom Team readiness belongs to a stale extension generation.');\n }\n };\n const readinessPi = new Proxy(pi, {\n get(target, property) {\n if (property === 'registerTool') {\n return (tool: RegisteredTool): void => {\n target.registerTool({\n ...tool,\n execute: async (toolCallId, params, signal, onUpdate, ctx) => {\n await waitForSessionReadiness(ctx, signal);\n return tool.execute(toolCallId, params, signal, onUpdate, ctx);\n },\n });\n };\n }\n if (property === 'registerCommand') {\n return (name: string, options: CommandOptions): void => {\n target.registerCommand(name, {\n ...options,\n handler: async (args, ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(args, ctx);\n },\n });\n };\n }\n if (property === 'registerShortcut') {\n return (shortcut: Parameters<ExtensionAPI['registerShortcut']>[0], options: ShortcutOptions): void => {\n target.registerShortcut(shortcut, {\n ...options,\n handler: async (ctx) => {\n await waitForSessionReadiness(ctx);\n return options.handler(ctx);\n },\n });\n };\n }\n return boundPiValue(target, property);\n },\n });\n\n subagentTool.registerTool(readinessPi);\n\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => registerSubagentLeaderContribution(requireDoomUiHub(uiContext)));\n\n const teamRuntime = teamChannel.createRuntime(readinessPi);\n cordis.effect(() => () => teamRuntime.dispose(), '@agimon-ai/doompi-team/channel');\n teamChannel.registerClient(readinessPi);\n\n // Attach before `ResultWatcher.start()`, which `session_start` drives.\n completionNotifier.attachHost(pi);\n cordis.effect(() => () => completionNotifier.dispose(), '@agimon-ai/doompi-team/completions');\n\n pollScheduler.start();\n cordis.effect(() => () => pollScheduler.stop(), '@agimon-ai/doompi-team/polling');\n\n asyncJobTracker.start();\n cordis.effect(() => () => asyncJobTracker.stop(), '@agimon-ai/doompi-team/jobs');\n\n let collaborationFiber: Fiber | undefined;\n let currentSessionJobs: TrackedAsyncJobsContract | undefined;\n // The scope `ResultWatcher` is currently watching. See its start site in\n // the `session_start` handler for why the watcher is latched on this\n // rather than started once.\n let watchedScopeKey: string | undefined;\n const delegationBridge = createDelegationBridge({\n planner: spawnPlanner,\n management,\n waiter: subagentWaiter,\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n loadConfig: () => loadConfig().config,\n });\n const fablePlanBridge = createFablePlanBridge({\n spawner: asyncSubagentSpawner,\n waiter: subagentWaiter,\n management,\n policies: capabilityPolicies,\n report: (event, attributes) => {\n void telemetry?.recordEvent(event, attributes);\n },\n });\n cordis.effect(\n () => async () => {\n await collaborationFiber?.dispose();\n collaborationFiber = undefined;\n },\n '@agimon-ai/doompi-team/collaboration',\n );\n cordis.effect(\n () => () => {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n },\n '@agimon-ai/doompi-team/result-watcher',\n );\n\n const unregisterStaleRunReconciler = pollScheduler.register({\n id: 'stale-run-reconciler',\n // Crash detection, not status freshness - `AsyncJobTracker` owns that and\n // polls on its own 250ms. This subscriber only ever acts on a run whose\n // process is provably dead, so noticing that 2s later is invisible, while\n // running it 4x/second per active run was not: each pass is a status read,\n // a run-id resolve and a pid probe that a healthy child always discards.\n intervalMs: STALE_RUN_RECONCILE_INTERVAL_MS,\n run: async () => {\n let repaired = false;\n let hasActiveRun = false;\n for (const job of currentSessionJobs?.list() ?? []) {\n if (job.status && TERMINAL_ASYNC_JOB_STATES.has(job.status)) continue;\n hasActiveRun = true;\n // One pass, one `inspect()`. Calling `reconcile` and\n // `sweepOrphanedClaims` separately probed the same pid twice for the\n // same answer - see `StaleRunReconcilerContract.reconcileAndSweep`.\n const { reconcile: outcome, sweep } = staleRunReconciler.reconcileAndSweepAsync\n ? await staleRunReconciler.reconcileAndSweepAsync(job.runId)\n : staleRunReconciler.reconcileAndSweep(job.runId);\n if (outcome.repaired) {\n currentSessionJobs?.track(job.runId);\n repaired = true;\n }\n if (sweep.recovered.length > 0) repaired = true;\n }\n // An active runner is useful work for scheduling purposes: keeping\n // the shared scheduler at its floor prevents crash detection from\n // inheriting the idle five-second backoff ceiling.\n return repaired || hasActiveRun;\n },\n });\n cordis.effect(() => unregisterStaleRunReconciler, '@agimon-ai/doompi-team/stale-runs');\n // The orchestration addendum. Registered here rather than inside the\n // `subagent` tool's description because it is behavioural guidance, and it\n // has to land before the model decides whether to reach for the tool at\n // all - see `orchestratorPrompt.ts`. Read config per turn so toggling it\n // does not need a session restart; `loadConfig` is memoized on the file's\n // own mtime, so this is a stat, not a read.\n pi.on('before_agent_start', (event: { systemPrompt?: string }) => {\n if (!active || !shouldInjectOrchestratorPrompt(loadConfig().config)) return undefined;\n return { systemPrompt: appendOrchestratorPrompt(event.systemPrompt) };\n });\n\n const state: SlashCommandState = { baseCwd: undefined };\n\n /**\n * Publish what this session can launch, for the cockpit hub to read.\n *\n * The hub and the session API run in `doompi-server`, which never receives\n * the domain projection's agent directories or the harness state behind the\n * Team model policy, so discovery run there misses every domain-provided\n * agent. This process has both. Republished on each turn so an agent file\n * added mid-session shows up without a restart.\n */\n const publishCatalogSnapshot = (cwd: string, sessionId: string): void => {\n // A spawned child shares its parent's scope directory; only the root\n // session may write the catalog the user is looking at.\n if (resolveRootSessionId(sessionId) !== sessionId) return;\n try {\n writeSessionCatalogSnapshot(sessionId, {\n cwd,\n agents: presentCatalog(discovery.discover(cwd, 'both').agents),\n models: resolveActiveTeamModelSpecs() ?? [],\n });\n } catch {\n // Best effort: the hub falls back to its own discovery, and every launch\n // path reports discovery failures on its own.\n }\n };\n\n pi.on('before_agent_start', (_event: { systemPrompt?: string }, ctx: ExtensionContext) => {\n if (!active) return undefined;\n publishCatalogSnapshot(ctx.cwd, ctx.sessionManager.getSessionId());\n return undefined;\n });\n const slashCommandDeps = buildSlashCommandDeps(\n discovery,\n skills,\n spawnPlanner,\n asyncJobTracker,\n pollScheduler,\n management,\n );\n registerSlashCommands(readinessPi, state, slashCommandDeps);\n registerAgentListCommand(readinessPi, {\n discovery,\n skills,\n policies: capabilityPolicies,\n // The catalog's r/R keys spawn through the same path as `/run`, so a run\n // started from the overlay is tracked and reported like any other.\n launchAgent: (ctx, request) =>\n startSingleAgentRun(readinessPi, ctx, state, slashCommandDeps, {\n agent: request.agent,\n task: request.task,\n fork: request.context === 'fork',\n }),\n });\n registerFleetCommand(readinessPi, {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n management,\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n return registerAgentStatus(pi, requireDoomUiHub(uiContext), {\n scheduler: pollScheduler,\n tracker: asyncJobTracker,\n });\n });\n registerCompletionRenderer(pi);\n registerSlashRunRenderer(pi);\n\n const retireSessionBindings = async (): Promise<Error | undefined> => {\n const errors: unknown[] = [];\n const previousCollaboration = collaborationFiber;\n collaborationFiber = undefined;\n try {\n await previousCollaboration?.dispose();\n } catch (error) {\n errors.push(error);\n }\n for (const cleanup of [\n () => capabilityPolicies.clear(),\n () => delegationBridge.abandonAll(),\n () => fablePlanBridge.abandonAll(),\n () => teamRuntime.dispose(),\n ]) {\n try {\n cleanup();\n } catch (error) {\n errors.push(error);\n }\n }\n currentSessionJobs = undefined;\n if (errors.length === 0) return undefined;\n if (errors.length === 1 && errors[0] instanceof Error) return errors[0];\n return new AggregateError(errors, 'Team session cleanup failed.');\n };\n\n // Bind the root session directly into the one intercom transport. Child\n // processes bind from the team environment written during spawn.\n pi.on('session_start', (_event, ctx: ExtensionContext) => {\n if (!active) return undefined;\n const ownGeneration = ++sessionGeneration;\n const previousReadiness = readinessHandle;\n readinessAbort?.abort(new Error('Doom Team session initialization was superseded.'));\n const ownReadinessAbort = new AbortController();\n readinessAbort = ownReadinessAbort;\n readinessSessionManager = ctx.sessionManager;\n readinessNotificationContext = ctx;\n const activeTelemetry = getTelemetry(ctx);\n void activeTelemetry.recordEvent('doom_team.session_started', {\n 'team.root_session':\n resolveRootSessionId(ctx.sessionManager.getSessionId()) === ctx.sessionManager.getSessionId(),\n });\n // Before anything else: every scoped path helper reads this. A child\n // keeps the root inherited from its spawn environment, while a top-level\n // session falls back to its own Pi session id.\n const sessionId = ctx.sessionManager.getSessionId();\n const scope = createSessionScope(resolveRootSessionId(sessionId));\n setCurrentSessionScope(scope);\n state.baseCwd = ctx.cwd;\n publishCatalogSnapshot(ctx.cwd, sessionId);\n if (watchedScopeKey !== undefined && watchedScopeKey !== scope.scopeKey) {\n resultWatcher.stop();\n watchedScopeKey = undefined;\n }\n const retirement = retireSessionBindings();\n const coordinator = coordinatorFor(ctx);\n const ownsSession = (): boolean =>\n active &&\n ownGeneration === sessionGeneration &&\n readinessSessionManager === ctx.sessionManager &&\n !ownReadinessAbort.signal.aborted;\n\n readinessHandle = (async (): Promise<DoomReadinessHandle<void>> => {\n if (previousReadiness) {\n await Promise.allSettled([previousReadiness.then((handle) => handle.wait())]);\n }\n if (!ownsSession()) throw new Error('Doom Team session initialization was superseded.');\n return coordinator.start(PACKAGE_SOURCE, `${sessionId}:${ownGeneration}`, async (signal) => {\n signal.throwIfAborted();\n const retirementError = await retirement;\n if (retirementError) throw retirementError;\n if (!ownsSession()) return { value: undefined };\n // Claim the scope for this process, so a later sweep can tell an\n // abandoned tree from a live one. Rewritten on every session_start\n // because /resume legitimately re-adopts a scope under a new process.\n await writeScopeOwnerAsync(scope);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n // Prune records whose process is gone, reap sibling scopes holding\n // nothing alive, and REPORT what is suspended. Nothing is restarted:\n // reopening a session must not silently spend tokens on stale work.\n const opened = await openScopeAsync(scope, {\n readStatusAsync: async (runId) =>\n management.statusAsync ? (await management.statusAsync(runId)).status : management.status(runId).status,\n });\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n if (opened.suspended.length > 0 && ctx.hasUI) {\n const message = await formatSuspendedRunsAsync(opened.suspended);\n signal.throwIfAborted();\n if (!ownsSession()) return { value: undefined };\n ctx.ui.notify(message, 'info');\n }\n void activeTelemetry.recordEvent('doom_team.runs_restored', {\n 'team.suspended_count': opened.suspended.length,\n 'team.pruned_count': opened.pruned.length,\n 'team.reaped_count': opened.reaped,\n outcome: 'reported',\n });\n currentSessionJobs = asyncJobTracker.forSession(sessionId);\n // ResultWatcher needs the session scope, but a repeated start for the\n // same scope must not reset its in-flight claims.\n if (watchedScopeKey !== scope.scopeKey) {\n watchedScopeKey = scope.scopeKey;\n resultWatcher.start(async (result) => {\n const jobs = currentSessionJobs;\n if (!jobs?.get(result.runId)) return false;\n const accepted = await completionNotifier.deliver(result);\n if (accepted) asyncJobTracker.acknowledgeHandoff(sessionId, result.runId);\n return accepted;\n });\n }\n const parentModel = normalizeParentModel(ctx.model);\n const activeSession = activeCordisSession;\n const collaborationParent =\n activeSession?.service.context.sessionManager === ctx.sessionManager ? activeSession.cordis : cordis;\n const nextCollaboration = collaborationParent.plugin(teamCollaborationPlugin, {\n session: {\n sessionId,\n cwd: ctx.cwd,\n availableModels: authenticatedModelInfos(ctx.modelRegistry),\n ...(parentModel ? { parentModel } : {}),\n captureForkSource: () => captureSessionForkSource(ctx.sessionManager, 'tool'),\n },\n directRunTracker: asyncJobTracker,\n delegation: delegationBridge,\n fablePlan: fablePlanBridge,\n policies: capabilityPolicies,\n observeDelegation: (observation) => recordDelegationObservation(activeTelemetry, observation),\n });\n collaborationFiber = nextCollaboration;\n await nextCollaboration.await();\n if (!ownsSession()) {\n await nextCollaboration.dispose();\n if (collaborationFiber === nextCollaboration) collaborationFiber = undefined;\n return { value: undefined };\n }\n teamRuntime.bindMainSession(sessionId);\n return { value: undefined };\n });\n })();\n // Config's coordinator owns the single user-facing failure notification.\n void Promise.allSettled([readinessHandle.then((handle) => handle.wait())]);\n return undefined;\n });\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team runtime was disposed.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n if (ownedReadiness) await ownedReadiness.dispose();\n else if (pendingReadiness) await Promise.allSettled([pendingReadiness.then((handle) => handle.wait())]);\n readinessNotificationContext = undefined;\n },\n '@agimon-ai/doompi-team/session-fence',\n );\n\n /**\n * Every reason suspends, with no exception for `reload`.\n *\n * Pi emits this for `quit`, `reload`, `new`, `resume` and `fork`. Treating\n * `reload` differently - keeping children alive because `/domains`, `/major-mode`\n * and `/profile` only reload the extension - would be the one case someone\n * has to remember, and the one that silently misbehaves if Pi adds a sixth\n * reason. Suspending uniformly costs a respawn on reload; restore continues\n * the child's own transcript rather than restarting its task.\n */\n let shutdownPromise: Promise<void> | undefined;\n const shutdownSession = async (event: { reason?: string }): Promise<void> => {\n active = false;\n sessionGeneration += 1;\n const pendingReadiness = readinessHandle;\n readinessHandle = undefined;\n readinessSessionManager = undefined;\n readinessAbort?.abort(new Error('Doom Team session ended.'));\n readinessAbort = undefined;\n const ownedReadiness = standaloneReadiness;\n standaloneReadiness = undefined;\n await Promise.allSettled([\n ...(pendingReadiness ? [pendingReadiness.then((handle) => handle.wait())] : []),\n ...(ownedReadiness ? [ownedReadiness.dispose()] : []),\n ]);\n const scope = tryCurrentSessionScope();\n const suspensionPromise = scope\n ? suspendScopeRuns({\n scope,\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n readStatus: (runId) => management.status(runId).status,\n })\n : Promise.resolve(undefined);\n let suspension: Awaited<ReturnType<typeof suspendScopeRuns>> | undefined;\n try {\n suspension = await suspensionPromise;\n await telemetry?.recordEvent('doom_team.session_finished', {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n 'team.suspended_count': suspension?.suspended.length ?? 0,\n 'team.unstoppable_count': suspension?.unstoppable.length ?? 0,\n outcome: suspension?.unstoppable.length ? 'degraded' : 'suspended',\n });\n } catch (error) {\n await telemetry?.recordError('doom_team.session_shutdown_failed', error, {\n reason: event.reason ?? SESSION_SHUTDOWN_REASON_FALLBACK,\n });\n } finally {\n await telemetry?.shutdown();\n telemetry = undefined;\n }\n };\n pi.on('session_shutdown', (event: { reason?: string }) => {\n shutdownPromise ??= shutdownSession(event);\n return shutdownPromise;\n });\n\n return runtime;\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n,r){let i=t.register({provider:`team-direct-runs`,listActiveWork:()=>r.listBackgroundWork(n).map(e=>({id:e.runId,sessionId:n,...e.agent?{label:e.agent}:{},...e.status?{status:e.status}:{}}))}),a=r.subscribe(n,()=>i.update());e.effect(()=>()=>{a(),i.dispose()},`@agimon-ai/doompi-team/direct-run-background-work`)}exports.registerDirectRunBackgroundWork=e;
2
+ //# sourceMappingURL=directRunBackgroundWork.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directRunBackgroundWork.cjs","names":[],"sources":["../../../../../src/adapters/pi/extensions/directRunBackgroundWork.ts"],"sourcesContent":["import type {\n BackgroundProviderWorkItem,\n DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { AsyncJobTracker } from '../../asyncJobTracker';\n\nconst PROVIDER_NAME = 'team-direct-runs';\n\n/** Publish direct Team runs owned by exactly one Pi session. */\nexport function registerDirectRunBackgroundWork(\n ctx: Context,\n service: DoomBackgroundWorkService,\n sessionId: string,\n tracker: Pick<AsyncJobTracker, 'listBackgroundWork' | 'subscribe'>,\n): void {\n const registration = service.register({\n provider: PROVIDER_NAME,\n listActiveWork: (): BackgroundProviderWorkItem[] =>\n tracker.listBackgroundWork(sessionId).map((job) => ({\n id: job.runId,\n sessionId,\n ...(job.agent ? { label: job.agent } : {}),\n ...(job.status ? { status: job.status } : {}),\n })),\n });\n const unsubscribe = tracker.subscribe(sessionId, () => registration.update());\n ctx.effect(\n () => () => {\n unsubscribe();\n registration.dispose();\n },\n '@agimon-ai/doompi-team/direct-run-background-work',\n );\n}\n"],"mappings":"AAUA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAe,EAAQ,SAAS,CACpC,SAAU,mBACV,mBACE,EAAQ,mBAAmB,CAAS,CAAC,CAAC,IAAK,IAAS,CAClD,GAAI,EAAI,MACR,YACA,GAAI,EAAI,MAAQ,CAAE,MAAO,EAAI,KAAM,EAAI,CAAC,EACxC,GAAI,EAAI,OAAS,CAAE,OAAQ,EAAI,MAAO,EAAI,CAAC,CAC7C,EAAE,CACN,CAAC,EACK,EAAc,EAAQ,UAAU,MAAiB,EAAa,OAAO,CAAC,EAC5E,EAAI,eACU,CACV,EAAY,EACZ,EAAa,QAAQ,CACvB,EACA,mDACF,CACF"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n,r){let i=t.register({provider:`team-direct-runs`,listActiveWork:()=>r.listBackgroundWork(n).map(e=>({id:e.runId,sessionId:n,...e.agent?{label:e.agent}:{},...e.status?{status:e.status}:{}}))}),a=r.subscribe(n,()=>i.update());e.effect(()=>()=>{a(),i.dispose()},`@agimon-ai/doompi-team/direct-run-background-work`)}export{e as registerDirectRunBackgroundWork};
2
+ //# sourceMappingURL=directRunBackgroundWork.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"directRunBackgroundWork.mjs","names":[],"sources":["../../../../../src/adapters/pi/extensions/directRunBackgroundWork.ts"],"sourcesContent":["import type {\n BackgroundProviderWorkItem,\n DoomBackgroundWorkService,\n} from '@agimon-ai/doompi-extension-contracts/background-work';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { AsyncJobTracker } from '../../asyncJobTracker';\n\nconst PROVIDER_NAME = 'team-direct-runs';\n\n/** Publish direct Team runs owned by exactly one Pi session. */\nexport function registerDirectRunBackgroundWork(\n ctx: Context,\n service: DoomBackgroundWorkService,\n sessionId: string,\n tracker: Pick<AsyncJobTracker, 'listBackgroundWork' | 'subscribe'>,\n): void {\n const registration = service.register({\n provider: PROVIDER_NAME,\n listActiveWork: (): BackgroundProviderWorkItem[] =>\n tracker.listBackgroundWork(sessionId).map((job) => ({\n id: job.runId,\n sessionId,\n ...(job.agent ? { label: job.agent } : {}),\n ...(job.status ? { status: job.status } : {}),\n })),\n });\n const unsubscribe = tracker.subscribe(sessionId, () => registration.update());\n ctx.effect(\n () => () => {\n unsubscribe();\n registration.dispose();\n },\n '@agimon-ai/doompi-team/direct-run-background-work',\n );\n}\n"],"mappings":"AAUA,SAAgB,EACd,EACA,EACA,EACA,EACM,CACN,IAAM,EAAe,EAAQ,SAAS,CACpC,SAAU,mBACV,mBACE,EAAQ,mBAAmB,CAAS,CAAC,CAAC,IAAK,IAAS,CAClD,GAAI,EAAI,MACR,YACA,GAAI,EAAI,MAAQ,CAAE,MAAO,EAAI,KAAM,EAAI,CAAC,EACxC,GAAI,EAAI,OAAS,CAAE,OAAQ,EAAI,MAAO,EAAI,CAAC,CAC7C,EAAE,CACN,CAAC,EACK,EAAc,EAAQ,UAAU,MAAiB,EAAa,OAAO,CAAC,EAC5E,EAAI,eACU,CACV,EAAY,EACZ,EAAa,QAAQ,CACvB,EACA,mDACF,CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-team",
3
- "version": "0.0.1-alpha.53",
3
+ "version": "0.0.1-alpha.55",
4
4
  "description": "Asynchronous named subagents, team runs, intercom, and model policy for Pi coding agents.",
5
5
  "keywords": [
6
6
  "agent-team",
@@ -56,13 +56,13 @@
56
56
  "@deepseek-ai/cordis": "4.0.1",
57
57
  "typebox": "1.3.19",
58
58
  "yaml": "2.9.0",
59
- "@agimon-ai/doompi-cache": "0.0.1-alpha.19",
60
- "@agimon-ai/doompi-config": "0.0.1-alpha.52",
61
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.52",
62
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.52",
63
- "@agimon-ai/doompi-ui": "0.0.1-alpha.53",
64
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.13",
65
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.16"
59
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.21",
60
+ "@agimon-ai/doompi-config": "0.0.1-alpha.54",
61
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.54",
62
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.54",
63
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.15",
64
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.18",
65
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.55"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@earendil-works/pi-agent-core": "0.84.4",
@@ -40,6 +40,7 @@ export function LaunchAgentDialog({
40
40
  cwd,
41
41
  models,
42
42
  fork: initialFork,
43
+ initialTask,
43
44
  send,
44
45
  onClose,
45
46
  onLaunched,
@@ -49,11 +50,12 @@ export function LaunchAgentDialog({
49
50
  cwd: string;
50
51
  models: readonly string[];
51
52
  fork: boolean;
53
+ initialTask: string;
52
54
  send: SessionFrameSender;
53
55
  onClose: () => void;
54
56
  onLaunched: () => void;
55
57
  }) {
56
- const [task, setTask] = useState('');
58
+ const [task, setTask] = useState(initialTask);
57
59
  const [fork, setFork] = useState(initialFork);
58
60
  const [model, setModel] = useState(AGENT_DEFAULT);
59
61
  const request: LaunchRequest = { agent: agent.name, task, fork, ...(model === AGENT_DEFAULT ? {} : { model }) };
@@ -472,6 +472,7 @@ export function SubagentsPanel({
472
472
  cwd={shelf.cwd}
473
473
  models={shelf.models}
474
474
  fork={shelf.launch.fork}
475
+ initialTask={shelf.task}
475
476
  send={sendSessionFrame}
476
477
  onClose={() => closeLaunch(sessionId)}
477
478
  onLaunched={() => closeCatalog(sessionId)}
@@ -1,4 +1,4 @@
1
- import { defineSessionStore } from '@agimon-ai/doompi-web-contracts';
1
+ import { defineSessionStore, type ContextActionRunContext, type TransientTab } from '@agimon-ai/doompi-web-contracts';
2
2
  import {
3
3
  SUBAGENT_CATALOG_TYPE,
4
4
  type SubagentCatalogAgent,
@@ -14,6 +14,8 @@ export interface CatalogSession {
14
14
  /** True while the catalog drawer is shown in the subagents tab. */
15
15
  open: boolean;
16
16
  filter: string;
17
+ /** Task text carried from the surface that opened the catalog. */
18
+ task: string;
17
19
  /** The highlighted row, by agent name. */
18
20
  selected: string | undefined;
19
21
  /** The row whose resources are unfolded. */
@@ -29,6 +31,7 @@ export const catalog = defineSessionStore<CatalogSession>({
29
31
  warning: undefined,
30
32
  open: false,
31
33
  filter: '',
34
+ task: '',
32
35
  selected: undefined,
33
36
  inspected: undefined,
34
37
  launch: undefined,
@@ -38,12 +41,19 @@ function isRecord(value: unknown): value is Record<string, unknown> {
38
41
  return typeof value === 'object' && value !== null && !Array.isArray(value);
39
42
  }
40
43
 
41
- export function openCatalog(sessionId: string): void {
42
- catalog.update(sessionId, (current) => ({ ...current, open: true }));
44
+ export function openCatalog(sessionId: string, task = ''): void {
45
+ catalog.update(sessionId, (current) => ({ ...current, open: true, task }));
46
+ }
47
+
48
+ /** Opens the Team picker with context supplied by another installed plugin. */
49
+ export function openAgentCatalogForContext(context: ContextActionRunContext, tab: () => TransientTab): void {
50
+ if (context.sessionId === null) return;
51
+ openCatalog(context.sessionId, context.item.content);
52
+ context.openTransientTab(tab());
43
53
  }
44
54
 
45
55
  export function closeCatalog(sessionId: string): void {
46
- catalog.update(sessionId, (current) => ({ ...current, open: false, launch: undefined }));
56
+ catalog.update(sessionId, (current) => ({ ...current, open: false, task: '', launch: undefined }));
47
57
  }
48
58
 
49
59
  export function setCatalogFilter(sessionId: string, filter: string): void {
@@ -89,6 +99,7 @@ export const subagentCatalogChannel = catalog.channel<SubagentCatalogPayload>({
89
99
  warning: payload.warning,
90
100
  selected: current.selected !== undefined && names.has(current.selected) ? current.selected : undefined,
91
101
  inspected: current.inspected !== undefined && names.has(current.inspected) ? current.inspected : undefined,
102
+ launch: current.launch !== undefined && names.has(current.launch.agent) ? current.launch : undefined,
92
103
  };
93
104
  },
94
105
  });
package/src/web/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
2
2
  import { AgentsActivitySection } from './AgentsActivitySection.tsx';
3
3
  import { subagentsTab } from './SubagentsPanel.tsx';
4
- import { openCatalog, subagentCatalogChannel } from './catalogStore.ts';
4
+ import { openAgentCatalogForContext, openCatalog, subagentCatalogChannel } from './catalogStore.ts';
5
5
  import { RUN_ACTIONS_SLOT } from './runActionsSlot.ts';
6
6
  import { activityRuns, isTerminalRun, subagentRunsChannel, subagents } from './subagentsStore.ts';
7
7
  import { teamToolRenderers } from './toolRenderers.ts';
@@ -25,6 +25,16 @@ const agentsActivitySource = {
25
25
  export const webPlugin = defineWebPlugin({
26
26
  id: 'subagents',
27
27
  channels: [subagentRunsChannel, subagentCatalogChannel],
28
+ contextActions: [
29
+ {
30
+ id: 'launch-agent',
31
+ label: 'Launch Agent',
32
+ detail: 'Choose an agent, review the task, then launch it.',
33
+ kinds: ['work-item'],
34
+ order: 10,
35
+ run: (context) => openAgentCatalogForContext(context, subagentsTab),
36
+ },
37
+ ],
28
38
  // No declared tab: the fleet is a panel the reader opens from the dock's
29
39
  // agents group, and closes when they are done with it.
30
40
  activityGroups: [