@code-yeongyu/senpi 2026.5.14 → 2026.5.15-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +1107 -1182
  2. package/README.md +1 -2
  3. package/dist/core/agent-session.d.ts +9 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +109 -7
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/dynamic-prompt/verification.d.ts +31 -0
  8. package/dist/core/dynamic-prompt/verification.d.ts.map +1 -1
  9. package/dist/core/dynamic-prompt/verification.js +41 -0
  10. package/dist/core/dynamic-prompt/verification.js.map +1 -1
  11. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  12. package/dist/core/extensions/builtin/compaction/index.js +157 -29
  13. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  14. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +197 -0
  15. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -0
  16. package/dist/core/extensions/builtin/compaction/openai-remote.js +690 -0
  17. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -0
  18. package/dist/core/extensions/builtin/compaction/prompts.d.ts +3 -3
  19. package/dist/core/extensions/builtin/compaction/prompts.d.ts.map +1 -1
  20. package/dist/core/extensions/builtin/compaction/prompts.js +0 -22
  21. package/dist/core/extensions/builtin/compaction/prompts.js.map +1 -1
  22. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts +4 -0
  23. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts.map +1 -0
  24. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js +48 -0
  25. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js.map +1 -0
  26. package/dist/core/extensions/builtin/compaction/speculative.d.ts +3 -1
  27. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  28. package/dist/core/extensions/builtin/compaction/speculative.js +82 -33
  29. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  30. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts +8 -0
  31. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/todo-bridge.js +12 -6
  33. package/dist/core/extensions/builtin/compaction/todo-bridge.js.map +1 -1
  34. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  35. package/dist/core/extensions/builtin/index.js +0 -2
  36. package/dist/core/extensions/builtin/index.js.map +1 -1
  37. package/dist/core/extensions/builtin/permission-system/prompt.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/permission-system/prompt.js +0 -5
  39. package/dist/core/extensions/builtin/permission-system/prompt.js.map +1 -1
  40. package/dist/core/extensions/builtin/system-messages.d.ts +7 -7
  41. package/dist/core/extensions/builtin/system-messages.d.ts.map +1 -1
  42. package/dist/core/extensions/builtin/system-messages.js +10 -10
  43. package/dist/core/extensions/builtin/system-messages.js.map +1 -1
  44. package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts +1 -1
  45. package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/todotools/continuation/prompt.js +1 -1
  47. package/dist/core/extensions/builtin/todotools/continuation/prompt.js.map +1 -1
  48. package/dist/core/extensions/builtin/todotools/state.d.ts +1 -1
  49. package/dist/core/extensions/builtin/todotools/state.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/todotools/state.js +1 -1
  51. package/dist/core/extensions/builtin/todotools/state.js.map +1 -1
  52. package/dist/core/extensions/builtin/todotools/system-messages.d.ts +3 -3
  53. package/dist/core/extensions/builtin/todotools/system-messages.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/todotools/system-messages.js +6 -6
  55. package/dist/core/extensions/builtin/todotools/system-messages.js.map +1 -1
  56. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts +1 -1
  57. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/tool-pair-guard/index.js +8 -4
  59. package/dist/core/extensions/builtin/tool-pair-guard/index.js.map +1 -1
  60. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts +3 -0
  61. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts.map +1 -0
  62. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js +89 -0
  63. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js.map +1 -0
  64. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts +3 -0
  65. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts.map +1 -0
  66. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js +122 -0
  67. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js.map +1 -0
  68. package/dist/core/extensions/loader.d.ts.map +1 -1
  69. package/dist/core/extensions/loader.js +2 -0
  70. package/dist/core/extensions/loader.js.map +1 -1
  71. package/dist/core/extensions/runner.d.ts +3 -0
  72. package/dist/core/extensions/runner.d.ts.map +1 -1
  73. package/dist/core/extensions/runner.js +18 -0
  74. package/dist/core/extensions/runner.js.map +1 -1
  75. package/dist/core/extensions/types.d.ts +22 -0
  76. package/dist/core/extensions/types.d.ts.map +1 -1
  77. package/dist/core/extensions/types.js.map +1 -1
  78. package/dist/core/messages.d.ts +3 -3
  79. package/dist/core/messages.d.ts.map +1 -1
  80. package/dist/core/messages.js +5 -10
  81. package/dist/core/messages.js.map +1 -1
  82. package/dist/core/sdk.d.ts +1 -1
  83. package/dist/core/sdk.d.ts.map +1 -1
  84. package/dist/core/sdk.js +7 -22
  85. package/dist/core/sdk.js.map +1 -1
  86. package/dist/core/session-manager.d.ts.map +1 -1
  87. package/dist/core/session-manager.js +1 -1
  88. package/dist/core/session-manager.js.map +1 -1
  89. package/dist/core/settings-manager.d.ts +0 -5
  90. package/dist/core/settings-manager.d.ts.map +1 -1
  91. package/dist/core/settings-manager.js.map +1 -1
  92. package/dist/core/thinking-levels.d.ts +6 -0
  93. package/dist/core/thinking-levels.d.ts.map +1 -0
  94. package/dist/core/thinking-levels.js +36 -0
  95. package/dist/core/thinking-levels.js.map +1 -0
  96. package/dist/core/tools/bash.d.ts.map +1 -1
  97. package/dist/core/tools/bash.js +15 -1
  98. package/dist/core/tools/bash.js.map +1 -1
  99. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  100. package/dist/modes/interactive/components/compaction-summary-message.js +20 -2
  101. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  102. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  103. package/dist/modes/interactive/components/keybinding-hints.js +3 -1
  104. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  105. package/dist/modes/interactive/interactive-mode.d.ts +11 -0
  106. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  107. package/dist/modes/interactive/interactive-mode.js +96 -49
  108. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  109. package/docs/extensions.md +0 -1
  110. package/docs/index.md +0 -1
  111. package/docs/sdk.md +0 -1
  112. package/docs/settings.md +1 -29
  113. package/docs/termux.md +2 -2
  114. package/docs/usage.md +1 -1
  115. package/examples/README.md +1 -1
  116. package/examples/extensions/README.md +0 -1
  117. package/examples/extensions/overlay-qa-tests.ts +1 -1
  118. package/package.json +4 -4
  119. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts +0 -10
  120. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts.map +0 -1
  121. package/dist/core/extensions/builtin/background-task/cancel-tool.js +0 -109
  122. package/dist/core/extensions/builtin/background-task/cancel-tool.js.map +0 -1
  123. package/dist/core/extensions/builtin/background-task/index.d.ts +0 -3
  124. package/dist/core/extensions/builtin/background-task/index.d.ts.map +0 -1
  125. package/dist/core/extensions/builtin/background-task/index.js +0 -207
  126. package/dist/core/extensions/builtin/background-task/index.js.map +0 -1
  127. package/dist/core/extensions/builtin/background-task/manager.d.ts +0 -17
  128. package/dist/core/extensions/builtin/background-task/manager.d.ts.map +0 -1
  129. package/dist/core/extensions/builtin/background-task/manager.js +0 -114
  130. package/dist/core/extensions/builtin/background-task/manager.js.map +0 -1
  131. package/dist/core/extensions/builtin/background-task/notification.d.ts +0 -22
  132. package/dist/core/extensions/builtin/background-task/notification.d.ts.map +0 -1
  133. package/dist/core/extensions/builtin/background-task/notification.js +0 -105
  134. package/dist/core/extensions/builtin/background-task/notification.js.map +0 -1
  135. package/dist/core/extensions/builtin/background-task/output-tool.d.ts +0 -11
  136. package/dist/core/extensions/builtin/background-task/output-tool.d.ts.map +0 -1
  137. package/dist/core/extensions/builtin/background-task/output-tool.js +0 -127
  138. package/dist/core/extensions/builtin/background-task/output-tool.js.map +0 -1
  139. package/dist/core/extensions/builtin/background-task/spawner.d.ts +0 -8
  140. package/dist/core/extensions/builtin/background-task/spawner.d.ts.map +0 -1
  141. package/dist/core/extensions/builtin/background-task/spawner.js +0 -207
  142. package/dist/core/extensions/builtin/background-task/spawner.js.map +0 -1
  143. package/dist/core/extensions/builtin/background-task/task-tool.d.ts +0 -20
  144. package/dist/core/extensions/builtin/background-task/task-tool.d.ts.map +0 -1
  145. package/dist/core/extensions/builtin/background-task/task-tool.js +0 -302
  146. package/dist/core/extensions/builtin/background-task/task-tool.js.map +0 -1
  147. package/dist/core/extensions/builtin/background-task/types.d.ts +0 -72
  148. package/dist/core/extensions/builtin/background-task/types.d.ts.map +0 -1
  149. package/dist/core/extensions/builtin/background-task/types.js +0 -32
  150. package/dist/core/extensions/builtin/background-task/types.js.map +0 -1
  151. package/docs/agents.md +0 -348
  152. package/examples/extensions/subagent/README.md +0 -172
  153. package/examples/extensions/subagent/agents/planner.md +0 -37
  154. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  155. package/examples/extensions/subagent/agents/scout.md +0 -50
  156. package/examples/extensions/subagent/agents/worker.md +0 -24
  157. package/examples/extensions/subagent/agents.ts +0 -126
  158. package/examples/extensions/subagent/index.ts +0 -987
  159. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  160. package/examples/extensions/subagent/prompts/implement.md +0 -10
  161. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
