@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.
- package/CHANGELOG.md +32 -0
- package/dist/bundle/chunks/{chunk-NQC7MI2G.js → chunk-2EFVQWXZ.js} +3 -3
- package/dist/bundle/chunks/{chunk-RHHBXIRN.js → chunk-4WZRVZNX.js} +1 -1
- package/dist/bundle/chunks/{chunk-HKUXKTFW.js → chunk-B6TPQDBW.js} +1 -1
- package/dist/bundle/chunks/{chunk-VOBD6FQG.js → chunk-FHXR4ZAC.js} +1 -1
- package/dist/bundle/chunks/{chunk-7IRLFYNM.js → chunk-G2UETS2R.js} +2 -2
- package/dist/bundle/chunks/{chunk-GVJ76VQI.js → chunk-J2U5VDG7.js} +63 -63
- package/dist/bundle/chunks/{chunk-42OY5SZZ.js → chunk-JRXKLIEQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-3VOG6WFU.js → chunk-KJZAY5YH.js} +1 -1
- package/dist/bundle/chunks/{chunk-UT6AJHPK.js → chunk-KUE4AQN6.js} +1 -1
- package/dist/bundle/chunks/{chunk-FWXMMMCV.js → chunk-LYGAV6KU.js} +1 -1
- package/dist/bundle/chunks/{chunk-SGDOZT6B.js → chunk-MXZPAUFB.js} +1 -1
- package/dist/bundle/chunks/chunk-O5RYI3IW.js +2 -0
- package/dist/bundle/chunks/{chunk-II22E3CV.js → chunk-OI4NMCKP.js} +1 -1
- package/dist/bundle/chunks/{chunk-7UUOGZKR.js → chunk-RXKNOEQQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-QH2HSCMX.js → chunk-TTU2RNVX.js} +3 -3
- package/dist/bundle/chunks/{chunk-PXKF2RHD.js → chunk-UFJ2K7Q7.js} +1 -1
- package/dist/bundle/chunks/chunk-VNN2V74Y.js +5 -0
- package/dist/bundle/chunks/{chunk-BMYNMUVZ.js → chunk-W2BKZBYJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-RVYSJBZ7.js → chunk-WFXUCVIR.js} +1 -1
- package/dist/bundle/chunks/{chunk-IUHU5PEC.js → chunk-XBUSVKWC.js} +1 -1
- package/dist/bundle/chunks/{chunk-EWOEVVQT.js → chunk-YN7ELQ43.js} +1 -1
- package/dist/bundle/chunks/{cli-main-UH3MAREK.js → cli-main-SWKFMTZQ.js} +1 -1
- package/dist/bundle/chunks/{host-command-OQR4RDTM.js → host-command-BC6UWP33.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-U36U7JTE.js → host-lifecycle-TN5NEGNF.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +3 -3
- package/dist/bundle/chunks/{host-session-command-IGB27ZPJ.js → host-session-command-MGVBLBRV.js} +1 -1
- package/dist/bundle/chunks/{host-socket-request-ZDJGSSZO.js → host-socket-request-AS2OCRLP.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-R5RCSJ2F.js → interactive-mode-SCSROHD5.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-YZEMYUFY.js → multi-session-host-VTTY5MFT.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-5FOXDKUA.js → package-manager-cli-DZFOC3MF.js} +1 -1
- package/dist/bundle/chunks/{role-M2MTVACJ.js → role-JVSBNKGE.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-6K62CZBW.js → rpc-mode-TOXYD4XA.js} +1 -1
- package/dist/bundle/chunks/{rpc-monitor-source-OL5KU6NO.js → rpc-monitor-source-B5NQ4ZVH.js} +1 -1
- package/dist/bundle/chunks/{runtime-session-CLNODAYE.js → runtime-session-V5OO7ZRM.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-XBW4EOG5.js → schedule-command-SLISTG4V.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-54TMCS7Q.js → session-control-endpoint-HFFMBALG.js} +1 -1
- package/dist/bundle/chunks/{session-picker-P2VUU2MQ.js → session-picker-465FVXQG.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +69 -69
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +3 -3
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.js +28 -9
- package/dist/core/engine-turn-limit.d.ts +2 -0
- package/dist/core/engine-turn-limit.js +7 -1
- package/dist/core/extensions/builtin/anthropic-subscription/auth-attempt.js +5 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-binding.d.ts +2 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.d.ts +6 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-boundary.js +18 -7
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.d.ts +23 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-commit-divergence.js +107 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-entry-annotations.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-observability.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-observability.js +4 -0
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry-wiring.js +12 -7
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry.d.ts +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/session-registry.js +3 -2
- package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +2 -0
- package/dist/core/extensions/builtin/goal/stop-pause.js +2 -1
- package/dist/core/extensions/builtin/loop/session-hold.js +2 -1
- package/dist/core/lifecycle-runtime-facts.js +3 -0
- package/dist/core/lifecycle-session-binding.js +6 -2
- package/dist/core/run-resume-turn.d.ts +1 -0
- package/dist/core/run-resume-turn.js +4 -3
- package/dist/core/skill-access.js +15 -3
- package/dist/core/skill-discovery.d.ts +2 -0
- package/dist/core/skill-discovery.js +1 -1
- package/dist/core/stop-resume-directive.d.ts +1 -1
- package/dist/core/stop-resume-directive.js +10 -1
- package/dist/modes/interactive/interactive-mode.js +1 -0
- package/dist/modes/interactive/pause-footer.d.ts +5 -0
- package/dist/modes/interactive/pause-footer.js +2 -0
- package/dist/modes/rpc/host-reservations.js +3 -2
- package/dist/modes/rpc/host-stalled-evidence.js +2 -1
- package/dist/modes/rpc/tool-child/group-ownership.js +4 -2
- package/dist/utils/process-identity.d.ts +7 -0
- package/dist/utils/process-identity.js +32 -5
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-7ASLQ243.js +0 -2
- package/dist/bundle/chunks/chunk-XUFUKDKJ.js +0 -5
package/dist/bundle/rpc-entry.js
CHANGED
|
@@ -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-
|
|
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":"
|
|
1
|
+
{"buildId":"256057df5dd08be8","externals":["@earendil-works/chord","@earendil-works/pi-pty","@silvia-odwyer/photon-node","bufferutil","canvas","kerberos","supports-color","utf-8-validate"]}
|
|
@@ -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
|
-
|
|
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" ||
|
|
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
|
|
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):
|
|
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
|
-
|
|
25
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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, {
|
|
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") ===
|
|
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") ===
|
|
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":
|