@oh-my-pi/pi-coding-agent 15.10.12 → 15.11.0

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 (125) hide show
  1. package/CHANGELOG.md +60 -3
  2. package/dist/cli.js +841 -803
  3. package/dist/types/async/index.d.ts +0 -1
  4. package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
  5. package/dist/types/config/keybindings.d.ts +6 -1
  6. package/dist/types/config/settings-schema.d.ts +56 -33
  7. package/dist/types/export/html/template.generated.d.ts +1 -1
  8. package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
  9. package/dist/types/extensibility/shared-events.d.ts +2 -2
  10. package/dist/types/internal-urls/history-protocol.d.ts +14 -0
  11. package/dist/types/internal-urls/index.d.ts +1 -0
  12. package/dist/types/internal-urls/types.d.ts +1 -1
  13. package/dist/types/irc/bus.d.ts +66 -0
  14. package/dist/types/modes/components/agent-hub.d.ts +30 -0
  15. package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
  16. package/dist/types/modes/components/custom-editor.d.ts +2 -0
  17. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  18. package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
  19. package/dist/types/modes/components/welcome.d.ts +3 -9
  20. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  21. package/dist/types/modes/interactive-mode.d.ts +3 -2
  22. package/dist/types/modes/theme/theme.d.ts +2 -1
  23. package/dist/types/modes/types.d.ts +3 -2
  24. package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
  25. package/dist/types/registry/agent-lifecycle.d.ts +51 -0
  26. package/dist/types/registry/agent-registry.d.ts +16 -5
  27. package/dist/types/session/agent-session.d.ts +35 -30
  28. package/dist/types/session/messages.d.ts +2 -4
  29. package/dist/types/session/session-history-format.d.ts +12 -0
  30. package/dist/types/session/session-manager.d.ts +21 -3
  31. package/dist/types/session/streaming-output.d.ts +23 -0
  32. package/dist/types/task/executor.d.ts +11 -2
  33. package/dist/types/task/index.d.ts +11 -4
  34. package/dist/types/task/output-manager.d.ts +0 -7
  35. package/dist/types/task/repair-args.d.ts +8 -7
  36. package/dist/types/task/types.d.ts +55 -51
  37. package/dist/types/tools/browser/tab-worker.d.ts +3 -1
  38. package/dist/types/tools/find.d.ts +0 -11
  39. package/dist/types/tools/grouped-file-output.d.ts +0 -49
  40. package/dist/types/tools/index.d.ts +1 -3
  41. package/dist/types/tools/irc.d.ts +76 -38
  42. package/dist/types/tools/job.d.ts +7 -1
  43. package/examples/extensions/with-deps/package.json +1 -0
  44. package/package.json +11 -10
  45. package/scripts/bundle-dist.ts +28 -19
  46. package/src/async/index.ts +0 -1
  47. package/src/cli/gallery-cli.ts +1 -1
  48. package/src/cli/gallery-fixtures/agentic.ts +230 -115
  49. package/src/cli/gallery-fixtures/types.ts +5 -0
  50. package/src/cli.ts +20 -6
  51. package/src/commit/agentic/tools/analyze-file.ts +38 -19
  52. package/src/config/keybindings.ts +6 -1
  53. package/src/config/settings-schema.ts +56 -40
  54. package/src/config/settings.ts +7 -0
  55. package/src/eval/__tests__/agent-bridge.test.ts +5 -3
  56. package/src/eval/agent-bridge.ts +3 -16
  57. package/src/eval/js/shared/prelude.txt +1 -1
  58. package/src/eval/py/prelude.py +5 -6
  59. package/src/export/html/template.generated.ts +1 -1
  60. package/src/export/html/template.js +38 -13
  61. package/src/extensibility/custom-tools/types.ts +2 -2
  62. package/src/extensibility/shared-events.ts +2 -2
  63. package/src/internal-urls/docs-index.generated.ts +8 -8
  64. package/src/internal-urls/history-protocol.ts +113 -0
  65. package/src/internal-urls/index.ts +1 -0
  66. package/src/internal-urls/router.ts +3 -1
  67. package/src/internal-urls/types.ts +1 -1
  68. package/src/irc/bus.ts +292 -0
  69. package/src/main.ts +8 -60
  70. package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
  71. package/src/modes/components/compaction-summary-message.ts +68 -32
  72. package/src/modes/components/custom-editor.ts +10 -0
  73. package/src/modes/components/tool-execution.ts +31 -1
  74. package/src/modes/components/ttsr-notification.ts +72 -30
  75. package/src/modes/components/welcome.ts +9 -33
  76. package/src/modes/controllers/event-controller.ts +65 -0
  77. package/src/modes/controllers/extension-ui-controller.ts +8 -8
  78. package/src/modes/controllers/input-controller.ts +18 -2
  79. package/src/modes/controllers/selector-controller.ts +21 -17
  80. package/src/modes/interactive-mode.ts +8 -13
  81. package/src/modes/theme/theme.ts +18 -5
  82. package/src/modes/types.ts +3 -5
  83. package/src/modes/utils/hotkeys-markdown.ts +1 -0
  84. package/src/modes/utils/ui-helpers.ts +51 -49
  85. package/src/prompts/system/irc-incoming.md +3 -4
  86. package/src/prompts/system/orchestrate-notice.md +2 -2
  87. package/src/prompts/system/subagent-system-prompt.md +0 -5
  88. package/src/prompts/system/system-prompt.md +1 -0
  89. package/src/prompts/system/workflow-notice.md +2 -2
  90. package/src/prompts/tools/eval.md +3 -3
  91. package/src/prompts/tools/irc.md +29 -19
  92. package/src/prompts/tools/read.md +2 -2
  93. package/src/prompts/tools/task-summary.md +5 -16
  94. package/src/prompts/tools/task.md +38 -29
  95. package/src/registry/agent-lifecycle.ts +218 -0
  96. package/src/registry/agent-registry.ts +16 -5
  97. package/src/sdk.ts +29 -9
  98. package/src/session/agent-session.ts +243 -237
  99. package/src/session/messages.ts +11 -78
  100. package/src/session/session-history-format.ts +246 -0
  101. package/src/session/session-manager.ts +59 -5
  102. package/src/session/streaming-output.ts +60 -0
  103. package/src/task/executor.ts +855 -466
  104. package/src/task/index.ts +718 -794
  105. package/src/task/output-manager.ts +0 -11
  106. package/src/task/render.ts +133 -63
  107. package/src/task/repair-args.ts +21 -9
  108. package/src/task/types.ts +73 -66
  109. package/src/tools/ask.ts +4 -2
  110. package/src/tools/bash.ts +15 -5
  111. package/src/tools/browser/tab-worker.ts +26 -7
  112. package/src/tools/browser.ts +28 -1
  113. package/src/tools/find.ts +2 -27
  114. package/src/tools/grouped-file-output.ts +1 -118
  115. package/src/tools/index.ts +4 -12
  116. package/src/tools/irc.ts +596 -171
  117. package/src/tools/job.ts +41 -7
  118. package/src/tools/read.ts +57 -1
  119. package/src/tools/renderers.ts +2 -0
  120. package/src/tools/resolve.ts +4 -1
  121. package/dist/types/async/support.d.ts +0 -2
  122. package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
  123. package/dist/types/task/simple-mode.d.ts +0 -8
  124. package/src/async/support.ts +0 -5
  125. package/src/task/simple-mode.ts +0 -27
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.10.12",
4
+ "version": "15.11.0",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,15 +47,16 @@
47
47
  "@agentclientprotocol/sdk": "0.22.1",
