@oh-my-pi/pi-coding-agent 3.30.0 → 3.31.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 (155) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/package.json +5 -5
  3. package/src/cli/args.ts +4 -0
  4. package/src/core/agent-session.ts +29 -2
  5. package/src/core/bash-executor.ts +2 -1
  6. package/src/core/custom-commands/bundled/review/index.ts +369 -14
  7. package/src/core/custom-commands/bundled/wt/index.ts +1 -1
  8. package/src/core/session-manager.ts +158 -246
  9. package/src/core/session-storage.ts +379 -0
  10. package/src/core/settings-manager.ts +155 -4
  11. package/src/core/system-prompt.ts +62 -64
  12. package/src/core/tools/ask.ts +5 -4
  13. package/src/core/tools/bash-interceptor.ts +26 -61
  14. package/src/core/tools/bash.ts +13 -8
  15. package/src/core/tools/edit-diff.ts +11 -4
  16. package/src/core/tools/edit.ts +7 -13
  17. package/src/core/tools/find.ts +111 -50
  18. package/src/core/tools/gemini-image.ts +128 -147
  19. package/src/core/tools/grep.ts +397 -415
  20. package/src/core/tools/index.test.ts +5 -1
  21. package/src/core/tools/index.ts +6 -8
  22. package/src/core/tools/ls.ts +12 -10
  23. package/src/core/tools/lsp/client.ts +58 -9
  24. package/src/core/tools/lsp/config.ts +205 -656
  25. package/src/core/tools/lsp/defaults.json +465 -0
  26. package/src/core/tools/lsp/index.ts +55 -32
  27. package/src/core/tools/lsp/rust-analyzer.ts +49 -10
  28. package/src/core/tools/lsp/types.ts +1 -0
  29. package/src/core/tools/lsp/utils.ts +1 -1
  30. package/src/core/tools/read.ts +150 -74
  31. package/src/core/tools/render-utils.ts +70 -10
  32. package/src/core/tools/review.ts +38 -126
  33. package/src/core/tools/task/artifacts.ts +5 -4
  34. package/src/core/tools/task/executor.ts +94 -83
  35. package/src/core/tools/task/index.ts +129 -92
  36. package/src/core/tools/task/parallel.ts +30 -3
  37. package/src/core/tools/task/render.ts +85 -39
  38. package/src/core/tools/task/types.ts +15 -6
  39. package/src/core/tools/task/worker.ts +124 -89
  40. package/src/core/tools/web-fetch.ts +112 -377
  41. package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
  42. package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
  43. package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
  44. package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
  45. package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
  46. package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
  47. package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
  48. package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
  49. package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
  50. package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
  51. package/src/core/tools/web-scrapers/clojars.ts +180 -0
  52. package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
  53. package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
  54. package/src/core/tools/web-scrapers/crossref.ts +149 -0
  55. package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
  56. package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
  57. package/src/core/tools/web-scrapers/discourse.ts +221 -0
  58. package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
  59. package/src/core/tools/web-scrapers/fdroid.ts +158 -0
  60. package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
  61. package/src/core/tools/web-scrapers/flathub.ts +239 -0
  62. package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
  63. package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
  64. package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
  65. package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
  66. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
  67. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
  68. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
  69. package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
  70. package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
  71. package/src/core/tools/web-scrapers/index.ts +250 -0
  72. package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
  73. package/src/core/tools/web-scrapers/lemmy.ts +220 -0
  74. package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
  75. package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
  76. package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
  77. package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
  78. package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
  79. package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
  80. package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
  81. package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
  82. package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
  83. package/src/core/tools/web-scrapers/ollama.ts +267 -0
  84. package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
  85. package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
  86. package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
  87. package/src/core/tools/web-scrapers/orcid.ts +299 -0
  88. package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
  89. package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
  90. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
  91. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
  92. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
  93. package/src/core/tools/web-scrapers/rawg.ts +124 -0
  94. package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
  95. package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
  96. package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
  97. package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
  98. package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
  99. package/src/core/tools/web-scrapers/searchcode.ts +217 -0
  100. package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
  101. package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
  102. package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
  103. package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
  104. package/src/core/tools/web-scrapers/spdx.ts +121 -0
  105. package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
  106. package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
  107. package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
  108. package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
  109. package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
  110. package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
  111. package/src/core/tools/web-scrapers/utils.ts +162 -0
  112. package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
  113. package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
  114. package/src/core/tools/web-scrapers/w3c.ts +163 -0
  115. package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
  116. package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
  117. package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
  118. package/src/core/tools/write.ts +21 -18
  119. package/src/core/voice.ts +3 -2
  120. package/src/lib/worktree/collapse.ts +2 -1
  121. package/src/lib/worktree/git.ts +2 -18
  122. package/src/main.ts +59 -3
  123. package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
  124. package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
  125. package/src/modes/interactive/components/hook-editor.ts +2 -1
  126. package/src/modes/interactive/components/model-selector.ts +19 -4
  127. package/src/modes/interactive/interactive-mode.ts +41 -38
  128. package/src/modes/interactive/theme/theme.ts +58 -58
  129. package/src/modes/rpc/rpc-mode.ts +10 -9
  130. package/src/prompts/review-request.md +27 -0
  131. package/src/prompts/reviewer.md +64 -68
  132. package/src/prompts/tools/output.md +22 -3
  133. package/src/prompts/tools/task.md +32 -33
  134. package/src/utils/clipboard.ts +2 -1
  135. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  136. package/src/core/tools/web-fetch-handlers/index.ts +0 -69
  137. package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
  138. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
  139. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
  140. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
  141. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
  142. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
  143. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
  144. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
  145. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
  146. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
  147. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
  148. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
  149. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
  150. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
  151. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
  152. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
  153. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
  154. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
  155. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
