@agimon-ai/doompi-team 0.0.1-alpha.40 → 0.0.1-alpha.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,7 +13,7 @@ requests while Task remains the source of truth for task status and dependencies
13
13
 
14
14
  - Node.js 22.19.0 or newer
15
15
  - `@earendil-works/pi-agent-core`, `@earendil-works/pi-ai`,
16
- `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` 0.84.3
16
+ `@earendil-works/pi-coding-agent`, and `@earendil-works/pi-tui` 0.84.4
17
17
 
18
18
  ## Install
19
19
 
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/webSubagentRuns.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);let a=require("node:crypto");function o(e){if(e.uid===void 0)return;let t=(0,a.createHash)(`sha256`).update(e.sessionId.trim()).digest(`hex`).slice(0,16);return`${e.tmpdir}/doom-team-uid-${String(e.uid)}/sessions/${t}/runs`}function s(e,a){let s=o({sessionId:e,tmpdir:i.default.tmpdir(),uid:process.getuid?.()});if(s===void 0)return{close:()=>void 0};let c,l,u=`[]`,d=!1,f=()=>{let e;try{e=n.default.readdirSync(s)}catch{return[]}let i=[];for(let a of e){let e;try{e=n.default.readFileSync(r.default.join(s,a,t.RUN_STATUS_FILE_NAME),`utf8`)}catch{continue}let o=t.parseSubagentRun(e);o&&i.push(o)}return t.presentRuns(i,Date.now())},p=()=>{if(d)return;let e=f(),t=JSON.stringify(e);t!==u&&(u=t,a(e))},m=()=>{if(!(c||d))try{c=n.default.watch(s,{recursive:!0},()=>{l&&clearTimeout(l),l=setTimeout(p,60)}),c.on(`error`,()=>{c?.close(),c=void 0})}catch{}};m(),p();let h=setInterval(()=>{m(),p()},1e3);return{close(){d=!0,l&&clearTimeout(l),clearInterval(h),c?.close()}}}exports.teamRunsDirFor=o,exports.watchSubagentRuns=s;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../services/webSubagentRuns.cjs");let n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);let i=require("node:os");i=e.__toESM(i,1);let a=require("node:crypto");function o(e){if(e.uid===void 0)return;let t=(0,a.createHash)(`sha256`).update(e.sessionId.trim()).digest(`hex`).slice(0,16);return`${e.tmpdir}/doom-team-uid-${String(e.uid)}/sessions/${t}/runs`}function s(e,a){let s=o({sessionId:e,tmpdir:i.default.tmpdir(),uid:process.getuid?.()});if(s===void 0)return{close:()=>void 0};let c=r.default.dirname(s),l,u,d=`[]`,f=!1,p=()=>{let e;try{e=n.default.readdirSync(s)}catch{return[]}let i=[];for(let a of e){let e;try{e=n.default.readFileSync(r.default.join(s,a,t.RUN_STATUS_FILE_NAME),`utf8`)}catch{continue}let o=t.parseSubagentRun(e);o&&i.push(o)}let a;try{let e=n.default.readFileSync(r.default.join(c,t.RUN_REGISTRY_FILE_NAME),`utf8`);a=t.activeRunIdsFromRegistry(e,e=>{try{return process.kill(e,0),!0}catch(e){return e.code===`EPERM`}})}catch{}return t.presentRuns(t.reconcileRunLifecycles(i,a),Date.now())},m=()=>{if(f)return;let e=p(),t=JSON.stringify(e);t!==d&&(d=t,a(e))},h=()=>{if(!(l||f))try{l=n.default.watch(c,{recursive:!0},()=>{u&&clearTimeout(u),u=setTimeout(m,60)}),l.on(`error`,()=>{l?.close(),l=void 0})}catch{}};h(),m();let g=setInterval(()=>{h(),m()},1e3);return{close(){f=!0,u&&clearTimeout(u),clearInterval(g),l?.close()}}}exports.teamRunsDirFor=o,exports.watchSubagentRuns=s;
2
2
  //# sourceMappingURL=webSubagentWatcher.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentWatcher.cjs","names":["createHash","os","fs","path","RUN_STATUS_FILE_NAME","parseSubagentRun","presentRuns"],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { parseSubagentRun, presentRuns, RUN_STATUS_FILE_NAME } from '../services/webSubagentRuns.ts';\nimport type { SubagentRun } from '../types/webSubagents.ts';\n\nconst POLL_MS = 1000;\nconst DEBOUNCE_MS = 60;\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst RUNS_DIR_NAME = 'runs';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\nexport interface TeamRunsDirInput {\n /** The Pi session id, which doom-team scopes its runs by. */\n sessionId: string;\n /** os.tmpdir(), supplied by the caller. */\n tmpdir: string;\n /** process.getuid(), absent on platforms without one. */\n uid: number | undefined;\n}\n\n/** Where this package keeps a session's runs, or undefined when the user cannot be scoped. */\nexport function teamRunsDirFor(input: TeamRunsDirInput): string | undefined {\n if (input.uid === undefined) return undefined;\n const scopeKey = createHash('sha256').update(input.sessionId.trim()).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n return `${input.tmpdir}/${TEMP_ROOT_PREFIX}-uid-${String(input.uid)}/${SESSIONS_DIR_NAME}/${scopeKey}/${RUNS_DIR_NAME}`;\n}\n\nexport interface SubagentRunsSource {\n close(): void;\n}\n\n/**\n * Watches doom-team's per-session runs directory for one session.\n *\n * Same reliability posture as the registry watcher: the poll is the source of\n * truth, fs.watch is only an accelerator, and the directory not existing yet\n * (no run ever started) is a normal state, not an error. Emits the presented\n * run list whenever it changes, including the transition to empty.\n */\nexport function watchSubagentRuns(sessionId: string, onRuns: (runs: SubagentRun[]) => void): SubagentRunsSource {\n const runsDir = teamRunsDirFor({ sessionId, tmpdir: os.tmpdir(), uid: process.getuid?.() });\n if (runsDir === undefined) return { close: () => undefined };\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a session with no runs yet should not\n // announce an empty fleet, only a fleet that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n\n const scan = (): SubagentRun[] => {\n let names: string[];\n try {\n names = fs.readdirSync(runsDir);\n } catch {\n return []; // The directory appears with the first run.\n }\n const runs: SubagentRun[] = [];\n for (const name of names) {\n let raw: string;\n try {\n raw = fs.readFileSync(path.join(runsDir, name, RUN_STATUS_FILE_NAME), 'utf8');\n } catch {\n continue; // Not a run dir, or the status is mid-write; the next pass settles it.\n }\n const run = parseSubagentRun(raw);\n if (run) runs.push(run);\n }\n return presentRuns(runs, Date.now());\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(runsDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The directory does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"kQAwBA,SAAgB,EAAe,EAA6C,CAC1E,GAAI,EAAM,MAAQ,IAAA,GAAW,OAC7B,IAAM,GAAA,EAAWA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,EAAM,UAAU,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAqB,EACjH,MAAO,GAAG,EAAM,OAAO,iBAA2B,OAAO,EAAM,GAAG,EAAE,YAAwB,EAAS,MACvG,CAcA,SAAgB,EAAkB,EAAmB,EAA2D,CAC9G,IAAM,EAAU,EAAe,CAAE,YAAW,OAAQC,EAAAA,QAAG,OAAO,EAAG,IAAK,QAAQ,SAAS,CAAE,CAAC,EAC1F,GAAI,IAAY,IAAA,GAAW,MAAO,CAAE,UAAa,IAAA,EAAU,EAE3D,IAAI,EACA,EAGA,EAAkC,KAClC,EAAS,GAEP,MAA4B,CAChC,IAAI,EACJ,GAAI,CACF,EAAQC,EAAAA,QAAG,YAAY,CAAO,CAChC,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAsB,CAAC,EAC7B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAMA,EAAAA,QAAG,aAAaC,EAAAA,QAAK,KAAK,EAAS,EAAMC,EAAAA,oBAAoB,EAAG,MAAM,CAC9E,MAAQ,CACN,QACF,CACA,IAAM,EAAMC,EAAAA,iBAAiB,CAAG,EAC5B,GAAK,EAAK,KAAK,CAAG,CACxB,CACA,OAAOC,EAAAA,YAAY,EAAM,KAAK,IAAI,CAAC,CACrC,EAEM,MAAmB,CACvB,GAAI,EAAQ,OACZ,IAAM,EAAO,EAAK,EACZ,EAAc,KAAK,UAAU,CAAI,EACnC,IAAgB,IACpB,EAAc,EACd,EAAO,CAAI,EACb,EAEM,MAA4B,CAC5B,QAAW,GACf,GAAI,CACF,EAAUJ,EAAAA,QAAG,MAAM,EAAS,CAAE,UAAW,EAAK,MAAS,CACjD,GAAU,aAAa,CAAQ,EACnC,EAAW,WAAW,EAAM,EAAW,CACzC,CAAC,EACD,EAAQ,GAAG,YAAe,CACxB,GAAS,MAAM,EACf,EAAU,IAAA,EACZ,CAAC,CACH,MAAQ,CAER,CACF,EAEA,EAAc,EACd,EAAK,EACL,IAAM,EAAO,gBAAkB,CAC7B,EAAc,EACd,EAAK,CACP,EAAG,GAAO,EAEV,MAAO,CACL,OAAQ,CACN,EAAS,GACL,GAAU,aAAa,CAAQ,EACnC,cAAc,CAAI,EAClB,GAAS,MAAM,CACjB,CACF,CACF"}