@@ -2576,7 +2576,6 @@ All examples in [examples/extensions/](../examples/extensions/).
2576
2576
  | `ssh.ts` | SSH remote execution | `registerFlag`, `on("user_bash")`, `on("before_agent_start")`, tool operations |
2577
2577
  | `interactive-shell.ts` | Persistent shell session | `on("user_bash")` |
2578
2578
  | `sandbox/` | Sandboxed tool execution | Tool operations |
2579
- | `subagent/` | Spawn sub-agents | `registerTool`, `exec` |
2580
2579
  | **Games** |||
2581
2580
  | `snake.ts` | Snake game | `registerCommand`, `ui.custom`, keyboard handling |
2582
2581
  | `space-invaders.ts` | Space Invaders game | `registerCommand`, `ui.custom` |
package/docs/index.md CHANGED
@@ -51,7 +51,6 @@ For the full first-run flow, see [Quickstart](quickstart.md).
51
51
  ## Reference
52
52
 
53
53
  - [Session format](session-format.md) - JSONL session file format, entry types, and SessionManager API.
54
- - [Agents](agents.md) - subagent profiles, custom agent definitions, and permission rules.
55
54
 
56
55
  ## Platform setup
57
56
 
package/docs/sdk.md CHANGED
@@ -8,7 +8,6 @@ The SDK provides programmatic access to pi's agent capabilities. Use it to embed
8
8
  - Build a custom UI (web, desktop, mobile)
9
9
  - Integrate agent capabilities into existing applications
10
10
  - Create automated pipelines with agent reasoning
11
- - Build custom tools that spawn sub-agents
12
11
  - Test agent behavior programmatically
13
12
 
14
13
  See [examples/sdk/](../examples/sdk/) for working examples from minimal to full control.
package/docs/settings.md CHANGED
@@ -266,28 +266,6 @@ Object form filters which resources to load:
266
266
 
267
267
  See [packages.md](packages.md) for package management details.
268
268
 
269
- ### Agent Defaults
270
-
271
- | Setting | Type | Default | Description |
272
- |---------|------|---------|-------------|
273
- | `agentDefaults.permission` | object | `{}` | Default tool permissions applied to all agents (lowest priority) |
274
- | `agentDefaults.model` | string | - | Default model ID for agents spawned via `task()` |
275
-
276
- Permission values: `"allow"`, `"deny"`, `"ask"`. See [agents.md](agents.md) for details.
277
-
278
- ```json
279
- {
280
- "agentDefaults": {
281
- "permission": {
282
- "edit": "ask",
283
- "write": "ask",
284
- "bash": "allow"
285
- },
286
- "model": "anthropic/claude-haiku-4-5"
287
- }
288
- }
289
- ```
290
-
291
269
  ## Example
292
270
 
293
271
  ```json
@@ -310,13 +288,7 @@ Permission values: `"allow"`, `"deny"`, `"ask"`. See [agents.md](agents.md) for
310
288
  "warnings": {
311
289
  "anthropicExtraUsage": true
312
290
  },
313
- "packages": ["pi-skills"],
314
- "agentDefaults": {
315
- "permission": {
316
- "edit": "ask",
317
- "write": "ask"
318
- }
319
- }
291
+ "packages": ["pi-skills"]
320
292
  }
321
293
  ```
322
294
 