@@ -19,6 +19,7 @@ import type { AgentSessionEvent } from "../../agent-session";
19
19
  import { parseModelPattern, parseModelString } from "../../model-resolver";
20
20
  import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
21
21
  import { SessionManager } from "../../session-manager";
22
+ import { untilAborted } from "../../utils";
22
23
  import type { SubagentWorkerRequest, SubagentWorkerResponse, SubagentWorkerStartPayload } from "./worker-protocol";
23
24
 
24
25
  type PostMessageFn = (message: SubagentWorkerResponse) => void;
@@ -53,11 +54,33 @@ const isAgentEvent = (event: AgentSessionEvent): event is AgentEvent => {
53
54
  return agentEventTypes.has(event.type as AgentEvent["type"]);
54
55
  };
55
56
 
56
- let running = false;
57
- let abortRequested = false;
58
- let doneSent = false;
59
- let activeSession: { abort: () => Promise<void>; dispose: () => Promise<void> } | null = null;
60
- let unsubscribe: (() => void) | null = null;
57
+ interface RunState {
58
+ abortController: AbortController;
59
+ startTime: number;
60
+ session: { abort: () => Promise<void>; dispose: () => Promise<void> } | null;
61
+ unsubscribe: (() => void) | null;
62
+ sendDoneOnce: (message: Extract<SubagentWorkerResponse, { type: "done" }>) => void;
63
+ }
64
+
65
+ const createSendDoneOnce = (): RunState["sendDoneOnce"] => {
66
+ let sent = false;
67
+ return (message) => {
68
+ if (sent) return;
69
+ sent = true;
70
+ postMessageSafe(message);
71
+ };
72
+ };
73
+
74
+ const createRunState = (): RunState => ({
75
+ abortController: new AbortController(),
76
+ startTime: Date.now(),
77
+ session: null,
78
+ unsubscribe: null,
79
+ sendDoneOnce: createSendDoneOnce(),
80
+ });
81
+
82
+ let activeRun: RunState | null = null;
83
+ let pendingAbort = false;
61
84
 
62
85
  /**
63
86
  * Resolve model string to Model object with optional thinking level.
@@ -98,26 +121,35 @@ function resolveModelOverride(
98
121
  * - OMP_BLOCKED_AGENT: payload.blockedAgent (prevents same-agent recursion)
99
122
  * - OMP_SPAWNS: payload.spawnsEnv (controls nested spawn permissions)
100
123
  */