48
48
  "@babel/parser": "^7.29.7",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/hashline": "15.10.12",
51
- "@oh-my-pi/omp-stats": "15.10.12",
52
- "@oh-my-pi/pi-agent-core": "15.10.12",
53
- "@oh-my-pi/pi-ai": "15.10.12",
54
- "@oh-my-pi/pi-catalog": "15.10.12",
55
- "@oh-my-pi/pi-mnemopi": "15.10.12",
56
- "@oh-my-pi/pi-natives": "15.10.12",
57
- "@oh-my-pi/pi-tui": "15.10.12",
58
- "@oh-my-pi/pi-utils": "15.10.12",
50
+ "@oh-my-pi/hashline": "15.11.0",
51
+ "@oh-my-pi/omp-stats": "15.11.0",
52
+ "@oh-my-pi/pi-agent-core": "15.11.0",
53
+ "@oh-my-pi/pi-ai": "15.11.0",
54
+ "@oh-my-pi/pi-catalog": "15.11.0",
55
+ "@oh-my-pi/pi-mnemopi": "15.11.0",
56
+ "@oh-my-pi/pi-natives": "15.11.0",
57
+ "@oh-my-pi/pi-tui": "15.11.0",
58
+ "@oh-my-pi/pi-utils": "15.11.0",
59
+ "@oh-my-pi/snapcompact": "15.11.0",
59
60
  "@opentelemetry/api": "^1.9.1",
