@code-yeongyu/senpi 2026.10.24-4 → 2026.10.25-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 (84) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/bundle/chunks/{chunk-NQC7MI2G.js → chunk-2EFVQWXZ.js} +3 -3
  3. package/dist/bundle/chunks/{chunk-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-HKUXKTFW.js → chunk-B6TPQDBW.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-G2UETS2R.js} +2 -2
  7. package/dist/bundle/chunks/{chunk-GVJ76VQI.js → chunk-J2U5VDG7.js} +63 -63
  8. package/dist/bundle/chunks/{chunk-42OY5SZZ.js → chunk-JRXKLIEQ.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-3VOG6WFU.js → chunk-KJZAY5YH.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-UT6AJHPK.js → chunk-KUE4AQN6.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-LYGAV6KU.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-MXZPAUFB.js} +1 -1
  13. package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
  14. package/dist/bundle/chunks/{chunk-II22E3CV.js → chunk-OI4NMCKP.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-RXKNOEQQ.js} +1 -1
  16. package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
  17. package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
  18. package/dist/bundle/chunks/chunk-VNN2V74Y.js +5 -0
  19. package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-W2BKZBYJ.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-WFXUCVIR.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-IUHU5PEC.js → chunk-XBUSVKWC.js} +1 -1
  22. package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-YN7ELQ43.js} +1 -1
  23. package/dist/bundle/chunks/{cli-main-UH3MAREK.js → cli-main-SWKFMTZQ.js} +1 -1
  24. package/dist/bundle/chunks/{host-command-OQR4RDTM.js → host-command-BC6UWP33.js} +1 -1
  25. package/dist/bundle/chunks/{host-lifecycle-U36U7JTE.js → host-lifecycle-TN5NEGNF.js} +1 -1
  26. package/dist/bundle/chunks/host-lifecycle.js +3 -3
  27. package/dist/bundle/chunks/{host-session-command-IGB27ZPJ.js → host-session-command-MGVBLBRV.js} +1 -1
  28. package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-AS2OCRLP.js} +1 -1
  29. package/dist/bundle/chunks/{interactive-mode-R5RCSJ2F.js → interactive-mode-SCSROHD5.js} +1 -1
  30. package/dist/bundle/chunks/{multi-session-host-YZEMYUFY.js → multi-session-host-VTTY5MFT.js} +1 -1
  31. package/dist/bundle/chunks/{package-manager-cli-5FOXDKUA.js → package-manager-cli-DZFOC3MF.js} +1 -1
  32. package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
  33. package/dist/bundle/chunks/{rpc-mode-6K62CZBW.js → rpc-mode-TOXYD4XA.js} +1 -1
  34. package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-B5NQ4ZVH.js} +1 -1
  35. package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
  36. package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-SLISTG4V.js} +1 -1
  37. package/dist/bundle/chunks/{session-control-endpoint-54TMCS7Q.js → session-control-endpoint-HFFMBALG.js} +1 -1
  38. package/dist/bundle/chunks/{session-picker-P2VUU2MQ.js → session-picker-465FVXQG.js} +1 -1
  39. package/dist/bundle/chunks/session-worker.js +69 -69
  40. package/dist/bundle/cli.js +2 -2
  41. package/dist/bundle/host-supervisor.js +3 -3
  42. package/dist/bundle/index.js +1 -1
  43. package/dist/bundle/rpc-entry.js +1 -1
  44. package/dist/bundle/runtime-manifest.json +1 -1
  45. package/dist/core/agent-session.d.ts +1 -0
  46. package/dist/core/agent-session.js +28 -9
  47. package/dist/core/engine-turn-limit.d.ts +2 -0
  48. package/dist/core/engine-turn-limit.js +7 -1
  49. package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
  50. package/dist/core/extensions/builtin/anthropic-subscription/session-binding.d.ts +2 -0
  51. package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.d.ts +6 -1
  52. package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.js +18 -7
  53. package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.d.ts +23 -0
  54. package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.js +107 -0
  55. package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.d.ts +1 -1
  56. package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.js +3 -1
  57. package/dist/core/extensions/builtin/anthropic-subscription/session-observability.d.ts +3 -0
  58. package/dist/core/extensions/builtin/anthropic-subscription/session-observability.js +4 -0
  59. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +12 -7
  60. package/dist/core/extensions/builtin/anthropic-subscription/session-registry.d.ts +3 -1
  61. package/dist/core/extensions/builtin/anthropic-subscription/session-registry.js +3 -2
  62. package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +2 -0
  63. package/dist/core/extensions/builtin/goal/stop-pause.js +2 -1
  64. package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
  65. package/dist/core/lifecycle-runtime-facts.js +3 -0
  66. package/dist/core/lifecycle-session-binding.js +6 -2
  67. package/dist/core/run-resume-turn.d.ts +1 -0
  68. package/dist/core/run-resume-turn.js +4 -3
  69. package/dist/core/skill-access.js +15 -3
  70. package/dist/core/skill-discovery.d.ts +2 -0
  71. package/dist/core/skill-discovery.js +1 -1
  72. package/dist/core/stop-resume-directive.d.ts +1 -1
  73. package/dist/core/stop-resume-directive.js +10 -1
  74. package/dist/modes/interactive/interactive-mode.js +1 -0
  75. package/dist/modes/interactive/pause-footer.d.ts +5 -0
  76. package/dist/modes/interactive/pause-footer.js +2 -0
  77. package/dist/modes/rpc/host-reservations.js +3 -2
  78. package/dist/modes/rpc/host-stalled-evidence.js +2 -1
  79. package/dist/modes/rpc/tool-child/group-ownership.js +4 -2
  80. package/dist/utils/process-identity.d.ts +7 -0
  81. package/dist/utils/process-identity.js +32 -5
  82. package/package.json +6 -6
  83. package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
  84. package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