101
- async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
102
- const startTime = Date.now();
124
+ async function runTask(runState: RunState, payload: SubagentWorkerStartPayload): Promise<void> {
125
+ const { signal } = runState.abortController;
126
+ const startTime = runState.startTime;
103
127
  let exitCode = 0;
104
128
  let error: string | undefined;
105
129
  let aborted = false;
130
+ const sessionAbortController = new AbortController();
106
131
 
107
- try {
108
- // Check for pre-start abort
109
- if (abortRequested) {
132
+ // Helper to check abort status - throws if aborted to exit early
133
+ const checkAbort = (): void => {
134
+ if (signal.aborted) {
110
135
  aborted = true;
111
136
  exitCode = 1;
112
- return;
137
+ throw new Error("Aborted");
113
138
  }
139
+ };
140
+
141
+ try {
142
+ // Check for pre-start abort
143
+ checkAbort();
114
144
 
115
145
  // Set working directory (CLI does this implicitly)
116
146
  process.chdir(payload.cwd);
117
147
 
118
148
  // Discover auth and models (equivalent to CLI's discoverAuthStorage/discoverModels)
119
149
  const authStorage = await discoverAuthStorage();
150
+ checkAbort();
120
151
  const modelRegistry = await discoverModels(authStorage);
152
+ checkAbort();
121
153
 
122
154
  // Resolve model override (equivalent to CLI's parseModelPattern with --model)
123
155
  const { model, thinkingLevel } = resolveModelOverride(payload.model, modelRegistry);
@@ -126,6 +158,7 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
126
158
  const sessionManager = payload.sessionFile
127
159
  ? await SessionManager.open(payload.sessionFile)
128
160
  : SessionManager.inMemory(payload.cwd);
161
+ checkAbort();
129
162
 
130
163
  // Create agent session (equivalent to CLI's createAgentSession)
131
164
  // Note: hasUI: false disables interactive features
@@ -149,18 +182,16 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
149
182
  spawns: payload.spawnsEnv,
150
183
  });
151
184
 
152
- activeSession = session;
185
+ runState.session = session;
186
+ checkAbort();
153
187
 
154
- if (abortRequested) {
155
- aborted = true;
156
- exitCode = 1;
157
- try {
158
- await session.abort();
159
- } catch {
160
- // Ignore abort errors
161
- }
162
- return;
163
- }
188
+ signal.addEventListener(
189
+ "abort",
190
+ () => {
191
+ void session.abort();
192
+ },
193
+ { once: true, signal: sessionAbortController.signal },
194
+ );
164
195
 
165
196
  // Initialize extensions (equivalent to CLI's extension initialization)
166
197
  // Note: Does not support --extension CLI flag or extension CLI flags
@@ -191,7 +222,7 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
191
222
  let completeCalled = false;
192
223
 
193
224
  // Subscribe to events and forward to parent (equivalent to --mode json output)
194
- unsubscribe = session.subscribe((event: AgentSessionEvent) => {
225
+ runState.unsubscribe = session.subscribe((event: AgentSessionEvent) => {
195
226
  if (isAgentEvent(event)) {
196
227
  postMessageSafe({ type: "event", event });
197
228
  // Track when complete tool is called
@@ -206,7 +237,7 @@ async function runTask(payload: SubagentWorkerStartPayload): Promise<void> {
206
237
 
207
238
  // Retry loop if complete was not called
208
239
  let retryCount = 0;
209
- while (!completeCalled && retryCount < MAX_COMPLETE_RETRIES && !abortRequested) {
240
+ while (!completeCalled && retryCount < MAX_COMPLETE_RETRIES && !signal.aborted) {
210
241
  retryCount++;
211
242
  const reminder = `<system-reminder>
212
243
  CRITICAL: You stopped without calling the complete tool. This is reminder ${retryCount} of ${MAX_COMPLETE_RETRIES}.
@@ -231,58 +262,93 @@ Call complete now.`;
231
262
  }
232
263
  } catch (err) {
233
264
  exitCode = 1;
234
- error = err instanceof Error ? err.stack || err.message : String(err);
265
+ // Don't record abort as error - it's handled via the aborted flag
266
+ if (!signal.aborted) {
267
+ error = err instanceof Error ? err.stack || err.message : String(err);
268
+ }
235
269
  } finally {
236
270
  // Handle abort requested during execution
237
- if (abortRequested) {
271
+ if (signal.aborted) {
238
272
  aborted = true;
239
273
  if (exitCode === 0) exitCode = 1;
240
274
  }
241
275
 
242
- if (unsubscribe) {
276
+ sessionAbortController.abort();
277
+
278
+ if (runState.unsubscribe) {
243
279
  try {
244
- unsubscribe();
280
+ runState.unsubscribe();
245
281
  } catch {
246
282
  // Ignore unsubscribe errors
247
283
  }
248
- unsubscribe = null;
284
+ runState.unsubscribe = null;
249
285
  }
250
286
 
251
287
  // Cleanup session with timeout to prevent hanging
252
- if (activeSession) {
253
- const session = activeSession;
254
- activeSession = null;
288
+ if (runState.session) {
289
+ const session = runState.session;
290
+ runState.session = null;
255
291
  try {
256
- await Promise.race([session.dispose(), new Promise<void>((resolve) => setTimeout(resolve, 5000))]);
292
+ await untilAborted(AbortSignal.timeout(5000), () => session.dispose());
257
293
  } catch {
258
294
  // Ignore cleanup errors
259
295
  }
260
296
  }
261
297
 
262
- running = false;
298
+ if (activeRun === runState) {
299
+ activeRun = null;
300
+ }
263
301
 
264
302
  // Send completion message to parent (only once)
265
- if (!doneSent) {
266
- doneSent = true;
267
- postMessageSafe({
268
- type: "done",
269
- exitCode,
270
- durationMs: Date.now() - startTime,
271
- error,
272
- aborted,
273
- });
274
- }
303
+ runState.sendDoneOnce({
304
+ type: "done",
305
+ exitCode,
306
+ durationMs: Date.now() - startTime,
307
+ error,
308
+ aborted,
309
+ });
275
310
  }
276
311
  }
277
312
 
278
313
  /** Handle abort request from parent */
279
314
  function handleAbort(): void {
280
- abortRequested = true;
281
- if (activeSession) {
282
- void activeSession.abort();
315
+ const runState = activeRun;
316
+ if (!runState) {
317
+ pendingAbort = true;
318
+ return;
319
+ }
320
+ runState.abortController.abort();
321
+ if (runState.session) {
322
+ void runState.session.abort();
283
323
  }
284
324
  }
285
325
 
326
+ const reportFatal = (message: string): void => {
327
+ const runState = activeRun;
328
+ if (runState) {
329
+ runState.abortController.abort();
330
+ if (runState.session) {
331
+ void runState.session.abort();
332
+ }
333
+ runState.sendDoneOnce({
334
+ type: "done",
335
+ exitCode: 1,
336
+ durationMs: Date.now() - runState.startTime,
337
+ error: message,
338
+ aborted: false,
339
+ });
340
+ return;
341
+ }
342
+
343
+ postMessageSafe({
344
+ type: "done",
345
+ exitCode: 1,
346
+ durationMs: 0,
347
+ error: message,
348
+ aborted: false,
349
+ });
350
+ };
351
+
286
352
  // Global error handlers to ensure we always send a done message
287
353
  // Using self instead of globalThis for proper worker scope typing
288
354
  declare const self: {
@@ -292,53 +358,17 @@ declare const self: {
292
358
  };
293
359
 
294
360
  self.addEventListener("error", (event) => {
295
- if (!running || doneSent) return;
296
- doneSent = true;
297
- abortRequested = true;
298
- if (activeSession) {
299
- void activeSession.abort();
300
- }
301
- postMessageSafe({
302
- type: "done",
303
- exitCode: 1,
304
- durationMs: 0,
305
- error: `Uncaught error: ${event.message || "Unknown error"}`,
306
- aborted: false,
307
- });
361
+ reportFatal(`Uncaught error: ${event.message || "Unknown error"}`);
308
362
  });
309
363
 
310
364
  self.addEventListener("unhandledrejection", (event) => {
311
- if (!running || doneSent) return;
312
- doneSent = true;
313
- abortRequested = true;
314
- if (activeSession) {
315
- void activeSession.abort();
316
- }
317
365
  const reason = event.reason;
318
366
  const message = reason instanceof Error ? reason.stack || reason.message : String(reason);
319
- postMessageSafe({
320
- type: "done",
321
- exitCode: 1,
322
- durationMs: 0,
323
- error: `Unhandled rejection: ${message}`,
324
- aborted: false,
325
- });
367
+ reportFatal(`Unhandled rejection: ${message}`);
326
368
  });
327
369
 
328
370
  self.addEventListener("messageerror", () => {
329
- if (doneSent) return;
330
- doneSent = true;
331
- abortRequested = true;
332
- if (activeSession) {
333
- void activeSession.abort();
334
- }
335
- postMessageSafe({
336
- type: "done",
337
- exitCode: 1,
338
- durationMs: 0,
339
- error: "Failed to deserialize parent message",
340
- aborted: false,
341
- });
371
+ reportFatal("Failed to deserialize parent message");
342
372
  });
343
373
 
344
374
  // Message handler - receives start/abort commands from parent
@@ -353,8 +383,13 @@ globalThis.addEventListener("message", (event: WorkerMessageEvent<SubagentWorker
353
383
 
354
384
  if (message.type === "start") {
355
385
  // Only allow one task per worker
356
- if (running) return;
357
- running = true;
358
- void runTask(message.payload);
386
+ if (activeRun) return;
387
+ const runState = createRunState();
388
+ if (pendingAbort) {
389
+ pendingAbort = false;
390
+ runState.abortController.abort();
391
+ }
392
+ activeRun = runState;
393
+ void runTask(runState, message.payload);
359
394
  }
360
395
  });