60
61
  "@opentelemetry/context-async-hooks": "^2.7.1",
61
62
  "@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
@@ -51,25 +51,34 @@ async function cleanBundleOutputs(): Promise<void> {
51
51
  async function main(): Promise<void> {
52
52
  const start = Bun.nanoseconds();
53
53
  await cleanBundleOutputs();
54
- await runCommand([
55
- "bun",
56
- "build",
57
- "--target=bun",
58
- "--outdir",
59
- "dist",
60
- "--minify-whitespace",
61
- "--minify-syntax",
62
- "--keep-names",
63
- "--external",
64
- "mupdf",
65
- "--external",
66
- "@oh-my-pi/pi-natives",
67
- "--external",
68
- "@huggingface/transformers",
69
- "--define",
70
- 'process.env.PI_BUNDLED="true"',
71
- "./src/cli.ts",
72
- ]);
54
+ // The npm bundle ships no stats dashboard sources or prebuilt dist/client,
55
+ // so embed the dashboard archive the same way compiled binaries do
56
+ // (scripts/build-binary.ts). Reset afterwards to keep the checked-in
57
+ // placeholder empty.
58
+ await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
59
+ try {
60
+ await runCommand([
61
+ "bun",
62
+ "build",
63
+ "--target=bun",
64
+ "--outdir",
65
+ "dist",
66
+ "--minify-whitespace",
67
+ "--minify-syntax",
68
+ "--keep-names",
69
+ "--external",
70
+ "mupdf",
71
+ "--external",
72
+ "@oh-my-pi/pi-natives",
73
+ "--external",
74
+ "@huggingface/transformers",
75
+ "--define",
76
+ 'process.env.PI_BUNDLED="true"',
77
+ "./src/cli.ts",
78
+ ]);
79
+ } finally {
80
+ await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]);
81
+ }
73
82
  await ensureShebang();
74
83
  const stat = await fs.stat(cliPath);
75
84
  const elapsedMs = (Bun.nanoseconds() - start) / 1_000_000;
@@ -1,2 +1 @@
1
1
  export * from "./job-manager";
2
- export * from "./support";
@@ -69,7 +69,7 @@ function fakeToolFor(name: string, fixture: GalleryFixture | undefined): AgentTo
69
69
  if (!fixture?.label && !fixture?.editMode && !fixture?.customRendered) return undefined;
70
70
  const tool: Record<string, unknown> = { name, label: fixture.label ?? name, mode: fixture.editMode };
