gsd-pi 2.78.1-dev.8a893322c → 2.78.1-dev.a7b6e59b7

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 (79) hide show
  1. package/dist/cli-auto-routing.d.ts +1 -0
  2. package/dist/cli-auto-routing.js +5 -0
  3. package/dist/cli.js +5 -14
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +23 -11
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +55 -21
  7. package/dist/resources/extensions/gsd/auto-prompts.js +6 -0
  8. package/dist/resources/extensions/gsd/auto-worktree.js +15 -0
  9. package/dist/resources/extensions/gsd/auto.js +25 -9
  10. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  11. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  12. package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  13. package/dist/resources/extensions/gsd/worktree-resolver.js +24 -0
  14. package/dist/resources/skills/lint/SKILL.md +4 -0
  15. package/dist/resources/skills/review/SKILL.md +4 -0
  16. package/dist/resources/skills/test/SKILL.md +3 -0
  17. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  18. package/dist/web/standalone/.next/BUILD_ID +1 -1
  19. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  20. package/dist/web/standalone/.next/build-manifest.json +2 -2
  21. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  22. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.html +1 -1
  39. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  46. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  48. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  49. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  50. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  51. package/package.json +1 -1
  52. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +278 -0
  53. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  55. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/agent-session.js +125 -55
  57. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  58. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +319 -0
  59. package/packages/pi-coding-agent/src/core/agent-session.ts +128 -59
  60. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  61. package/src/resources/extensions/gsd/auto/run-unit.ts +23 -11
  62. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +60 -24
  63. package/src/resources/extensions/gsd/auto-prompts.ts +6 -0
  64. package/src/resources/extensions/gsd/auto-worktree.ts +15 -0
  65. package/src/resources/extensions/gsd/auto.ts +23 -6
  66. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  67. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +2 -0
  68. package/src/resources/extensions/gsd/prompts/rewrite-docs.md +2 -0
  69. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -0
  70. package/src/resources/extensions/gsd/tests/stash-pop-gsd-conflict.test.ts +8 -2
  71. package/src/resources/extensions/gsd/tests/stash-queued-context-files.test.ts +12 -6
  72. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +235 -0
  73. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +85 -0
  74. package/src/resources/extensions/gsd/worktree-resolver.ts +24 -0
  75. package/src/resources/skills/lint/SKILL.md +4 -0
  76. package/src/resources/skills/review/SKILL.md +4 -0
  77. package/src/resources/skills/test/SKILL.md +3 -0
  78. /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_buildManifest.js +0 -0
  79. /package/dist/web/standalone/.next/static/{QK8fABiGPmonfTgboN0Y9 → GlYncvckBGG33CSoJaSnB}/_ssgManifest.js +0 -0
@@ -84,6 +84,12 @@ export class AgentSession {
84
84
  // Extension system
85
85
  this._extensionRunner = undefined;
86
86
  this._turnIndex = 0;
87
+ this._processingAgentEnd = false;
88
+ /** True while newSession()/switchSession() is in progress; signals agent_end
89
+ * post-handlers to bail rather than corrupt new-session state. */
90
+ this._sessionSwitchPending = false;
91
+ this._processingQueuedAgentEnd = false;
92
+ this._sessionTransitionStartedDuringAgentEnd = false;
87
93
  this._baseToolRegistry = new Map();
88
94
  // Tool registry for extension getTools/setTools
89
95
  this._toolRegistry = new Map();
@@ -201,7 +207,24 @@ export class AgentSession {
201
207
  }
202
208
  }
203
209
  // Emit to extensions first
204
- await this._emitExtensionEvent(event);
210
+ let skipAgentEndPostHandlers = false;
211
+ if (event.type === "agent_end") {
212
+ this._processingQueuedAgentEnd = true;
213
+ try {
214
+ await this._emitExtensionEvent(event);
215
+ }
216
+ finally {
217
+ this._processingQueuedAgentEnd = false;
218
+ skipAgentEndPostHandlers = this._sessionTransitionStartedDuringAgentEnd;
219
+ this._sessionTransitionStartedDuringAgentEnd = false;
220
+ }
221
+ if (skipAgentEndPostHandlers) {
222
+ return;
223
+ }
224
+ }
225
+ else {
226
+ await this._emitExtensionEvent(event);
227
+ }
205
228
  // Notify all listeners