package/docs/termux.md CHANGED
@@ -36,7 +36,7 @@ Image clipboard is not supported on Termux (the `ctrl+v` image paste feature wil
36
36
 
37
37
  Create `~/.senpi/agent/AGENTS.md` to help the agent understand the Termux environment:
38
38
 
39
- ```markdown
39
+ ````markdown
40
40
  # Agent Environment: Termux on Android
41
41
 
42
42
  ## Location
@@ -91,7 +91,7 @@ termux-camera-photo out.jpg # Take photo
91
91
  - Termux:API app must be installed for `termux-*` commands
92
92
  - Use `pkg install termux-api` for the command-line tools
93
93
  - Storage permission needed for `/storage/emulated/0` access
94
- ```
94
+ ````
95
95
 
96
96
  ## Limitations
97
97
 
package/docs/usage.md CHANGED
@@ -272,6 +272,6 @@ pi --tools read,grep,find,ls -p "Review the code"
272
272
 
273
273
  Pi keeps the core small and pushes workflow-specific behavior into extensions, skills, prompt templates, and packages.
274
274
 
275
- It intentionally does not include built-in MCP, sub-agents, permission popups, plan mode, to-dos, or background bash. You can build or install those workflows as extensions or packages, or use external tools such as containers and tmux.
275
+ It intentionally does not include built-in MCP, permission popups, plan mode, to-dos, or long-running shell orchestration. You can build or install those workflows as extensions or packages, or use external tools such as containers and tmux.
276
276
 
277
277
  For the full rationale, read the [blog post](https://mariozechner.at/posts/2025-11-30-pi-coding-agent/).
@@ -10,7 +10,7 @@ Programmatic usage via `createAgentSession()`. Shows how to customize models, pr
10
10
  ### [extensions/](extensions/)
11
11
  Example extensions demonstrating:
12
12
  - Lifecycle event handlers (tool interception, safety gates, context modifications)
13
- - Custom tools (todo lists, questions, subagents, output truncation)
13
+ - Custom tools (todo lists, questions, output truncation)
14
14
  - Commands and keyboard shortcuts
15
15
  - Custom UI (footers, headers, editors, overlays)
16
16
  - Git integration (checkpoints, auto-commit)
@@ -39,7 +39,6 @@ cp permission-gate.ts ~/.senpi/agent/extensions/
39
39
  | `minimal-mode.ts` | Override built-in tool rendering for minimal display (only tool calls, no output in collapsed mode) |
40
40
  | `truncated-tool.ts` | Wraps ripgrep with proper output truncation (50KB/2000 lines) |
41
41
  | `ssh.ts` | Delegate all tools to a remote machine via SSH using pluggable operations |
42
- | `subagent/` | Delegate tasks to specialized subagents with isolated context windows |
43
42
 
44
43
  ### Commands & UI
45
44
 
@@ -469,7 +469,7 @@ class StreamingOverflowComponent extends BaseOverlay {
469
469
  "-c",
470
470
  `
471
471
  echo "Starting streaming overflow test (30+ seconds)..."
472
- echo "This simulates subagent output with colors, hyperlinks, and long paths"
472
+ echo "This simulates streaming output with colors, hyperlinks, and long paths"
473
473
  echo ""
474
474
  for i in $(seq 1 100); do
475
475
  # Simulate long file paths with OSC 8 hyperlinks (clickable) - tests width overflow
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi",
3
- "version": "2026.5.14",
3
+ "version": "2026.5.15-2",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -40,9 +40,9 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@anthropic-ai/sdk": "^0.91.1",
43
- "@earendil-works/pi-agent-core": "^2026.5.14",
44
- "@earendil-works/pi-ai": "^2026.5.14",
45
- "@earendil-works/pi-tui": "^2026.5.14",
43
+ "@earendil-works/pi-agent-core": "^2026.5.15-2",
44
+ "@earendil-works/pi-ai": "^2026.5.15-2",
45
+ "@earendil-works/pi-tui": "^2026.5.15-2",
46
46
  "@silvia-odwyer/photon-node": "^0.3.4",
47
47
  "chalk": "^5.5.0",
48
48
  "diff": "^8.0.2",
@@ -1,10 +0,0 @@
1
- import { Type } from "typebox";
2
- import type { ToolDefinition } from "../../types.js";
3
- import type { BackgroundManager } from "./manager.js";
4
- declare const BackgroundCancelParams: Type.TObject<{
5
- taskId: Type.TOptional<Type.TString>;
6
- all: Type.TOptional<Type.TBoolean>;
7
- }>;
8
- export declare function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams>;
9
- export {};
10
- //# sourceMappingURL=cancel-tool.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancel-tool.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/cancel-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,QAAA,MAAM,sBAAsB;;;EAG1B,CAAC;AAEH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAAC,OAAO,sBAAsB,CAAC,CAmHpH","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../types.js\";\nimport type { BackgroundManager } from \"./manager.js\";\n\nconst BackgroundCancelParams = Type.Object({\n\ttaskId: Type.Optional(Type.String({ description: \"Task ID to cancel (required if all=false)\" })),\n\tall: Type.Optional(Type.Boolean({ description: \"Cancel all running background tasks (default: false)\" })),\n});\n\nexport function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams> {\n\treturn {\n\t\tname: \"background_cancel\",\n\t\tlabel: \"BackgroundCancel\",\n\t\tdescription: \"Cancel a background task by taskId, or cancel all running tasks with all=true.\",\n\t\tpromptSnippet: \"Cancel background tasks by ID or cancel all running tasks.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool to cancel pending or running background tasks.\",\n\t\t\t\"Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.\",\n\t\t\t\"Cancelled tasks will be marked as 'cancelled' and cannot be resumed.\",\n\t\t],\n\t\tparameters: BackgroundCancelParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n\t\t\tif (!params.taskId && !params.all) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.all) {\n\t\t\t\tconst cancelled = manager.cancelAll();\n\n\t\t\t\tif (cancelled.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No active tasks to cancel\" }],\n\t\t\t\t\t\tdetails: { cancelledTasks: [] },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst lines: string[] = [`Cancelled ${cancelled.length} task(s):`];\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tlines.push(`- ${task.id}: ${task.description}`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: lines.join(\"\\n\") }],\n\t\t\t\t\tdetails: { cancelledTasks: cancelled },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.taskId) {\n\t\t\t\tconst task = manager.getTask(params.taskId);\n\t\t\t\tif (!task) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Error: Task not found: ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst wasCancelled = manager.cancelTask(params.taskId);\n\n\t\t\t\tif (!wasCancelled) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Task ${params.taskId} is not active (status: ${task.status})` }],\n\t\t\t\t\t\tdetails: { task },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst cancelledTask = manager.getTask(params.taskId);\n\t\t\t\tif (!cancelledTask) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],\n\t\t\t\t\tdetails: { task: cancelledTask },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\tdetails: undefined,\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tif (args.all) {\n\t\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", \"[all]\"), 0, 0);\n\t\t\t}\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", args.taskId ?? \"unknown\"),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst firstContent = result.content[0];\n\t\t\tconst text = firstContent?.type === \"text\" ? firstContent.text : \"(no output)\";\n\t\t\treturn new Text(theme.fg(\"muted\", text), 0, 0);\n\t\t},\n\t};\n}\n"]}
@@ -1,109 +0,0 @@
1
- import { Text } from "@earendil-works/pi-tui";
2
- import { Type } from "typebox";
3
- const BackgroundCancelParams = Type.Object({
4
- taskId: Type.Optional(Type.String({ description: "Task ID to cancel (required if all=false)" })),
5
- all: Type.Optional(Type.Boolean({ description: "Cancel all running background tasks (default: false)" })),
6
- });
7
- export function createBackgroundCancelTool(manager) {
8
- return {
9
- name: "background_cancel",
10
- label: "BackgroundCancel",
11
- description: "Cancel a background task by taskId, or cancel all running tasks with all=true.",
12
- promptSnippet: "Cancel background tasks by ID or cancel all running tasks.",
13
- promptGuidelines: [
14
- "Use this tool to cancel pending or running background tasks.",
15
- "Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.",
16
- "Cancelled tasks will be marked as 'cancelled' and cannot be resumed.",
17
- ],
18
- parameters: BackgroundCancelParams,
19
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
20
- if (!params.taskId && !params.all) {
21
- return {
22
- content: [{ type: "text", text: "Error: Provide taskId or set all=true" }],
23
- details: undefined,
24
- isError: true,
25
- };
26
- }
27
- if (params.all) {
28
- const cancelled = manager.cancelAll();
29
- if (cancelled.length === 0) {
30
- return {
31
- content: [{ type: "text", text: "No active tasks to cancel" }],
32
- details: { cancelledTasks: [] },
33
- };
34
- }
35
- for (const task of cancelled) {
36
- if (task.pid !== undefined) {
37
- try {
38
- process.kill(task.pid, "SIGTERM");
39
- }
40
- catch {
41
- /* process may already be dead */
42
- }
43
- }
44
- }
45
- const lines = [`Cancelled ${cancelled.length} task(s):`];
46
- for (const task of cancelled) {
47
- lines.push(`- ${task.id}: ${task.description}`);
48
- }
49
- return {
50
- content: [{ type: "text", text: lines.join("\n") }],
51
- details: { cancelledTasks: cancelled },
52
- };
53
- }
54
- if (params.taskId) {
55
- const task = manager.getTask(params.taskId);
56
- if (!task) {
57
- return {
58
- content: [{ type: "text", text: `Error: Task not found: ${params.taskId}` }],
59
- details: undefined,
60
- isError: true,
61
- };
62
- }
63
- const wasCancelled = manager.cancelTask(params.taskId);
64
- if (!wasCancelled) {
65
- return {
66
- content: [{ type: "text", text: `Task ${params.taskId} is not active (status: ${task.status})` }],
67
- details: { task },
68
- };
69
- }
70
- if (task.pid !== undefined) {
71
- try {
72
- process.kill(task.pid, "SIGTERM");
73
- }
74
- catch {
75
- /* process may already be dead */
76
- }
77
- }
78
- const cancelledTask = manager.getTask(params.taskId);
79
- if (!cancelledTask) {
80
- return {
81
- content: [{ type: "text", text: `Cancelled task ${params.taskId}` }],
82
- details: undefined,
83
- };
84
- }
85
- return {
86
- content: [{ type: "text", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],
87
- details: { task: cancelledTask },
88
- };
89
- }
90
- return {
91
- content: [{ type: "text", text: "Error: Provide taskId or set all=true" }],
92
- details: undefined,
93
- isError: true,
94
- };
95
- },
96
- renderCall(args, theme) {
97
- if (args.all) {
98
- return new Text(theme.fg("toolTitle", theme.bold("BackgroundCancel ")) + theme.fg("accent", "[all]"), 0, 0);
99
- }
100
- return new Text(theme.fg("toolTitle", theme.bold("BackgroundCancel ")) + theme.fg("accent", args.taskId ?? "unknown"), 0, 0);
101
- },
102
- renderResult(result, _options, theme) {
103
- const firstContent = result.content[0];
104
- const text = firstContent?.type === "text" ? firstContent.text : "(no output)";
105
- return new Text(theme.fg("muted", text), 0, 0);
106
- },
107
- };
108
- }
109
- //# sourceMappingURL=cancel-tool.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cancel-tool.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/cancel-tool.ts"],"names":[],"mappings":"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","sourcesContent":["import { Text } from \"@earendil-works/pi-tui\";\nimport { Type } from \"typebox\";\nimport type { ToolDefinition } from \"../../types.js\";\nimport type { BackgroundManager } from \"./manager.js\";\n\nconst BackgroundCancelParams = Type.Object({\n\ttaskId: Type.Optional(Type.String({ description: \"Task ID to cancel (required if all=false)\" })),\n\tall: Type.Optional(Type.Boolean({ description: \"Cancel all running background tasks (default: false)\" })),\n});\n\nexport function createBackgroundCancelTool(manager: BackgroundManager): ToolDefinition<typeof BackgroundCancelParams> {\n\treturn {\n\t\tname: \"background_cancel\",\n\t\tlabel: \"BackgroundCancel\",\n\t\tdescription: \"Cancel a background task by taskId, or cancel all running tasks with all=true.\",\n\t\tpromptSnippet: \"Cancel background tasks by ID or cancel all running tasks.\",\n\t\tpromptGuidelines: [\n\t\t\t\"Use this tool to cancel pending or running background tasks.\",\n\t\t\t\"Provide taskId to cancel a specific task, or set all=true to cancel all active tasks.\",\n\t\t\t\"Cancelled tasks will be marked as 'cancelled' and cannot be resumed.\",\n\t\t],\n\t\tparameters: BackgroundCancelParams,\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n\t\t\tif (!params.taskId && !params.all) {\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t\tisError: true,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.all) {\n\t\t\t\tconst cancelled = manager.cancelAll();\n\n\t\t\t\tif (cancelled.length === 0) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No active tasks to cancel\" }],\n\t\t\t\t\t\tdetails: { cancelledTasks: [] },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst lines: string[] = [`Cancelled ${cancelled.length} task(s):`];\n\t\t\t\tfor (const task of cancelled) {\n\t\t\t\t\tlines.push(`- ${task.id}: ${task.description}`);\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: lines.join(\"\\n\") }],\n\t\t\t\t\tdetails: { cancelledTasks: cancelled },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tif (params.taskId) {\n\t\t\t\tconst task = manager.getTask(params.taskId);\n\t\t\t\tif (!task) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Error: Task not found: ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t\tisError: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tconst wasCancelled = manager.cancelTask(params.taskId);\n\n\t\t\t\tif (!wasCancelled) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Task ${params.taskId} is not active (status: ${task.status})` }],\n\t\t\t\t\t\tdetails: { task },\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (task.pid !== undefined) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t/* process may already be dead */\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst cancelledTask = manager.getTask(params.taskId);\n\t\t\t\tif (!cancelledTask) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}` }],\n\t\t\t\t\t\tdetails: undefined,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: `Cancelled task ${params.taskId}: ${cancelledTask.description}` }],\n\t\t\t\t\tdetails: { task: cancelledTask },\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: \"Error: Provide taskId or set all=true\" }],\n\t\t\t\tdetails: undefined,\n\t\t\t\tisError: true,\n\t\t\t};\n\t\t},\n\t\trenderCall(args, theme) {\n\t\t\tif (args.all) {\n\t\t\t\treturn new Text(theme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", \"[all]\"), 0, 0);\n\t\t\t}\n\t\t\treturn new Text(\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"BackgroundCancel \")) + theme.fg(\"accent\", args.taskId ?? \"unknown\"),\n\t\t\t\t0,\n\t\t\t\t0,\n\t\t\t);\n\t\t},\n\t\trenderResult(result, _options, theme) {\n\t\t\tconst firstContent = result.content[0];\n\t\t\tconst text = firstContent?.type === \"text\" ? firstContent.text : \"(no output)\";\n\t\t\treturn new Text(theme.fg(\"muted\", text), 0, 0);\n\t\t},\n\t};\n}\n"]}
@@ -1,3 +0,0 @@
1
- import type { ExtensionAPI } from "../../types.js";
2
- export default function backgroundTaskExtension(pi: ExtensionAPI): void;
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AA8LrE,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAiEtE","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.js\";\nimport { sendBuiltinCustomMessage } from \"../system-messages.js\";\nimport { createBackgroundCancelTool } from \"./cancel-tool.js\";\nimport { BackgroundManager, getWidgetLines } from \"./manager.js\";\nimport { sendCompletionNotification } from \"./notification.js\";\nimport { createBackgroundOutputTool } from \"./output-tool.js\";\nimport { spawnSubagent } from \"./spawner.js\";\nimport { createTaskTool } from \"./task-tool.js\";\nimport { type BackgroundTask, TASK_ENTRY_TYPE } from \"./types.js\";\n\ntype SessionEntry = {\n\ttype: string;\n\tcustomType?: string;\n\tdata?: unknown;\n\tmessage?: unknown;\n};\n\nfunction isTaskStatus(value: unknown): value is BackgroundTask[\"status\"] {\n\treturn (\n\t\tvalue === \"pending\" || value === \"running\" || value === \"completed\" || value === \"error\" || value === \"cancelled\"\n\t);\n}\n\nfunction parseDate(value: unknown): Date | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? undefined : value;\n\t}\n\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\n\tconst parsedDate = new Date(value);\n\treturn Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate;\n}\n\nfunction parseBackgroundTask(value: unknown): BackgroundTask | undefined {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\n\tconst taskRecord = value as Record<string, unknown>;\n\tconst startedAt = parseDate(taskRecord.startedAt);\n\tconst completedAt = parseDate(taskRecord.completedAt);\n\n\tif (\n\t\ttypeof taskRecord.id !== \"string\" ||\n\t\ttypeof taskRecord.description !== \"string\" ||\n\t\ttypeof taskRecord.prompt !== \"string\" ||\n\t\t!isTaskStatus(taskRecord.status) ||\n\t\t(taskRecord.model !== undefined && typeof taskRecord.model !== \"string\") ||\n\t\t(taskRecord.agentType !== undefined && typeof taskRecord.agentType !== \"string\") ||\n\t\t(taskRecord.pid !== undefined && typeof taskRecord.pid !== \"number\") ||\n\t\t(taskRecord.sessionPath !== undefined && typeof taskRecord.sessionPath !== \"string\") ||\n\t\t(taskRecord.activeToolNames !== undefined &&\n\t\t\t(!Array.isArray(taskRecord.activeToolNames) ||\n\t\t\t\ttaskRecord.activeToolNames.some((toolName) => typeof toolName !== \"string\"))) ||\n\t\tstartedAt === undefined ||\n\t\t(taskRecord.completedAt !== undefined && completedAt === undefined) ||\n\t\t(taskRecord.result !== undefined && typeof taskRecord.result !== \"string\") ||\n\t\t(taskRecord.error !== undefined && typeof taskRecord.error !== \"string\") ||\n\t\ttypeof taskRecord.parentSessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tid: taskRecord.id,\n\t\tdescription: taskRecord.description,\n\t\tprompt: taskRecord.prompt,\n\t\tmodel: taskRecord.model,\n\t\tagentType: taskRecord.agentType,\n\t\tstatus: taskRecord.status,\n\t\tpid: taskRecord.pid,\n\t\tsessionPath: taskRecord.sessionPath,\n\t\tactiveToolNames: Array.isArray(taskRecord.activeToolNames) ? taskRecord.activeToolNames : [],\n\t\tstartedAt,\n\t\tcompletedAt,\n\t\tresult: taskRecord.result,\n\t\terror: taskRecord.error,\n\t\tparentSessionId: taskRecord.parentSessionId,\n\t};\n}\n\nfunction isTerminalTask(task: BackgroundTask): boolean {\n\treturn task.status === \"completed\" || task.status === \"error\" || task.status === \"cancelled\";\n}\n\nfunction getRestoredTasks(entries: SessionEntry[]): BackgroundTask[] {\n\tconst restoredTasks = new Map<string, BackgroundTask>();\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== TASK_ENTRY_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst task = parseBackgroundTask(entry.data);\n\t\tif (!task) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isTerminalTask(task)) {\n\t\t\trestoredTasks.set(task.id, task);\n\t\t} else {\n\t\t\trestoredTasks.delete(task.id);\n\t\t}\n\t}\n\n\treturn Array.from(restoredTasks.values());\n}\n\nfunction getCompletionTaskId(message: { content?: unknown }): string | undefined {\n\tif (!Array.isArray(message.content)) {\n\t\treturn undefined;\n\t}\n\n\tfor (const part of message.content) {\n\t\tif (typeof part !== \"object\" || part === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst contentPart = part as Record<string, unknown>;\n\t\tif (contentPart.type !== \"text\" || typeof contentPart.text !== \"string\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = contentPart.text.match(/\\b(bg_[0-9a-f]{8})\\b/);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nclass ObservableBackgroundManager extends BackgroundManager {\n\tconstructor(\n\t\tprivate readonly onChange: () => void,\n\t\tprivate readonly persistTask: (task: BackgroundTask) => void,\n\t) {\n\t\tsuper();\n\t}\n\n\toverride launch(task: Omit<BackgroundTask, \"id\" | \"status\" | \"startedAt\">): BackgroundTask {\n\t\tconst launchedTask = super.launch(task);\n\t\tthis.onChange();\n\t\treturn launchedTask;\n\t}\n\n\toverride updateTask(id: string, updates: Partial<BackgroundTask>): void {\n\t\tsuper.updateTask(id, updates);\n\t\tthis.onChange();\n\t}\n\n\toverride cancelTask(id: string): boolean {\n\t\tconst wasCancelled = super.cancelTask(id);\n\t\tif (wasCancelled) {\n\t\t\tconst cancelledTask = this.getTask(id);\n\t\t\tif (cancelledTask) {\n\t\t\t\tthis.persistTask(cancelledTask);\n\t\t\t}\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn wasCancelled;\n\t}\n\n\toverride cancelAll(): BackgroundTask[] {\n\t\tconst cancelledTasks = super.cancelAll();\n\t\tfor (const task of cancelledTasks) {\n\t\t\tthis.persistTask(task);\n\t\t}\n\t\tif (cancelledTasks.length > 0) {\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn cancelledTasks;\n\t}\n\n\treplaceTasks(tasks: BackgroundTask[]): void {\n\t\tthis.clearTasks();\n\t\tfor (const task of tasks) {\n\t\t\tthis.restoreTask(task);\n\t\t}\n\t\tthis.onChange();\n\t}\n}\n\nexport default function backgroundTaskExtension(pi: ExtensionAPI): void {\n\tlet currentContext: ExtensionContext | undefined;\n\n\tconst syncWidget = (): void => {\n\t\tcurrentContext?.ui.setWidget(\"background-tasks\", getWidgetLines(manager));\n\t};\n\n\tconst manager = new ObservableBackgroundManager(\n\t\t() => {\n\t\t\tsyncWidget();\n\t\t},\n\t\t(task) => {\n\t\t\tpi.appendEntry(TASK_ENTRY_TYPE, task);\n\t\t},\n\t);\n\n\tconst syncFromSession = (ctx: ExtensionContext): void => {\n\t\tcurrentContext = ctx;\n\t\tmanager.replaceTasks(getRestoredTasks(ctx.sessionManager.getBranch() as SessionEntry[]));\n\t};\n\n\tconst notifyingPi = {\n\t\t...pi,\n\t\tsendMessage(message, options) {\n\t\t\tif (message.customType === \"background-task.complete\") {\n\t\t\t\tconst taskId = getCompletionTaskId(message);\n\t\t\t\tif (taskId) {\n\t\t\t\t\tconst task = manager.getTask(taskId);\n\t\t\t\t\tif (task && isTerminalTask(task)) {\n\t\t\t\t\t\tsendCompletionNotification(pi, task, manager);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsendBuiltinCustomMessage(pi, \"background-task.notification\", message, options);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tpi.sendMessage(message, options);\n\t\t},\n\t} satisfies ExtensionAPI;\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.registerTool(createTaskTool(manager, spawnSubagent, notifyingPi));\n\tpi.registerTool(createBackgroundOutputTool(manager));\n\tpi.registerTool(createBackgroundCancelTool(manager));\n\n\tprocess.on(\"exit\", () => {\n\t\tfor (const task of manager.getActiveTasks()) {\n\t\t\tif (task.pid === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t} catch {}\n\t\t}\n\t});\n}\n"]}
@@ -1,207 +0,0 @@
1
- import { sendBuiltinCustomMessage } from "../system-messages.js";
2
- import { createBackgroundCancelTool } from "./cancel-tool.js";
3
- import { BackgroundManager, getWidgetLines } from "./manager.js";
4
- import { sendCompletionNotification } from "./notification.js";
5
- import { createBackgroundOutputTool } from "./output-tool.js";
6
- import { spawnSubagent } from "./spawner.js";
7
- import { createTaskTool } from "./task-tool.js";
8
- import { TASK_ENTRY_TYPE } from "./types.js";
9
- function isTaskStatus(value) {
10
- return (value === "pending" || value === "running" || value === "completed" || value === "error" || value === "cancelled");
11
- }
12
- function parseDate(value) {
13
- if (value === undefined) {
14
- return undefined;
15
- }
16
- if (value instanceof Date) {
17
- return Number.isNaN(value.getTime()) ? undefined : value;
18
- }
19
- if (typeof value !== "string") {
20
- return undefined;
21
- }
22
- const parsedDate = new Date(value);
23
- return Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate;
24
- }
25
- function parseBackgroundTask(value) {
26
- if (typeof value !== "object" || value === null) {
27
- return undefined;
28
- }
29
- const taskRecord = value;
30
- const startedAt = parseDate(taskRecord.startedAt);
31
- const completedAt = parseDate(taskRecord.completedAt);
32
- if (typeof taskRecord.id !== "string" ||
33
- typeof taskRecord.description !== "string" ||
34
- typeof taskRecord.prompt !== "string" ||
35
- !isTaskStatus(taskRecord.status) ||
36
- (taskRecord.model !== undefined && typeof taskRecord.model !== "string") ||
37
- (taskRecord.agentType !== undefined && typeof taskRecord.agentType !== "string") ||
38
- (taskRecord.pid !== undefined && typeof taskRecord.pid !== "number") ||
39
- (taskRecord.sessionPath !== undefined && typeof taskRecord.sessionPath !== "string") ||
40
- (taskRecord.activeToolNames !== undefined &&
41
- (!Array.isArray(taskRecord.activeToolNames) ||
42
- taskRecord.activeToolNames.some((toolName) => typeof toolName !== "string"))) ||
43
- startedAt === undefined ||
44
- (taskRecord.completedAt !== undefined && completedAt === undefined) ||
45
- (taskRecord.result !== undefined && typeof taskRecord.result !== "string") ||
46
- (taskRecord.error !== undefined && typeof taskRecord.error !== "string") ||
47
- typeof taskRecord.parentSessionId !== "string") {
48
- return undefined;
49
- }
50
- return {
51
- id: taskRecord.id,
52
- description: taskRecord.description,
53
- prompt: taskRecord.prompt,
54
- model: taskRecord.model,
55
- agentType: taskRecord.agentType,
56
- status: taskRecord.status,
57
- pid: taskRecord.pid,
58
- sessionPath: taskRecord.sessionPath,
59
- activeToolNames: Array.isArray(taskRecord.activeToolNames) ? taskRecord.activeToolNames : [],
60
- startedAt,
61
- completedAt,
62
- result: taskRecord.result,
63
- error: taskRecord.error,
64
- parentSessionId: taskRecord.parentSessionId,
65
- };
66
- }
67
- function isTerminalTask(task) {
68
- return task.status === "completed" || task.status === "error" || task.status === "cancelled";
69
- }
70
- function getRestoredTasks(entries) {
71
- const restoredTasks = new Map();
72
- for (const entry of entries) {
73
- if (entry.type !== "custom" || entry.customType !== TASK_ENTRY_TYPE) {
74
- continue;
75
- }
76
- const task = parseBackgroundTask(entry.data);
77
- if (!task) {
78
- continue;
79
- }
80
- if (isTerminalTask(task)) {
81
- restoredTasks.set(task.id, task);
82
- }
83
- else {
84
- restoredTasks.delete(task.id);
85
- }
86
- }
87
- return Array.from(restoredTasks.values());
88
- }
89
- function getCompletionTaskId(message) {
90
- if (!Array.isArray(message.content)) {
91
- return undefined;
92
- }
93
- for (const part of message.content) {
94
- if (typeof part !== "object" || part === null) {
95
- continue;
96
- }
97
- const contentPart = part;
98
- if (contentPart.type !== "text" || typeof contentPart.text !== "string") {
99
- continue;
100
- }
101
- const match = contentPart.text.match(/\b(bg_[0-9a-f]{8})\b/);
102
- if (match?.[1]) {
103
- return match[1];
104
- }
105
- }
106
- return undefined;
107
- }
108
- class ObservableBackgroundManager extends BackgroundManager {
109
- onChange;
110
- persistTask;
111
- constructor(onChange, persistTask) {
112
- super();
113
- this.onChange = onChange;
114
- this.persistTask = persistTask;
115
- }
116
- launch(task) {
117
- const launchedTask = super.launch(task);
118
- this.onChange();
119
- return launchedTask;
120
- }
121
- updateTask(id, updates) {
122
- super.updateTask(id, updates);
123
- this.onChange();
124
- }
125
- cancelTask(id) {
126
- const wasCancelled = super.cancelTask(id);
127
- if (wasCancelled) {
128
- const cancelledTask = this.getTask(id);
129
- if (cancelledTask) {
130
- this.persistTask(cancelledTask);
131
- }
132
- this.onChange();
133
- }
134
- return wasCancelled;
135
- }
136
- cancelAll() {
137
- const cancelledTasks = super.cancelAll();
138
- for (const task of cancelledTasks) {
139
- this.persistTask(task);
140
- }
141
- if (cancelledTasks.length > 0) {
142
- this.onChange();
143
- }
144
- return cancelledTasks;
145
- }
146
- replaceTasks(tasks) {
147
- this.clearTasks();
148
- for (const task of tasks) {
149
- this.restoreTask(task);
150
- }
151
- this.onChange();
152
- }
153
- }
154
- export default function backgroundTaskExtension(pi) {
155
- let currentContext;
156
- const syncWidget = () => {
157
- currentContext?.ui.setWidget("background-tasks", getWidgetLines(manager));
158
- };
159
- const manager = new ObservableBackgroundManager(() => {
160
- syncWidget();
161
- }, (task) => {
162
- pi.appendEntry(TASK_ENTRY_TYPE, task);
163
- });
164
- const syncFromSession = (ctx) => {
165
- currentContext = ctx;
166
- manager.replaceTasks(getRestoredTasks(ctx.sessionManager.getBranch()));
167
- };
168
- const notifyingPi = {
169
- ...pi,
170
- sendMessage(message, options) {
171
- if (message.customType === "background-task.complete") {
172
- const taskId = getCompletionTaskId(message);
173
- if (taskId) {
174
- const task = manager.getTask(taskId);
175
- if (task && isTerminalTask(task)) {
176
- sendCompletionNotification(pi, task, manager);
177
- return;
178
- }
179
- }
180
- sendBuiltinCustomMessage(pi, "background-task.notification", message, options);
181
- return;
182
- }
183
- pi.sendMessage(message, options);
184
- },
185
- };
186
- pi.on("session_start", async (_event, ctx) => {
187
- syncFromSession(ctx);
188
- });
189
- pi.on("session_tree", async (_event, ctx) => {
190
- syncFromSession(ctx);
191
- });
192
- pi.registerTool(createTaskTool(manager, spawnSubagent, notifyingPi));
193
- pi.registerTool(createBackgroundOutputTool(manager));
194
- pi.registerTool(createBackgroundCancelTool(manager));
195
- process.on("exit", () => {
196
- for (const task of manager.getActiveTasks()) {
197
- if (task.pid === undefined) {
198
- continue;
199
- }
200
- try {
201
- process.kill(task.pid, "SIGTERM");
202
- }
203
- catch { }
204
- }
205
- });
206
- }
207
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.js\";\nimport { sendBuiltinCustomMessage } from \"../system-messages.js\";\nimport { createBackgroundCancelTool } from \"./cancel-tool.js\";\nimport { BackgroundManager, getWidgetLines } from \"./manager.js\";\nimport { sendCompletionNotification } from \"./notification.js\";\nimport { createBackgroundOutputTool } from \"./output-tool.js\";\nimport { spawnSubagent } from \"./spawner.js\";\nimport { createTaskTool } from \"./task-tool.js\";\nimport { type BackgroundTask, TASK_ENTRY_TYPE } from \"./types.js\";\n\ntype SessionEntry = {\n\ttype: string;\n\tcustomType?: string;\n\tdata?: unknown;\n\tmessage?: unknown;\n};\n\nfunction isTaskStatus(value: unknown): value is BackgroundTask[\"status\"] {\n\treturn (\n\t\tvalue === \"pending\" || value === \"running\" || value === \"completed\" || value === \"error\" || value === \"cancelled\"\n\t);\n}\n\nfunction parseDate(value: unknown): Date | undefined {\n\tif (value === undefined) {\n\t\treturn undefined;\n\t}\n\n\tif (value instanceof Date) {\n\t\treturn Number.isNaN(value.getTime()) ? undefined : value;\n\t}\n\n\tif (typeof value !== \"string\") {\n\t\treturn undefined;\n\t}\n\n\tconst parsedDate = new Date(value);\n\treturn Number.isNaN(parsedDate.getTime()) ? undefined : parsedDate;\n}\n\nfunction parseBackgroundTask(value: unknown): BackgroundTask | undefined {\n\tif (typeof value !== \"object\" || value === null) {\n\t\treturn undefined;\n\t}\n\n\tconst taskRecord = value as Record<string, unknown>;\n\tconst startedAt = parseDate(taskRecord.startedAt);\n\tconst completedAt = parseDate(taskRecord.completedAt);\n\n\tif (\n\t\ttypeof taskRecord.id !== \"string\" ||\n\t\ttypeof taskRecord.description !== \"string\" ||\n\t\ttypeof taskRecord.prompt !== \"string\" ||\n\t\t!isTaskStatus(taskRecord.status) ||\n\t\t(taskRecord.model !== undefined && typeof taskRecord.model !== \"string\") ||\n\t\t(taskRecord.agentType !== undefined && typeof taskRecord.agentType !== \"string\") ||\n\t\t(taskRecord.pid !== undefined && typeof taskRecord.pid !== \"number\") ||\n\t\t(taskRecord.sessionPath !== undefined && typeof taskRecord.sessionPath !== \"string\") ||\n\t\t(taskRecord.activeToolNames !== undefined &&\n\t\t\t(!Array.isArray(taskRecord.activeToolNames) ||\n\t\t\t\ttaskRecord.activeToolNames.some((toolName) => typeof toolName !== \"string\"))) ||\n\t\tstartedAt === undefined ||\n\t\t(taskRecord.completedAt !== undefined && completedAt === undefined) ||\n\t\t(taskRecord.result !== undefined && typeof taskRecord.result !== \"string\") ||\n\t\t(taskRecord.error !== undefined && typeof taskRecord.error !== \"string\") ||\n\t\ttypeof taskRecord.parentSessionId !== \"string\"\n\t) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\tid: taskRecord.id,\n\t\tdescription: taskRecord.description,\n\t\tprompt: taskRecord.prompt,\n\t\tmodel: taskRecord.model,\n\t\tagentType: taskRecord.agentType,\n\t\tstatus: taskRecord.status,\n\t\tpid: taskRecord.pid,\n\t\tsessionPath: taskRecord.sessionPath,\n\t\tactiveToolNames: Array.isArray(taskRecord.activeToolNames) ? taskRecord.activeToolNames : [],\n\t\tstartedAt,\n\t\tcompletedAt,\n\t\tresult: taskRecord.result,\n\t\terror: taskRecord.error,\n\t\tparentSessionId: taskRecord.parentSessionId,\n\t};\n}\n\nfunction isTerminalTask(task: BackgroundTask): boolean {\n\treturn task.status === \"completed\" || task.status === \"error\" || task.status === \"cancelled\";\n}\n\nfunction getRestoredTasks(entries: SessionEntry[]): BackgroundTask[] {\n\tconst restoredTasks = new Map<string, BackgroundTask>();\n\n\tfor (const entry of entries) {\n\t\tif (entry.type !== \"custom\" || entry.customType !== TASK_ENTRY_TYPE) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst task = parseBackgroundTask(entry.data);\n\t\tif (!task) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (isTerminalTask(task)) {\n\t\t\trestoredTasks.set(task.id, task);\n\t\t} else {\n\t\t\trestoredTasks.delete(task.id);\n\t\t}\n\t}\n\n\treturn Array.from(restoredTasks.values());\n}\n\nfunction getCompletionTaskId(message: { content?: unknown }): string | undefined {\n\tif (!Array.isArray(message.content)) {\n\t\treturn undefined;\n\t}\n\n\tfor (const part of message.content) {\n\t\tif (typeof part !== \"object\" || part === null) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst contentPart = part as Record<string, unknown>;\n\t\tif (contentPart.type !== \"text\" || typeof contentPart.text !== \"string\") {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst match = contentPart.text.match(/\\b(bg_[0-9a-f]{8})\\b/);\n\t\tif (match?.[1]) {\n\t\t\treturn match[1];\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nclass ObservableBackgroundManager extends BackgroundManager {\n\tconstructor(\n\t\tprivate readonly onChange: () => void,\n\t\tprivate readonly persistTask: (task: BackgroundTask) => void,\n\t) {\n\t\tsuper();\n\t}\n\n\toverride launch(task: Omit<BackgroundTask, \"id\" | \"status\" | \"startedAt\">): BackgroundTask {\n\t\tconst launchedTask = super.launch(task);\n\t\tthis.onChange();\n\t\treturn launchedTask;\n\t}\n\n\toverride updateTask(id: string, updates: Partial<BackgroundTask>): void {\n\t\tsuper.updateTask(id, updates);\n\t\tthis.onChange();\n\t}\n\n\toverride cancelTask(id: string): boolean {\n\t\tconst wasCancelled = super.cancelTask(id);\n\t\tif (wasCancelled) {\n\t\t\tconst cancelledTask = this.getTask(id);\n\t\t\tif (cancelledTask) {\n\t\t\t\tthis.persistTask(cancelledTask);\n\t\t\t}\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn wasCancelled;\n\t}\n\n\toverride cancelAll(): BackgroundTask[] {\n\t\tconst cancelledTasks = super.cancelAll();\n\t\tfor (const task of cancelledTasks) {\n\t\t\tthis.persistTask(task);\n\t\t}\n\t\tif (cancelledTasks.length > 0) {\n\t\t\tthis.onChange();\n\t\t}\n\t\treturn cancelledTasks;\n\t}\n\n\treplaceTasks(tasks: BackgroundTask[]): void {\n\t\tthis.clearTasks();\n\t\tfor (const task of tasks) {\n\t\t\tthis.restoreTask(task);\n\t\t}\n\t\tthis.onChange();\n\t}\n}\n\nexport default function backgroundTaskExtension(pi: ExtensionAPI): void {\n\tlet currentContext: ExtensionContext | undefined;\n\n\tconst syncWidget = (): void => {\n\t\tcurrentContext?.ui.setWidget(\"background-tasks\", getWidgetLines(manager));\n\t};\n\n\tconst manager = new ObservableBackgroundManager(\n\t\t() => {\n\t\t\tsyncWidget();\n\t\t},\n\t\t(task) => {\n\t\t\tpi.appendEntry(TASK_ENTRY_TYPE, task);\n\t\t},\n\t);\n\n\tconst syncFromSession = (ctx: ExtensionContext): void => {\n\t\tcurrentContext = ctx;\n\t\tmanager.replaceTasks(getRestoredTasks(ctx.sessionManager.getBranch() as SessionEntry[]));\n\t};\n\n\tconst notifyingPi = {\n\t\t...pi,\n\t\tsendMessage(message, options) {\n\t\t\tif (message.customType === \"background-task.complete\") {\n\t\t\t\tconst taskId = getCompletionTaskId(message);\n\t\t\t\tif (taskId) {\n\t\t\t\t\tconst task = manager.getTask(taskId);\n\t\t\t\t\tif (task && isTerminalTask(task)) {\n\t\t\t\t\t\tsendCompletionNotification(pi, task, manager);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsendBuiltinCustomMessage(pi, \"background-task.notification\", message, options);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tpi.sendMessage(message, options);\n\t\t},\n\t} satisfies ExtensionAPI;\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsyncFromSession(ctx);\n\t});\n\n\tpi.registerTool(createTaskTool(manager, spawnSubagent, notifyingPi));\n\tpi.registerTool(createBackgroundOutputTool(manager));\n\tpi.registerTool(createBackgroundCancelTool(manager));\n\n\tprocess.on(\"exit\", () => {\n\t\tfor (const task of manager.getActiveTasks()) {\n\t\t\tif (task.pid === undefined) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tprocess.kill(task.pid, \"SIGTERM\");\n\t\t\t} catch {}\n\t\t}\n\t});\n}\n"]}
@@ -1,17 +0,0 @@
1
- import type { BackgroundTask } from "./types.js";
2
- export declare class BackgroundManager {
3
- private tasks;
4
- constructor();
5
- launch(task: Omit<BackgroundTask, "id" | "status" | "startedAt">): BackgroundTask;
6
- getTask(id: string): BackgroundTask | undefined;
7
- getAllTasks(): BackgroundTask[];
8
- getActiveTasks(): BackgroundTask[];
9
- updateTask(id: string, updates: Partial<BackgroundTask>): void;
10
- cancelTask(id: string): boolean;
11
- cancelAll(): BackgroundTask[];
12
- getTasksByParent(parentSessionId: string): BackgroundTask[];
13
- restoreTask(task: BackgroundTask): void;
14
- clearTasks(): void;
15
- }
16
- export declare function getWidgetLines(manager: BackgroundManager): string[] | undefined;
17
- //# sourceMappingURL=manager.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/background-task/manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGjD,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,KAAK,CAA8B;IAE3C,cAEC;IAED,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC,GAAG,cAAc,CAiBhF;IAED,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE9C;IAED,WAAW,IAAI,cAAc,EAAE,CAE9B;IAED,cAAc,IAAI,cAAc,EAAE,CAEjC;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAQ7D;IAED,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAY9B;IAED,SAAS,IAAI,cAAc,EAAE,CAW5B;IAED,gBAAgB,CAAC,eAAe,EAAE,MAAM,GAAG,cAAc,EAAE,CAE1D;IAED,WAAW,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAEtC;IAED,UAAU,IAAI,IAAI,CAEjB;CACD;AA2BD,wBAAgB,cAAc,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,EAAE,GAAG,SAAS,CAyB/E","sourcesContent":["import type { BackgroundTask } from \"./types.js\";\nimport { MAX_CONCURRENT_TASKS } from \"./types.js\";\n\nexport class BackgroundManager {\n\tprivate tasks: Map<string, BackgroundTask>;\n\n\tconstructor() {\n\t\tthis.tasks = new Map();\n\t}\n\n\tlaunch(task: Omit<BackgroundTask, \"id\" | \"status\" | \"startedAt\">): BackgroundTask {\n\t\tconst activeTasks = this.getActiveTasks();\n\t\tif (activeTasks.length >= MAX_CONCURRENT_TASKS) {\n\t\t\tthrow new Error(\n\t\t\t\t`Maximum concurrent tasks (${MAX_CONCURRENT_TASKS}) reached. Cancel some tasks before launching new ones.`,\n\t\t\t);\n\t\t}\n\n\t\tconst newTask: BackgroundTask = {\n\t\t\t...task,\n\t\t\tid: generateTaskId(),\n\t\t\tstatus: \"pending\",\n\t\t\tstartedAt: new Date(),\n\t\t};\n\n\t\tthis.tasks.set(newTask.id, newTask);\n\t\treturn newTask;\n\t}\n\n\tgetTask(id: string): BackgroundTask | undefined {\n\t\treturn this.tasks.get(id);\n\t}\n\n\tgetAllTasks(): BackgroundTask[] {\n\t\treturn Array.from(this.tasks.values());\n\t}\n\n\tgetActiveTasks(): BackgroundTask[] {\n\t\treturn this.getAllTasks().filter((task) => task.status === \"pending\" || task.status === \"running\");\n\t}\n\n\tupdateTask(id: string, updates: Partial<BackgroundTask>): void {\n\t\tconst task = this.tasks.get(id);\n\t\tif (!task) {\n\t\t\tthrow new Error(`Task ${id} not found`);\n\t\t}\n\n\t\tconst updatedTask = { ...task, ...updates };\n\t\tthis.tasks.set(id, updatedTask);\n\t}\n\n\tcancelTask(id: string): boolean {\n\t\tconst task = this.tasks.get(id);\n\t\tif (!task) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (task.status === \"pending\" || task.status === \"running\") {\n\t\t\tthis.tasks.set(id, { ...task, status: \"cancelled\" });\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tcancelAll(): BackgroundTask[] {\n\t\tconst cancelled: BackgroundTask[] = [];\n\n\t\tfor (const task of this.tasks.values()) {\n\t\t\tif (task.status === \"pending\" || task.status === \"running\") {\n\t\t\t\tthis.tasks.set(task.id, { ...task, status: \"cancelled\" });\n\t\t\t\tcancelled.push(this.tasks.get(task.id)!);\n\t\t\t}\n\t\t}\n\n\t\treturn cancelled;\n\t}\n\n\tgetTasksByParent(parentSessionId: string): BackgroundTask[] {\n\t\treturn this.getAllTasks().filter((task) => task.parentSessionId === parentSessionId);\n\t}\n\n\trestoreTask(task: BackgroundTask): void {\n\t\tthis.tasks.set(task.id, task);\n\t}\n\n\tclearTasks(): void {\n\t\tthis.tasks.clear();\n\t}\n}\n\nfunction formatTaskMetadata(task: BackgroundTask): string | undefined {\n\tconst metadata = [task.agentType, task.model].filter(\n\t\t(value): value is string => typeof value === \"string\" && value.length > 0,\n\t);\n\tif (metadata.length === 0) {\n\t\treturn undefined;\n\t}\n\treturn metadata.join(\" · \");\n}\n\nfunction formatActiveTools(task: BackgroundTask): string | undefined {\n\tif (task.activeToolNames.length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst counts = new Map<string, number>();\n\tfor (const toolName of task.activeToolNames) {\n\t\tcounts.set(toolName, (counts.get(toolName) ?? 0) + 1);\n\t}\n\n\treturn Array.from(counts.entries())\n\t\t.map(([toolName, count]) => (count > 1 ? `${toolName}×${count}` : toolName))\n\t\t.join(\", \");\n}\n\nexport function getWidgetLines(manager: BackgroundManager): string[] | undefined {\n\tconst activeTasks = manager.getActiveTasks();\n\n\tif (activeTasks.length === 0) {\n\t\treturn undefined;\n\t}\n\n\tconst taskLines = activeTasks.flatMap((task) => {\n\t\tconst indicator = task.status === \"pending\" ? \"[⏳]\" : \"[▶]\";\n\t\tconst lines = [`${indicator} ${task.description}`];\n\t\tconst metadata = formatTaskMetadata(task);\n\t\tconst activeTools = formatActiveTools(task);\n\n\t\tif (metadata) {\n\t\t\tlines.push(` ${metadata}`);\n\t\t}\n\n\t\tif (activeTools) {\n\t\t\tlines.push(` tools: ${activeTools}`);\n\t\t}\n\n\t\treturn lines;\n\t});\n\n\treturn [\"Background Tasks\", ...taskLines];\n}\n\nfunction generateTaskId(): string {\n\treturn (\n\t\t\"bg_\" +\n\t\tMath.floor(Math.random() * 0xffffffff)\n\t\t\t.toString(16)\n\t\t\t.padStart(8, \"0\")\n\t);\n}\n"]}