3
- import"./chunks/chunk-PXKF2RHD.js";import{main}from"./chunks/chunk-GVJ76VQI.js";import"./chunks/chunk-SGDOZT6B.js";import"./chunks/chunk-VOBD6FQG.js";import{configureHttpDispatcher}from"./chunks/chunk-NQC7MI2G.js";import"./chunks/chunk-DXJJP6RP.js";import"./chunks/chunk-GXY5JEI5.js";import"./chunks/chunk-42OY5SZZ.js";import"./chunks/chunk-GY5MR5KR.js";import"./chunks/chunk-EHQXLHRB.js";import"./chunks/chunk-XF3LJC3O.js";import"./chunks/chunk-CNPDUC4D.js";import"./chunks/chunk-CDRFFHLL.js";import"./chunks/chunk-7UUOGZKR.js";import"./chunks/chunk-Y5OTHMXO.js";import"./chunks/chunk-MLVIYDHK.js";import"./chunks/chunk-TIVW4P37.js";import"./chunks/chunk-H4QOEVS5.js";import"./chunks/chunk-3LNSLJ7N.js";import"./chunks/chunk-MSQCTH6V.js";import"./chunks/chunk-KLAX467B.js";import"./chunks/chunk-JFFS72FL.js";import"./chunks/chunk-ORXZXUMC.js";import"./chunks/chunk-GZVCRV7I.js";import"./chunks/chunk-SOMFLLPD.js";import"./chunks/chunk-MTQQ2VQH.js";import"./chunks/chunk-3VOG6WFU.js";import"./chunks/chunk-BMYNMUVZ.js";import"./chunks/chunk-QMRJYMNH.js";import"./chunks/chunk-FWXMMMCV.js";import"./chunks/chunk-4AOMNOGY.js";import"./chunks/chunk-KUPQO7MC.js";import"./chunks/chunk-U67ML6CQ.js";import"./chunks/chunk-26UBFF6N.js";import"./chunks/chunk-EWOEVVQT.js";import"./chunks/chunk-II22E3CV.js";import"./chunks/chunk-7IRLFYNM.js";import"./chunks/chunk-XUFUKDKJ.js";import"./chunks/chunk-RHHBXIRN.js";import"./chunks/chunk-U3VOXCGP.js";import"./chunks/chunk-RVYSJBZ7.js";import"./chunks/chunk-HB35RBP4.js";import"./chunks/chunk-Z6WGAK7W.js";import"./chunks/chunk-DLAY7CIU.js";import"./chunks/chunk-3BTN337S.js";import"./chunks/chunk-QXREUA5F.js";import"./chunks/chunk-RUGYFYH7.js";import"./chunks/chunk-OEFCY46F.js";import"./chunks/chunk-KRNXUTZJ.js";import"./chunks/chunk-TAV6QZFM.js";import"./chunks/chunk-PELNNWGO.js";import"./chunks/chunk-QH2HSCMX.js";import"./chunks/chunk-FDL4CF6F.js";import"./chunks/chunk-BWA4FVFW.js";import"./chunks/chunk-YU4AYH4C.js";import"./chunks/chunk-E6WYH3ZL.js";import"./chunks/chunk-COH5F7QV.js";import"./chunks/chunk-RFB5CBW7.js";import"./chunks/chunk-2VZJTDUX.js";import"./chunks/chunk-E4LBKOZG.js";import"./chunks/chunk-GVSBCJFI.js";import"./chunks/chunk-WASCMT6Y.js";import"./chunks/chunk-T36LJ6MW.js";import"./chunks/chunk-QGJLZ6BI.js";import"./chunks/chunk-WHPDEDCC.js";import"./chunks/chunk-AA2L2RHH.js";import"./chunks/chunk-HK46JMSC.js";import"./chunks/chunk-4P7WARQZ.js";import"./chunks/chunk-WDTJ6JKE.js";import"./chunks/chunk-5ROMKDLK.js";import"./chunks/chunk-3W47LTXQ.js";import"./chunks/chunk-6AOVUTUR.js";import"./chunks/chunk-45SZOLCU.js";import"./chunks/chunk-DMI55M2N.js";import"./chunks/chunk-PJ34QVL5.js";import"./chunks/chunk-BMX2U22F.js";import"./chunks/chunk-YU2P3CAR.js";import"./chunks/chunk-IUHU5PEC.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import"./chunks/chunk-V3CK2AVC.js";import"./chunks/chunk-VRDU425E.js";import"./chunks/chunk-ZGAB7WQA.js";import{APP_NAME,scrubBrandFromEnvironment}from"./chunks/chunk-7YO7VQZW.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-MIENF2YK.js";import"./chunks/chunk-OXNWERO4.js";import"./chunks/chunk-QN2BHP3M.js";import"./chunks/chunk-NTK2MHJ5.js";import"./chunks/chunk-7ASLQ243.js";import"./chunks/chunk-IKOFCODM.js";process.title=`${APP_NAME}-rpc`;scrubBrandFromEnvironment();process.env.PI_CODING_AGENT="true";process.env.AI_AGENT=APP_NAME;process.emitWarning=(()=>{});configureHttpDispatcher();main(["--mode","rpc",...process.argv.slice(2)]);
3
+ import"./chunks/chunk-UFJ2K7Q7.js";import{main}from"./chunks/chunk-J2U5VDG7.js";import"./chunks/chunk-MXZPAUFB.js";import"./chunks/chunk-FHXR4ZAC.js";import{configureHttpDispatcher}from"./chunks/chunk-2EFVQWXZ.js";import"./chunks/chunk-DXJJP6RP.js";import"./chunks/chunk-GXY5JEI5.js";import"./chunks/chunk-JRXKLIEQ.js";import"./chunks/chunk-GY5MR5KR.js";import"./chunks/chunk-EHQXLHRB.js";import"./chunks/chunk-XF3LJC3O.js";import"./chunks/chunk-CNPDUC4D.js";import"./chunks/chunk-CDRFFHLL.js";import"./chunks/chunk-RXKNOEQQ.js";import"./chunks/chunk-Y5OTHMXO.js";import"./chunks/chunk-MLVIYDHK.js";import"./chunks/chunk-TIVW4P37.js";import"./chunks/chunk-H4QOEVS5.js";import"./chunks/chunk-3LNSLJ7N.js";import"./chunks/chunk-MSQCTH6V.js";import"./chunks/chunk-KLAX467B.js";import"./chunks/chunk-JFFS72FL.js";import"./chunks/chunk-ORXZXUMC.js";import"./chunks/chunk-GZVCRV7I.js";import"./chunks/chunk-SOMFLLPD.js";import"./chunks/chunk-MTQQ2VQH.js";import"./chunks/chunk-KJZAY5YH.js";import"./chunks/chunk-W2BKZBYJ.js";import"./chunks/chunk-QMRJYMNH.js";import"./chunks/chunk-LYGAV6KU.js";import"./chunks/chunk-4AOMNOGY.js";import"./chunks/chunk-KUPQO7MC.js";import"./chunks/chunk-U67ML6CQ.js";import"./chunks/chunk-26UBFF6N.js";import"./chunks/chunk-YN7ELQ43.js";import"./chunks/chunk-OI4NMCKP.js";import"./chunks/chunk-G2UETS2R.js";import"./chunks/chunk-VNN2V74Y.js";import"./chunks/chunk-4WZRVZNX.js";import"./chunks/chunk-U3VOXCGP.js";import"./chunks/chunk-WFXUCVIR.js";import"./chunks/chunk-HB35RBP4.js";import"./chunks/chunk-Z6WGAK7W.js";import"./chunks/chunk-DLAY7CIU.js";import"./chunks/chunk-3BTN337S.js";import"./chunks/chunk-QXREUA5F.js";import"./chunks/chunk-RUGYFYH7.js";import"./chunks/chunk-OEFCY46F.js";import"./chunks/chunk-KRNXUTZJ.js";import"./chunks/chunk-TAV6QZFM.js";import"./chunks/chunk-PELNNWGO.js";import"./chunks/chunk-TTU2RNVX.js";import"./chunks/chunk-FDL4CF6F.js";import"./chunks/chunk-BWA4FVFW.js";import"./chunks/chunk-YU4AYH4C.js";import"./chunks/chunk-E6WYH3ZL.js";import"./chunks/chunk-COH5F7QV.js";import"./chunks/chunk-RFB5CBW7.js";import"./chunks/chunk-2VZJTDUX.js";import"./chunks/chunk-E4LBKOZG.js";import"./chunks/chunk-GVSBCJFI.js";import"./chunks/chunk-WASCMT6Y.js";import"./chunks/chunk-T36LJ6MW.js";import"./chunks/chunk-QGJLZ6BI.js";import"./chunks/chunk-WHPDEDCC.js";import"./chunks/chunk-AA2L2RHH.js";import"./chunks/chunk-HK46JMSC.js";import"./chunks/chunk-4P7WARQZ.js";import"./chunks/chunk-WDTJ6JKE.js";import"./chunks/chunk-5ROMKDLK.js";import"./chunks/chunk-3W47LTXQ.js";import"./chunks/chunk-6AOVUTUR.js";import"./chunks/chunk-45SZOLCU.js";import"./chunks/chunk-DMI55M2N.js";import"./chunks/chunk-PJ34QVL5.js";import"./chunks/chunk-BMX2U22F.js";import"./chunks/chunk-YU2P3CAR.js";import"./chunks/chunk-XBUSVKWC.js";import"./chunks/chunk-ZOFZRJSL.js";import"./chunks/chunk-2SOVDETB.js";import"./chunks/chunk-V3CK2AVC.js";import"./chunks/chunk-VRDU425E.js";import"./chunks/chunk-ZGAB7WQA.js";import{APP_NAME,scrubBrandFromEnvironment}from"./chunks/chunk-7YO7VQZW.js";import"./chunks/chunk-ATQFN5HI.js";import"./chunks/chunk-MIENF2YK.js";import"./chunks/chunk-OXNWERO4.js";import"./chunks/chunk-QN2BHP3M.js";import"./chunks/chunk-NTK2MHJ5.js";import"./chunks/chunk-O5RYI3IW.js";import"./chunks/chunk-IKOFCODM.js";process.title=`${APP_NAME}-rpc`;scrubBrandFromEnvironment();process.env.PI_CODING_AGENT="true";process.env.AI_AGENT=APP_NAME;process.emitWarning=(()=>{});configureHttpDispatcher();main(["--mode","rpc",...process.argv.slice(2)]);
@@ -1 +1 @@
1
- {"buildId":"3f292cb226833e18","externals":["@earendil-works/chord","@earendil-works/pi-pty","@silvia-odwyer/photon-node","bufferutil","canvas","kerberos","supports-color","utf-8-validate"]}
1
+ {"buildId":"256057df5dd08be8","externals":["@earendil-works/chord","@earendil-works/pi-pty","@silvia-odwyer/photon-node","bufferutil","canvas","kerberos","supports-color","utf-8-validate"]}
@@ -1708,6 +1708,7 @@ export declare class AgentSession {
1708
1708
  noteAutomaticInput(text: string): boolean;
1709
1709
  private _stopAddedSince;
1710
1710
  private _stopHoldLive;
1711
+ private _resumeCause;
1711
1712
  get pauseView(): {
1712
1713
  pauseSeq: number;
1713
1714
  records: {
@@ -56,7 +56,7 @@ import { assertExpectedUserLeaf, buildEditedUserMessage, UserEditError, userText
56
56
  import { ENGINE_PAUSED_ENTRY_TYPE, enginePauseSinceLastTurn, settledRepetitiveTurnPause } from "./engine-paused.js";
57
57
  import { EngineTurnHold, engineTurnHoldHistory, engineWakeSource } from "./engine-turn-hold.js";
58
58
  import { EngineTurnInput } from "./engine-turn-input.js";
59
- import { ENGINE_TURN_LIMIT_ENTRY_TYPE, ENGINE_TURN_LIMIT_EVENT, ENGINE_TURN_START_ENTRY_TYPE, engineTurnHistory, engineTurnLimitNotice, engineTurnStop, isUserDirectedTurn, } from "./engine-turn-limit.js";
59
+ import { ENGINE_TURN_LIMIT_ENTRY_TYPE, ENGINE_TURN_LIMIT_EVENT, ENGINE_TURN_START_ENTRY_TYPE, engineTurnHistory, engineTurnLimitNotice, engineTurnStop, isUserDirectedTurn, resetsEngineTurnHistory, } from "./engine-turn-limit.js";
60
60
  import { EngineTurnRelease } from "./engine-turn-release.js";
61
61
  import { environmentContextMessageIfChanged, resolveEnvironmentContext, } from "./environment-context.js";
62
62
  import { areExperimentalFeaturesEnabled } from "./experimental.js";
@@ -5771,7 +5771,11 @@ export class AgentSession {
5771
5771
  });
5772
5772
  const deliveredNotices = this._engineTurnHold.noticeCount;
5773
5773
  const deliveredHeldInputs = this._heldInputs.pending().length;
5774
- if (this._stopHoldLive() || this._engineTurnHold.fences.length > 0 || deliveredHeldInputs > 0) {
5774
+ // Payload-free notices (including goals) revalidate through their producer after release.
5775
+ // An interruption must not add a directive-only turn when that work has already completed.
5776
+ const resumeHeldTurn = this._stopHoldLive() &&
5777
+ (this._resumeCause() === "stop" || deliveredNotices === 0 || this._engineTurnHold.messages().length > 0);
5778
+ if (resumeHeldTurn || this._engineTurnHold.fences.length > 0 || deliveredHeldInputs > 0) {
5775
5779
  const { turnStarted, settled } = await this._resumeAfterStop(onQueued);
5776
5780
  return {
5777
5781
  answer: { resumed: true, pauseSeq, turnStarted, deliveredNotices, deliveredHeldInputs },
@@ -5810,8 +5814,9 @@ export class AgentSession {
5810
5814
  * resume settlement delivered the turn.
5811
5815
  */
5812
5816
  async _resumeAfterStop(onQueued) {
5817
+ const resumeCause = this._resumeCause();
5813
5818
  const sources = this._engineTurnHold.releaseAll("resume_session");
5814
- const { turnId, delivered } = await this._startResumeTurn(onQueued);
5819
+ const { turnId, delivered } = await this._startResumeTurn(onQueued, resumeCause);
5815
5820
  const settled = (async () => {
5816
5821
  if (!(await delivered)) {
5817
5822
  this._engineTurnHold.rejectInput();
@@ -5833,7 +5838,7 @@ export class AgentSession {
5833
5838
  return this._resumeSettlement;
5834
5839
  }
5835
5840
  /** The resume turn both resume paths start; held input and notices are consumed only if no newer Stop aborted it. */
5836
- async _startResumeTurn(onQueued) {
5841
+ async _startResumeTurn(onQueued, resumeCause = "stop") {
5837
5842
  const settlement = Promise.withResolvers();
5838
5843
  this._resumeSettlement = settlement.promise;
5839
5844
  try {
@@ -5864,7 +5869,7 @@ export class AgentSession {
5864
5869
  }));
5865
5870
  // The hold is already released: include new or re-held input before sending, and omit withdrawals.
5866
5871
  }
5867
- const started = await this._startResumeTurnWith(generation, continues, heldInputs, onQueued);
5872
+ const started = await this._startResumeTurnWith(generation, continues, heldInputs, onQueued, resumeCause);
5868
5873
  started.delivered.then(() => settlement.resolve(), () => settlement.resolve());
5869
5874
  return started;
5870
5875
  }
@@ -5873,8 +5878,9 @@ export class AgentSession {
5873
5878
  throw error;
5874
5879
  }
5875
5880
  }
5876
- _startResumeTurnWith(generation, continues, heldInputs, onQueued) {
5881
+ _startResumeTurnWith(generation, continues, heldInputs, onQueued, resumeCause = "stop") {
5877
5882
  return startRunResumeTurn({
5883
+ resumeCause,
5878
5884
  heldInputs: () => heldInputs,
5879
5885
  continueInputs: () => [...continues],
5880
5886
  heldWakes: () => this._engineTurnHold.messages(),
@@ -5906,7 +5912,7 @@ export class AgentSession {
5906
5912
  return;
5907
5913
  this._releaseExtrasGeneration = this._userAbortGeneration;
5908
5914
  if (this._stopHoldLive())
5909
- this._releaseExtras.push({ role: "custom", ...stopResumeDirective(), timestamp: Date.now() });
5915
+ this._releaseExtras.push({ role: "custom", ...stopResumeDirective(this._resumeCause()), timestamp: Date.now() });
5910
5916
  const { messages, commands } = this._takeHeldInputs();
5911
5917
  this._releaseExtras.push(...messages);
5912
5918
  // A later capture that finds no commands keeps the run an earlier one (a question answer's) still owes.
@@ -6019,7 +6025,7 @@ export class AgentSession {
6019
6025
  });
6020
6026
  }
6021
6027
  noteAutomaticInput(text) {
6022
- const userHold = this._engineTurnHold.holds.some((hold) => hold.kind === "session_hold" || hold.kind === "inherited_hold");
6028
+ const userHold = this._engineTurnHold.holds.some((hold) => (hold.kind === "session_hold" && hold.key !== "interrupted-run") || hold.kind === "inherited_hold");
6023
6029
  if (!userHold)
6024
6030
  return false;
6025
6031
  const message = {
@@ -6039,6 +6045,13 @@ export class AgentSession {
6039
6045
  _stopHoldLive() {
6040
6046
  return this._engineTurnHold.holds.some((hold) => hold.kind === "session_hold");
6041
6047
  }
6048
+ _resumeCause() {
6049
+ const holds = this._engineTurnHold.holds;
6050
+ return holds.some((hold) => hold.kind === "session_hold" && hold.key === "interrupted-run") &&
6051
+ !holds.some((hold) => hold.kind === "session_hold" && hold.key !== "interrupted-run")
6052
+ ? "interrupted"
6053
+ : "stop";
6054
+ }
6042
6055
  get pauseView() {
6043
6056
  return {
6044
6057
  pauseSeq: this._engineTurnHold.currentPauseSeq,
@@ -6150,7 +6163,8 @@ export class AgentSession {
6150
6163
  }
6151
6164
  /** Keeps this process's engine-turn record: a user message clears it, a tool call marks the running engine turn. */
6152
6165
  _observeEngineTurnMessage(message) {
6153
- if (message.role === "user" || (message.role === "custom" && isUserDirectedTurn(message.customType))) {
6166
+ if (message.role === "user" ||
6167
+ (message.role === "custom" && resetsEngineTurnHistory(message.customType, message.details))) {
6154
6168
  this._engineTurnStarts = [];
6155
6169
  this._engineResumeWindow = false;
6156
6170
  this._engineTurnPauseRecorded = false;
@@ -6615,6 +6629,7 @@ export class AgentSession {
6615
6629
  /** A user Stop: hold automatic turns until human input or resume_session, then stop the running turn (senpi#3811). */
6616
6630
  async stop() {
6617
6631
  this._runHolds.stamp();
6632
+ this.refreshLifecycle();
6618
6633
  this._stopInterrupt = { pauseSeq: this._engineTurnHold.currentPauseSeq, at: new Date().toISOString() };
6619
6634
  try {
6620
6635
  await this.abort();
@@ -9211,6 +9226,10 @@ export class AgentSession {
9211
9226
  this._extensionErrorListener = bindings.onError;
9212
9227
  }
9213
9228
  this._applyExtensionBindings(this._extensionRunner);
9229
+ // Opening a session is not explicit Resume. Install the same hold used by Stop before
9230
+ // session_start producers run, so automatic work waits quietly in notice custody.
9231
+ if (this._lifecycleBinding.controller.requiresHumanContinuation)
9232
+ this._engineTurnHold.addHold("session_hold", { key: "interrupted-run" });
9214
9233
  this._engineTurnHold.start();
9215
9234
  this.syncPromptCacheSafeWaitEnv();
9216
9235
  await this._extensionRunner.emit({
@@ -27,6 +27,8 @@ export interface EngineTurnStop {
27
27
  export declare function engineTurnLimitNotice(stop: EngineTurnStop): string;
28
28
  /** A `.` manual continue or a continue-from-leaf is the user's own request, not an automatic turn. */
29
29
  export declare function isUserDirectedTurn(customType: string): boolean;
30
+ /** Recovering an interrupted run is not a new message that resets the unattended turn budget. */
31
+ export declare function resetsEngineTurnHistory(customType: string, details?: unknown): boolean;
30
32
  /** The session's engine turns since the user's last message, in order: when each started and whether its reply called a tool. */
31
33
  export interface EngineTurnHistory {
32
34
  readonly starts: EngineTurnStartRecord[];
@@ -23,9 +23,15 @@ export function isUserDirectedTurn(customType) {
23
23
  customType === CONTINUE_FROM_LEAF_CUSTOM_TYPE ||
24
24
  customType === STOP_RESUME_CUSTOM_TYPE);
25
25
  }
26
+ /** Recovering an interrupted run is not a new message that resets the unattended turn budget. */
27
+ export function resetsEngineTurnHistory(customType, details) {
28
+ return (isUserDirectedTurn(customType) &&
29
+ !(customType === STOP_RESUME_CUSTOM_TYPE &&
30
+ details?.resumeCause === "interrupted"));
31
+ }
26
32
  function isUserMessage(entry) {
27
33
  if (entry.type === "custom_message")
28
- return isUserDirectedTurn(entry.customType);
34
+ return resetsEngineTurnHistory(entry.customType, entry.details);
29
35
  return entry.type === "message" && entry.message.role === "user";
30
36
  }
31
37
  function isEngineTurnStart(entry) {
@@ -24,6 +24,11 @@ async function* closingQueryMessages(query) {
24
24
  }
25
25
  }
26
26
  export async function createAttemptMessages(input, auth) {
27
+ // Claude Code loads auto memory independently of settingSources. Keep its private
28
+ // notes out of every subscription session, after all auth and caller overrides.
29
+ auth.options.env = { ...auth.options.env, CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
30
+ if (typeof auth.options.settings !== "string")
31
+ auth.options.settings = { ...auth.options.settings, autoMemoryEnabled: false };
27
32
  if (input.createAttempt)
28
33
  return recordingLimitWindows(retainedAttemptMessages(await input.createAttempt(auth)), auth);
29
34
  const query = input.query({ prompt: input.prompt, options: auth.options });
@@ -9,6 +9,8 @@ export type BindingInvalidation = {
9
9
  readonly schemaVersion: 1;
10
10
  readonly invalidated: true;
11
11
  readonly reason: string;
12
+ /** senpi#1975: first diverged field path, only on assistant_rewritten records. Paths only, never values. */
13
+ readonly divergedPath?: string;
12
14
  };
13
15
  type BranchEntry = {
14
16
  readonly id?: string;
@@ -1,5 +1,10 @@
1
1
  import type { AssistantMessage } from "@earendil-works/pi-ai";
2
2
  export type AssistantCommitOutcome = "clean" | "rewritten" | "not-resident";
3
+ export type AssistantCommitResult = {
4
+ outcome: AssistantCommitOutcome;
5
+ /** senpi#1975: first diverged field path on a rewrite. Paths only, never values. */
6
+ divergedPath?: string;
7
+ };
3
8
  export declare function assistantContentHash(message: AssistantMessage): string;
4
9
  export declare function isResidentAssistant(message: AssistantMessage, modelId: string): boolean;
5
10
  export declare function isTerminalFailure(message: AssistantMessage): boolean;
@@ -12,7 +17,7 @@ export declare function isTerminalFailure(message: AssistantMessage): boolean;
12
17
  export declare class AssistantCommitBoundary {
13
18
  private readonly providerFinalByKey;
14
19
  captureProviderFinal(key: string, message: AssistantMessage): void;
15
- commit(key: string, message: AssistantMessage, modelId: string): AssistantCommitOutcome;
20
+ commit(key: string, message: AssistantMessage, modelId: string): AssistantCommitResult;
16
21
  forget(key: string): void;
17
22
  }
18
23
  //# sourceMappingURL=session-commit-boundary.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.js";
2
2
  import { CLAUDE_SDK_OAUTH_API_ID } from "./api-id.js";
3
+ import { firstDivergedPath, snapshotProjection, } from "./session-commit-divergence.js";
3
4
  import { sessionSyncDigest } from "./session-sync.js";
4
5
  /**
5
6
  * Only the payload the model produced is fingerprinted. Everything the stream
@@ -21,14 +22,17 @@ function semanticContentBlock(block) {
21
22
  return block;
22
23
  }
23
24
  }
24
- export function assistantContentHash(message) {
25
- return sessionSyncDigest({
25
+ function semanticProjection(message) {
26
+ return {
26
27
  role: message.role,
27
28
  api: message.api,
28
29
  provider: message.provider,
29
30
  model: message.model,
30
31
  content: message.content.map(semanticContentBlock),
31
- });
32
+ };
33
+ }
34
+ export function assistantContentHash(message) {
35
+ return sessionSyncDigest(semanticProjection(message));
32
36
  }
33
37
  export function isResidentAssistant(message, modelId) {
34
38
  return (message.api === CLAUDE_SDK_OAUTH_API_ID &&
@@ -49,16 +53,23 @@ export class AssistantCommitBoundary {
49
53
  this.providerFinalByKey = new Map();
50
54
  }
51
55
  captureProviderFinal(key, message) {
52
- this.providerFinalByKey.set(key, assistantContentHash(message));
56
+ const projection = semanticProjection(message);
57
+ this.providerFinalByKey.set(key, {
58
+ hash: sessionSyncDigest(projection),
59
+ projection: snapshotProjection(projection),
60
+ });
53
61
  }
54
62
  commit(key, message, modelId) {
55
63
  const providerFinal = this.providerFinalByKey.get(key);
56
64
  this.providerFinalByKey.delete(key);
57
65
  if (!isResidentAssistant(message, modelId))
58
- return "not-resident";
66
+ return { outcome: "not-resident" };
59
67
  if (providerFinal === undefined)
60
- return "clean";
61
- return providerFinal === assistantContentHash(message) ? "clean" : "rewritten";
68
+ return { outcome: "clean" };
69
+ const committed = semanticProjection(message);
70
+ if (providerFinal.hash === sessionSyncDigest(committed))
71
+ return { outcome: "clean" };
72
+ return { outcome: "rewritten", divergedPath: firstDivergedPath(providerFinal.projection, committed) };
62
73
  }
63
74
  forget(key) {
64
75
  this.providerFinalByKey.delete(key);
@@ -0,0 +1,23 @@
1
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
2
+ export type AssistantSemanticProjection = {
3
+ role: AssistantMessage["role"];
4
+ api: AssistantMessage["api"];
5
+ provider: AssistantMessage["provider"];
6
+ model: AssistantMessage["model"];
7
+ content: unknown[];
8
+ };
9
+ /**
10
+ * The captured projection must be a snapshot, not a reference: the rewrite
11
+ * class this exists for (senpi#1472, senpi#1975) mutates tool-call arguments in
12
+ * place between `message_update` and `message_end`, and a shared reference
13
+ * would follow the mutation and blind the walk to exactly that case.
14
+ */
15
+ export declare function snapshotProjection(projection: AssistantSemanticProjection): AssistantSemanticProjection;
16
+ /**
17
+ * First diverged path between the provider-final and committed projections:
18
+ * `content.length`, then per block `type`, its semantic fields, and for
19
+ * toolCall blocks `arguments.<path>`, then `api` / `provider` / `model`.
20
+ * Only paths leave this function; no value from either side is embedded.
21
+ */
22
+ export declare function firstDivergedPath(providerFinal: AssistantSemanticProjection, committed: AssistantSemanticProjection): string;
23
+ //# sourceMappingURL=session-commit-divergence.d.ts.map
@@ -0,0 +1,107 @@
1
+ const MAX_DIVERGENCE_DEPTH = 16;
2
+ // The walk starts at `arguments`, one level below the block the snapshot starts at, and still compares one level past
3
+ // its cap; the snapshot copies two levels further so every value the walk can reach is a copy, not a shared reference.
4
+ const MAX_SNAPSHOT_DEPTH = MAX_DIVERGENCE_DEPTH + 2;
5
+ // Argument keys are model-produced data: only a short plain identifier is named in a path. Anything else (long,
6
+ // or containing `.`/`[` that would make the path ambiguous) is named by its sorted position, never by its text.
7
+ const PLAIN_KEY = /^[A-Za-z_$][A-Za-z0-9_$-]{0,63}$/;
8
+ function keySegment(key, position) {
9
+ return PLAIN_KEY.test(key) ? `.${key}` : `.<key#${position}>`;
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ /**
15
+ * The captured projection must be a snapshot, not a reference: the rewrite
16
+ * class this exists for (senpi#1472, senpi#1975) mutates tool-call arguments in
17
+ * place between `message_update` and `message_end`, and a shared reference
18
+ * would follow the mutation and blind the walk to exactly that case.
19
+ */
20
+ export function snapshotProjection(projection) {
21
+ return { ...projection, content: projection.content.map((block) => snapshotValue(block, 0)) };
22
+ }
23
+ function snapshotValue(value, depth) {
24
+ if (depth > MAX_SNAPSHOT_DEPTH)
25
+ return value;
26
+ if (Array.isArray(value))
27
+ return value.map((item) => snapshotValue(item, depth + 1));
28
+ if (!isRecord(value))
29
+ return value;
30
+ return Object.fromEntries(Object.keys(value).map((key) => [key, snapshotValue(value[key], depth + 1)]));
31
+ }
32
+ function fieldOf(block, field) {
33
+ return isRecord(block) ? block[field] : undefined;
34
+ }
35
+ /**
36
+ * First diverged sub-path between two JSON-shaped values: objects contribute
37
+ * `.key` (sorted, so the path is deterministic), arrays contribute `[n]`, and a
38
+ * length difference names the first index present on only one side. Returns ""
39
+ * when the values at this position differ and undefined when they are equal;
40
+ * past the depth cap the parent position is named instead of recursing.
41
+ */
42
+ function firstDivergedSegment(left, right, depth) {
43
+ if (left === right)
44
+ return undefined;
45
+ if (depth > MAX_DIVERGENCE_DEPTH)
46
+ return "";
47
+ if (Array.isArray(left) && Array.isArray(right)) {
48
+ const shared = Math.min(left.length, right.length);
49
+ for (let index = 0; index < shared; index += 1) {
50
+ const inner = firstDivergedSegment(left[index], right[index], depth + 1);
51
+ if (inner !== undefined)
52
+ return `[${index}]${inner}`;
53
+ }
54
+ return left.length === right.length ? undefined : `[${shared}]`;
55
+ }
56
+ if (isRecord(left) && isRecord(right)) {
57
+ const keys = [...new Set([...Object.keys(left), ...Object.keys(right)])].sort();
58
+ for (const [position, key] of keys.entries()) {
59
+ const inner = firstDivergedSegment(left[key], right[key], depth + 1);
60
+ if (inner !== undefined)
61
+ return `${keySegment(key, position)}${inner}`;
62
+ }
63
+ return undefined;
64
+ }
65
+ return "";
66
+ }
67
+ function firstDivergedBlockField(index, left, right) {
68
+ const kind = fieldOf(left, "type");
69
+ if (kind !== fieldOf(right, "type"))
70
+ return `content[${index}].type`;
71
+ const scalarFields = kind === "text"
72
+ ? ["text"]
73
+ : kind === "thinking"
74
+ ? ["thinking", "thinkingSignature"]
75
+ : kind === "toolCall"
76
+ ? ["id", "name"]
77
+ : undefined;
78
+ if (scalarFields === undefined) {
79
+ const segment = firstDivergedSegment(left, right, 0);
80
+ return segment === undefined ? undefined : `content[${index}]${segment}`;
81
+ }
82
+ const field = scalarFields.find((name) => fieldOf(left, name) !== fieldOf(right, name));
83
+ if (field !== undefined)
84
+ return `content[${index}].${field}`;
85
+ if (kind !== "toolCall")
86
+ return undefined;
87
+ const argumentsSegment = firstDivergedSegment(fieldOf(left, "arguments"), fieldOf(right, "arguments"), 0);
88
+ return argumentsSegment === undefined ? undefined : `content[${index}].arguments${argumentsSegment}`;
89
+ }
90
+ /**
91
+ * First diverged path between the provider-final and committed projections:
92
+ * `content.length`, then per block `type`, its semantic fields, and for
93
+ * toolCall blocks `arguments.<path>`, then `api` / `provider` / `model`.
94
+ * Only paths leave this function; no value from either side is embedded.
95
+ */
96
+ export function firstDivergedPath(providerFinal, committed) {
97
+ if (providerFinal.content.length !== committed.content.length)
98
+ return "content.length";
99
+ for (let index = 0; index < providerFinal.content.length; index += 1) {
100
+ const path = firstDivergedBlockField(index, providerFinal.content[index], committed.content[index]);
101
+ if (path !== undefined)
102
+ return path;
103
+ }
104
+ const identityField = ["api", "provider", "model"].find((field) => providerFinal[field] !== committed[field]);
105
+ return identityField ?? "content";
106
+ }
107
+ //# sourceMappingURL=session-commit-divergence.js.map
@@ -4,7 +4,7 @@ export type AnnotatableRegistry = {
4
4
  touch(entry: AnthropicSubscriptionSessionEntry): void;
5
5
  };
6
6
  export declare function switchEntryModel(registry: AnnotatableRegistry, senpiSessionId: string, modelId: string, sdkModel?: string): Promise<boolean>;
7
- export declare function annotatePendingFork(registry: AnnotatableRegistry, senpiSessionId: string, reason: string): void;
7
+ export declare function annotatePendingFork(registry: AnnotatableRegistry, senpiSessionId: string, reason: string, divergedPath?: string): void;
8
8
  export declare function annotateTainted(registry: AnnotatableRegistry, senpiSessionId: string, reason: string): void;
9
9
  export declare function annotateBranchInfo(registry: AnnotatableRegistry, senpiSessionId: string, info: SessionBranchInfo): void;
10
10
  //# sourceMappingURL=session-entry-annotations.d.ts.map
@@ -8,11 +8,13 @@ export async function switchEntryModel(registry, senpiSessionId, modelId, sdkMod
8
8
  registry.touch(entry);
9
9
  return true;
10
10
  }
11
- export function annotatePendingFork(registry, senpiSessionId, reason) {
11
+ export function annotatePendingFork(registry, senpiSessionId, reason, divergedPath) {
12
12
  const entry = registry.get(senpiSessionId);
13
13
  if (!entry)
14
14
  return;
15
15
  entry.pendingForkReason = reason;
16
+ // A fork recorded without a path (compaction, tree change) must not inherit an earlier rewrite's path.
17
+ entry.pendingForkDivergedPath = divergedPath ?? null;
16
18
  registry.touch(entry);
17
19
  }
18
20
  export function annotateTainted(registry, senpiSessionId, reason) {
@@ -27,6 +27,8 @@ export type ContinuityObservation = {
27
27
  seat?: string;
28
28
  /** The attempt runs on a different seat than the lineage's last one: its prompt cache is cold. */
29
29
  seatChanged?: boolean;
30
+ /** senpi#1975: first diverged field path behind an assistant_rewritten decision. Paths only, never values. */
31
+ divergedPath?: string;
30
32
  };
31
33
  export type ContinuityObservabilityBoundary = {
32
34
  emit: (observation: ContinuityObservation) => void;
@@ -62,6 +64,7 @@ export declare function observeSessionSyncDecision(input: {
62
64
  senpiSessionId: string;
63
65
  payloadBytes?: number;
64
66
  collapsedDirectives?: number;
67
+ divergedPath?: string;
65
68
  }): ContinuityObservation;
66
69
  export type StagedContinuityDecision = {
67
70
  observation: ContinuityObservation;
@@ -133,6 +133,7 @@ export function consumePendingCloseCause(senpiSessionId) {
133
133
  * incremental→delta, resume→fork, cold-seed→flatten.
134
134
  */
135
135
  export function observeSessionSyncDecision(input) {
136
+ const diverged = input.divergedPath === undefined ? {} : { divergedPath: input.divergedPath };
136
137
  if (input.kind === "incremental") {
137
138
  return { kind: "delta", reason: "prefix_matched", deltaMessages: input.deltaMessages };
138
139
  }
@@ -142,6 +143,7 @@ export function observeSessionSyncDecision(input) {
142
143
  kind: "fork",
143
144
  reason: retainedCause ?? (input.reason === undefined ? "branch_resume" : sanitizeReason(input.reason)),
144
145
  deltaMessages: input.deltaMessages,
146
+ ...diverged,
145
147
  };
146
148
  }
147
149
  // Peek, not consume: the staged observation is emitted only when the
@@ -155,6 +157,7 @@ export function observeSessionSyncDecision(input) {
155
157
  deltaMessages: input.deltaMessages,
156
158
  ...(input.payloadBytes !== undefined ? { payloadBytes: input.payloadBytes } : {}),
157
159
  ...(input.collapsedDirectives !== undefined ? { collapsedDirectives: input.collapsedDirectives } : {}),
160
+ ...diverged,
158
161
  };
159
162
  }
160
163
  /** Stages an attempt's decision; `emit` fires only when the attempt is retained. */
@@ -181,6 +184,7 @@ export function emitContinuityObservation(observation, sessionId, onDecision) {
181
184
  ...(observation.settingSource === undefined ? {} : { settingSource: observation.settingSource }),
182
185
  ...(observation.seat === undefined ? {} : { seat: observation.seat }),
183
186
  ...(observation.seatChanged === undefined ? {} : { seatChanged: observation.seatChanged }),
187
+ ...(observation.divergedPath === undefined ? {} : { divergedPath: observation.divergedPath }),
184
188
  ...(sessionId === undefined ? {} : { sessionId }),
185
189
  });
186
190
  }
@@ -9,17 +9,22 @@ import { bindingFromEntry, forgetBinding, getBinding, rememberBinding, rememberB
9
9
  import { closeSession, getSession, recordBranchInfo, recordPendingFork, switchSessionModel, } from "./session-registry.js";
10
10
  import { sentHashesForEntry, sentMessageHashes, sentMessages } from "./session-sync.js";
11
11
  const commitBoundary = new AssistantCommitBoundary();
12
- function persistBindingInvalidation(pi, sessionId, reason) {
13
- pi.appendEntry?.(BINDING_ENTRY_TYPE, { schemaVersion: 1, invalidated: true, reason });
12
+ function persistBindingInvalidation(pi, sessionId, reason, divergedPath) {
13
+ pi.appendEntry?.(BINDING_ENTRY_TYPE, {
14
+ schemaVersion: 1,
15
+ invalidated: true,
16
+ reason,
17
+ ...(divergedPath === undefined ? {} : { divergedPath }),
18
+ });
14
19
  rememberBindingInvalidation(sessionId, reason);
15
20
  }
16
- async function invalidateBinding(pi, ctx, reason) {
21
+ async function invalidateBinding(pi, ctx, reason, divergedPath) {
17
22
  const sessionId = ctx.sessionManager.getSessionId();
18
23
  forgetBinding(sessionId);
19
24
  const sessionFile = ctx.sessionManager.getSessionFile?.();
20
25
  if (sessionFile)
21
26
  await deleteStoredBinding(sessionFile);
22
- persistBindingInvalidation(pi, sessionId, reason);
27
+ persistBindingInvalidation(pi, sessionId, reason, divergedPath);
23
28
  }
24
29
  function keepBindingThenClose(sessionId, reason) {
25
30
  const entry = getSession(sessionId);
@@ -129,10 +134,10 @@ export function registerSessionRegistry(pi) {
129
134
  commitBoundary.forget(sessionId);
130
135
  return;
131
136
  }
132
- const outcome = commitBoundary.commit(sessionId, event.message, modelId);
137
+ const { outcome, divergedPath } = commitBoundary.commit(sessionId, event.message, modelId);
133
138
  if (outcome === "rewritten") {
134
- recordPendingFork(sessionId, "assistant_rewritten");
135
- await invalidateBinding(pi, ctx, "assistant_rewritten");
139
+ recordPendingFork(sessionId, "assistant_rewritten", divergedPath);
140
+ await invalidateBinding(pi, ctx, "assistant_rewritten", divergedPath);
136
141
  return;
137
142
  }
138
143
  // Only an assistant this provider actually produced may anchor a record.
@@ -41,6 +41,8 @@ export interface AnthropicSubscriptionSessionEntry {
41
41
  taintedReason: string | null;
42
42
  /** Wave C seam: a divergence boundary recorded for the next continuity decision. */
43
43
  pendingForkReason: string | null;
44
+ /** senpi#1975: first diverged field path the commit boundary named for the pending fork, when it named one. */
45
+ pendingForkDivergedPath: string | null;
44
46
  lastUsedAt: number;
45
47
  /** Digest of the access token the resident subprocess was spawned with. */
46
48
  credentialDigest?: string;
@@ -91,7 +93,7 @@ export declare function closeSession(senpiSessionId: string, reason: string): vo
91
93
  export declare function isIdleExpired(entry: Pick<AnthropicSubscriptionSessionEntry, "lastUsedAt">): boolean;
92
94
  export declare function markTainted(senpiSessionId: string, reason: string): void;
93
95
  export declare function switchSessionModel(senpiSessionId: string, modelId: string, sdkModel?: string): Promise<boolean>;
94
- export declare function recordPendingFork(senpiSessionId: string, reason: string): void;
96
+ export declare function recordPendingFork(senpiSessionId: string, reason: string, divergedPath?: string): void;
95
97
  export declare function recordBranchInfo(senpiSessionId: string, info: SessionBranchInfo): void;
96
98
  export declare function isCurrentGeneration(senpiSessionId: string, generation: number): boolean;
97
99
  export declare function isBoundAccountTokenExpiring(entry: Pick<AnthropicSubscriptionSessionEntry, "accountName">, accounts: readonly Pick<AccountSlot, "name" | "expires" | "source">[]): boolean;
@@ -162,6 +162,7 @@ export class AnthropicSubscriptionSessionRegistry {
162
162
  branchInfo: null,
163
163
  taintedReason: null,
164
164
  pendingForkReason: null,
165
+ pendingForkDivergedPath: null,
165
166
  lastUsedAt: now,
166
167
  };
167
168
  // oxlint-disable-next-line eslint/prefer-const -- a closure reads it; const would throw in the TDZ if that closure ran before the assignment, so the rewrite is reviewed separately
@@ -250,8 +251,8 @@ export function markTainted(senpiSessionId, reason) {
250
251
  export async function switchSessionModel(senpiSessionId, modelId, sdkModel) {
251
252
  return switchEntryModel(sessionRegistry, senpiSessionId, modelId, sdkModel);
252
253
  }
253
- export function recordPendingFork(senpiSessionId, reason) {
254
- annotatePendingFork(sessionRegistry, senpiSessionId, reason);
254
+ export function recordPendingFork(senpiSessionId, reason, divergedPath) {
255
+ annotatePendingFork(sessionRegistry, senpiSessionId, reason, divergedPath);
255
256
  }
256
257
  export function recordBranchInfo(senpiSessionId, info) {
257
258
  annotateBranchInfo(sessionRegistry, senpiSessionId, info);
@@ -155,6 +155,7 @@ async function createResidentAttempt(input, auth) {
155
155
  ? flattenResult.blocks
156
156
  : buildDeltaPromptBlocks(messages.slice(from), input.customToolNameToSdk);
157
157
  const payloadBytes = flattenResult ? serializedPayloadBytes(flattenResult.blocks) : undefined;
158
+ const divergedPath = observedReason === "assistant_rewritten" ? (existing?.pendingForkDivergedPath ?? undefined) : undefined;
158
159
  const staged = stageContinuityDecision({
159
160
  ...observeSessionSyncDecision({
160
161
  kind: observedKind,
@@ -166,6 +167,7 @@ async function createResidentAttempt(input, auth) {
166
167
  ...(flattenResult?.collapsedDirectives !== undefined
167
168
  ? { collapsedDirectives: flattenResult.collapsedDirectives }
168
169
  : {}),
170
+ ...(divergedPath === undefined ? {} : { divergedPath }),
169
171
  }),
170
172
  ...seat,
171
173
  }, sessionId, input.onContinuityDecision,
@@ -1,7 +1,8 @@
1
1
  export const GOAL_STOP_PAUSED_ENTRY = "goal-stop-paused";
2
2
  export const GOAL_STOP_RESUMED_ENTRY = "goal-stop-resumed";
3
3
  export function stopHoldLive(ctx) {
4
- return ctx.getPauseState?.().holds.some((hold) => hold.kind === "session_hold") === true;
4
+ return (ctx.getPauseState?.().holds.some((hold) => hold.kind === "session_hold" && hold.key !== "interrupted-run") ===
5
+ true);
5
6
  }
6
7
  export function markStopPaused(pi, goalId) {
7
8
  pi.appendEntry(GOAL_STOP_PAUSED_ENTRY, { goalId });
@@ -2,6 +2,7 @@ export function sessionHeld(ctx) {
2
2
  return ctx.getPauseState?.().holds.some((hold) => hold.kind !== "run_fence") === true;
3
3
  }
4
4
  export function userStopped(ctx) {
5
- return ctx.getPauseState?.().holds.some((hold) => hold.kind === "session_hold") === true;
5
+ return (ctx.getPauseState?.().holds.some((hold) => hold.kind === "session_hold" && hold.key !== "interrupted-run") ===
6
+ true);
6
7
  }
7
8
  //# sourceMappingURL=session-hold.js.map
@@ -35,6 +35,9 @@ export function readLifecycleRuntimeFacts(session) {
35
35
  });
36
36
  }
37
37
  const holds = session.holds.flatMap((hold) => {
38
+ // The interruption gate is a human wait, not a user Stop. Preserve unattended and real Stop precedence.
39
+ if (hold.kind === "session_hold" && hold.key === "interrupted-run")
40
+ return [];
38
41
  switch (hold.kind) {
39
42
  case "unattended":
40
43
  case "session_hold":