1
+ {"version":3,"file":"webSubagentWatcher.cjs","names":["createHash","os","path","fs","RUN_STATUS_FILE_NAME","parseSubagentRun","RUN_REGISTRY_FILE_NAME","activeRunIdsFromRegistry","presentRuns","reconcileRunLifecycles"],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport {\n activeRunIdsFromRegistry,\n parseSubagentRun,\n presentRuns,\n reconcileRunLifecycles,\n RUN_REGISTRY_FILE_NAME,\n RUN_STATUS_FILE_NAME,\n} from '../services/webSubagentRuns.ts';\nimport type { SubagentRun } from '../types/webSubagents.ts';\n\nconst POLL_MS = 1000;\nconst DEBOUNCE_MS = 60;\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst RUNS_DIR_NAME = 'runs';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\nexport interface TeamRunsDirInput {\n /** The Pi session id, which doom-team scopes its runs by. */\n sessionId: string;\n /** os.tmpdir(), supplied by the caller. */\n tmpdir: string;\n /** process.getuid(), absent on platforms without one. */\n uid: number | undefined;\n}\n\n/** Where this package keeps a session's runs, or undefined when the user cannot be scoped. */\nexport function teamRunsDirFor(input: TeamRunsDirInput): string | undefined {\n if (input.uid === undefined) return undefined;\n const scopeKey = createHash('sha256').update(input.sessionId.trim()).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n return `${input.tmpdir}/${TEMP_ROOT_PREFIX}-uid-${String(input.uid)}/${SESSIONS_DIR_NAME}/${scopeKey}/${RUNS_DIR_NAME}`;\n}\n\nexport interface SubagentRunsSource {\n close(): void;\n}\n\n/**\n * Watches doom-team's per-session runs directory for one session.\n *\n * Same reliability posture as the registry watcher: the poll is the source of\n * truth, fs.watch is only an accelerator, and the directory not existing yet\n * (no run ever started) is a normal state, not an error. Emits the presented\n * run list whenever it changes, including the transition to empty.\n */\nexport function watchSubagentRuns(sessionId: string, onRuns: (runs: SubagentRun[]) => void): SubagentRunsSource {\n const runsDir = teamRunsDirFor({ sessionId, tmpdir: os.tmpdir(), uid: process.getuid?.() });\n if (runsDir === undefined) return { close: () => undefined };\n const scopeDir = path.dirname(runsDir);\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a session with no runs yet should not\n // announce an empty fleet, only a fleet that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n\n const scan = (): SubagentRun[] => {\n let names: string[];\n try {\n names = fs.readdirSync(runsDir);\n } catch {\n return []; // The directory appears with the first run.\n }\n const runs: SubagentRun[] = [];\n for (const name of names) {\n let raw: string;\n try {\n raw = fs.readFileSync(path.join(runsDir, name, RUN_STATUS_FILE_NAME), 'utf8');\n } catch {\n continue; // Not a run dir, or the status is mid-write; the next pass settles it.\n }\n const run = parseSubagentRun(raw);\n if (run) runs.push(run);\n }\n\n let activeRunIds: ReadonlySet<string> | undefined;\n try {\n const registry = fs.readFileSync(path.join(scopeDir, RUN_REGISTRY_FILE_NAME), 'utf8');\n activeRunIds = activeRunIdsFromRegistry(registry, (pid) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n });\n } catch {\n // Without a readable authoritative registry, preserve status snapshots.\n }\n return presentRuns(reconcileRunLifecycles(runs, activeRunIds), Date.now());\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(scopeDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The directory does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"kQA+BA,SAAgB,EAAe,EAA6C,CAC1E,GAAI,EAAM,MAAQ,IAAA,GAAW,OAC7B,IAAM,GAAA,EAAWA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,EAAM,UAAU,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAqB,EACjH,MAAO,GAAG,EAAM,OAAO,iBAA2B,OAAO,EAAM,GAAG,EAAE,YAAwB,EAAS,MACvG,CAcA,SAAgB,EAAkB,EAAmB,EAA2D,CAC9G,IAAM,EAAU,EAAe,CAAE,YAAW,OAAQC,EAAAA,QAAG,OAAO,EAAG,IAAK,QAAQ,SAAS,CAAE,CAAC,EAC1F,GAAI,IAAY,IAAA,GAAW,MAAO,CAAE,UAAa,IAAA,EAAU,EAC3D,IAAM,EAAWC,EAAAA,QAAK,QAAQ,CAAO,EAEjC,EACA,EAGA,EAAkC,KAClC,EAAS,GAEP,MAA4B,CAChC,IAAI,EACJ,GAAI,CACF,EAAQC,EAAAA,QAAG,YAAY,CAAO,CAChC,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAsB,CAAC,EAC7B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAMA,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAK,EAAS,EAAME,EAAAA,oBAAoB,EAAG,MAAM,CAC9E,MAAQ,CACN,QACF,CACA,IAAM,EAAMC,EAAAA,iBAAiB,CAAG,EAC5B,GAAK,EAAK,KAAK,CAAG,CACxB,CAEA,IAAI,EACJ,GAAI,CACF,IAAM,EAAWF,EAAAA,QAAG,aAAaD,EAAAA,QAAK,KAAK,EAAUI,EAAAA,sBAAsB,EAAG,MAAM,EACpF,EAAeC,EAAAA,yBAAyB,EAAW,GAAQ,CACzD,GAAI,CAEF,OADA,QAAQ,KAAK,EAAK,CAAC,EACZ,EACT,OAAS,EAAO,CACd,OAAQ,EAAgC,OAAS,OACnD,CACF,CAAC,CACH,MAAQ,CAER,CACA,OAAOC,EAAAA,YAAYC,EAAAA,uBAAuB,EAAM,CAAY,EAAG,KAAK,IAAI,CAAC,CAC3E,EAEM,MAAmB,CACvB,GAAI,EAAQ,OACZ,IAAM,EAAO,EAAK,EACZ,EAAc,KAAK,UAAU,CAAI,EACnC,IAAgB,IACpB,EAAc,EACd,EAAO,CAAI,EACb,EAEM,MAA4B,CAC5B,QAAW,GACf,GAAI,CACF,EAAUN,EAAAA,QAAG,MAAM,EAAU,CAAE,UAAW,EAAK,MAAS,CAClD,GAAU,aAAa,CAAQ,EACnC,EAAW,WAAW,EAAM,EAAW,CACzC,CAAC,EACD,EAAQ,GAAG,YAAe,CACxB,GAAS,MAAM,EACf,EAAU,IAAA,EACZ,CAAC,CACH,MAAQ,CAER,CACF,EAEA,EAAc,EACd,EAAK,EACL,IAAM,EAAO,gBAAkB,CAC7B,EAAc,EACd,EAAK,CACP,EAAG,GAAO,EAEV,MAAO,CACL,OAAQ,CACN,EAAS,GACL,GAAU,aAAa,CAAQ,EACnC,cAAc,CAAI,EAClB,GAAS,MAAM,CACjB,CACF,CACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentWatcher.d.cts","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"mappings":";;UA8BiB;EACf;;;;;;;;;;iBAWc,kBAAkB,mBAAmB,SAAS,MAAM,yBAAyB"}
1
+ {"version":3,"file":"webSubagentWatcher.d.cts","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"mappings":";;UAqCiB;EACf;;;;;;;;;;iBAWc,kBAAkB,mBAAmB,SAAS,MAAM,yBAAyB"}
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentWatcher.d.mts","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"mappings":";;UA8BiB;EACf;;;;;;;;;;iBAWc,kBAAkB,mBAAmB,SAAS,MAAM,yBAAyB"}
1
+ {"version":3,"file":"webSubagentWatcher.d.mts","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"mappings":";;UAqCiB;EACf;;;;;;;;;;iBAWc,kBAAkB,mBAAmB,SAAS,MAAM,yBAAyB"}
@@ -1,2 +1,2 @@
1
- import{RUN_STATUS_FILE_NAME as e,parseSubagentRun as t,presentRuns as n}from"../services/webSubagentRuns.mjs";import r from"node:fs";import i from"node:path";import a from"node:os";import{createHash as o}from"node:crypto";function s(e){if(e.uid===void 0)return;let t=o(`sha256`).update(e.sessionId.trim()).digest(`hex`).slice(0,16);return`${e.tmpdir}/doom-team-uid-${String(e.uid)}/sessions/${t}/runs`}function c(o,c){let l=s({sessionId:o,tmpdir:a.tmpdir(),uid:process.getuid?.()});if(l===void 0)return{close:()=>void 0};let u,d,f=`[]`,p=!1,m=()=>{let a;try{a=r.readdirSync(l)}catch{return[]}let o=[];for(let n of a){let a;try{a=r.readFileSync(i.join(l,n,e),`utf8`)}catch{continue}let s=t(a);s&&o.push(s)}return n(o,Date.now())},h=()=>{if(p)return;let e=m(),t=JSON.stringify(e);t!==f&&(f=t,c(e))},g=()=>{if(!(u||p))try{u=r.watch(l,{recursive:!0},()=>{d&&clearTimeout(d),d=setTimeout(h,60)}),u.on(`error`,()=>{u?.close(),u=void 0})}catch{}};g(),h();let _=setInterval(()=>{g(),h()},1e3);return{close(){p=!0,d&&clearTimeout(d),clearInterval(_),u?.close()}}}export{s as teamRunsDirFor,c as watchSubagentRuns};
1
+ import{RUN_REGISTRY_FILE_NAME as e,RUN_STATUS_FILE_NAME as t,activeRunIdsFromRegistry as n,parseSubagentRun as r,presentRuns as i,reconcileRunLifecycles as a}from"../services/webSubagentRuns.mjs";import o from"node:fs";import s from"node:path";import c from"node:os";import{createHash as l}from"node:crypto";function u(e){if(e.uid===void 0)return;let t=l(`sha256`).update(e.sessionId.trim()).digest(`hex`).slice(0,16);return`${e.tmpdir}/doom-team-uid-${String(e.uid)}/sessions/${t}/runs`}function d(l,d){let f=u({sessionId:l,tmpdir:c.tmpdir(),uid:process.getuid?.()});if(f===void 0)return{close:()=>void 0};let p=s.dirname(f),m,h,g=`[]`,_=!1,v=()=>{let c;try{c=o.readdirSync(f)}catch{return[]}let l=[];for(let e of c){let n;try{n=o.readFileSync(s.join(f,e,t),`utf8`)}catch{continue}let i=r(n);i&&l.push(i)}let u;try{let t=o.readFileSync(s.join(p,e),`utf8`);u=n(t,e=>{try{return process.kill(e,0),!0}catch(e){return e.code===`EPERM`}})}catch{}return i(a(l,u),Date.now())},y=()=>{if(_)return;let e=v(),t=JSON.stringify(e);t!==g&&(g=t,d(e))},b=()=>{if(!(m||_))try{m=o.watch(p,{recursive:!0},()=>{h&&clearTimeout(h),h=setTimeout(y,60)}),m.on(`error`,()=>{m?.close(),m=void 0})}catch{}};b(),y();let x=setInterval(()=>{b(),y()},1e3);return{close(){_=!0,h&&clearTimeout(h),clearInterval(x),m?.close()}}}export{u as teamRunsDirFor,d as watchSubagentRuns};
2
2
  //# sourceMappingURL=webSubagentWatcher.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentWatcher.mjs","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport { parseSubagentRun, presentRuns, RUN_STATUS_FILE_NAME } from '../services/webSubagentRuns.ts';\nimport type { SubagentRun } from '../types/webSubagents.ts';\n\nconst POLL_MS = 1000;\nconst DEBOUNCE_MS = 60;\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst RUNS_DIR_NAME = 'runs';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\nexport interface TeamRunsDirInput {\n /** The Pi session id, which doom-team scopes its runs by. */\n sessionId: string;\n /** os.tmpdir(), supplied by the caller. */\n tmpdir: string;\n /** process.getuid(), absent on platforms without one. */\n uid: number | undefined;\n}\n\n/** Where this package keeps a session's runs, or undefined when the user cannot be scoped. */\nexport function teamRunsDirFor(input: TeamRunsDirInput): string | undefined {\n if (input.uid === undefined) return undefined;\n const scopeKey = createHash('sha256').update(input.sessionId.trim()).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n return `${input.tmpdir}/${TEMP_ROOT_PREFIX}-uid-${String(input.uid)}/${SESSIONS_DIR_NAME}/${scopeKey}/${RUNS_DIR_NAME}`;\n}\n\nexport interface SubagentRunsSource {\n close(): void;\n}\n\n/**\n * Watches doom-team's per-session runs directory for one session.\n *\n * Same reliability posture as the registry watcher: the poll is the source of\n * truth, fs.watch is only an accelerator, and the directory not existing yet\n * (no run ever started) is a normal state, not an error. Emits the presented\n * run list whenever it changes, including the transition to empty.\n */\nexport function watchSubagentRuns(sessionId: string, onRuns: (runs: SubagentRun[]) => void): SubagentRunsSource {\n const runsDir = teamRunsDirFor({ sessionId, tmpdir: os.tmpdir(), uid: process.getuid?.() });\n if (runsDir === undefined) return { close: () => undefined };\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a session with no runs yet should not\n // announce an empty fleet, only a fleet that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n\n const scan = (): SubagentRun[] => {\n let names: string[];\n try {\n names = fs.readdirSync(runsDir);\n } catch {\n return []; // The directory appears with the first run.\n }\n const runs: SubagentRun[] = [];\n for (const name of names) {\n let raw: string;\n try {\n raw = fs.readFileSync(path.join(runsDir, name, RUN_STATUS_FILE_NAME), 'utf8');\n } catch {\n continue; // Not a run dir, or the status is mid-write; the next pass settles it.\n }\n const run = parseSubagentRun(raw);\n if (run) runs.push(run);\n }\n return presentRuns(runs, Date.now());\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(runsDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The directory does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"8NAwBA,SAAgB,EAAe,EAA6C,CAC1E,GAAI,EAAM,MAAQ,IAAA,GAAW,OAC7B,IAAM,EAAW,EAAW,QAAQ,CAAC,CAAC,OAAO,EAAM,UAAU,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAqB,EACjH,MAAO,GAAG,EAAM,OAAO,iBAA2B,OAAO,EAAM,GAAG,EAAE,YAAwB,EAAS,MACvG,CAcA,SAAgB,EAAkB,EAAmB,EAA2D,CAC9G,IAAM,EAAU,EAAe,CAAE,YAAW,OAAQ,EAAG,OAAO,EAAG,IAAK,QAAQ,SAAS,CAAE,CAAC,EAC1F,GAAI,IAAY,IAAA,GAAW,MAAO,CAAE,UAAa,IAAA,EAAU,EAE3D,IAAI,EACA,EAGA,EAAkC,KAClC,EAAS,GAEP,MAA4B,CAChC,IAAI,EACJ,GAAI,CACF,EAAQ,EAAG,YAAY,CAAO,CAChC,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAsB,CAAC,EAC7B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAK,KAAK,EAAS,EAAM,CAAoB,EAAG,MAAM,CAC9E,MAAQ,CACN,QACF,CACA,IAAM,EAAM,EAAiB,CAAG,EAC5B,GAAK,EAAK,KAAK,CAAG,CACxB,CACA,OAAO,EAAY,EAAM,KAAK,IAAI,CAAC,CACrC,EAEM,MAAmB,CACvB,GAAI,EAAQ,OACZ,IAAM,EAAO,EAAK,EACZ,EAAc,KAAK,UAAU,CAAI,EACnC,IAAgB,IACpB,EAAc,EACd,EAAO,CAAI,EACb,EAEM,MAA4B,CAC5B,QAAW,GACf,GAAI,CACF,EAAU,EAAG,MAAM,EAAS,CAAE,UAAW,EAAK,MAAS,CACjD,GAAU,aAAa,CAAQ,EACnC,EAAW,WAAW,EAAM,EAAW,CACzC,CAAC,EACD,EAAQ,GAAG,YAAe,CACxB,GAAS,MAAM,EACf,EAAU,IAAA,EACZ,CAAC,CACH,MAAQ,CAER,CACF,EAEA,EAAc,EACd,EAAK,EACL,IAAM,EAAO,gBAAkB,CAC7B,EAAc,EACd,EAAK,CACP,EAAG,GAAO,EAEV,MAAO,CACL,OAAQ,CACN,EAAS,GACL,GAAU,aAAa,CAAQ,EACnC,cAAc,CAAI,EAClB,GAAS,MAAM,CACjB,CACF,CACF"}
1
+ {"version":3,"file":"webSubagentWatcher.mjs","names":[],"sources":["../../../src/adapters/webSubagentWatcher.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { createHash } from 'node:crypto';\nimport {\n activeRunIdsFromRegistry,\n parseSubagentRun,\n presentRuns,\n reconcileRunLifecycles,\n RUN_REGISTRY_FILE_NAME,\n RUN_STATUS_FILE_NAME,\n} from '../services/webSubagentRuns.ts';\nimport type { SubagentRun } from '../types/webSubagents.ts';\n\nconst POLL_MS = 1000;\nconst DEBOUNCE_MS = 60;\nconst TEMP_ROOT_PREFIX = 'doom-team';\nconst SESSIONS_DIR_NAME = 'sessions';\nconst RUNS_DIR_NAME = 'runs';\nconst SCOPE_KEY_HASH_LENGTH = 16;\n\nexport interface TeamRunsDirInput {\n /** The Pi session id, which doom-team scopes its runs by. */\n sessionId: string;\n /** os.tmpdir(), supplied by the caller. */\n tmpdir: string;\n /** process.getuid(), absent on platforms without one. */\n uid: number | undefined;\n}\n\n/** Where this package keeps a session's runs, or undefined when the user cannot be scoped. */\nexport function teamRunsDirFor(input: TeamRunsDirInput): string | undefined {\n if (input.uid === undefined) return undefined;\n const scopeKey = createHash('sha256').update(input.sessionId.trim()).digest('hex').slice(0, SCOPE_KEY_HASH_LENGTH);\n return `${input.tmpdir}/${TEMP_ROOT_PREFIX}-uid-${String(input.uid)}/${SESSIONS_DIR_NAME}/${scopeKey}/${RUNS_DIR_NAME}`;\n}\n\nexport interface SubagentRunsSource {\n close(): void;\n}\n\n/**\n * Watches doom-team's per-session runs directory for one session.\n *\n * Same reliability posture as the registry watcher: the poll is the source of\n * truth, fs.watch is only an accelerator, and the directory not existing yet\n * (no run ever started) is a normal state, not an error. Emits the presented\n * run list whenever it changes, including the transition to empty.\n */\nexport function watchSubagentRuns(sessionId: string, onRuns: (runs: SubagentRun[]) => void): SubagentRunsSource {\n const runsDir = teamRunsDirFor({ sessionId, tmpdir: os.tmpdir(), uid: process.getuid?.() });\n if (runsDir === undefined) return { close: () => undefined };\n const scopeDir = path.dirname(runsDir);\n\n let watcher: fs.FSWatcher | undefined;\n let debounce: NodeJS.Timeout | undefined;\n // Seeded with the empty fingerprint: a session with no runs yet should not\n // announce an empty fleet, only a fleet that changed.\n let lastEmitted: string | undefined = '[]';\n let closed = false;\n\n const scan = (): SubagentRun[] => {\n let names: string[];\n try {\n names = fs.readdirSync(runsDir);\n } catch {\n return []; // The directory appears with the first run.\n }\n const runs: SubagentRun[] = [];\n for (const name of names) {\n let raw: string;\n try {\n raw = fs.readFileSync(path.join(runsDir, name, RUN_STATUS_FILE_NAME), 'utf8');\n } catch {\n continue; // Not a run dir, or the status is mid-write; the next pass settles it.\n }\n const run = parseSubagentRun(raw);\n if (run) runs.push(run);\n }\n\n let activeRunIds: ReadonlySet<string> | undefined;\n try {\n const registry = fs.readFileSync(path.join(scopeDir, RUN_REGISTRY_FILE_NAME), 'utf8');\n activeRunIds = activeRunIdsFromRegistry(registry, (pid) => {\n try {\n process.kill(pid, 0);\n return true;\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM';\n }\n });\n } catch {\n // Without a readable authoritative registry, preserve status snapshots.\n }\n return presentRuns(reconcileRunLifecycles(runs, activeRunIds), Date.now());\n };\n\n const emit = (): void => {\n if (closed) return;\n const runs = scan();\n const fingerprint = JSON.stringify(runs);\n if (fingerprint === lastEmitted) return;\n lastEmitted = fingerprint;\n onRuns(runs);\n };\n\n const ensureWatcher = (): void => {\n if (watcher || closed) return;\n try {\n watcher = fs.watch(scopeDir, { recursive: true }, () => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(emit, DEBOUNCE_MS);\n });\n watcher.on('error', () => {\n watcher?.close();\n watcher = undefined; // The poll re-establishes it once the dir is back.\n });\n } catch {\n // The directory does not exist yet; the poll keeps trying.\n }\n };\n\n ensureWatcher();\n emit();\n const poll = setInterval(() => {\n ensureWatcher();\n emit();\n }, POLL_MS);\n\n return {\n close() {\n closed = true;\n if (debounce) clearTimeout(debounce);\n clearInterval(poll);\n watcher?.close();\n },\n };\n}\n"],"mappings":"oTA+BA,SAAgB,EAAe,EAA6C,CAC1E,GAAI,EAAM,MAAQ,IAAA,GAAW,OAC7B,IAAM,EAAW,EAAW,QAAQ,CAAC,CAAC,OAAO,EAAM,UAAU,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,EAAG,EAAqB,EACjH,MAAO,GAAG,EAAM,OAAO,iBAA2B,OAAO,EAAM,GAAG,EAAE,YAAwB,EAAS,MACvG,CAcA,SAAgB,EAAkB,EAAmB,EAA2D,CAC9G,IAAM,EAAU,EAAe,CAAE,YAAW,OAAQ,EAAG,OAAO,EAAG,IAAK,QAAQ,SAAS,CAAE,CAAC,EAC1F,GAAI,IAAY,IAAA,GAAW,MAAO,CAAE,UAAa,IAAA,EAAU,EAC3D,IAAM,EAAW,EAAK,QAAQ,CAAO,EAEjC,EACA,EAGA,EAAkC,KAClC,EAAS,GAEP,MAA4B,CAChC,IAAI,EACJ,GAAI,CACF,EAAQ,EAAG,YAAY,CAAO,CAChC,MAAQ,CACN,MAAO,CAAC,CACV,CACA,IAAM,EAAsB,CAAC,EAC7B,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EACJ,GAAI,CACF,EAAM,EAAG,aAAa,EAAK,KAAK,EAAS,EAAM,CAAoB,EAAG,MAAM,CAC9E,MAAQ,CACN,QACF,CACA,IAAM,EAAM,EAAiB,CAAG,EAC5B,GAAK,EAAK,KAAK,CAAG,CACxB,CAEA,IAAI,EACJ,GAAI,CACF,IAAM,EAAW,EAAG,aAAa,EAAK,KAAK,EAAU,CAAsB,EAAG,MAAM,EACpF,EAAe,EAAyB,EAAW,GAAQ,CACzD,GAAI,CAEF,OADA,QAAQ,KAAK,EAAK,CAAC,EACZ,EACT,OAAS,EAAO,CACd,OAAQ,EAAgC,OAAS,OACnD,CACF,CAAC,CACH,MAAQ,CAER,CACA,OAAO,EAAY,EAAuB,EAAM,CAAY,EAAG,KAAK,IAAI,CAAC,CAC3E,EAEM,MAAmB,CACvB,GAAI,EAAQ,OACZ,IAAM,EAAO,EAAK,EACZ,EAAc,KAAK,UAAU,CAAI,EACnC,IAAgB,IACpB,EAAc,EACd,EAAO,CAAI,EACb,EAEM,MAA4B,CAC5B,QAAW,GACf,GAAI,CACF,EAAU,EAAG,MAAM,EAAU,CAAE,UAAW,EAAK,MAAS,CAClD,GAAU,aAAa,CAAQ,EACnC,EAAW,WAAW,EAAM,EAAW,CACzC,CAAC,EACD,EAAQ,GAAG,YAAe,CACxB,GAAS,MAAM,EACf,EAAU,IAAA,EACZ,CAAC,CACH,MAAQ,CAER,CACF,EAEA,EAAc,EACd,EAAK,EACL,IAAM,EAAO,gBAAkB,CAC7B,EAAc,EACd,EAAK,CACP,EAAG,GAAO,EAEV,MAAO,CACL,OAAQ,CACN,EAAS,GACL,GAAU,aAAa,CAAQ,EACnC,cAAc,CAAI,EAClB,GAAS,MAAM,CACjB,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=6e5,t=/^[\w.-]+$/;function n(e){let t=e.sessionFile;if(!(typeof t!=`string`||!t.startsWith(`/`)))return t.endsWith(`.jsonl`)?t:void 0}const r={queued:`queued`,running:`running`,complete:`done`,completed:`done`,failed:`failed`,paused:`stopped`,stopped:`stopped`},i=new Set([`done`,`failed`,`stopped`]);function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return typeof e==`string`&&e!==``?e:void 0}function s(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function c(e){if(typeof e==`string`)return e.slice(0,200);if(a(e)){let t=e.text??e.line??e.output??e.name;if(typeof t==`string`)return t.slice(0,200)}}function l(e){let t;try{t=JSON.parse(e)}catch{return}if(!a(t)||t.internal===!0)return;let n=o(t.runId),i=o(t.agent),l=o(t.state),u=s(t.startedAt);if(!n||!i||!l||u===void 0)return;let d=r[l];if(d===void 0)return;let f=Array.isArray(t.recentOutput)?t.recentOutput.map(c).filter(e=>e!==void 0).slice(-12):[],p=s(t.endedAt),m=o(t.summary),h=o(t.error);return{runId:n,agent:i,state:d,rawState:l,task:o(t.task)??``,...o(t.taskRef)===void 0?{}:{taskRef:o(t.taskRef)},...o(t.model)===void 0?{}:{model:o(t.model)},cwd:o(t.cwd)??``,startedAt:u,...p===void 0?{}:{endedAt:p},lastUpdate:s(t.lastUpdate)??u,...o(t.currentTool)===void 0?{}:{currentTool:o(t.currentTool)},...s(t.toolCount)===void 0?{}:{toolCount:s(t.toolCount)},...s(t.tokens)===void 0?{}:{tokens:s(t.tokens)},...m===void 0?{}:{summary:m},...h===void 0?{}:{error:h},tail:f}}function u(t,n){return t.filter(t=>!i.has(t.state)||n-(t.endedAt??t.lastUpdate)<e).sort((e,t)=>{let n=+!!i.has(e.state),r=+!!i.has(t.state);return n===r?t.startedAt-e.startedAt:n-r})}exports.RUN_ID_PATTERN=t,exports.RUN_RETENTION_MS=e,exports.RUN_STATUS_FILE_NAME=`status.json`,exports.journalPathOf=n,exports.parseSubagentRun=l,exports.presentRuns=u;
1
+ const e=6e5,t=/^[\w.-]+$/;function n(e){let t=e.sessionFile;if(!(typeof t!=`string`||!t.startsWith(`/`)))return t.endsWith(`.jsonl`)?t:void 0}const r={queued:`queued`,running:`running`,complete:`done`,completed:`done`,failed:`failed`,paused:`stopped`,stopped:`stopped`,canceled:`stopped`,cancelled:`stopped`},i=new Set([`done`,`failed`,`stopped`]);function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(e){return typeof e==`string`&&e!==``?e:void 0}function s(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function c(e){if(typeof e==`string`)return e.slice(0,200);if(a(e)){let t=e.text??e.line??e.output??e.name;if(typeof t==`string`)return t.slice(0,200)}}function l(e){let t;try{t=JSON.parse(e)}catch{return}if(!a(t)||t.internal===!0)return;let n=o(t.runId),i=o(t.agent),l=o(t.state),u=s(t.startedAt);if(!n||!i||!l||u===void 0)return;let d=r[l];if(d===void 0)return;let f=Array.isArray(t.recentOutput)?t.recentOutput.map(c).filter(e=>e!==void 0).slice(-12):[],p=s(t.endedAt),m=o(t.summary),h=o(t.error);return{runId:n,agent:i,state:d,rawState:l,task:o(t.task)??``,...o(t.taskRef)===void 0?{}:{taskRef:o(t.taskRef)},...o(t.model)===void 0?{}:{model:o(t.model)},cwd:o(t.cwd)??``,startedAt:u,...p===void 0?{}:{endedAt:p},lastUpdate:s(t.lastUpdate)??u,...o(t.currentTool)===void 0?{}:{currentTool:o(t.currentTool)},...s(t.toolCount)===void 0?{}:{toolCount:s(t.toolCount)},...s(t.tokens)===void 0?{}:{tokens:s(t.tokens)},...m===void 0?{}:{summary:m},...h===void 0?{}:{error:h},tail:f}}function u(e,t){let n;try{n=JSON.parse(e)}catch{return}if(!a(n)||n.version!==1||!Array.isArray(n.runs))return;let r=[];for(let e of n.runs){if(!a(e))return;let t=o(e.runId),n=s(e.pid);if(!t||n===void 0||!Number.isSafeInteger(n)||n<=0)return;r.push({runId:t,pid:n})}return new Set(r.filter(e=>t(e.pid)).map(e=>e.runId))}function d(e,t){return t===void 0?[...e]:e.map(e=>i.has(e.state)||t.has(e.runId)?e:{...e,state:`stopped`,endedAt:e.endedAt??e.lastUpdate})}function f(t,n){return t.filter(t=>!i.has(t.state)||n-(t.endedAt??t.lastUpdate)<e).sort((e,t)=>{let n=+!!i.has(e.state),r=+!!i.has(t.state);return n===r?t.startedAt-e.startedAt:n-r})}exports.RUN_ID_PATTERN=t,exports.RUN_REGISTRY_FILE_NAME=`registry.json`,exports.RUN_RETENTION_MS=e,exports.RUN_STATUS_FILE_NAME=`status.json`,exports.activeRunIdsFromRegistry=u,exports.journalPathOf=n,exports.parseSubagentRun=l,exports.presentRuns=f,exports.reconcileRunLifecycles=d;
2
2
  //# sourceMappingURL=webSubagentRuns.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentRuns.cjs","names":[],"sources":["../../../src/services/webSubagentRuns.ts"],"sourcesContent":["import type { SubagentRun, SubagentRunState } from '../types/webSubagents.ts';\n\n/**\n * MIRRORS @agimon-ai/doompi-team's on-disk run layout\n * (layers/team/doompi-team/src/adapters/filesystem/paths.ts and\n * runs/background/asyncExecution.ts). The team runtime writes one directory\n * per session scope under the OS temp root and one status.json per run; the\n * hub only ever reads them. The derivation is duplicated rather than imported\n * because doompi-web must not depend on the team package at runtime.\n */\nexport const RUN_STATUS_FILE_NAME = 'status.json';\n\nconst TAIL_LIMIT = 12;\nconst TAIL_ENTRY_LIMIT = 200;\n/** Terminal runs older than this leave the fleet the page sees. */\nexport const RUN_RETENTION_MS = 10 * 60 * 1000;\n\n/** A run id is a path segment under the runs dir, so only plain names may be looked up. */\nexport const RUN_ID_PATTERN = /^[\\w.-]+$/;\nconst JOURNAL_EXTENSION = '.jsonl';\n\n/**\n * The Pi session journal a run's status names, once the child's session has\n * started; undefined before then or when the status names something else.\n * Runs are uid-scoped, so an absolute path is a POSIX one.\n */\nexport function journalPathOf(status: { sessionFile?: unknown }): string | undefined {\n const candidate = status.sessionFile;\n if (typeof candidate !== 'string' || !candidate.startsWith('/')) return undefined;\n return candidate.endsWith(JOURNAL_EXTENSION) ? candidate : undefined;\n}\n\nconst STATE_MAP: Readonly<Record<string, SubagentRunState>> = {\n queued: 'queued',\n running: 'running',\n complete: 'done',\n completed: 'done',\n failed: 'failed',\n paused: 'stopped',\n stopped: 'stopped',\n};\n\nconst TERMINAL_STATES: ReadonlySet<SubagentRunState> = new Set(['done', 'failed', 'stopped']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** Recent-entry arrays are declared `unknown[]` upstream; render what reads as text. */\nfunction entryText(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry.slice(0, TAIL_ENTRY_LIMIT);\n if (isRecord(entry)) {\n const candidate = entry.text ?? entry.line ?? entry.output ?? entry.name;\n if (typeof candidate === 'string') return candidate.slice(0, TAIL_ENTRY_LIMIT);\n }\n return undefined;\n}\n\n/**\n * Validates one run status file into the wire shape.\n *\n * Returns undefined for anything unreadable, a foreign format, or a run\n * marked internal: the fleet view is for the runs the user asked about.\n */\nexport function parseSubagentRun(raw: string): SubagentRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n if (parsed.internal === true) return undefined;\n const runId = asOptionalString(parsed.runId);\n const agent = asOptionalString(parsed.agent);\n const rawState = asOptionalString(parsed.state);\n const startedAt = asOptionalNumber(parsed.startedAt);\n if (!runId || !agent || !rawState || startedAt === undefined) return undefined;\n const state = STATE_MAP[rawState];\n if (state === undefined) return undefined;\n\n const tail = Array.isArray(parsed.recentOutput)\n ? parsed.recentOutput\n .map(entryText)\n .filter((line): line is string => line !== undefined)\n .slice(-TAIL_LIMIT)\n : [];\n\n const endedAt = asOptionalNumber(parsed.endedAt);\n const summary = asOptionalString(parsed.summary);\n const error = asOptionalString(parsed.error);\n return {\n runId,\n agent,\n state,\n rawState,\n task: asOptionalString(parsed.task) ?? '',\n ...(asOptionalString(parsed.taskRef) === undefined ? {} : { taskRef: asOptionalString(parsed.taskRef) }),\n ...(asOptionalString(parsed.model) === undefined ? {} : { model: asOptionalString(parsed.model) }),\n cwd: asOptionalString(parsed.cwd) ?? '',\n startedAt,\n ...(endedAt === undefined ? {} : { endedAt }),\n lastUpdate: asOptionalNumber(parsed.lastUpdate) ?? startedAt,\n ...(asOptionalString(parsed.currentTool) === undefined\n ? {}\n : { currentTool: asOptionalString(parsed.currentTool) }),\n ...(asOptionalNumber(parsed.toolCount) === undefined ? {} : { toolCount: asOptionalNumber(parsed.toolCount) }),\n ...(asOptionalNumber(parsed.tokens) === undefined ? {} : { tokens: asOptionalNumber(parsed.tokens) }),\n ...(summary === undefined ? {} : { summary }),\n ...(error === undefined ? {} : { error }),\n tail,\n };\n}\n\n/** Active runs first, then newest first; terminal runs leave after the retention window. */\nexport function presentRuns(runs: readonly SubagentRun[], now: number): SubagentRun[] {\n return runs\n .filter((run) => {\n if (!TERMINAL_STATES.has(run.state)) return true;\n return now - (run.endedAt ?? run.lastUpdate) < RUN_RETENTION_MS;\n })\n .sort((left, right) => {\n const leftActive = TERMINAL_STATES.has(left.state) ? 1 : 0;\n const rightActive = TERMINAL_STATES.has(right.state) ? 1 : 0;\n if (leftActive !== rightActive) return leftActive - rightActive;\n return right.startedAt - left.startedAt;\n });\n}\n"],"mappings":"AAUA,MAKa,EAAmB,IAGnB,EAAiB,YAQ9B,SAAgB,EAAc,EAAuD,CACnF,IAAM,EAAY,EAAO,YACrB,YAAO,GAAc,UAAY,CAAC,EAAU,WAAW,GAAG,GAC9D,OAAO,EAAU,SAAS,QAAiB,EAAI,EAAY,IAAA,EAC7D,CAEA,MAAM,EAAwD,CAC5D,OAAQ,SACR,QAAS,UACT,SAAU,OACV,UAAW,OACX,OAAQ,SACR,OAAQ,UACR,QAAS,SACX,EAEM,EAAiD,IAAI,IAAI,CAAC,OAAQ,SAAU,SAAS,CAAC,EAE5F,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAiB,EAAoC,CAC5D,OAAO,OAAO,GAAU,UAAY,IAAU,GAAK,EAAQ,IAAA,EAC7D,CAEA,SAAS,EAAiB,EAAoC,CAC5D,OAAO,OAAO,GAAU,UAAY,OAAO,SAAS,CAAK,EAAI,EAAQ,IAAA,EACvE,CAGA,SAAS,EAAU,EAAoC,CACrD,GAAI,OAAO,GAAU,SAAU,OAAO,EAAM,MAAM,EAAG,GAAgB,EACrE,GAAI,EAAS,CAAK,EAAG,CACnB,IAAM,EAAY,EAAM,MAAQ,EAAM,MAAQ,EAAM,QAAU,EAAM,KACpE,GAAI,OAAO,GAAc,SAAU,OAAO,EAAU,MAAM,EAAG,GAAgB,CAC/E,CAEF,CAQA,SAAgB,EAAiB,EAAsC,CACrE,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CAEA,GADI,CAAC,EAAS,CAAM,GAChB,EAAO,WAAa,GAAM,OAC9B,IAAM,EAAQ,EAAiB,EAAO,KAAK,EACrC,EAAQ,EAAiB,EAAO,KAAK,EACrC,EAAW,EAAiB,EAAO,KAAK,EACxC,EAAY,EAAiB,EAAO,SAAS,EACnD,GAAI,CAAC,GAAS,CAAC,GAAS,CAAC,GAAY,IAAc,IAAA,GAAW,OAC9D,IAAM,EAAQ,EAAU,GACxB,GAAI,IAAU,IAAA,GAAW,OAEzB,IAAM,EAAO,MAAM,QAAQ,EAAO,YAAY,EAC1C,EAAO,aACJ,IAAI,CAAS,CAAC,CACd,OAAQ,GAAyB,IAAS,IAAA,EAAS,CAAC,CACpD,MAAM,GAAW,EACpB,CAAC,EAEC,EAAU,EAAiB,EAAO,OAAO,EACzC,EAAU,EAAiB,EAAO,OAAO,EACzC,EAAQ,EAAiB,EAAO,KAAK,EAC3C,MAAO,CACL,QACA,QACA,QACA,WACA,KAAM,EAAiB,EAAO,IAAI,GAAK,GACvC,GAAI,EAAiB,EAAO,OAAO,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAiB,EAAO,OAAO,CAAE,EACtG,GAAI,EAAiB,EAAO,KAAK,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAiB,EAAO,KAAK,CAAE,EAChG,IAAK,EAAiB,EAAO,GAAG,GAAK,GACrC,YACA,GAAI,IAAY,IAAA,GAAY,CAAC,EAAI,CAAE,SAAQ,EAC3C,WAAY,EAAiB,EAAO,UAAU,GAAK,EACnD,GAAI,EAAiB,EAAO,WAAW,IAAM,IAAA,GACzC,CAAC,EACD,CAAE,YAAa,EAAiB,EAAO,WAAW,CAAE,EACxD,GAAI,EAAiB,EAAO,SAAS,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,UAAW,EAAiB,EAAO,SAAS,CAAE,EAC5G,GAAI,EAAiB,EAAO,MAAM,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,EAAiB,EAAO,MAAM,CAAE,EACnG,GAAI,IAAY,IAAA,GAAY,CAAC,EAAI,CAAE,SAAQ,EAC3C,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,EACvC,MACF,CACF,CAGA,SAAgB,EAAY,EAA8B,EAA4B,CACpF,OAAO,EACJ,OAAQ,GACP,CAAK,EAAgB,IAAI,EAAI,KAAK,GAC3B,GAAO,EAAI,SAAW,EAAI,YAAc,CAChD,CAAC,CACD,MAAM,EAAM,IAAU,CACrB,IAAM,EAAa,KAAgB,IAAI,EAAK,KAAK,EAC3C,EAAc,KAAgB,IAAI,EAAM,KAAK,EAEnD,OADI,IAAe,EACZ,EAAM,UAAY,EAAK,UADS,EAAa,CAEtD,CAAC,CACL"}
1
+ {"version":3,"file":"webSubagentRuns.cjs","names":[],"sources":["../../../src/services/webSubagentRuns.ts"],"sourcesContent":["import type { SubagentRun, SubagentRunState } from '../types/webSubagents.ts';\n\n/**\n * MIRRORS @agimon-ai/doompi-team's on-disk run layout\n * (layers/team/doompi-team/src/adapters/filesystem/paths.ts and\n * runs/background/asyncExecution.ts). The team runtime writes one directory\n * per session scope under the OS temp root and one status.json per run; the\n * hub only ever reads them. The derivation is duplicated rather than imported\n * because doompi-web must not depend on the team package at runtime.\n */\nexport const RUN_STATUS_FILE_NAME = 'status.json';\n/** Process registry beside the runs directory, mirrored from runRegistry.ts. */\nexport const RUN_REGISTRY_FILE_NAME = 'registry.json';\n\nconst TAIL_LIMIT = 12;\nconst TAIL_ENTRY_LIMIT = 200;\n/** Terminal runs older than this leave the fleet the page sees. */\nexport const RUN_RETENTION_MS = 10 * 60 * 1000;\n\n/** A run id is a path segment under the runs dir, so only plain names may be looked up. */\nexport const RUN_ID_PATTERN = /^[\\w.-]+$/;\nconst JOURNAL_EXTENSION = '.jsonl';\n\n/**\n * The Pi session journal a run's status names, once the child's session has\n * started; undefined before then or when the status names something else.\n * Runs are uid-scoped, so an absolute path is a POSIX one.\n */\nexport function journalPathOf(status: { sessionFile?: unknown }): string | undefined {\n const candidate = status.sessionFile;\n if (typeof candidate !== 'string' || !candidate.startsWith('/')) return undefined;\n return candidate.endsWith(JOURNAL_EXTENSION) ? candidate : undefined;\n}\n\nconst STATE_MAP: Readonly<Record<string, SubagentRunState>> = {\n queued: 'queued',\n running: 'running',\n complete: 'done',\n completed: 'done',\n failed: 'failed',\n paused: 'stopped',\n stopped: 'stopped',\n canceled: 'stopped',\n cancelled: 'stopped',\n};\n\nconst TERMINAL_STATES: ReadonlySet<SubagentRunState> = new Set(['done', 'failed', 'stopped']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** Recent-entry arrays are declared `unknown[]` upstream; render what reads as text. */\nfunction entryText(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry.slice(0, TAIL_ENTRY_LIMIT);\n if (isRecord(entry)) {\n const candidate = entry.text ?? entry.line ?? entry.output ?? entry.name;\n if (typeof candidate === 'string') return candidate.slice(0, TAIL_ENTRY_LIMIT);\n }\n return undefined;\n}\n\n/**\n * Validates one run status file into the wire shape.\n *\n * Returns undefined for anything unreadable, a foreign format, or a run\n * marked internal: the fleet view is for the runs the user asked about.\n */\nexport function parseSubagentRun(raw: string): SubagentRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n if (parsed.internal === true) return undefined;\n const runId = asOptionalString(parsed.runId);\n const agent = asOptionalString(parsed.agent);\n const rawState = asOptionalString(parsed.state);\n const startedAt = asOptionalNumber(parsed.startedAt);\n if (!runId || !agent || !rawState || startedAt === undefined) return undefined;\n const state = STATE_MAP[rawState];\n if (state === undefined) return undefined;\n\n const tail = Array.isArray(parsed.recentOutput)\n ? parsed.recentOutput\n .map(entryText)\n .filter((line): line is string => line !== undefined)\n .slice(-TAIL_LIMIT)\n : [];\n\n const endedAt = asOptionalNumber(parsed.endedAt);\n const summary = asOptionalString(parsed.summary);\n const error = asOptionalString(parsed.error);\n return {\n runId,\n agent,\n state,\n rawState,\n task: asOptionalString(parsed.task) ?? '',\n ...(asOptionalString(parsed.taskRef) === undefined ? {} : { taskRef: asOptionalString(parsed.taskRef) }),\n ...(asOptionalString(parsed.model) === undefined ? {} : { model: asOptionalString(parsed.model) }),\n cwd: asOptionalString(parsed.cwd) ?? '',\n startedAt,\n ...(endedAt === undefined ? {} : { endedAt }),\n lastUpdate: asOptionalNumber(parsed.lastUpdate) ?? startedAt,\n ...(asOptionalString(parsed.currentTool) === undefined\n ? {}\n : { currentTool: asOptionalString(parsed.currentTool) }),\n ...(asOptionalNumber(parsed.toolCount) === undefined ? {} : { toolCount: asOptionalNumber(parsed.toolCount) }),\n ...(asOptionalNumber(parsed.tokens) === undefined ? {} : { tokens: asOptionalNumber(parsed.tokens) }),\n ...(summary === undefined ? {} : { summary }),\n ...(error === undefined ? {} : { error }),\n tail,\n };\n}\n\n/**\n * Reads the runtime-owned process registry into the ids whose runner process is\n * still alive. An unreadable or foreign registry returns undefined so callers\n * fail open rather than hiding work whose lifecycle cannot be proven.\n */\nexport function activeRunIdsFromRegistry(\n raw: string,\n isAlive: (pid: number) => boolean,\n): ReadonlySet<string> | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed) || parsed.version !== 1 || !Array.isArray(parsed.runs)) return undefined;\n\n const records: Array<{ runId: string; pid: number }> = [];\n for (const entry of parsed.runs) {\n if (!isRecord(entry)) return undefined;\n const runId = asOptionalString(entry.runId);\n const pid = asOptionalNumber(entry.pid);\n if (!runId || pid === undefined || !Number.isSafeInteger(pid) || pid <= 0) return undefined;\n records.push({ runId, pid });\n }\n return new Set(records.filter((record) => isAlive(record.pid)).map((record) => record.runId));\n}\n\n/** Reconciles status snapshots that outlived their authoritative runner record. */\nexport function reconcileRunLifecycles(\n runs: readonly SubagentRun[],\n activeRunIds: ReadonlySet<string> | undefined,\n): SubagentRun[] {\n if (activeRunIds === undefined) return [...runs];\n return runs.map((run) => {\n if (TERMINAL_STATES.has(run.state) || activeRunIds.has(run.runId)) return run;\n return { ...run, state: 'stopped', endedAt: run.endedAt ?? run.lastUpdate };\n });\n}\n\n/** Active runs first, then newest first; terminal runs leave after the retention window. */\nexport function presentRuns(runs: readonly SubagentRun[], now: number): SubagentRun[] {\n return runs\n .filter((run) => {\n if (!TERMINAL_STATES.has(run.state)) return true;\n return now - (run.endedAt ?? run.lastUpdate) < RUN_RETENTION_MS;\n })\n .sort((left, right) => {\n const leftActive = TERMINAL_STATES.has(left.state) ? 1 : 0;\n const rightActive = TERMINAL_STATES.has(right.state) ? 1 : 0;\n if (leftActive !== rightActive) return leftActive - rightActive;\n return right.startedAt - left.startedAt;\n });\n}\n"],"mappings":"AAUA,MAOa,EAAmB,IAGnB,EAAiB,YAQ9B,SAAgB,EAAc,EAAuD,CACnF,IAAM,EAAY,EAAO,YACrB,YAAO,GAAc,UAAY,CAAC,EAAU,WAAW,GAAG,GAC9D,OAAO,EAAU,SAAS,QAAiB,EAAI,EAAY,IAAA,EAC7D,CAEA,MAAM,EAAwD,CAC5D,OAAQ,SACR,QAAS,UACT,SAAU,OACV,UAAW,OACX,OAAQ,SACR,OAAQ,UACR,QAAS,UACT,SAAU,UACV,UAAW,SACb,EAEM,EAAiD,IAAI,IAAI,CAAC,OAAQ,SAAU,SAAS,CAAC,EAE5F,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAiB,EAAoC,CAC5D,OAAO,OAAO,GAAU,UAAY,IAAU,GAAK,EAAQ,IAAA,EAC7D,CAEA,SAAS,EAAiB,EAAoC,CAC5D,OAAO,OAAO,GAAU,UAAY,OAAO,SAAS,CAAK,EAAI,EAAQ,IAAA,EACvE,CAGA,SAAS,EAAU,EAAoC,CACrD,GAAI,OAAO,GAAU,SAAU,OAAO,EAAM,MAAM,EAAG,GAAgB,EACrE,GAAI,EAAS,CAAK,EAAG,CACnB,IAAM,EAAY,EAAM,MAAQ,EAAM,MAAQ,EAAM,QAAU,EAAM,KACpE,GAAI,OAAO,GAAc,SAAU,OAAO,EAAU,MAAM,EAAG,GAAgB,CAC/E,CAEF,CAQA,SAAgB,EAAiB,EAAsC,CACrE,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CAEA,GADI,CAAC,EAAS,CAAM,GAChB,EAAO,WAAa,GAAM,OAC9B,IAAM,EAAQ,EAAiB,EAAO,KAAK,EACrC,EAAQ,EAAiB,EAAO,KAAK,EACrC,EAAW,EAAiB,EAAO,KAAK,EACxC,EAAY,EAAiB,EAAO,SAAS,EACnD,GAAI,CAAC,GAAS,CAAC,GAAS,CAAC,GAAY,IAAc,IAAA,GAAW,OAC9D,IAAM,EAAQ,EAAU,GACxB,GAAI,IAAU,IAAA,GAAW,OAEzB,IAAM,EAAO,MAAM,QAAQ,EAAO,YAAY,EAC1C,EAAO,aACJ,IAAI,CAAS,CAAC,CACd,OAAQ,GAAyB,IAAS,IAAA,EAAS,CAAC,CACpD,MAAM,GAAW,EACpB,CAAC,EAEC,EAAU,EAAiB,EAAO,OAAO,EACzC,EAAU,EAAiB,EAAO,OAAO,EACzC,EAAQ,EAAiB,EAAO,KAAK,EAC3C,MAAO,CACL,QACA,QACA,QACA,WACA,KAAM,EAAiB,EAAO,IAAI,GAAK,GACvC,GAAI,EAAiB,EAAO,OAAO,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAiB,EAAO,OAAO,CAAE,EACtG,GAAI,EAAiB,EAAO,KAAK,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,MAAO,EAAiB,EAAO,KAAK,CAAE,EAChG,IAAK,EAAiB,EAAO,GAAG,GAAK,GACrC,YACA,GAAI,IAAY,IAAA,GAAY,CAAC,EAAI,CAAE,SAAQ,EAC3C,WAAY,EAAiB,EAAO,UAAU,GAAK,EACnD,GAAI,EAAiB,EAAO,WAAW,IAAM,IAAA,GACzC,CAAC,EACD,CAAE,YAAa,EAAiB,EAAO,WAAW,CAAE,EACxD,GAAI,EAAiB,EAAO,SAAS,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,UAAW,EAAiB,EAAO,SAAS,CAAE,EAC5G,GAAI,EAAiB,EAAO,MAAM,IAAM,IAAA,GAAY,CAAC,EAAI,CAAE,OAAQ,EAAiB,EAAO,MAAM,CAAE,EACnG,GAAI,IAAY,IAAA,GAAY,CAAC,EAAI,CAAE,SAAQ,EAC3C,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,EACvC,MACF,CACF,CAOA,SAAgB,EACd,EACA,EACiC,CACjC,IAAI,EACJ,GAAI,CACF,EAAS,KAAK,MAAM,CAAG,CACzB,MAAQ,CACN,MACF,CACA,GAAI,CAAC,EAAS,CAAM,GAAK,EAAO,UAAY,GAAK,CAAC,MAAM,QAAQ,EAAO,IAAI,EAAG,OAE9E,IAAM,EAAiD,CAAC,EACxD,IAAK,IAAM,KAAS,EAAO,KAAM,CAC/B,GAAI,CAAC,EAAS,CAAK,EAAG,OACtB,IAAM,EAAQ,EAAiB,EAAM,KAAK,EACpC,EAAM,EAAiB,EAAM,GAAG,EACtC,GAAI,CAAC,GAAS,IAAQ,IAAA,IAAa,CAAC,OAAO,cAAc,CAAG,GAAK,GAAO,EAAG,OAC3E,EAAQ,KAAK,CAAE,QAAO,KAAI,CAAC,CAC7B,CACA,OAAO,IAAI,IAAI,EAAQ,OAAQ,GAAW,EAAQ,EAAO,GAAG,CAAC,CAAC,CAAC,IAAK,GAAW,EAAO,KAAK,CAAC,CAC9F,CAGA,SAAgB,EACd,EACA,EACe,CAEf,OADI,IAAiB,IAAA,GAAkB,CAAC,GAAG,CAAI,EACxC,EAAK,IAAK,GACX,EAAgB,IAAI,EAAI,KAAK,GAAK,EAAa,IAAI,EAAI,KAAK,EAAU,EACnE,CAAE,GAAG,EAAK,MAAO,UAAW,QAAS,EAAI,SAAW,EAAI,UAAW,CAC3E,CACH,CAGA,SAAgB,EAAY,EAA8B,EAA4B,CACpF,OAAO,EACJ,OAAQ,GACP,CAAK,EAAgB,IAAI,EAAI,KAAK,GAC3B,GAAO,EAAI,SAAW,EAAI,YAAc,CAChD,CAAC,CACD,MAAM,EAAM,IAAU,CACrB,IAAM,EAAa,KAAgB,IAAI,EAAK,KAAK,EAC3C,EAAc,KAAgB,IAAI,EAAM,KAAK,EAEnD,OADI,IAAe,EACZ,EAAM,UAAY,EAAK,UADS,EAAa,CAEtD,CAAC,CACL"}
@@ -1,2 +1,2 @@
1
- const e=`status.json`,t=6e5,n=/^[\w.-]+$/;function r(e){let t=e.sessionFile;if(!(typeof t!=`string`||!t.startsWith(`/`)))return t.endsWith(`.jsonl`)?t:void 0}const i={queued:`queued`,running:`running`,complete:`done`,completed:`done`,failed:`failed`,paused:`stopped`,stopped:`stopped`},a=new Set([`done`,`failed`,`stopped`]);function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e){return typeof e==`string`&&e!==``?e:void 0}function c(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function l(e){if(typeof e==`string`)return e.slice(0,200);if(o(e)){let t=e.text??e.line??e.output??e.name;if(typeof t==`string`)return t.slice(0,200)}}function u(e){let t;try{t=JSON.parse(e)}catch{return}if(!o(t)||t.internal===!0)return;let n=s(t.runId),r=s(t.agent),a=s(t.state),u=c(t.startedAt);if(!n||!r||!a||u===void 0)return;let d=i[a];if(d===void 0)return;let f=Array.isArray(t.recentOutput)?t.recentOutput.map(l).filter(e=>e!==void 0).slice(-12):[],p=c(t.endedAt),m=s(t.summary),h=s(t.error);return{runId:n,agent:r,state:d,rawState:a,task:s(t.task)??``,...s(t.taskRef)===void 0?{}:{taskRef:s(t.taskRef)},...s(t.model)===void 0?{}:{model:s(t.model)},cwd:s(t.cwd)??``,startedAt:u,...p===void 0?{}:{endedAt:p},lastUpdate:c(t.lastUpdate)??u,...s(t.currentTool)===void 0?{}:{currentTool:s(t.currentTool)},...c(t.toolCount)===void 0?{}:{toolCount:c(t.toolCount)},...c(t.tokens)===void 0?{}:{tokens:c(t.tokens)},...m===void 0?{}:{summary:m},...h===void 0?{}:{error:h},tail:f}}function d(e,n){return e.filter(e=>!a.has(e.state)||n-(e.endedAt??e.lastUpdate)<t).sort((e,t)=>{let n=+!!a.has(e.state),r=+!!a.has(t.state);return n===r?t.startedAt-e.startedAt:n-r})}export{n as RUN_ID_PATTERN,t as RUN_RETENTION_MS,e as RUN_STATUS_FILE_NAME,r as journalPathOf,u as parseSubagentRun,d as presentRuns};
1
+ const e=`status.json`,t=`registry.json`,n=6e5,r=/^[\w.-]+$/;function i(e){let t=e.sessionFile;if(!(typeof t!=`string`||!t.startsWith(`/`)))return t.endsWith(`.jsonl`)?t:void 0}const a={queued:`queued`,running:`running`,complete:`done`,completed:`done`,failed:`failed`,paused:`stopped`,stopped:`stopped`,canceled:`stopped`,cancelled:`stopped`},o=new Set([`done`,`failed`,`stopped`]);function s(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function c(e){return typeof e==`string`&&e!==``?e:void 0}function l(e){return typeof e==`number`&&Number.isFinite(e)?e:void 0}function u(e){if(typeof e==`string`)return e.slice(0,200);if(s(e)){let t=e.text??e.line??e.output??e.name;if(typeof t==`string`)return t.slice(0,200)}}function d(e){let t;try{t=JSON.parse(e)}catch{return}if(!s(t)||t.internal===!0)return;let n=c(t.runId),r=c(t.agent),i=c(t.state),o=l(t.startedAt);if(!n||!r||!i||o===void 0)return;let d=a[i];if(d===void 0)return;let f=Array.isArray(t.recentOutput)?t.recentOutput.map(u).filter(e=>e!==void 0).slice(-12):[],p=l(t.endedAt),m=c(t.summary),h=c(t.error);return{runId:n,agent:r,state:d,rawState:i,task:c(t.task)??``,...c(t.taskRef)===void 0?{}:{taskRef:c(t.taskRef)},...c(t.model)===void 0?{}:{model:c(t.model)},cwd:c(t.cwd)??``,startedAt:o,...p===void 0?{}:{endedAt:p},lastUpdate:l(t.lastUpdate)??o,...c(t.currentTool)===void 0?{}:{currentTool:c(t.currentTool)},...l(t.toolCount)===void 0?{}:{toolCount:l(t.toolCount)},...l(t.tokens)===void 0?{}:{tokens:l(t.tokens)},...m===void 0?{}:{summary:m},...h===void 0?{}:{error:h},tail:f}}function f(e,t){let n;try{n=JSON.parse(e)}catch{return}if(!s(n)||n.version!==1||!Array.isArray(n.runs))return;let r=[];for(let e of n.runs){if(!s(e))return;let t=c(e.runId),n=l(e.pid);if(!t||n===void 0||!Number.isSafeInteger(n)||n<=0)return;r.push({runId:t,pid:n})}return new Set(r.filter(e=>t(e.pid)).map(e=>e.runId))}function p(e,t){return t===void 0?[...e]:e.map(e=>o.has(e.state)||t.has(e.runId)?e:{...e,state:`stopped`,endedAt:e.endedAt??e.lastUpdate})}function m(e,t){return e.filter(e=>!o.has(e.state)||t-(e.endedAt??e.lastUpdate)<n).sort((e,t)=>{let n=+!!o.has(e.state),r=+!!o.has(t.state);return n===r?t.startedAt-e.startedAt:n-r})}export{r as RUN_ID_PATTERN,t as RUN_REGISTRY_FILE_NAME,n as RUN_RETENTION_MS,e as RUN_STATUS_FILE_NAME,f as activeRunIdsFromRegistry,i as journalPathOf,d as parseSubagentRun,m as presentRuns,p as reconcileRunLifecycles};
2
2
  //# sourceMappingURL=webSubagentRuns.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"webSubagentRuns.mjs","names":[],"sources":["../../../src/services/webSubagentRuns.ts"],"sourcesContent":["import type { SubagentRun, SubagentRunState } from '../types/webSubagents.ts';\n\n/**\n * MIRRORS @agimon-ai/doompi-team's on-disk run layout\n * (layers/team/doompi-team/src/adapters/filesystem/paths.ts and\n * runs/background/asyncExecution.ts). The team runtime writes one directory\n * per session scope under the OS temp root and one status.json per run; the\n * hub only ever reads them. The derivation is duplicated rather than imported\n * because doompi-web must not depend on the team package at runtime.\n */\nexport const RUN_STATUS_FILE_NAME = 'status.json';\n\nconst TAIL_LIMIT = 12;\nconst TAIL_ENTRY_LIMIT = 200;\n/** Terminal runs older than this leave the fleet the page sees. */\nexport const RUN_RETENTION_MS = 10 * 60 * 1000;\n\n/** A run id is a path segment under the runs dir, so only plain names may be looked up. */\nexport const RUN_ID_PATTERN = /^[\\w.-]+$/;\nconst JOURNAL_EXTENSION = '.jsonl';\n\n/**\n * The Pi session journal a run's status names, once the child's session has\n * started; undefined before then or when the status names something else.\n * Runs are uid-scoped, so an absolute path is a POSIX one.\n */\nexport function journalPathOf(status: { sessionFile?: unknown }): string | undefined {\n const candidate = status.sessionFile;\n if (typeof candidate !== 'string' || !candidate.startsWith('/')) return undefined;\n return candidate.endsWith(JOURNAL_EXTENSION) ? candidate : undefined;\n}\n\nconst STATE_MAP: Readonly<Record<string, SubagentRunState>> = {\n queued: 'queued',\n running: 'running',\n complete: 'done',\n completed: 'done',\n failed: 'failed',\n paused: 'stopped',\n stopped: 'stopped',\n};\n\nconst TERMINAL_STATES: ReadonlySet<SubagentRunState> = new Set(['done', 'failed', 'stopped']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** Recent-entry arrays are declared `unknown[]` upstream; render what reads as text. */\nfunction entryText(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry.slice(0, TAIL_ENTRY_LIMIT);\n if (isRecord(entry)) {\n const candidate = entry.text ?? entry.line ?? entry.output ?? entry.name;\n if (typeof candidate === 'string') return candidate.slice(0, TAIL_ENTRY_LIMIT);\n }\n return undefined;\n}\n\n/**\n * Validates one run status file into the wire shape.\n *\n * Returns undefined for anything unreadable, a foreign format, or a run\n * marked internal: the fleet view is for the runs the user asked about.\n */\nexport function parseSubagentRun(raw: string): SubagentRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n if (parsed.internal === true) return undefined;\n const runId = asOptionalString(parsed.runId);\n const agent = asOptionalString(parsed.agent);\n const rawState = asOptionalString(parsed.state);\n const startedAt = asOptionalNumber(parsed.startedAt);\n if (!runId || !agent || !rawState || startedAt === undefined) return undefined;\n const state = STATE_MAP[rawState];\n if (state === undefined) return undefined;\n\n const tail = Array.isArray(parsed.recentOutput)\n ? parsed.recentOutput\n .map(entryText)\n .filter((line): line is string => line !== undefined)\n .slice(-TAIL_LIMIT)\n : [];\n\n const endedAt = asOptionalNumber(parsed.endedAt);\n const summary = asOptionalString(parsed.summary);\n const error = asOptionalString(parsed.error);\n return {\n runId,\n agent,\n state,\n rawState,\n task: asOptionalString(parsed.task) ?? '',\n ...(asOptionalString(parsed.taskRef) === undefined ? {} : { taskRef: asOptionalString(parsed.taskRef) }),\n ...(asOptionalString(parsed.model) === undefined ? {} : { model: asOptionalString(parsed.model) }),\n cwd: asOptionalString(parsed.cwd) ?? '',\n startedAt,\n ...(endedAt === undefined ? {} : { endedAt }),\n lastUpdate: asOptionalNumber(parsed.lastUpdate) ?? startedAt,\n ...(asOptionalString(parsed.currentTool) === undefined\n ? {}\n : { currentTool: asOptionalString(parsed.currentTool) }),\n ...(asOptionalNumber(parsed.toolCount) === undefined ? {} : { toolCount: asOptionalNumber(parsed.toolCount) }),\n ...(asOptionalNumber(parsed.tokens) === undefined ? {} : { tokens: asOptionalNumber(parsed.tokens) }),\n ...(summary === undefined ? {} : { summary }),\n ...(error === undefined ? {} : { error }),\n tail,\n };\n}\n\n/** Active runs first, then newest first; terminal runs leave after the retention window. */\nexport function presentRuns(runs: readonly SubagentRun[], now: number): SubagentRun[] {\n return runs\n .filter((run) => {\n if (!TERMINAL_STATES.has(run.state)) return true;\n return now - (run.endedAt ?? run.lastUpdate) < RUN_RETENTION_MS;\n })\n .sort((left, right) => {\n const leftActive = TERMINAL_STATES.has(left.state) ? 1 : 0;\n const rightActive = TERMINAL_STATES.has(right.state) ? 1 : 0;\n if (leftActive !== rightActive) return leftActive - rightActive;\n return right.startedAt - left.startedAt;\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"webSubagentRuns.mjs","names":[],"sources":["../../../src/services/webSubagentRuns.ts"],"sourcesContent":["import type { SubagentRun, SubagentRunState } from '../types/webSubagents.ts';\n\n/**\n * MIRRORS @agimon-ai/doompi-team's on-disk run layout\n * (layers/team/doompi-team/src/adapters/filesystem/paths.ts and\n * runs/background/asyncExecution.ts). The team runtime writes one directory\n * per session scope under the OS temp root and one status.json per run; the\n * hub only ever reads them. The derivation is duplicated rather than imported\n * because doompi-web must not depend on the team package at runtime.\n */\nexport const RUN_STATUS_FILE_NAME = 'status.json';\n/** Process registry beside the runs directory, mirrored from runRegistry.ts. */\nexport const RUN_REGISTRY_FILE_NAME = 'registry.json';\n\nconst TAIL_LIMIT = 12;\nconst TAIL_ENTRY_LIMIT = 200;\n/** Terminal runs older than this leave the fleet the page sees. */\nexport const RUN_RETENTION_MS = 10 * 60 * 1000;\n\n/** A run id is a path segment under the runs dir, so only plain names may be looked up. */\nexport const RUN_ID_PATTERN = /^[\\w.-]+$/;\nconst JOURNAL_EXTENSION = '.jsonl';\n\n/**\n * The Pi session journal a run's status names, once the child's session has\n * started; undefined before then or when the status names something else.\n * Runs are uid-scoped, so an absolute path is a POSIX one.\n */\nexport function journalPathOf(status: { sessionFile?: unknown }): string | undefined {\n const candidate = status.sessionFile;\n if (typeof candidate !== 'string' || !candidate.startsWith('/')) return undefined;\n return candidate.endsWith(JOURNAL_EXTENSION) ? candidate : undefined;\n}\n\nconst STATE_MAP: Readonly<Record<string, SubagentRunState>> = {\n queued: 'queued',\n running: 'running',\n complete: 'done',\n completed: 'done',\n failed: 'failed',\n paused: 'stopped',\n stopped: 'stopped',\n canceled: 'stopped',\n cancelled: 'stopped',\n};\n\nconst TERMINAL_STATES: ReadonlySet<SubagentRunState> = new Set(['done', 'failed', 'stopped']);\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asOptionalString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction asOptionalNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n/** Recent-entry arrays are declared `unknown[]` upstream; render what reads as text. */\nfunction entryText(entry: unknown): string | undefined {\n if (typeof entry === 'string') return entry.slice(0, TAIL_ENTRY_LIMIT);\n if (isRecord(entry)) {\n const candidate = entry.text ?? entry.line ?? entry.output ?? entry.name;\n if (typeof candidate === 'string') return candidate.slice(0, TAIL_ENTRY_LIMIT);\n }\n return undefined;\n}\n\n/**\n * Validates one run status file into the wire shape.\n *\n * Returns undefined for anything unreadable, a foreign format, or a run\n * marked internal: the fleet view is for the runs the user asked about.\n */\nexport function parseSubagentRun(raw: string): SubagentRun | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed)) return undefined;\n if (parsed.internal === true) return undefined;\n const runId = asOptionalString(parsed.runId);\n const agent = asOptionalString(parsed.agent);\n const rawState = asOptionalString(parsed.state);\n const startedAt = asOptionalNumber(parsed.startedAt);\n if (!runId || !agent || !rawState || startedAt === undefined) return undefined;\n const state = STATE_MAP[rawState];\n if (state === undefined) return undefined;\n\n const tail = Array.isArray(parsed.recentOutput)\n ? parsed.recentOutput\n .map(entryText)\n .filter((line): line is string => line !== undefined)\n .slice(-TAIL_LIMIT)\n : [];\n\n const endedAt = asOptionalNumber(parsed.endedAt);\n const summary = asOptionalString(parsed.summary);\n const error = asOptionalString(parsed.error);\n return {\n runId,\n agent,\n state,\n rawState,\n task: asOptionalString(parsed.task) ?? '',\n ...(asOptionalString(parsed.taskRef) === undefined ? {} : { taskRef: asOptionalString(parsed.taskRef) }),\n ...(asOptionalString(parsed.model) === undefined ? {} : { model: asOptionalString(parsed.model) }),\n cwd: asOptionalString(parsed.cwd) ?? '',\n startedAt,\n ...(endedAt === undefined ? {} : { endedAt }),\n lastUpdate: asOptionalNumber(parsed.lastUpdate) ?? startedAt,\n ...(asOptionalString(parsed.currentTool) === undefined\n ? {}\n : { currentTool: asOptionalString(parsed.currentTool) }),\n ...(asOptionalNumber(parsed.toolCount) === undefined ? {} : { toolCount: asOptionalNumber(parsed.toolCount) }),\n ...(asOptionalNumber(parsed.tokens) === undefined ? {} : { tokens: asOptionalNumber(parsed.tokens) }),\n ...(summary === undefined ? {} : { summary }),\n ...(error === undefined ? {} : { error }),\n tail,\n };\n}\n\n/**\n * Reads the runtime-owned process registry into the ids whose runner process is\n * still alive. An unreadable or foreign registry returns undefined so callers\n * fail open rather than hiding work whose lifecycle cannot be proven.\n */\nexport function activeRunIdsFromRegistry(\n raw: string,\n isAlive: (pid: number) => boolean,\n): ReadonlySet<string> | undefined {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return undefined;\n }\n if (!isRecord(parsed) || parsed.version !== 1 || !Array.isArray(parsed.runs)) return undefined;\n\n const records: Array<{ runId: string; pid: number }> = [];\n for (const entry of parsed.runs) {\n if (!isRecord(entry)) return undefined;\n const runId = asOptionalString(entry.runId);\n const pid = asOptionalNumber(entry.pid);\n if (!runId || pid === undefined || !Number.isSafeInteger(pid) || pid <= 0) return undefined;\n records.push({ runId, pid });\n }\n return new Set(records.filter((record) => isAlive(record.pid)).map((record) => record.runId));\n}\n\n/** Reconciles status snapshots that outlived their authoritative runner record. */\nexport function reconcileRunLifecycles(\n runs: readonly SubagentRun[],\n activeRunIds: ReadonlySet<string> | undefined,\n): SubagentRun[] {\n if (activeRunIds === undefined) return [...runs];\n return runs.map((run) => {\n if (TERMINAL_STATES.has(run.state) || activeRunIds.has(run.runId)) return run;\n return { ...run, state: 'stopped', endedAt: run.endedAt ?? run.lastUpdate };\n });\n}\n\n/** Active runs first, then newest first; terminal runs leave after the retention window. */\nexport function presentRuns(runs: readonly SubagentRun[], now: number): SubagentRun[] {\n return runs\n .filter((run) => {\n if (!TERMINAL_STATES.has(run.state)) return true;\n return now - (run.endedAt ?? run.lastUpdate) < RUN_RETENTION_MS;\n })\n .sort((left, right) => {\n const leftActive = TERMINAL_STATES.has(left.state) ? 1 : 0;\n const rightActive = TERMINAL_STATES.has(right.state) ? 1 : 0;\n if (leftActive !== rightActive) return leftActive - rightActive;\n return right.startedAt - left.startedAt;\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-team",
3
- "version": "0.0.1-alpha.40",
3
+ "version": "0.0.1-alpha.43",
4
4
  "description": "Asynchronous named subagents, team runs, intercom, and model policy for Pi coding agents.",
5
5
  "keywords": [
6
6
  "agent-team",
@@ -55,19 +55,19 @@
55
55
  "@deepseek-ai/cordis": "4.0.1",
56
56
  "typebox": "1.3.16",
57
57
  "yaml": "2.9.0",
58
- "@agimon-ai/doompi-config": "0.0.1-alpha.40",
59
- "@agimon-ai/doompi-cache": "0.0.1-alpha.7",
60
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.40",
61
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.40",
62
- "@agimon-ai/doompi-ui": "0.0.1-alpha.40",
63
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.1",
64
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.3"
58
+ "@agimon-ai/doompi-cache": "0.0.1-alpha.10",
59
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.43",
60
+ "@agimon-ai/doompi-config": "0.0.1-alpha.43",
61
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.43",
62
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.43",
63
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.6",
64
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.4"
65
65
  },
66
66
  "devDependencies": {
67
- "@earendil-works/pi-agent-core": "0.84.3",
68
- "@earendil-works/pi-ai": "0.84.3",
69
- "@earendil-works/pi-coding-agent": "0.84.3",
70
- "@earendil-works/pi-tui": "0.84.3",
67
+ "@earendil-works/pi-agent-core": "0.84.4",
68
+ "@earendil-works/pi-ai": "0.84.4",
69
+ "@earendil-works/pi-coding-agent": "0.84.4",
70
+ "@earendil-works/pi-tui": "0.84.4",
71
71
  "@tanstack/react-store": "0.11.1",
72
72
  "@tanstack/store": "0.11.1",
73
73
  "@types/node": "26.2.0",
@@ -78,10 +78,10 @@
78
78
  "vitest": "4.1.11"
79
79
  },
80
80
  "peerDependencies": {
81
- "@earendil-works/pi-agent-core": "0.84.3",
82
- "@earendil-works/pi-ai": "0.84.3",
83
- "@earendil-works/pi-coding-agent": "0.84.3",
84
- "@earendil-works/pi-tui": "0.84.3"
81
+ "@earendil-works/pi-agent-core": "0.84.4",
82
+ "@earendil-works/pi-ai": "0.84.4",
83
+ "@earendil-works/pi-coding-agent": "0.84.4",
84
+ "@earendil-works/pi-tui": "0.84.4"
85
85
  },
86
86
  "peerDependenciesMeta": {
87
87
  "@earendil-works/pi-agent-core": {
@@ -1,4 +1,4 @@
1
- import { Button, Input, StatusBadge } from '@agimon-ai/doompi-web-components';
1
+ import { Button, StatusBadge, Textarea } from '@agimon-ai/doompi-web-components';
2
2
  import type { TransientTab, WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
3
3
  import { useStore } from '@tanstack/react-store';
4
4
  import { type FormEvent, useEffect, useState } from 'react';
@@ -92,14 +92,16 @@ export function AgentThreadPanel({
92
92
  {canSteer ? (
93
93
  <form
94
94
  data-testid="agent-steer-composer"
95
- className="flex shrink-0 items-center gap-2 border-t border-doom-border-soft px-[26px] py-2"
95
+ className="flex shrink-0 items-end gap-2 border-t border-doom-border-soft px-[26px] py-3"
96
96
  onSubmit={submitGuidance}
97
97
  >
98
- <Input
98
+ <Textarea
99
99
  data-testid="agent-steer-input"
100
100
  aria-label="Steering guidance"
101
+ rows={3}
101
102
  value={guidance}
102
103
  placeholder="Guide this agent…"
104
+ className="min-h-20 flex-1 resize-y"
103
105
  onChange={(event) => setGuidance(event.target.value)}
104
106
  />
105
107
  <Button data-testid="agent-steer-submit" type="submit" size="xs" disabled={guidance.trim() === ''}>
@@ -6,7 +6,7 @@ import type { SubagentRun } from '../src/types/webSubagents.ts';
6
6
  import { agentThreadTab } from './AgentThreadPanel.tsx';
7
7
  import { openCatalog } from './catalogStore.ts';
8
8
  import { formatRunDuration } from './format.ts';
9
- import { isTerminalRun, subagents, visibleRuns } from './subagentsStore.ts';
9
+ import { activityRuns, isTerminalRun, subagents } from './subagentsStore.ts';
10
10
 
11
11
  const TICK_MS = 10_000;
12
12
  const SUBAGENTS_TAB = 'subagents';
@@ -37,7 +37,7 @@ function detail(run: SubagentRun): string {
37
37
  * the runtime's footer one-liner, which only says whether anything is running.
38
38
  */
39
39
  export function AgentsActivitySection({ sessionId, openTab, openTransientTab }: WebPluginSlotProps) {
40
- const runs = useStore(subagents.store, (state) => visibleRuns(subagents.select(state, sessionId)));
40
+ const runs = useStore(subagents.store, (state) => activityRuns(subagents.select(state, sessionId)));
41
41
  const [now, setNow] = useState(() => Date.now());
42
42
 
43
43
  useEffect(() => {
@@ -44,13 +44,18 @@ function without(list: readonly string[], runId: string): string[] {
44
44
  return list.filter((id) => id !== runId);
45
45
  }
46
46
 
47
- /** The session's runs minus the ones the reader dismissed; the reported list itself while nothing is. */
47
+ /** The full Subagents tab's runs minus the ones the reader dismissed. */
48
48
  export function visibleRuns(session: SubagentsSession): SubagentRun[] {
49
49
  return session.dismissed.length === 0
50
50
  ? session.runs
51
51
  : session.runs.filter((run) => !session.dismissed.includes(run.runId));
52
52
  }
53
53
 
54
+ /** Only runs that can still be doing work belong in the activity group. */
55
+ export function activityRuns(session: SubagentsSession): SubagentRun[] {
56
+ return visibleRuns(session).filter((run) => run.state === 'queued' || run.state === 'running');
57
+ }
58
+
54
59
  /**
55
60
  * Asks the runtime to stop a run, through the same slash verb the TUI uses.
56
61
  * Pi executes extension commands at once even mid-turn, so this works whether