71
71
  if (fixture.customRendered) {
72
- const renderer = toolRenderers[name] as
72
+ const renderer = toolRenderers[fixture.renderer ?? name] as
73
73
  | { renderCall?: unknown; renderResult?: unknown; mergeCallAndResult?: unknown; inline?: unknown }
74
74
  | undefined;
75
75
  if (renderer) {
@@ -1,62 +1,76 @@
1
- // Gallery fixtures for the agentic orchestration tools (task, goal, job).
1
+ // Gallery fixtures for the agentic orchestration tools (task, irc, goal, job).
2
+ import type { Usage } from "@oh-my-pi/pi-ai";
3
+ import type { TaskToolDetails } from "../../task/types";
4
+ import type { IrcDetails } from "../../tools/irc";
2
5
  import type { GalleryFixture } from "./types";
3
6
 
7
+ /** Message/activity timestamps are offsets from load time so gallery ages stay plausible. */
8
+ const FIXTURE_NOW = Date.now();
9
+
10
+ /** Plausible cumulative usage for a fixture subagent run. */
11
+ const fixtureUsage = (tokens: { input: number; output: number }, costTotal: number): Usage => ({
12
+ input: tokens.input,
13
+ output: tokens.output,
14
+ cacheRead: 0,
15
+ cacheWrite: 0,
16
+ totalTokens: tokens.input + tokens.output,
17
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: costTotal },
18
+ });
19
+
4
20
  export const agenticFixtures: Record<string, GalleryFixture> = {
5
21
  task: {
6
22
  label: "Task",
7
23
  customRendered: true,
8
- // Streaming: agent chosen, first task fully arrived, second still landing.
24
+ // Streaming: agent chosen, assignment still landing.
9
25
  streamingArgs: {
10
26
  agent: "task",
11
- tasks: [
12
- {
13
- id: "AuthLoader",
14
- description: "Load auth middleware",
15
- assignment: "Read packages/server/src/auth/*.ts and summarize the session-cookie flow.",
16
- },
17
- { id: "RateLimiter", description: "Audit rate limiter" },
18
- ],
27
+ id: "AuthLoader",
28
+ description: "Load auth middleware",
29
+ assignment: "Read packages/server/src/auth/*.ts and summarize the session-cookie",
19
30
  },
20
31
  args: {
21
32
  agent: "task",
22
- context: [
23
- "# Goal",
24
- "Harden the HTTP auth stack before the release cut.",
25
- "# Constraints",
26
- "Touch only files under packages/server/src/auth/. Do not run gates.",
27
- ].join("\n"),
28
- tasks: [
29
- {
30
- id: "AuthLoader",
31
- description: "Load auth middleware",
32
- assignment:
33
- "Read packages/server/src/auth/session.ts and middleware.ts, then document the session-cookie validation flow and any TODOs.",
34
- },
35
- {
36
- id: "RateLimiter",
37
- description: "Audit rate limiter",
38
- assignment:
39
- "Inspect packages/server/src/auth/rate-limit.ts. Confirm the 429 path sets Retry-After and report gaps.",
40
- },
41
- {
42
- id: "TokenRotation",
43
- description: "Check token rotation",
44
- assignment:
45
- "Trace refresh-token rotation in packages/server/src/auth/tokens.ts and flag any reuse window.",
46
- },
47
- ],
33
+ id: "AuthLoader",
34
+ description: "Load auth middleware",
35
+ assignment:
36
+ "Read packages/server/src/auth/session.ts and middleware.ts, then document the session-cookie validation flow and any TODOs.",
48
37
  },
49
38
  result: {
50
39
  content: [
51
40
  {
52
41
  type: "text",
53
- text: "3 agents completed: AuthLoader, RateLimiter, TokenRotation.",
42
+ text: "Agent AuthLoader completed.",
54
43
  },
55
44
  ],
56
45
  details: {
57
46
  projectAgentsDir: null,
58
47
  totalDurationMs: 48_200,
59
- usage: { cost: { total: 0.34 } },
48
+ usage: fixtureUsage({ input: 52_600, output: 8_800 }, 0.12),
49
+ progress: [
50
+ {
51
+ index: 0,
52
+ id: "AuthLoader",
53
+ agent: "task",
54
+ agentSource: "bundled",
55
+ status: "completed",
56
+ task: "Read packages/server/src/auth/session.ts and middleware.ts",
57
+ description: "Load auth middleware",
58
+ lastIntent: "Documenting session-cookie flow",
59
+ recentTools: [
60
+ { tool: "read", args: "packages/server/src/auth/session.ts", endMs: 1_749_200_040_000 },
61
+ { tool: "read", args: "packages/server/src/auth/middleware.ts", endMs: 1_749_200_052_000 },
62
+ ],
63
+ recentOutput: ["Session validation runs in middleware.ts:42 via verifySessionCookie()."],
64
+ toolCount: 9,
65
+ requests: 6,
66
+ tokens: 61_400,
67
+ contextTokens: 23_100,
68
+ contextWindow: 200_000,
69
+ cost: 0.12,
70
+ durationMs: 41_900,
71
+ resolvedModel: "anthropic/claude-sonnet",
72
+ },
73
+ ],
60
74
  results: [
61
75
  {
62
76
  index: 0,
@@ -77,100 +91,31 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
77
91
  truncated: false,
78
92
  durationMs: 41_900,
79
93
  tokens: 61_400,
94
+ requests: 6,
80
95
  contextTokens: 23_100,
81
96
  contextWindow: 200_000,
82
97
  resolvedModel: "anthropic/claude-sonnet",
83
- usage: { cost: { total: 0.12 } },
98
+ usage: fixtureUsage({ input: 52_600, output: 8_800 }, 0.12),
84
99
  outputMeta: { lineCount: 3, charCount: 214 },
85
100
  },
86
- {
87
- index: 1,
88
- id: "RateLimiter",
89
- agent: "task",
90
- agentSource: "bundled",
91
- description: "Audit rate limiter",
92
- task: "Inspect packages/server/src/auth/rate-limit.ts",
93
- assignment:
94
- "Inspect packages/server/src/auth/rate-limit.ts. Confirm the 429 path sets Retry-After and report gaps.",
95
- exitCode: 0,
96
- output: [
97
- "rate-limit.ts uses a fixed-window counter keyed by client IP.",
98
- "429 responses set Retry-After (rate-limit.ts:57).",
99
- "Gap: no per-account limit, so a botnet across IPs bypasses the cap.",
100
- ].join("\n"),
101
- stderr: "",
102
- truncated: false,
103
- durationMs: 38_500,
104
- tokens: 54_800,
105
- contextTokens: 19_700,
106
- contextWindow: 200_000,
107
- resolvedModel: "anthropic/claude-sonnet",
108
- usage: { cost: { total: 0.1 } },
109
- outputMeta: { lineCount: 3, charCount: 198 },
110
- },
111
- {
112
- index: 2,
113
- id: "TokenRotation",
114
- agent: "task",
115
- agentSource: "bundled",
116
- description: "Check token rotation",
117
- task: "Trace refresh-token rotation in packages/server/src/auth/tokens.ts",
118
- assignment:
119
- "Trace refresh-token rotation in packages/server/src/auth/tokens.ts and flag any reuse window.",
120
- exitCode: 0,
121
- output: [
122
- "Refresh tokens rotate on every use (tokens.ts:120) and the old jti is revoked.",
123
- "Reuse of a rotated token triggers full-family revocation — no reuse window found.",
124
- ].join("\n"),
125
- stderr: "",
126
- truncated: false,
127
- durationMs: 48_200,
128
- tokens: 49_200,
129
- contextTokens: 17_500,
130
- contextWindow: 200_000,
131
- resolvedModel: "anthropic/claude-sonnet",
132
- usage: { cost: { total: 0.12 } },
133
- outputMeta: { lineCount: 2, charCount: 160 },
134
- },
135
101
  ],
136
- },
102
+ } satisfies TaskToolDetails,
137
103
  },
138
104
  errorResult: {
139
105
  isError: true,
140
106
  content: [
141
107
  {
142
108
  type: "text",
143
- text: "1 of 3 agents failed: RateLimiter.",
109
+ text: "Agent RateLimiter failed.",
144
110
  },
145
111
  ],
146
112
  details: {
147
113
  projectAgentsDir: null,
148
- totalDurationMs: 39_400,
149
- usage: { cost: { total: 0.21 } },
114
+ totalDurationMs: 9_800,
115
+ usage: fixtureUsage({ input: 10_900, output: 1_400 }, 0.1),
150
116
  results: [
151
117
  {
152
118
  index: 0,
153
- id: "AuthLoader",
154
- agent: "task",
155
- agentSource: "bundled",
156
- description: "Load auth middleware",
157
- task: "Read packages/server/src/auth/session.ts and middleware.ts",
158
- assignment:
159
- "Read packages/server/src/auth/session.ts and middleware.ts, then document the session-cookie validation flow and any TODOs.",
160
- exitCode: 0,
161
- output: "Session validation runs in middleware.ts:42 via verifySessionCookie().",
162
- stderr: "",
163
- truncated: false,
164
- durationMs: 31_200,
165
- tokens: 58_100,
166
- contextTokens: 21_900,
167
- contextWindow: 200_000,
168
- resolvedModel: "anthropic/claude-sonnet",
169
- usage: { cost: { total: 0.11 } },
170
- outputMeta: { lineCount: 1, charCount: 70 },
171
- },
172
- {
173
- index: 1,
174
119
  id: "RateLimiter",
175
120
  agent: "task",
176
121
  agentSource: "bundled",
@@ -184,15 +129,185 @@ export const agenticFixtures: Record<string, GalleryFixture> = {
184
129
  truncated: false,
185
130
  durationMs: 9_800,
186
131
  tokens: 12_300,
132
+ requests: 3,
187
133
  contextTokens: 6_400,
188
134
  contextWindow: 200_000,
189
135
  resolvedModel: "anthropic/claude-sonnet",
190
- usage: { cost: { total: 0.1 } },
136
+ usage: fixtureUsage({ input: 10_900, output: 1_400 }, 0.1),
191
137
  error: "Subagent exited 1: target file packages/server/src/auth/rate-limit.ts does not exist.",
192
138
  outputMeta: { lineCount: 0, charCount: 0 },
193
139
  },
194
140
  ],
195
- },
141
+ } satisfies TaskToolDetails,
142
+ },
143
+ },
144
+
145
+ irc: {
146
+ label: "IRC",
147
+ // Streaming: recipient known; the message body still arriving.
148
+ streamingArgs: { op: "send", to: "AuthLoader", message: "Are you still touching" },
149
+ args: {
150
+ op: "send",
151
+ to: "AuthLoader",
152
+ message: "Are you still touching src/server/auth.ts? I need to add a 401 path.",
153
+ await: true,
154
+ },
155
+ result: {
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: [
160
+ "Delivered to 1 peer(s):",
161
+ "- AuthLoader: revived",
162
+ "",
163
+ "Reply from AuthLoader:",
164
+ "Done with auth.ts — go ahead, just rebase past my session-store rename.",
165
+ ].join("\n"),
166
+ },
167
+ ],
168
+ details: {
169
+ op: "send",
170
+ from: "Main",
171
+ to: "AuthLoader",
172
+ receipts: [{ to: "AuthLoader", outcome: "revived" }],
173
+ waited: {
174
+ id: "7181122334455667789",
175
+ from: "AuthLoader",
176
+ to: "Main",
177
+ body: "Done with auth.ts — go ahead, just rebase past my session-store rename.",
178
+ ts: FIXTURE_NOW - 5_000,
179
+ replyTo: "7181122334455667788",
180
+ },
181
+ } satisfies IrcDetails,
182
+ },
183
+ errorResult: {
184
+ isError: true,
185
+ content: [
186
+ {
187
+ type: "text",
188
+ text: 'No recipients received the message.\n- RateLimiter: failed — unknown agent "RateLimiter"',
189
+ },
190
+ ],
191
+ details: {
192
+ op: "send",
193
+ from: "Main",
194
+ to: "RateLimiter",
195
+ receipts: [{ to: "RateLimiter", outcome: "failed", error: 'unknown agent "RateLimiter"' }],
196
+ } satisfies IrcDetails,
197
+ },
198
+ },
199
+
200
+ irc_wait: {
201
+ label: "IRC (wait)",
202
+ customRendered: true,
203
+ renderer: "irc",
204
+ streamingArgs: { op: "wait", from: "AuthLoader" },
205
+ args: { op: "wait", from: "AuthLoader", timeoutMs: 60_000 },
206
+ result: {
207
+ content: [
208
+ {
209
+ type: "text",
210
+ text: "[7181122334455667790] AuthLoader: session-store rename is merged; auth.ts is yours.",
211
+ },
212
+ ],
213
+ details: {
214
+ op: "wait",
215
+ from: "Main",
216
+ waited: {
217
+ id: "7181122334455667790",
218
+ from: "AuthLoader",
219
+ to: "Main",
220
+ body: "session-store rename is merged; auth.ts is yours.",
221
+ ts: FIXTURE_NOW - 30_000,
222
+ },
223
+ } satisfies IrcDetails,
224
+ },
225
+ },
226
+
227
+ irc_inbox: {
228
+ label: "IRC (inbox)",
229
+ customRendered: true,
230
+ renderer: "irc",
231
+ streamingArgs: { op: "inbox" },
232
+ args: { op: "inbox", peek: true },
233
+ result: {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: [
238
+ "2 unread message(s):",
239
+ "- [7181122334455667791] AuthLoader: hub table reads unreadCount — ping me when the bus lands.",
240
+ "- [7181122334455667792] RateLimiter (reply to 7181122334455667791): bus is in; receipts carry outcome.",
241
+ ].join("\n"),
242
+ },
243
+ ],
244
+ details: {
245
+ op: "inbox",
246
+ from: "Main",
247
+ inbox: [
248
+ {
249
+ id: "7181122334455667791",
250
+ from: "AuthLoader",
251
+ to: "Main",
252
+ body: "hub table reads unreadCount — ping me when the bus lands.",
253
+ ts: FIXTURE_NOW - 4 * 60_000,
254
+ },
255
+ {
256
+ id: "7181122334455667792",
257
+ from: "RateLimiter",
258
+ to: "Main",
259
+ body: "bus is in; receipts carry outcome.",
260
+ ts: FIXTURE_NOW - 60_000,
261
+ replyTo: "7181122334455667791",
262
+ },
263
+ ],
264
+ } satisfies IrcDetails,
265
+ },
266
+ },
267
+
268
+ irc_list: {
269
+ label: "IRC (list)",
270
+ customRendered: true,
271
+ renderer: "irc",
272
+ streamingArgs: { op: "list" },
273
+ args: { op: "list" },
274
+ result: {
275
+ content: [
276
+ {
277
+ type: "text",
278
+ text: [
279
+ "2 peer(s):",
280
+ "- AuthLoader [task · sub · idle] — parent Main, active 2m ago",
281
+ "- RateLimiter [task · sub · parked] — unread 2, parent Main, active 12m ago",
282
+ "",
283
+ "Parked agents are revived automatically when you message them.",
284
+ ].join("\n"),
285
+ },
286
+ ],
287
+ details: {
288
+ op: "list",
289
+ from: "Main",
290
+ peers: [
291
+ {
292
+ id: "AuthLoader",
293
+ displayName: "task",
294
+ kind: "sub",
295
+ status: "idle",
296
+ parentId: "Main",
297
+ unread: 0,
298
+ lastActivity: FIXTURE_NOW - 2 * 60_000,
299
+ },
300
+ {
301
+ id: "RateLimiter",
302
+ displayName: "task",
303
+ kind: "sub",
304
+ status: "parked",
305
+ parentId: "Main",
306
+ unread: 2,
307
+ lastActivity: FIXTURE_NOW - 12 * 60_000,
308
+ },
309
+ ],
310
+ } satisfies IrcDetails,
196
311
  },
197
312
  },