206
229
  this._emit(event);
207
230
  // Handle session persistence
@@ -245,6 +268,12 @@ export class AgentSession {
245
268
  }
246
269
  // Check auto-retry and auto-compaction after agent completes
247
270
  if (event.type === "agent_end" && this._lastAssistantMessage) {
271
+ // A session transition started during agent_end handler execution -
272
+ // bail to avoid running retry/compaction against new-session state.
273
+ if (this._sessionSwitchPending) {
274
+ this._lastAssistantMessage = undefined;
275
+ return;
276
+ }
248
277
  const msg = this._lastAssistantMessage;
249
278
  this._lastAssistantMessage = undefined;
250
279
  // Check for retryable errors first (overloaded, rate limit, server errors)
@@ -361,26 +390,33 @@ export class AgentSession {
361
390
  }
362
391
  /** Emit extension events based on agent events */
363
392
  async _emitExtensionEvent(event) {
364
- if (!this._extensionRunner)
393
+ const extensionRunner = this._extensionRunner;
394
+ if (!extensionRunner)
365
395
  return;
366
396
  if (event.type === "agent_start") {
367
397
  this._turnIndex = 0;
368
- await this._extensionRunner.emit({ type: "agent_start" });
398
+ await extensionRunner.emit({ type: "agent_start" });
369
399
  }
370
400
  else if (event.type === "agent_end") {
371
- await this._extensionRunner.emit({ type: "agent_end", messages: event.messages });
372
- // `stop` fires on true quiescence: the agent cleanly completed and is now
373
- // waiting for the user. Use the last assistant message's stopReason to
374
- // distinguish clean completion from error/cancellation.
375
- const last = event.messages[event.messages.length - 1];
376
- const stopReason = last?.role === "assistant"
377
- ? last.stopReason === "aborted"
378
- ? "cancelled"
379
- : last.stopReason === "error"
380
- ? "error"
381
- : "completed"
382
- : "completed";
383
- await this._extensionRunner.emitStop({ reason: stopReason, lastMessage: last });
401
+ this._processingAgentEnd = true;
402
+ try {
403
+ await extensionRunner.emit({ type: "agent_end", messages: event.messages });
404
+ // `stop` fires on true quiescence: the agent cleanly completed and is now
405
+ // waiting for the user. Use the last assistant message's stopReason to
406
+ // distinguish clean completion from error/cancellation.
407
+ const last = event.messages[event.messages.length - 1];
408
+ const stopReason = last?.role === "assistant"
409
+ ? last.stopReason === "aborted"
410
+ ? "cancelled"
411
+ : last.stopReason === "error"
412
+ ? "error"
413
+ : "completed"
414
+ : "completed";
415
+ await extensionRunner.emitStop({ reason: stopReason, lastMessage: last });
416
+ }
417
+ finally {
418
+ this._processingAgentEnd = false;
419
+ }
384
420
  }
385
421
  else if (event.type === "turn_start") {
386
422
  const extensionEvent = {
@@ -388,7 +424,7 @@ export class AgentSession {
388
424
  turnIndex: this._turnIndex,
389
425
  timestamp: Date.now(),
390
426
  };
391
- await this._extensionRunner.emit(extensionEvent);
427
+ await extensionRunner.emit(extensionEvent);
392
428
  }
393
429
  else if (event.type === "turn_end") {
394
430
  const extensionEvent = {
@@ -397,7 +433,7 @@ export class AgentSession {
397
433
  message: event.message,
398
434
  toolResults: event.toolResults,
399
435
  };
400
- await this._extensionRunner.emit(extensionEvent);
436
+ await extensionRunner.emit(extensionEvent);
401
437
  this._turnIndex++;
402
438
  }
403
439
  else if (event.type === "message_start") {
@@ -405,7 +441,7 @@ export class AgentSession {
405
441
  type: "message_start",
406
442
  message: event.message,
407
443
  };
408
- await this._extensionRunner.emit(extensionEvent);
444
+ await extensionRunner.emit(extensionEvent);
409
445
  }
410
446
  else if (event.type === "message_update") {
411
447
  const extensionEvent = {
@@ -413,14 +449,14 @@ export class AgentSession {
413
449
  message: event.message,
414
450
  assistantMessageEvent: event.assistantMessageEvent,
415
451
  };
416
- await this._extensionRunner.emit(extensionEvent);
452
+ await extensionRunner.emit(extensionEvent);
417
453
  }
418
454
  else if (event.type === "message_end") {
419
455
  const extensionEvent = {
420
456
  type: "message_end",
421
457
  message: event.message,
422
458
  };
423
- await this._extensionRunner.emit(extensionEvent);
459
+ await extensionRunner.emit(extensionEvent);
424
460
  }
425
461
  else if (event.type === "tool_execution_start") {
426
462
  const extensionEvent = {
@@ -429,7 +465,7 @@ export class AgentSession {
429
465
  toolName: event.toolName,
430
466
  args: event.args,
431
467
  };
432
- await this._extensionRunner.emit(extensionEvent);
468
+ await extensionRunner.emit(extensionEvent);
433
469
  }
434
470
  else if (event.type === "tool_execution_update") {
435
471
  const extensionEvent = {
@@ -439,7 +475,7 @@ export class AgentSession {
439
475
  args: event.args,
440
476
  partialResult: event.partialResult,
441
477
  };
442
- await this._extensionRunner.emit(extensionEvent);
478
+ await extensionRunner.emit(extensionEvent);
443
479
  }
444
480
  else if (event.type === "tool_execution_end") {
445
481
  const extensionEvent = {
@@ -449,7 +485,7 @@ export class AgentSession {
449
485
  result: event.result,
450
486
  isError: event.isError,
451
487
  };
452
- await this._extensionRunner.emit(extensionEvent);
488
+ await extensionRunner.emit(extensionEvent);
453
489
  }
454
490
  }
455
491
  /**
@@ -1175,21 +1211,49 @@ export class AgentSession {
1175
1211
  // between tool execution and response processing. Also fire Stop so
1176
1212
  // Layer 0 hooks see a consistent view of session quiescence.
1177
1213
  if (!this.isStreaming && this._extensionRunner) {
1178
- const messages = this.agent.state.messages;
1179
- await this._extensionRunner.emit({
1180
- type: "agent_end",
1181
- messages,
1182
- });
1183
- const last = messages[messages.length - 1];
1184
- const stopReason = last?.role === "assistant"
1185
- ? last.stopReason === "aborted"
1186
- ? "cancelled"
1187
- : last.stopReason === "error"
1188
- ? "error"
1189
- : "completed"
1190
- : "cancelled";
1191
- await this._extensionRunner.emitStop({ reason: stopReason, lastMessage: last });
1214
+ const wasProcessingAgentEnd = this._processingAgentEnd;
1215
+ this._processingAgentEnd = true;
1216
+ try {
1217
+ const messages = this.agent.state.messages;
1218
+ await this._extensionRunner.emit({
1219
+ type: "agent_end",
1220
+ messages,
1221
+ });
1222
+ const last = messages[messages.length - 1];
1223
+ const stopReason = last?.role === "assistant"
1224
+ ? last.stopReason === "aborted"
1225
+ ? "cancelled"
1226
+ : last.stopReason === "error"
1227
+ ? "error"
1228
+ : "completed"
1229
+ : "cancelled";
1230
+ await this._extensionRunner.emitStop({ reason: stopReason, lastMessage: last });
1231
+ }
1232
+ finally {
1233
+ this._processingAgentEnd = wasProcessingAgentEnd;
1234
+ }
1235
+ }
1236
+ }
1237
+ async _settleCurrentTurnForSessionTransition() {
1238
+ if (this._processingAgentEnd) {
1239
+ // Wait for the agent to fully settle. When called from inside an
1240
+ // agent_end extension handler, the agent may already be idle - but
1241
+ // _processAgentEvent still has retry/compaction tail work to run after
1242
+ // _emitExtensionEvent returns. waitForIdle() is effectively a no-op when
1243
+ // already idle, so awaiting it unconditionally is safe and ensures we
1244
+ // don't proceed into the session reset while that tail is still on the stack.
1245
+ await this.agent.waitForIdle();
1246
+ if (this._processingQueuedAgentEnd) {
1247
+ this._sessionTransitionStartedDuringAgentEnd = true;
1248
+ this._lastAssistantMessage = undefined;
1249
+ }
1250
+ return;
1192
1251
  }
1252
+ // #4243: Normal session transitions must abort before disconnecting so
1253
+ // message_end/agent_end events fire while listeners are still connected.
1254
+ // During agent_end handling the turn is already ending; aborting there can
1255
+ // convert a successful auto-mode handoff into an aborted provider message.
1256
+ await this.abort();
1193
1257
  }
1194
1258
  /**
1195
1259
  * Start a new session, optionally with initial messages and parent tracking.
@@ -1211,19 +1275,22 @@ export class AgentSession {
1211
1275
  return false;
1212
1276
  }
1213
1277
  }
1214
- // #4243: Must call abort() BEFORE _disconnectFromAgent() so that
1215
- // message_end/agent_end events fire and the #4216 finalization code
1216
- // can run before we unsubscribe from the event bus.
1217
- await this.abort();
1218
- // #3731: If the caller aborted (e.g. runUnit() timed out and restored cwd to
1219
- // project root), discard this session before capturing process.cwd() and
1220
- // rebuilding the tool runtime. Without this check, the late newSession()
1221
- // would rebuild tools with root cwd, breaking worktree isolation.
1222
- if (options?.abortSignal?.aborted) {
1223
- return false;
1278
+ this._sessionSwitchPending = true;
1279
+ try {
1280
+ await this._settleCurrentTurnForSessionTransition();
1281
+ // #3731: If the caller aborted (e.g. runUnit() timed out and restored cwd to
1282
+ // project root), discard this session before capturing process.cwd() and
1283
+ // rebuilding the tool runtime. Without this check, the late newSession()
1284
+ // would rebuild tools with root cwd, breaking worktree isolation.
1285
+ if (options?.abortSignal?.aborted) {
1286
+ return false;
1287
+ }
1288
+ this._disconnectFromAgent();
1289
+ this.agent.reset();
1290
+ }
1291
+ finally {
1292
+ this._sessionSwitchPending = false;
1224
1293
  }
1225
- this._disconnectFromAgent();
1226
- this.agent.reset();
1227
1294
  // Update cwd to current process directory — auto-mode may have chdir'd
1228
1295
  // into a worktree since the original session was created.
1229
1296
  const previousCwd = this._cwd;
@@ -1949,11 +2016,14 @@ export class AgentSession {
1949
2016
  return false;
1950
2017
  }
1951
2018
  }
1952
- // #4243: Must call abort() BEFORE _disconnectFromAgent() so that
1953
- // message_end/agent_end events fire and the #4216 finalization code
1954
- // can run before we unsubscribe from the event bus.
1955
- await this.abort();
1956
- this._disconnectFromAgent();
2019
+ this._sessionSwitchPending = true;
2020
+ try {
2021
+ await this._settleCurrentTurnForSessionTransition();
2022
+ this._disconnectFromAgent();
2023
+ }
2024
+ finally {
2025
+ this._sessionSwitchPending = false;
2026
+ }
1957
2027
  this._steeringMessages = [];
1958
2028
  this._followUpMessages = [];
1959
2029
  this._pendingNextTurnMessages = [];