@selesai/code 0.5.24 → 0.5.26

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.
Files changed (55) hide show
  1. package/dist/extensions/pi-powerline-footer/fixed-editor/terminal-split.ts +4 -1
  2. package/dist/extensions/pi-powerline-footer/index.ts +1 -0
  3. package/dist/extensions/workflow/adapter.ts +2 -2
  4. package/dist/extensions/workflow/modes/loop.ts +30 -11
  5. package/docs/workflows.md +3 -3
  6. package/package.json +2 -2
  7. package/dist/core/provider-display-names.d.ts +0 -2
  8. package/dist/core/provider-display-names.d.ts.map +0 -1
  9. package/dist/core/provider-display-names.js +0 -36
  10. package/dist/core/provider-display-names.js.map +0 -1
  11. package/dist/data/capabilities.d.ts +0 -48
  12. package/dist/data/capabilities.d.ts.map +0 -1
  13. package/dist/data/capabilities.js +0 -282
  14. package/dist/data/capabilities.js.map +0 -1
  15. package/dist/data/navigation.d.ts +0 -3
  16. package/dist/data/navigation.d.ts.map +0 -1
  17. package/dist/data/navigation.js +0 -90
  18. package/dist/data/navigation.js.map +0 -1
  19. package/dist/extensions/index.d.ts +0 -3
  20. package/dist/extensions/index.d.ts.map +0 -1
  21. package/dist/extensions/index.js +0 -9
  22. package/dist/extensions/index.js.map +0 -1
  23. package/dist/extensions/llama/client.d.ts +0 -61
  24. package/dist/extensions/llama/client.d.ts.map +0 -1
  25. package/dist/extensions/llama/client.js +0 -309
  26. package/dist/extensions/llama/client.js.map +0 -1
  27. package/dist/extensions/llama/huggingface.d.ts +0 -23
  28. package/dist/extensions/llama/huggingface.d.ts.map +0 -1
  29. package/dist/extensions/llama/huggingface.js +0 -146
  30. package/dist/extensions/llama/huggingface.js.map +0 -1
  31. package/dist/extensions/llama/index.d.ts +0 -3
  32. package/dist/extensions/llama/index.d.ts.map +0 -1
  33. package/dist/extensions/llama/index.js +0 -211
  34. package/dist/extensions/llama/index.js.map +0 -1
  35. package/dist/extensions/llama/provider.d.ts +0 -10
  36. package/dist/extensions/llama/provider.d.ts.map +0 -1
  37. package/dist/extensions/llama/provider.js +0 -106
  38. package/dist/extensions/llama/provider.js.map +0 -1
  39. package/dist/extensions/llama/ui.d.ts +0 -42
  40. package/dist/extensions/llama/ui.d.ts.map +0 -1
  41. package/dist/extensions/llama/ui.js +0 -420
  42. package/dist/extensions/llama/ui.js.map +0 -1
  43. package/dist/extensions/pi-intercom/lifecycle.test.ts +0 -60
  44. package/dist/extensions/pi-subagents/src/runs/background/wait.ts +0 -353
  45. package/dist/extensions/pi-subagents/test/unit/live-async-resume.test.ts +0 -170
  46. package/dist/extensions/pi-subagents/test/unit/wait.test.ts +0 -398
  47. package/dist/extensions/question/dialog-adapter.ts +0 -96
  48. package/dist/extensions/question/tests/ui-protocol.test.ts +0 -157
  49. package/dist/extensions/question/tui-adapter.ts +0 -15
  50. package/dist/extensions/question/ui-protocol.ts +0 -31
  51. package/dist/extensions/workflow/modes/quick.ts +0 -150
  52. package/dist/modes/interactive/interactive-mode.test.d.ts +0 -2
  53. package/dist/modes/interactive/interactive-mode.test.d.ts.map +0 -1
  54. package/dist/modes/interactive/interactive-mode.test.js +0 -45
  55. package/dist/modes/interactive/interactive-mode.test.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/extensions/llama/ui.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tfuzzyFilter,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\tSpacer,\n\tText,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { ExtensionCommandContext } from \"../../core/extensions/types.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport { DynamicBorder } from \"../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../modes/interactive/theme/theme.ts\";\nimport type { LlamaModelInfo, LlamaProgress } from \"./client.ts\";\nimport type { HuggingFaceModel } from \"./huggingface.ts\";\n\nconst DOWNLOAD_VALUE = \"\\0download\";\n\nexport type LlamaManagerAction = { type: \"model\"; model: LlamaModelInfo } | { type: \"download\" } | { type: \"close\" };\n\ninterface ProgressState extends LlamaProgress {\n\ttitle: string;\n\tmodel: string;\n}\n\nfunction contextLabel(model: LlamaModelInfo): string | undefined {\n\tconst context = model.meta?.n_ctx ?? model.meta?.n_ctx_train;\n\tif (context) return context >= 1000 ? `${Math.round(context / 1000)}k` : String(context);\n\tconst args = model.status.args ?? [];\n\tfor (let index = 0; index < args.length - 1; index++) {\n\t\tif (args[index] !== \"--ctx-size\" && args[index] !== \"-c\" && args[index] !== \"-ctx\") continue;\n\t\tconst value = Number(args[index + 1]);\n\t\tif (Number.isFinite(value) && value > 0) return value >= 1000 ? `${Math.round(value / 1000)}k` : String(value);\n\t}\n\treturn undefined;\n}\n\nfunction modelDescription(model: LlamaModelInfo): string {\n\tconst details: string[] = [];\n\tconst loaded = model.status.value === \"loaded\" || model.status.value === \"sleeping\";\n\tif (loaded) details.push(\"loaded\");\n\telse if (model.status.value !== \"unloaded\") details.push(model.status.value);\n\tconst context = loaded ? contextLabel(model) : undefined;\n\tif (context) details.push(`${context} context`);\n\treturn details.join(\" · \");\n}\n\nfunction selectTheme(theme: Theme) {\n\treturn {\n\t\tselectedPrefix: (text: string) => theme.fg(\"accent\", text),\n\t\tselectedText: (text: string) => theme.fg(\"accent\", text),\n\t\tdescription: (text: string) => theme.fg(\"muted\", text),\n\t\tscrollInfo: (text: string) => theme.fg(\"dim\", text),\n\t\tnoMatch: (text: string) => theme.fg(\"warning\", text),\n\t};\n}\n\nfunction frame(theme: Theme, title: string, body: Component[], footer?: string): Container {\n\tconst container = new Container();\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\tcontainer.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\tfor (const child of body) container.addChild(child);\n\tif (footer) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"dim\", footer), 1, 0));\n\t}\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\treturn container;\n}\n\nexport interface LlamaUi {\n\tshowModels(serverUrl: string, models: LlamaModelInfo[]): Promise<LlamaManagerAction>;\n\tselect(title: string, options: string[]): Promise<string | undefined>;\n\tconfirm(title: string, message: string): Promise<boolean>;\n\tconnectionError(serverUrl: string, message: string): Promise<\"retry\" | \"close\">;\n\tsearchModels(\n\t\tsearch: (query: string, signal: AbortSignal) => Promise<HuggingFaceModel[]>,\n\t): Promise<string | undefined>;\n\tshowStatus(title: string, message: string): void;\n\tprogress(state: ProgressState): Promise<void>;\n\tupdateProgress(state: ProgressState): void;\n}\n\nfunction compactCount(value: number): string {\n\tif (value >= 1_000_000) return `${(value / 1_000_000).toFixed(value >= 10_000_000 ? 0 : 1)}M`;\n\tif (value >= 1_000) return `${(value / 1_000).toFixed(value >= 100_000 ? 0 : 1)}k`;\n\treturn String(value);\n}\n\nclass HuggingFaceSearch extends Container implements Focusable {\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate readonly search: (query: string, signal: AbortSignal) => Promise<HuggingFaceModel[]>;\n\tprivate readonly cache: Map<string, HuggingFaceModel[]>;\n\tprivate readonly onSelectModel: (model: string | undefined) => void;\n\tprivate readonly input = new Input();\n\tprivate readonly resultsContainer = new Container();\n\tprivate results: HuggingFaceModel[] = [];\n\tprivate filteredResults: HuggingFaceModel[] = [];\n\tprivate selectedIndex = 0;\n\tprivate query = \"\";\n\tprivate status = \"Type at least 2 characters\";\n\tprivate debounce: ReturnType<typeof setTimeout> | undefined;\n\tprivate request: AbortController | undefined;\n\tprivate closed = false;\n\tprivate _focused = false;\n\n\tconstructor(\n\t\ttui: TUI,\n\t\ttheme: Theme,\n\t\tkeybindings: KeybindingsManager,\n\t\tsearch: (query: string, signal: AbortSignal) => Promise<HuggingFaceModel[]>,\n\t\tcache: Map<string, HuggingFaceModel[]>,\n\t\tonSelectModel: (model: string | undefined) => void,\n\t) {\n\t\tsuper();\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.keybindings = keybindings;\n\t\tthis.search = search;\n\t\tthis.cache = cache;\n\t\tthis.onSelectModel = onSelectModel;\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \"Model name or owner/repository[:quant]\"), 1, 0));\n\t\tthis.addChild(this.input);\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(this.resultsContainer);\n\t\tthis.updateResults();\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.input.focused = value;\n\t}\n\n\tprivate updateResults(): void {\n\t\tthis.resultsContainer.clear();\n\t\tconst maxVisible = 10;\n\t\tconst start = Math.max(\n\t\t\t0,\n\t\t\tMath.min(this.selectedIndex - Math.floor(maxVisible / 2), this.filteredResults.length - maxVisible),\n\t\t);\n\t\tconst end = Math.min(start + maxVisible, this.filteredResults.length);\n\t\tfor (let index = start; index < end; index++) {\n\t\t\tconst model = this.filteredResults[index];\n\t\t\tif (!model) continue;\n\t\t\tconst prefix = index === this.selectedIndex ? \"→ \" : \" \";\n\t\t\tconst details = `${compactCount(model.downloads)} downloads`;\n\t\t\tthis.resultsContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\tindex === this.selectedIndex\n\t\t\t\t\t\t? this.theme.fg(\"accent\", `${prefix}${model.id} ${details}`)\n\t\t\t\t\t\t: `${prefix}${model.id}${this.theme.fg(\"muted\", ` ${details}`)}`,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (start > 0 || end < this.filteredResults.length) {\n\t\t\tthis.resultsContainer.addChild(\n\t\t\t\tnew Text(this.theme.fg(\"dim\", ` (${this.selectedIndex + 1}/${this.filteredResults.length})`), 0, 0),\n\t\t\t);\n\t\t}\n\t\tif (this.filteredResults.length === 0) {\n\t\t\tthis.resultsContainer.addChild(new Text(this.theme.fg(\"dim\", ` ${this.status}`), 0, 0));\n\t\t} else if (this.status === \"Searching Hugging Face…\") {\n\t\t\tthis.resultsContainer.addChild(new Text(this.theme.fg(\"dim\", ` ${this.status}`), 0, 0));\n\t\t}\n\t\tthis.tui.requestRender();\n\t}\n\n\tprivate filterResults(): void {\n\t\tif (this.query) {\n\t\t\tconst matches = new Set(fuzzyFilter(this.results, this.query, (model) => model.id).map((model) => model.id));\n\t\t\tthis.filteredResults = this.results.filter((model) => matches.has(model.id));\n\t\t} else {\n\t\t\tthis.filteredResults = this.results;\n\t\t}\n\t\tthis.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredResults.length - 1));\n\t\tthis.updateResults();\n\t}\n\n\tprivate scheduleSearch(): void {\n\t\tif (this.debounce) clearTimeout(this.debounce);\n\t\tthis.request?.abort();\n\t\tthis.request = undefined;\n\t\tif (this.query.length < 2) {\n\t\t\tthis.status = \"Type at least 2 characters\";\n\t\t\tthis.filterResults();\n\t\t\treturn;\n\t\t}\n\t\tconst cached = this.cache.get(this.query.toLowerCase());\n\t\tif (cached) {\n\t\t\tthis.results = cached;\n\t\t\tthis.status = cached.length === 0 ? \"No GGUF models found\" : \"\";\n\t\t\tthis.filterResults();\n\t\t\treturn;\n\t\t}\n\t\tthis.status = \"Searching Hugging Face…\";\n\t\tthis.filterResults();\n\t\tthis.debounce = setTimeout(() => void this.runSearch(this.query), 500);\n\t}\n\n\tprivate async runSearch(query: string): Promise<void> {\n\t\tconst request = new AbortController();\n\t\tthis.request = request;\n\t\ttry {\n\t\t\tconst results = await this.search(query, request.signal);\n\t\t\tthis.cache.set(query.toLowerCase(), results);\n\t\t\tif (this.closed || request.signal.aborted || this.query !== query) return;\n\t\t\tthis.results = results;\n\t\t\tthis.selectedIndex = 0;\n\t\t\tthis.status = results.length === 0 ? \"No GGUF models found\" : \"\";\n\t\t\tthis.filterResults();\n\t\t} catch (error) {\n\t\t\tif (this.closed || request.signal.aborted || this.query !== query) return;\n\t\t\tthis.results = [];\n\t\t\tthis.status = error instanceof Error ? error.message : String(error);\n\t\t\tthis.filterResults();\n\t\t} finally {\n\t\t\tif (this.request === request) this.request = undefined;\n\t\t}\n\t}\n\n\tprivate close(model: string | undefined): void {\n\t\tif (this.closed) return;\n\t\tthis.closed = true;\n\t\tif (this.debounce) clearTimeout(this.debounce);\n\t\tthis.request?.abort();\n\t\tthis.onSelectModel(model);\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.keybindings.matches(data, \"tui.select.up\")) {\n\t\t\tif (this.filteredResults.length > 0) {\n\t\t\t\tthis.selectedIndex = this.selectedIndex === 0 ? this.filteredResults.length - 1 : this.selectedIndex - 1;\n\t\t\t\tthis.updateResults();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"tui.select.down\")) {\n\t\t\tif (this.filteredResults.length > 0) {\n\t\t\t\tthis.selectedIndex = this.selectedIndex === this.filteredResults.length - 1 ? 0 : this.selectedIndex + 1;\n\t\t\t\tthis.updateResults();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"tui.select.confirm\")) {\n\t\t\tconst exact = /^[^/\\s]+\\/[^:\\s]+(?::[^\\s:]+)?$/u.test(this.query) ? this.query : undefined;\n\t\t\tconst selected = exact ?? this.filteredResults[this.selectedIndex]?.id;\n\t\t\tif (selected) this.close(selected);\n\t\t\treturn;\n\t\t}\n\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.close(undefined);\n\t\t\treturn;\n\t\t}\n\t\tthis.input.handleInput(data);\n\t\tconst query = this.input.getValue().trim();\n\t\tif (query === this.query) return;\n\t\tthis.query = query;\n\t\tthis.scheduleSearch();\n\t}\n}\n\nclass LlamaView implements LlamaUi, Focusable {\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate readonly searchCache = new Map<string, HuggingFaceModel[]>();\n\tprivate content: Container;\n\tprivate inputHandler: { handleInput?(data: string): void } | undefined;\n\tprivate inputTarget: Focusable | undefined;\n\tprivate progressPromise: Promise<void> | undefined;\n\tprivate progressResolver: (() => void) | undefined;\n\tprivate showingProgress = false;\n\tprivate _focused = false;\n\n\tconstructor(tui: TUI, theme: Theme, keybindings: KeybindingsManager) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.keybindings = keybindings;\n\t\tthis.content = frame(theme, \"llama.cpp models\", [new Text(theme.fg(\"muted\", \"Loading…\"), 1, 1)]);\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tif (this.inputTarget) this.inputTarget.focused = value;\n\t}\n\n\tprivate setContent(\n\t\tcontent: Container,\n\t\tinputHandler?: { handleInput?(data: string): void },\n\t\tinputTarget?: Focusable,\n\t): void {\n\t\tif (this.inputTarget) this.inputTarget.focused = false;\n\t\tthis.progressPromise = undefined;\n\t\tthis.progressResolver = undefined;\n\t\tthis.showingProgress = false;\n\t\tthis.content = content;\n\t\tthis.inputHandler = inputHandler;\n\t\tthis.inputTarget = inputTarget;\n\t\tif (this.inputTarget) this.inputTarget.focused = this._focused;\n\t\tthis.tui.requestRender();\n\t}\n\n\tshowModels(serverUrl: string, models: LlamaModelInfo[]): Promise<LlamaManagerAction> {\n\t\tconst sorted = [...models].sort((left, right) => {\n\t\t\tconst loaded = Number(right.status.value === \"loaded\") - Number(left.status.value === \"loaded\");\n\t\t\treturn loaded || left.id.localeCompare(right.id);\n\t\t});\n\t\tconst byId = new Map(sorted.map((model) => [model.id, model]));\n\t\tconst items: SelectItem[] = [\n\t\t\t...sorted.map((model) => ({\n\t\t\t\tvalue: model.id,\n\t\t\t\tlabel: model.id,\n\t\t\t\tdescription: modelDescription(model),\n\t\t\t})),\n\t\t\t{ value: DOWNLOAD_VALUE, label: \"Download model…\", description: \"Hugging Face owner/repository[:quant]\" },\n\t\t];\n\t\treturn new Promise((resolve) => {\n\t\t\tconst list = new SelectList(items, Math.min(items.length, 12), selectTheme(this.theme), {\n\t\t\t\tminPrimaryColumnWidth: 36,\n\t\t\t\tmaxPrimaryColumnWidth: 56,\n\t\t\t});\n\t\t\tlist.onSelect = (item) => {\n\t\t\t\tif (item.value === DOWNLOAD_VALUE) resolve({ type: \"download\" });\n\t\t\t\telse {\n\t\t\t\t\tconst model = byId.get(item.value);\n\t\t\t\t\tif (model) resolve({ type: \"model\", model });\n\t\t\t\t}\n\t\t\t};\n\t\t\tlist.onCancel = () => resolve({ type: \"close\" });\n\t\t\tthis.setContent(\n\t\t\t\tframe(\n\t\t\t\t\tthis.theme,\n\t\t\t\t\t\"llama.cpp models\",\n\t\t\t\t\t[new Text(this.theme.fg(\"dim\", serverUrl), 1, 0), new Spacer(1), list],\n\t\t\t\t\t`${keyHint(\"tui.select.confirm\", \"load/unload/download\")} • ${keyHint(\"tui.select.cancel\", \"close\")}`,\n\t\t\t\t),\n\t\t\t\tlist,\n\t\t\t);\n\t\t});\n\t}\n\n\tselect(title: string, options: string[]): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst list = new SelectList(\n\t\t\t\toptions.map((option) => ({ value: option, label: option })),\n\t\t\t\tMath.min(options.length, 12),\n\t\t\t\tselectTheme(this.theme),\n\t\t\t);\n\t\t\tlist.onSelect = (item) => resolve(item.value);\n\t\t\tlist.onCancel = () => resolve(undefined);\n\t\t\tthis.setContent(\n\t\t\t\tframe(\n\t\t\t\t\tthis.theme,\n\t\t\t\t\ttitle,\n\t\t\t\t\t[new Spacer(1), list],\n\t\t\t\t\t`${keyHint(\"tui.select.confirm\", \"select\")} • ${keyHint(\"tui.select.cancel\", \"cancel\")}`,\n\t\t\t\t),\n\t\t\t\tlist,\n\t\t\t);\n\t\t});\n\t}\n\n\tasync confirm(title: string, message: string): Promise<boolean> {\n\t\treturn (await this.select(`${title}\\n${message}`, [\"Yes\", \"No\"])) === \"Yes\";\n\t}\n\n\tasync connectionError(serverUrl: string, message: string): Promise<\"retry\" | \"close\"> {\n\t\tconst choice = await this.select(`llama.cpp unavailable\\n${serverUrl}\\n\\n${message}`, [\"Retry\", \"Close\"]);\n\t\treturn choice === \"Retry\" ? \"retry\" : \"close\";\n\t}\n\n\tsearchModels(\n\t\tsearch: (query: string, signal: AbortSignal) => Promise<HuggingFaceModel[]>,\n\t): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tconst component = new HuggingFaceSearch(\n\t\t\t\tthis.tui,\n\t\t\t\tthis.theme,\n\t\t\t\tthis.keybindings,\n\t\t\t\tsearch,\n\t\t\t\tthis.searchCache,\n\t\t\t\tresolve,\n\t\t\t);\n\t\t\tthis.setContent(\n\t\t\t\tframe(\n\t\t\t\t\tthis.theme,\n\t\t\t\t\t\"Download model\",\n\t\t\t\t\t[new Spacer(1), component],\n\t\t\t\t\t`${keyHint(\"tui.select.confirm\", \"select\")} • ${keyHint(\"tui.select.cancel\", \"back\")}`,\n\t\t\t\t),\n\t\t\t\tcomponent,\n\t\t\t\tcomponent,\n\t\t\t);\n\t\t});\n\t}\n\n\tshowStatus(title: string, message: string): void {\n\t\tthis.setContent(frame(this.theme, title, [new Spacer(1), new Text(this.theme.fg(\"muted\", message), 1, 0)]));\n\t}\n\n\tprogress(state: ProgressState): Promise<void> {\n\t\tif (!this.progressPromise) {\n\t\t\tthis.progressPromise = new Promise((resolve) => {\n\t\t\t\tthis.progressResolver = resolve;\n\t\t\t});\n\t\t}\n\t\tthis.showingProgress = true;\n\t\tthis.updateProgress(state);\n\t\treturn this.progressPromise;\n\t}\n\n\tupdateProgress(state: ProgressState): void {\n\t\tif (!this.showingProgress) return;\n\t\tconst body = [\n\t\t\tnew Text(this.theme.fg(\"text\", state.model), 1, 0),\n\t\t\tnew Spacer(1),\n\t\t\tnew Text(this.theme.fg(\"muted\", state.message), 1, 0),\n\t\t];\n\t\tif (state.ratio !== undefined) {\n\t\t\tconst available = 40;\n\t\t\tconst filled = Math.round(Math.max(0, Math.min(1, state.ratio)) * available);\n\t\t\tbody.push(\n\t\t\t\tnew Text(\n\t\t\t\t\tthis.theme.fg(\n\t\t\t\t\t\t\"accent\",\n\t\t\t\t\t\t`${\"█\".repeat(filled)}${\"─\".repeat(available - filled)} ${Math.round(state.ratio * 100)}%`,\n\t\t\t\t\t),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (state.detail) body.push(new Text(this.theme.fg(\"dim\", state.detail), 1, 0));\n\t\tthis.content = frame(this.theme, state.title, body, keyHint(\"tui.select.cancel\", \"stop\"));\n\t\tthis.inputHandler = undefined;\n\t\tthis.tui.requestRender();\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.progressResolver && this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\tconst resolve = this.progressResolver;\n\t\t\tthis.progressPromise = undefined;\n\t\t\tthis.progressResolver = undefined;\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\t\tthis.inputHandler?.handleInput?.(data);\n\t\tthis.tui.requestRender();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.content\n\t\t\t.render(width)\n\t\t\t.map((line) => (visibleWidth(line) > width ? truncateToWidth(line, width, \"\") : line));\n\t}\n\n\tinvalidate(): void {\n\t\tthis.content.invalidate();\n\t}\n}\n\nexport async function showLlamaUi(ctx: ExtensionCommandContext, run: (ui: LlamaUi) => Promise<void>): Promise<void> {\n\tawait ctx.ui.custom<void>((tui, theme, keybindings, done) => {\n\t\tconst view = new LlamaView(tui, theme, keybindings);\n\t\tvoid run(view).then(\n\t\t\t() => done(),\n\t\t\t(error: unknown) => {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t\tdone();\n\t\t\t},\n\t\t);\n\t\treturn view;\n\t});\n}\n\nexport async function runWithProgress<T>(\n\tui: LlamaUi,\n\toptions: {\n\t\ttitle: string;\n\t\tmodel: string;\n\t\tinitialMessage: string;\n\t\tcancelTitle: string;\n\t\tcancelMessage: string;\n\t\trun(signal: AbortSignal, update: (progress: LlamaProgress) => void): Promise<T>;\n\t\tcancel(): Promise<void>;\n\t},\n): Promise<{ cancelled: true } | { cancelled: false; value: T }> {\n\tconst controller = new AbortController();\n\tconst state: ProgressState = { title: options.title, model: options.model, message: options.initialMessage };\n\tconst settled = options\n\t\t.run(controller.signal, (progress) => {\n\t\t\tObject.assign(state, progress);\n\t\t\tui.updateProgress(state);\n\t\t})\n\t\t.then(\n\t\t\t(value) => ({ ok: true as const, value }),\n\t\t\t(error: unknown) => ({ ok: false as const, error }),\n\t\t);\n\tlet completed = false;\n\tsettled.finally(() => {\n\t\tcompleted = true;\n\t});\n\n\twhile (!completed) {\n\t\tconst outcome = await Promise.race([\n\t\t\tsettled.then(() => \"settled\" as const),\n\t\t\tui.progress(state).then(() => \"stop\" as const),\n\t\t]);\n\t\tif (outcome === \"settled\") break;\n\t\tconst stop = await ui.confirm(options.cancelTitle, options.cancelMessage);\n\t\tif (!stop || completed) continue;\n\t\ttry {\n\t\t\tawait options.cancel();\n\t\t} finally {\n\t\t\tcontroller.abort(new Error(\"Cancelled\"));\n\t\t}\n\t\tawait settled;\n\t\treturn { cancelled: true };\n\t}\n\n\tconst result = await settled;\n\tif (!result.ok) throw result.error;\n\treturn { cancelled: false, value: result.value };\n}\n"]}