198
313
 
@@ -36,6 +36,11 @@ export interface GalleryFixture {
36
36
  * real one keeps the gallery honest for these tools.
37
37
  */
38
38
  customRendered?: boolean;
39
+ /**
40
+ * Renderer-registry key to use when the fixture key is a variant of a tool
41
+ * (e.g. `irc_wait` → `irc`). Defaults to the fixture key.
42
+ */
43
+ renderer?: string;
39
44
  /**
40
45
  * Arguments shown during the streaming state — a partial view of {@link args}
41
46
  * as if the tool-call JSON were still arriving. May include `__partialJson`
package/src/cli.ts CHANGED
@@ -43,19 +43,33 @@ async function showHelp(config: CliConfig): Promise<void> {
43
43
  }
44
44
  }
45
45
  /**
46
- * Smoke-test entry. Spawns bundled workers, pings them, exits.
46
+ * Smoke-test entry. Spawns bundled workers, serves the stats dashboard once,
47
+ * pings everything, then exits.
47
48
  *
48
- * Purpose: catch the silent worker-load regressions that hit compiled
49
- * binaries (issues #1011 and #1027). Version/help paths do not spawn worker
50
- * modules on a fresh install, so this probe is the minimal end-to-end test
51
- * that proves `new Worker(...)` resolves and bundled worker modules evaluate.
49
+ * Purpose: catch the silent worker-load and bundled-asset regressions that hit
50
+ * compiled binaries and the npm CLI bundle. Version/help paths do not spawn
51
+ * worker modules or serve dashboard assets on a fresh install, so this probe is
52
+ * the minimal end-to-end test that proves those distribution-only paths work.
52
53
  * Wired into `scripts/install-tests/run-ci.sh` so binary / source-link /
53
54
  * tarball installs all exercise it on every CI run.
54
55
  */
55
56
  async function runSmokeTest(): Promise<void> {
56
- const { smokeTestSyncWorker } = await import("@oh-my-pi/omp-stats");
57
+ const { smokeTestSyncWorker, startServer } = await import("@oh-my-pi/omp-stats");
57
58
  const { smokeTestTinyTitleWorker } = await import("./tiny/title-client");
58
59
  await smokeTestSyncWorker();
60
+
61
+ const statsServer = await startServer(0);
62
+ try {
63
+ const response = await fetch(`http://127.0.0.1:${statsServer.port}/`);
64
+ if (!response.ok) throw new Error(`stats dashboard smoke failed: HTTP ${response.status}`);
65
+ const html = await response.text();
66
+ if (!html.includes('<div id="root"></div>') || !html.includes("index.js")) {
67
+ throw new Error("stats dashboard smoke failed: dashboard HTML was not served");
68
+ }
69
+ } finally {
70
+ statsServer.stop();
71
+ }
72
+
59
73
  await smokeTestTinyTitleWorker();
60
74
  process.stdout.write("smoke-test: ok\n");
61
75
  }
@@ -43,6 +43,9 @@ function buildToolSession(
43
43
  settings: options.settings,
44
44
  authStorage: options.authStorage,
45
45
  modelRegistry: options.modelRegistry,
46
+ // The task tool no longer takes a per-call schema; the inherited session
47
+ // schema drives structured output for every spawn from this session.
48
+ outputSchema: analyzeFileOutputSchema,
46
49
  };
47
50
  }
48
51
 
@@ -59,29 +62,45 @@ export function createAnalyzeFileTool(options: {
59
62
  label: "Analyze Files",
60
63
  description: "Spawn quick_task agents to analyze files.",
61
64
  parameters: analyzeFileSchema,
62
- async execute(toolCallId, params, onUpdate, ctx, signal) {
65
+ async execute(toolCallId, params, _onUpdate, ctx, signal) {
63
66
  const toolSession = buildToolSession(ctx, options);
67
+ // The hand-built ToolSession carries no asyncJobManager, so every
68
+ // execute() below takes the task tool's sync fallback and resolves
69
+ // with the subagent's result inline — exactly what this flow needs.
70
+ // The tool's session semaphore bounds the parallel fan-out.
64
71
  const taskTool = await TaskTool.create(toolSession);
65
72
  const numstat = options.state.overview?.numstat ?? [];
66
- const tasks = params.files.map((file, index) => {
67
- const relatedFiles = formatRelatedFiles(params.files, file, numstat);
68
- const assignment = prompt.render(analyzeFilePrompt, {
69
- file,
70
- goal: params.goal,
71
- related_files: relatedFiles,
72
- });
73
- return {
74
- id: `AnalyzeFile${index + 1}`,
75
- description: `Analyze ${file}`,
76
- assignment,
77
- };
78
- });
79
- const taskParams: TaskParams = {
80
- agent: "quick_task",
81
- schema: JSON.stringify(analyzeFileOutputSchema),
82
- tasks,
73
+
74
+ const analyses = await Promise.all(
75
+ params.files.map((file, index) => {
76
+ const relatedFiles = formatRelatedFiles(params.files, file, numstat);
77
+ const assignment = prompt.render(analyzeFilePrompt, {
78
+ file,
79
+ goal: params.goal,
80
+ related_files: relatedFiles,
81
+ });
82
+ const taskParams: TaskParams = {
83
+ agent: "quick_task",
84
+ id: `AnalyzeFile${index + 1}`,
85
+ description: `Analyze ${file}`,
86
+ assignment,
87
+ };
88
+ return taskTool.execute(`${toolCallId}-${index + 1}`, taskParams, signal);
89
+ }),
90
+ );
91
+ const results = analyses.flatMap(analysis => analysis.details?.results ?? []);
92
+ const text = analyses
93
+ .map(analysis => analysis.content.find(part => part.type === "text")?.text ?? "")
94
+ .filter(Boolean)
95
+ .join("\n\n");
96
+ return {
97
+ content: [{ type: "text", text: text || "(no output)" }],
98
+ details: {
99
+ projectAgentsDir: null,
100
+ results,
101
+ totalDurationMs: analyses.reduce((sum, analysis) => sum + (analysis.details?.totalDurationMs ?? 0), 0),
102
+ },
83
103
  };
84
- return taskTool.execute(toolCallId, taskParams, signal, onUpdate);
85
104
  },
86
105
  };
87
106
  }
@@ -36,6 +36,7 @@ interface AppKeybindings {
36
36
  "app.clipboard.pasteTextRaw": true;
37
37
  "app.clipboard.copyLine": true;
38
38
  "app.clipboard.copyPrompt": true;
39
+ "app.agents.hub": true;
39
40
  "app.session.new": true;
40
41
  "app.session.tree": true;
41
42
  "app.session.fork": true;
@@ -166,9 +167,13 @@ export const KEYBINDINGS = {
166
167
  defaultKeys: [],
167
168
  description: "Resume session",
168
169
  },
170
+ "app.agents.hub": {
171
+ defaultKeys: "alt+a",
172
+ description: "Open the agent hub",
173
+ },
169
174
  "app.session.observe": {
170
175
  defaultKeys: "ctrl+s",
171
- description: "Observe subagent sessions",
176
+ description: "Open the agent hub",
172
177
  },
173
178
  "app.session.togglePath": {
174
179
  defaultKeys: "ctrl+p",