@@ -1,60 +0,0 @@
1
- import { EventEmitter } from "node:events";
2
- import { mkdtempSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import path from "node:path";
5
- import { describe, expect, it } from "vitest";
6
-
7
- describe("intercom lifecycle safety", () => {
8
- it("stale relay delivery cannot become an unhandled rejection", async () => {
9
- const agentDir = mkdtempSync(path.join(tmpdir(), "selesai-intercom-test-"));
10
- const previousAgentDir = process.env.SELESAI_CODING_AGENT_DIR;
11
- process.env.SELESAI_CODING_AGENT_DIR = agentDir;
12
-
13
- try {
14
- const { default: piIntercomExtension } = await import("./index.ts");
15
- const events = new EventEmitter();
16
- let sendAttempts = 0;
17
- const pi = {
18
- getSessionName: () => "orchestrator",
19
- events: {
20
- on: (name: string, handler: (payload: unknown) => void) => {
21
- events.on(name, handler);
22
- return () => events.off(name, handler);
23
- },
24
- emit: (name: string, payload: unknown) => events.emit(name, payload),
25
- },
26
- on: () => undefined,
27
- registerMessageRenderer: () => undefined,
28
- registerTool: () => undefined,
29
- registerCommand: () => undefined,
30
- registerShortcut: () => undefined,
31
- sendMessage: () => {
32
- sendAttempts += 1;
33
- throw new Error("stale extension context");
34
- },
35
- appendEntry: () => undefined,
36
- };
37
-
38
- const unhandled: unknown[] = [];
39
- const onUnhandledRejection = (reason: unknown) => unhandled.push(reason);
40
- process.on("unhandledRejection", onUnhandledRejection);
41
- try {
42
- piIntercomExtension(pi as never);
43
- events.emit("subagent:control-intercom", {
44
- to: "orchestrator",
45
- message: "delivery races with session replacement",
46
- });
47
- await new Promise<void>((resolve) => setImmediate(resolve));
48
- } finally {
49
- process.off("unhandledRejection", onUnhandledRejection);
50
- }
51
-
52
- expect(sendAttempts).toBe(1);
53
- expect(unhandled).toEqual([]);
54
- } finally {
55
- if (previousAgentDir === undefined) delete process.env.SELESAI_CODING_AGENT_DIR;
56
- else process.env.SELESAI_CODING_AGENT_DIR = previousAgentDir;
57
- rmSync(agentDir, { recursive: true, force: true });
58
- }
59
- });
60
- });
@@ -1,353 +0,0 @@
1
- /**
2
- * `wait` tool: block the current turn until outstanding async subagent runs
3
- * finish (or another completion notification arrives).
4
- *
5
- * Background subagent runs are detached. In an interactive session the parent
6
- * can end its turn and Pi will wake it with a completion notification. That
7
- * does not work when the parent is a skill that must run to completion, and it
8
- * cannot work at all non-interactively (`pi -p ...`), where the run is a single
9
- * turn: once the turn ends there is nothing left to receive the notification.
10
- *
11
- * `wait` closes that gap. It keeps the turn alive until a tracked async run for
12
- * this session reaches a terminal state (complete / failed / paused), the
13
- * caller-supplied timeout elapses, or the turn is aborted. Because it awaits
14
- * inside the turn, the completion the model was told to wait for is actually
15
- * observed before the tool returns.
16
- *
17
- * By default `wait` returns as soon as ONE run finishes, so a fleet manager can
18
- * use it in a rolling-replacement loop: launch N workers, wait for the next one
19
- * to finish, spawn its replacement, wait again — keeping N in flight instead of
20
- * draining to zero between batches. Pass `all: true` to block until every
21
- * tracked run is terminal, or `id` to block on one specific run.
22
- *
23
- * `wait` also returns when a run needs attention — not just on completion. A
24
- * child that goes idle or blocks for a decision surfaces `needs_attention`
25
- * (the same signal Pi shows as a control notice and, interactively, wakes the
26
- * parent with). Since `wait` is used exactly where there is no next turn to
27
- * receive that notice, it must break on it too, or a stuck child would stall
28
- * the loop until the timeout. Attention runs are reported so the caller can
29
- * inspect / nudge / resume / interrupt them.
30
- *
31
- * Wake mechanism: when given Pi's event bus (`deps.events`), `wait` subscribes
32
- * to the subagent completion/control channels and wakes the instant any fires,
33
- * rather than waiting out a fixed poll interval. A poll still runs on the
34
- * interval as a reconciliation fallback (crashed runners, missed events), and
35
- * the poll is the source of truth for what actually changed — the event only
36
- * ends the sleep early. With no bus, `wait` degrades to pure polling.
37
- */
38
-
39
- import type { AgentToolResult } from "@earendil-works/pi-agent-core";
40
- import { listAsyncRuns, type AsyncRunSummary } from "./async-status.ts";
41
- import {
42
- ASYNC_DIR,
43
- RESULTS_DIR,
44
- SUBAGENT_ASYNC_COMPLETE_EVENT,
45
- SUBAGENT_CONTROL_EVENT,
46
- SUBAGENT_CONTROL_INTERCOM_EVENT,
47
- SUBAGENT_RESULT_INTERCOM_EVENT,
48
- type Details,
49
- type SubagentState,
50
- } from "../../shared/types.ts";
51
- import { formatDuration } from "../../shared/formatters.ts";
52
-
53
- /** States that mean a run is still in flight (not yet resolved). */
54
- const ACTIVE_STATES: ReadonlyArray<AsyncRunSummary["state"]> = ["queued", "running"];
55
-
56
- const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
57
- const MIN_POLL_INTERVAL_MS = 250;
58
- const DEFAULT_POLL_INTERVAL_MS = 1000;
59
-
60
- export interface WaitParams {
61
- /** Optional run id/prefix to wait for. When omitted, waits across every active run in this session. */
62
- id?: string;
63
- /**
64
- * When true, block until EVERY active run in this session (or matching `id`)
65
- * is terminal. Default false: return as soon as the first run finishes, so a
66
- * fleet manager can spawn a replacement and wait again. Ignored when `id`
67
- * targets a single run.
68
- */
69
- all?: boolean;
70
- /** Give up after this many milliseconds. Defaults to 30 minutes. */
71
- timeoutMs?: number;
72
- }
73
-
74
- /** Minimal event-bus surface wait subscribes to (matches pi.events). */
75
- export interface WaitEventBus {
76
- on(channel: string, handler: (data: unknown) => void): () => void;
77
- }
78
-
79
- export interface WaitDeps {
80
- state: SubagentState;
81
- asyncDirRoot?: string;
82
- resultsDir?: string;
83
- kill?: (pid: number, signal?: NodeJS.Signals | 0) => boolean;
84
- now?: () => number;
85
- pollIntervalMs?: number;
86
- /** Injectable sleep for tests. */
87
- sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
88
- /**
89
- * Optional event bus (pi.events). When provided, wait wakes immediately on a
90
- * subagent completion/control event instead of waiting out the poll interval;
91
- * the poll then remains as a reconciliation fallback (crashed runners, missed
92
- * events). Omit in tests that want pure poll behavior.
93
- */
94
- events?: WaitEventBus;
95
- }
96
-
97
- /** Bus channels that indicate a run changed state or needs attention. */
98
- const WAKE_CHANNELS = [
99
- SUBAGENT_ASYNC_COMPLETE_EVENT,
100
- SUBAGENT_CONTROL_EVENT,
101
- SUBAGENT_CONTROL_INTERCOM_EVENT,
102
- SUBAGENT_RESULT_INTERCOM_EVENT,
103
- ];
104
-
105
- function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
106
- return new Promise((resolve) => {
107
- if (signal?.aborted) {
108
- resolve();
109
- return;
110
- }
111
- const timer = setTimeout(() => {
112
- signal?.removeEventListener("abort", onAbort);
113
- resolve();
114
- }, ms);
115
- const onAbort = () => {
116
- clearTimeout(timer);
117
- resolve();
118
- };
119
- signal?.addEventListener("abort", onAbort, { once: true });
120
- });
121
- }
122
-
123
- /**
124
- * Sleep up to `ms`, but wake early if a subagent event fires on the bus (or the
125
- * turn aborts). Returns when the first of those happens. With no bus this is a
126
- * plain sleep, so the poll interval alone drives progress.
127
- */
128
- function waitForWake(ms: number, signal: AbortSignal | undefined, deps: WaitDeps): Promise<void> {
129
- const sleep = deps.sleep ?? defaultSleep;
130
- const events = deps.events;
131
- if (!events) return sleep(ms, signal);
132
- return new Promise((resolve) => {
133
- let settled = false;
134
- const unsubs: Array<() => void> = [];
135
- const wakeController = new AbortController();
136
- const done = () => {
137
- if (settled) return;
138
- settled = true;
139
- wakeController.abort();
140
- signal?.removeEventListener("abort", done);
141
- for (const u of unsubs) {
142
- try { u(); } catch { /* best effort */ }
143
- }
144
- resolve();
145
- };
146
- if (signal?.aborted) {
147
- done();
148
- return;
149
- }
150
- signal?.addEventListener("abort", done, { once: true });
151
- for (const channel of WAKE_CHANNELS) {
152
- try { unsubs.push(events.on(channel, done)); } catch { /* ignore bad channel */ }
153
- }
154
- // Poll-interval fallback so we still reconcile even if no event arrives.
155
- // The local signal cancels that fallback timer when an event wakes us first.
156
- void sleep(ms, wakeController.signal).then(done);
157
- });
158
- }
159
-
160
- function matchesId(run: AsyncRunSummary, id: string): boolean {
161
- return run.id === id || run.id.startsWith(id);
162
- }
163
-
164
- /** A running run that has flagged it needs the parent's attention. */
165
- function needsAttention(run: AsyncRunSummary): boolean {
166
- return run.activityState === "needs_attention";
167
- }
168
-
169
- /** Queued/running runs from this session, including runs that need attention. */
170
- function activeRunsForSession(params: WaitParams, deps: WaitDeps): AsyncRunSummary[] {
171
- const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
172
- const resultsDir = deps.resultsDir ?? RESULTS_DIR;
173
- const runs = listAsyncRuns(asyncDirRoot, {
174
- states: [...ACTIVE_STATES],
175
- sessionId: deps.state.currentSessionId ?? undefined,
176
- resultsDir,
177
- kill: deps.kill,
178
- now: deps.now,
179
- });
180
- return params.id ? runs.filter((run) => matchesId(run, params.id!)) : runs;
181
- }
182
-
183
- /** Runs (from the initial set) currently flagged needs_attention, for reporting. */
184
- function attentionRunsForSession(params: WaitParams, deps: WaitDeps, initialIds: Set<string>): AsyncRunSummary[] {
185
- return activeRunsForSession(params, deps).filter((run) => needsAttention(run) && initialIds.has(run.id));
186
- }
187
-
188
- /** All runs (any state) for this session, for the final summary. */
189
- function allRunsForSession(params: WaitParams, deps: WaitDeps): AsyncRunSummary[] {
190
- const asyncDirRoot = deps.asyncDirRoot ?? ASYNC_DIR;
191
- const resultsDir = deps.resultsDir ?? RESULTS_DIR;
192
- const runs = listAsyncRuns(asyncDirRoot, {
193
- sessionId: deps.state.currentSessionId ?? undefined,
194
- resultsDir,
195
- kill: deps.kill,
196
- now: deps.now,
197
- });
198
- return params.id ? runs.filter((run) => matchesId(run, params.id!)) : runs;
199
- }
200
-
201
- function summarizeTerminalRuns(runs: AsyncRunSummary[]): string {
202
- if (runs.length === 0) return "";
203
- const counts = { complete: 0, failed: 0, paused: 0 } as Record<string, number>;
204
- for (const run of runs) {
205
- if (run.state in counts) counts[run.state] += 1;
206
- }
207
- const parts: string[] = [];
208
- if (counts.complete) parts.push(`${counts.complete} complete`);
209
- if (counts.failed) parts.push(`${counts.failed} failed`);
210
- if (counts.paused) parts.push(`${counts.paused} paused`);
211
- return parts.join(", ");
212
- }
213
-
214
- function result(text: string, isError = false): AgentToolResult<Details> {
215
- return {
216
- content: [{ type: "text", text }],
217
- ...(isError ? { isError: true } : {}),
218
- details: { mode: "management", results: [] },
219
- };
220
- }
221
-
222
- /**
223
- * Block until the targeted async runs finish, the timeout elapses, or the turn
224
- * is aborted. Resolves with a short human-readable summary either way.
225
- */
226
- export async function waitForSubagents(
227
- params: WaitParams,
228
- signal: AbortSignal | undefined,
229
- deps: WaitDeps,
230
- ): Promise<AgentToolResult<Details>> {
231
- const now = deps.now ?? Date.now;
232
- const pollIntervalMs = Math.max(MIN_POLL_INTERVAL_MS, deps.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
233
- const timeoutMs = params.timeoutMs !== undefined && params.timeoutMs > 0 ? params.timeoutMs : DEFAULT_TIMEOUT_MS;
234
- const startedAt = now();
235
-
236
- // A single named run always means "wait until that one is done", regardless
237
- // of `all`. Otherwise `all` decides: true → every run terminal; false → the
238
- // first run to finish.
239
- const waitForAll = params.id ? true : params.all === true;
240
-
241
- let active: AsyncRunSummary[];
242
- try {
243
- active = activeRunsForSession(params, deps);
244
- } catch (error) {
245
- return result(error instanceof Error ? error.message : String(error), true);
246
- }
247
-
248
- if (active.length === 0) {
249
- const finished = params.id
250
- ? `No active run matched "${params.id}". Nothing to wait for.`
251
- : "No active async runs in this session. Nothing to wait for.";
252
- return result(finished);
253
- }
254
- if (params.id) {
255
- const exact = active.filter((run) => run.id === params.id);
256
- if (exact.length === 1) active = exact;
257
- else if (active.length > 1) {
258
- return result(`Ambiguous async run id prefix "${params.id}" matched ${active.length} active runs: ${active.map((run) => run.id).join(", ")}. Pass a longer id.`, true);
259
- }
260
- }
261
- const waitParams = params.id ? { ...params, id: active[0]!.id } : params;
262
-
263
- // The set of runs in flight when the wait began. In first-completion mode we
264
- // return as soon as any of THESE leaves the active set — a run spawned by a
265
- // concurrent turn shouldn't satisfy this wait.
266
- const initialIds = new Set(active.map((run) => run.id));
267
- const initialCount = initialIds.size;
268
- let pending = active.filter((run) => !needsAttention(run));
269
-
270
- const done = (active: AsyncRunSummary[], attention: AsyncRunSummary[]): boolean => {
271
- // A run needing attention always breaks the wait, in either mode: the
272
- // caller has to act on it (nudge/resume/interrupt) and blocking longer
273
- // helps nothing.
274
- if (attention.length > 0) return true;
275
- if (waitForAll) return active.every((run) => !initialIds.has(run.id));
276
- // First-completion: satisfied once any initially-pending run is gone.
277
- const stillActiveInitial = active.filter((run) => initialIds.has(run.id));
278
- return stillActiveInitial.length < initialCount;
279
- };
280
-
281
- let attention = active.filter((run) => needsAttention(run));
282
-
283
- while (!done(pending, attention)) {
284
- if (signal?.aborted) {
285
- const stillActive = pending.map((run) => `${run.id} (${run.state})`).join(", ");
286
- return result(`Wait aborted after ${formatDuration(now() - startedAt)}. Still active: ${stillActive}.`, true);
287
- }
288
- if (now() - startedAt >= timeoutMs) {
289
- const stillActive = pending.map((run) => `${run.id} (${run.state})`).join(", ");
290
- return result(
291
- `Wait timed out after ${formatDuration(timeoutMs)} with ${pending.length} run(s) still active: ${stillActive}. `
292
- + `The runs are detached and keep going; call wait again or inspect with subagent({ action: "status" }).`,
293
- true,
294
- );
295
- }
296
- await waitForWake(pollIntervalMs, signal, deps);
297
- try {
298
- active = activeRunsForSession(waitParams, deps);
299
- pending = active.filter((run) => !needsAttention(run));
300
- attention = attentionRunsForSession(waitParams, deps, initialIds);
301
- } catch (error) {
302
- return result(error instanceof Error ? error.message : String(error), true);
303
- }
304
- }
305
-
306
- // Report how the finished run(s) came out. In first-completion mode, name the
307
- // runs from the initial set that are now terminal.
308
- let terminalSummary = "";
309
- let finishedCount = 0;
310
- try {
311
- const allNow = allRunsForSession(waitParams, deps);
312
- const terminal = allNow.filter((run) => !ACTIVE_STATES.includes(run.state) && initialIds.has(run.id));
313
- finishedCount = terminal.length;
314
- terminalSummary = summarizeTerminalRuns(terminal);
315
- } catch {
316
- // Summary is best-effort; the important part is that the wait resolved.
317
- }
318
-
319
- const attentionNote = attention.length > 0
320
- ? ` ${attention.length} run(s) need attention: ${attention.map((r) => r.id).join(", ")} — inspect with subagent({ action: "status" }) then nudge/resume/interrupt.`
321
- : "";
322
-
323
- const stillRunning = pending.filter((run) => initialIds.has(run.id)).length;
324
- const elapsed = formatDuration(now() - startedAt);
325
- const outcome = terminalSummary ? ` Outcome: ${terminalSummary}.` : "";
326
-
327
- if (waitForAll) {
328
- const scope = params.id ? `run "${params.id}"` : `${initialCount} async run(s)`;
329
- const status = attention.length > 0 ? "attention required" : "done";
330
- const notificationText = attention.length > 0
331
- ? "Relevant completion/control events have been observed; inspect status if the notification is not visible yet."
332
- : "Completion events have been observed; inspect status if the notification is not visible yet.";
333
- return result(
334
- `Waited ${elapsed} for ${scope}; ${status}.${outcome}${attentionNote} ${notificationText}`,
335
- );
336
- }
337
-
338
- // First-completion mode.
339
- const remainder = stillRunning > 0
340
- ? ` ${stillRunning} run(s) still in flight — call wait again to catch the next one.`
341
- : attention.length > 0
342
- ? " No other runs are waitable until attention is handled."
343
- : " No runs remain in flight.";
344
- const progress = attention.length > 0 && finishedCount === 0
345
- ? `${attention.length} of ${initialCount} run(s) need attention`
346
- : `${finishedCount} of ${initialCount} run(s) finished`;
347
- const notificationText = finishedCount > 0
348
- ? " Completion events for the finished run(s) have been observed; inspect status if the notification is not visible yet."
349
- : " Relevant control events have been observed; inspect status if the notification is not visible yet.";
350
- return result(
351
- `Waited ${elapsed}; ${progress}.${outcome}${attentionNote}${remainder}${notificationText}`,
352
- );
353
- }
@@ -1,170 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import * as fs from "node:fs";
3
- import * as os from "node:os";
4
- import * as path from "node:path";
5
- import { describe, it } from "node:test";
6
- import {
7
- ASYNC_RESUME_INTERRUPT_SIGNAL,
8
- interruptLiveAsyncResumeTarget,
9
- resolveAsyncResumeTarget,
10
- } from "../../src/runs/background/async-resume.ts";
11
-
12
- function writeJson(filePath: string, value: object): void {
13
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
14
- fs.writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
15
- }
16
-
17
- describe("live async resume interrupt", () => {
18
- it("interrupts a resolved live async child before the caller sends a follow-up", () => {
19
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-live-async-resume-"));
20
- try {
21
- const asyncRoot = path.join(root, "runs");
22
- const resultsDir = path.join(root, "results");
23
- const asyncDir = path.join(asyncRoot, "run-live");
24
- writeJson(path.join(asyncDir, "status.json"), {
25
- runId: "run-live",
26
- mode: "single",
27
- state: "running",
28
- pid: process.pid,
29
- cwd: root,
30
- startedAt: 100,
31
- lastUpdate: Date.now(),
32
- steps: [{ agent: "worker", status: "running", startedAt: 100 }],
33
- });
34
- const target = resolveAsyncResumeTarget({ id: "run-live" }, { asyncDirRoot: asyncRoot, resultsDir });
35
- assert.equal(target.kind, "live");
36
-
37
- const kills: Array<{ pid: number; signal?: NodeJS.Signals | 0 }> = [];
38
- const state = {
39
- asyncJobs: new Map([["run-live", {
40
- asyncId: "run-live",
41
- asyncDir,
42
- status: "running" as const,
43
- pid: process.pid,
44
- activityState: "needs_attention" as const,
45
- updatedAt: 100,
46
- }]]),
47
- };
48
-
49
- const result = interruptLiveAsyncResumeTarget({
50
- target,
51
- state,
52
- now: () => 1234,
53
- resultsDir,
54
- kill: (pid, signal) => {
55
- kills.push({ pid, signal });
56
- return true;
57
- },
58
- });
59
-
60
- assert.deepEqual(result, { ok: true, asyncId: "run-live" });
61
- assert.deepEqual(kills, [{ pid: process.pid, signal: 0 }, { pid: process.pid, signal: ASYNC_RESUME_INTERRUPT_SIGNAL }]);
62
- assert.equal(state.asyncJobs.get("run-live")?.activityState, undefined);
63
- assert.equal(state.asyncJobs.get("run-live")?.updatedAt, 1234);
64
- // The portable control request is dropped regardless of the signal path.
65
- assert.equal(fs.existsSync(path.join(asyncDir, "control", "interrupt.json")), true);
66
- } finally {
67
- fs.rmSync(root, { recursive: true, force: true });
68
- }
69
- });
70
-
71
- it("still interrupts a live async child when the OS signal is unavailable (ENOSYS on Windows)", () => {
72
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-live-async-resume-enosys-"));
73
- try {
74
- const asyncRoot = path.join(root, "runs");
75
- const resultsDir = path.join(root, "results");
76
- const asyncDir = path.join(asyncRoot, "run-live");
77
- writeJson(path.join(asyncDir, "status.json"), {
78
- runId: "run-live",
79
- mode: "single",
80
- state: "running",
81
- pid: process.pid,
82
- cwd: root,
83
- startedAt: 100,
84
- lastUpdate: Date.now(),
85
- steps: [{ agent: "worker", status: "running", startedAt: 100 }],
86
- });
87
- const target = resolveAsyncResumeTarget({ id: "run-live" }, { asyncDirRoot: asyncRoot, resultsDir });
88
- assert.equal(target.kind, "live");
89
-
90
- const state = {
91
- asyncJobs: new Map([["run-live", {
92
- asyncId: "run-live",
93
- asyncDir,
94
- status: "running" as const,
95
- pid: process.pid,
96
- activityState: "needs_attention" as const,
97
- updatedAt: 100,
98
- }]]),
99
- };
100
-
101
- const result = interruptLiveAsyncResumeTarget({
102
- target,
103
- state,
104
- now: () => 7,
105
- resultsDir,
106
- kill: () => {
107
- const error = new Error("kill ENOSYS") as NodeJS.ErrnoException;
108
- error.code = "ENOSYS";
109
- throw error;
110
- },
111
- });
112
-
113
- // The signal failed, but the file-based control inbox makes the interrupt succeed.
114
- assert.deepEqual(result, { ok: true, asyncId: "run-live" });
115
- assert.equal(fs.existsSync(path.join(asyncDir, "control", "interrupt.json")), true);
116
- assert.equal(state.asyncJobs.get("run-live")?.activityState, undefined);
117
- assert.equal(state.asyncJobs.get("run-live")?.updatedAt, 7);
118
- } finally {
119
- fs.rmSync(root, { recursive: true, force: true });
120
- }
121
- });
122
-
123
- it("does not report success or leave a stale request when the runner pid is gone", () => {
124
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-live-async-resume-esrch-"));
125
- try {
126
- const asyncRoot = path.join(root, "runs");
127
- const resultsDir = path.join(root, "results");
128
- const asyncDir = path.join(asyncRoot, "run-live");
129
- writeJson(path.join(asyncDir, "status.json"), {
130
- runId: "run-live",
131
- mode: "single",
132
- state: "running",
133
- pid: 12345,
134
- cwd: root,
135
- startedAt: 100,
136
- lastUpdate: Date.now(),
137
- steps: [{ agent: "worker", status: "running", startedAt: 100 }],
138
- });
139
- const target = resolveAsyncResumeTarget({ id: "run-live" }, {
140
- asyncDirRoot: asyncRoot,
141
- resultsDir,
142
- kill: (_pid, signal) => {
143
- if (signal === 0) return true;
144
- const error = new Error("missing process") as NodeJS.ErrnoException;
145
- error.code = "ESRCH";
146
- throw error;
147
- },
148
- });
149
- assert.equal(target.kind, "live");
150
-
151
- const result = interruptLiveAsyncResumeTarget({
152
- target,
153
- resultsDir,
154
- kill: () => {
155
- const error = new Error("missing process") as NodeJS.ErrnoException;
156
- error.code = "ESRCH";
157
- throw error;
158
- },
159
- });
160
-
161
- assert.deepEqual(result, { ok: false, message: "Async run run-live is live but no interrupt-capable runner pid was found." });
162
- assert.equal(fs.existsSync(path.join(asyncDir, "control", "interrupt.json")), false);
163
- const repairedStatus = JSON.parse(fs.readFileSync(path.join(asyncDir, "status.json"), "utf-8"));
164
- assert.equal(repairedStatus.state, "failed");
165
- assert.equal(fs.existsSync(path.join(resultsDir, "run-live.json")), true);
166
- } finally {
167
- fs.rmSync(root, { recursive: true, force: true });
168
- }
169
- });
170
- });