@code-yeongyu/senpi 2026.11.10-3 → 2026.11.11
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 +29 -0
- package/dist/bundle/chunks/{chunk-Z2C5N2LM.js → chunk-5F2XC6UC.js} +2 -2
- package/dist/bundle/chunks/{chunk-H3VJVKUK.js → chunk-5PIZWSNG.js} +2 -2
- package/dist/bundle/chunks/{chunk-DEQLHN2J.js → chunk-BPEA5SWW.js} +1 -1
- package/dist/bundle/chunks/{chunk-DIHIEJFV.js → chunk-BUDJLVMF.js} +1 -1
- package/dist/bundle/chunks/{chunk-47T6YMI2.js → chunk-IBDAV2AL.js} +1 -1
- package/dist/bundle/chunks/{chunk-O5WJVIEU.js → chunk-J4ADZ2OK.js} +1 -1
- package/dist/bundle/chunks/{chunk-T2SMQPRE.js → chunk-K3GAGIPX.js} +1 -1
- package/dist/bundle/chunks/{chunk-HZDKQOWB.js → chunk-L6DF5G6K.js} +1 -1
- package/dist/bundle/chunks/{chunk-E266ORGA.js → chunk-LJFQYCFH.js} +1 -1
- package/dist/bundle/chunks/{chunk-DHAAWL2H.js → chunk-NRZAJUUO.js} +1 -1
- package/dist/bundle/chunks/{chunk-R2Z7CX77.js → chunk-OSNR3IRD.js} +1 -1
- package/dist/bundle/chunks/{chunk-AC6MDRAE.js → chunk-PUZDKFRT.js} +81 -81
- package/dist/bundle/chunks/{chunk-6Q7Y2G5A.js → chunk-SANWO37B.js} +1 -1
- package/dist/bundle/chunks/{chunk-NN7NESW2.js → chunk-SS5VTBAV.js} +1 -1
- package/dist/bundle/chunks/{chunk-JSD6O3RJ.js → chunk-XUDKT4UH.js} +1 -1
- package/dist/bundle/chunks/{cli-main-ZTT3N4DO.js → cli-main-7II7GCIR.js} +1 -1
- package/dist/bundle/chunks/{credential-accounts-5KHVGSCY.js → credential-accounts-N3B5YJS7.js} +1 -1
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{hand-entry-UV5XPMBF.js → hand-entry-ONOLRMVB.js} +1 -1
- package/dist/bundle/chunks/{host-command-BZN7WYYV.js → host-command-KWMJLPBS.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-TNHASSFJ.js → host-lifecycle-4WQDCN3Y.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +4 -4
- package/dist/bundle/chunks/{host-session-command-O5WYHZXH.js → host-session-command-ZDYEKCUB.js} +1 -1
- package/dist/bundle/chunks/{interactive-mode-OCU4NDPC.js → interactive-mode-EPE4M32G.js} +1 -1
- package/dist/bundle/chunks/{main-CNDP6HCF.js → main-OM7BKVZG.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-ODGJAGJP.js → multi-session-host-DB6XMY53.js} +2 -2
- package/dist/bundle/chunks/{package-manager-cli-HSPUG4RJ.js → package-manager-cli-GLU5Q2KV.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-KOZPXU3U.js → rpc-mode-SDJVBT36.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-SG44FE23.js → schedule-command-5SQD7M5N.js} +1 -1
- package/dist/bundle/chunks/{session-control-endpoint-5A24JYPE.js → session-control-endpoint-R25A2QGS.js} +1 -1
- package/dist/bundle/chunks/{session-picker-73YRS3GE.js → session-picker-GZM5CVQL.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +88 -88
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/host-supervisor.js +4 -4
- 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 +2 -0
- package/dist/core/credential-accounts.d.ts +2 -0
- package/dist/core/credential-accounts.js +10 -1
- package/dist/core/dynamic-prompt/intent-gate.js +2 -2
- package/dist/core/dynamic-prompt/style.js +2 -2
- package/dist/core/dynamic-prompt/verification.js +2 -2
- package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-account-test.js +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.d.ts +3 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-connection-test.js +47 -16
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/claude-cli-login-slots.js +4 -2
- package/dist/core/extensions/builtin/anthropic-subscription/errors.d.ts +1 -0
- package/dist/core/extensions/builtin/anthropic-subscription/errors.js +14 -5
- package/dist/core/extensions/builtin/anthropic-subscription/oauth-login.js +3 -3
- package/dist/core/extensions/builtin/goal/continuation.d.ts +6 -0
- package/dist/core/extensions/builtin/goal/continuation.js +3 -2
- package/dist/core/extensions/builtin/goal/index.js +10 -26
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +0 -6
- package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +3 -37
- package/dist/core/extensions/builtin/goal/monitor-continuation.js +66 -7
- package/dist/core/extensions/builtin/goal/provider-retry.d.ts +9 -0
- package/dist/core/extensions/builtin/goal/provider-retry.js +16 -0
- package/dist/core/extensions/builtin/goal/reload-reengagement.d.ts +3 -2
- package/dist/core/extensions/builtin/goal/reload-reengagement.js +4 -4
- package/dist/core/extensions/builtin/goal/session-start-continuation.d.ts +28 -0
- package/dist/core/extensions/builtin/goal/session-start-continuation.js +75 -0
- package/dist/core/extensions/builtin/goal/store.d.ts +1 -0
- package/dist/core/extensions/builtin/goal/store.js +1 -1
- package/dist/core/extensions/builtin/handoff-hint/index.js +15 -11
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.d.ts +16 -0
- package/dist/core/extensions/builtin/handoff-hint/replacement-relay.js +17 -0
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-2.js +5 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-haiku-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/deepseek-v4-1-flash.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +2 -2
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +5 -5
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3-1.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +7 -6
- package/dist/core/resource-loader.js +19 -3
- package/dist/core/retry-fallback/controller-types.d.ts +2 -0
- package/dist/core/retry-fallback/controller.js +9 -2
- package/dist/core/retry-fallback/cooldown.d.ts +7 -0
- package/dist/core/retry-fallback/cooldown.js +13 -1
- package/dist/core/retry-fallback/credential-recovery.d.ts +7 -0
- package/dist/core/retry-fallback/credential-recovery.js +24 -0
- package/dist/core/retry-fallback/settings.d.ts +8 -5
- package/dist/core/retry-fallback/settings.js +9 -6
- package/dist/modes/rpc/delegation-declaration.js +1 -1
- package/dist/modes/rpc/host-gc-evidence.d.ts +1 -1
- package/dist/modes/rpc/host-gc-evidence.js +9 -3
- package/dist/modes/rpc/host-gc.js +4 -1
- package/dist/modes/rpc/rpc-session-state-types.d.ts +2 -0
- package/dist/modes/rpc/session-attachment-gate.js +3 -0
- package/dist/private-fd-startup-fillers.d.ts +2 -2
- package/dist/private-fd-startup-fillers.js +34 -4
- package/docs/rpc.md +2 -1
- package/docs/settings.md +1 -1
- package/native/socket-secret/prebuilds/win32-arm64/senpi_socket_secret.win32-arm64.node +0 -0
- package/native/socket-secret/prebuilds/win32-x64/senpi_socket_secret.win32-x64.node +0 -0
- package/package.json +6 -6
|
@@ -6,7 +6,6 @@ import { isRecord } from "../../../work-custody-record.js";
|
|
|
6
6
|
import { getLatestPhasesFromBranchEntries } from "../todotools/state.js";
|
|
7
7
|
import { buildGoalContinuationSignature, evaluateGoalContinuation, hashAssistantText, } from "./continuation.js";
|
|
8
8
|
import { CONTEXT_OVERFLOW_BLOCKED_REASON, CONTINUATION_CAP_BLOCKED_REASON, continuationCapRecoveryHint, LENGTH_EXHAUSTED_BLOCKED_REASON, NO_PROGRESS_BLOCKED_REASON, REPETITION_BLOCKED_REASON, } from "./continuation-recovery.js";
|
|
9
|
-
import { buildContinuationPrompt } from "./prompt.js";
|
|
10
9
|
import { recordContinuationDelivered, recordGoalContinuationStopped } from "./store.js";
|
|
11
10
|
import { goalStoreRef } from "./store-ref.js";
|
|
12
11
|
import { openTodoTaskContents } from "./todo-gate.js";
|
|
@@ -42,36 +41,15 @@ export async function admitAndQueueGoalContinuation(pi, ctx, goal, options) {
|
|
|
42
41
|
if (options.input.currentSignature === undefined) {
|
|
43
42
|
throw new Error("Cannot queue a goal continuation without a progress signature");
|
|
44
43
|
}
|
|
45
|
-
const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id,
|
|
44
|
+
const recordedGoal = await recordContinuationDelivered(goalStoreRef(ctx.sessionManager, ctx.cwd), options.input.currentSignature, goal.id, options.input.limitWait === true
|
|
45
|
+
? { countUnattended: false, countStreak: false }
|
|
46
|
+
: { countUnattended: options.input.path !== "monitorDelayed" });
|
|
46
47
|
if (recordedGoal === null)
|
|
47
48
|
return null;
|
|
48
49
|
options.markContinuationPending();
|
|
49
50
|
queueHiddenGoalPrompt(pi, options.content(verdict));
|
|
50
51
|
return recordedGoal;
|
|
51
52
|
}
|
|
52
|
-
/** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
|
|
53
|
-
export async function queueGoalContinuation(pi, ctx, goal, options) {
|
|
54
|
-
const signature = buildCurrentGoalContinuationSignature(ctx, goal, lastAssistantTextFromEntries(ctx.sessionManager.getBranch()));
|
|
55
|
-
return admitAndQueueGoalContinuation(pi, ctx, goal, {
|
|
56
|
-
input: {
|
|
57
|
-
isIdle: ctx.isIdle(),
|
|
58
|
-
hasPendingMessages: ctx.hasPendingMessages(),
|
|
59
|
-
path: "sessionStart",
|
|
60
|
-
lastStopReason: undefined,
|
|
61
|
-
lastTurnWasMalformedToolUse: false,
|
|
62
|
-
consecutiveContinuations: goal.consecutiveContinuations ?? 0,
|
|
63
|
-
lastContinuationSignature: goal.lastContinuationSignature,
|
|
64
|
-
currentSignature: signature,
|
|
65
|
-
consecutiveLengthRecoveries: 0,
|
|
66
|
-
recentNormalizedOutputHashes: [],
|
|
67
|
-
toollessContinuationStreak: 0,
|
|
68
|
-
continuationPending: options.continuationPending,
|
|
69
|
-
lastTurnStuckOnContextOverflow: isLastTurnStuckOnContextOverflow(ctx, lastAssistantFromEntries(ctx.sessionManager.getBranch())),
|
|
70
|
-
},
|
|
71
|
-
content: () => buildContinuationPrompt(goal, { modelId: ctx.model?.id }),
|
|
72
|
-
markContinuationPending: options.markContinuationPending,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
53
|
export function buildCurrentGoalContinuationSignature(ctx, goal, lastAssistantText) {
|
|
76
54
|
const entries = ctx.sessionManager.getBranch();
|
|
77
55
|
const openTodos = openTodoTaskContents(entries).length;
|
|
@@ -86,18 +64,6 @@ export function lastAssistantText(messages) {
|
|
|
86
64
|
}
|
|
87
65
|
return "";
|
|
88
66
|
}
|
|
89
|
-
function lastAssistantTextFromEntries(entries) {
|
|
90
|
-
const assistant = lastAssistantFromEntries(entries);
|
|
91
|
-
return assistant === undefined ? "" : textContent(assistant);
|
|
92
|
-
}
|
|
93
|
-
function lastAssistantFromEntries(entries) {
|
|
94
|
-
for (let index = entries.length - 1; index >= 0; index--) {
|
|
95
|
-
const entry = entries[index];
|
|
96
|
-
if (entry?.type === "message" && entry.message.role === "assistant")
|
|
97
|
-
return entry.message;
|
|
98
|
-
}
|
|
99
|
-
return undefined;
|
|
100
|
-
}
|
|
101
67
|
export function isLastTurnStuckOnContextOverflow(ctx, lastAssistant) {
|
|
102
68
|
return lastAssistant !== undefined && isTurnStuckOnContextOverflow(lastAssistant, ctx.model?.contextWindow ?? 0);
|
|
103
69
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deadlineStopPendingSinceLastTurn } from "../../../turn-deadline.js";
|
|
2
|
+
import { subscribeProviderAccountEvents } from "../anthropic-subscription/account-events.js";
|
|
2
3
|
import { isHumanInputMonitorNotification } from "../monitor-state-event.js";
|
|
3
4
|
import { createGoalCacheWarmScheduleData, estimateCacheWarmMetrics, GOAL_CACHE_WARMUP_ENTRY_TYPE, GOAL_MONITOR_CONTINUATION_FALLBACK_DELAY_MS, resolveGoalBackstopMaxSecondsForCache, resolveGoalMonitorContinuationDelayMs, resolveGoalPromptCacheLifetime, } from "./cache-warm.js";
|
|
4
5
|
import { subscribeGoalChannelState } from "./channel-state-subscriptions.js";
|
|
@@ -9,7 +10,7 @@ import { lastAssistantMessage } from "./last-assistant-message.js";
|
|
|
9
10
|
import { admitAndQueueGoalContinuation, buildCurrentGoalContinuationSignature, isLastTurnStuckOnContextOverflow, lastAssistantText, } from "./lifecycle-helpers.js";
|
|
10
11
|
import { findParkedGoalWait } from "./parked-wait.js";
|
|
11
12
|
import { buildContinuationPrompt, buildGoalStallNotice, buildTruncationRecoveryPrompt } from "./prompt.js";
|
|
12
|
-
import { exhaustedLadderResumeDelayMs, PROVIDER_RETRY_BASE_MS, providerRetryAt, providerRetryDelayMs, } from "./provider-retry.js";
|
|
13
|
+
import { exhaustedLadderResumeDelayMs, GOAL_LIMIT_WAIT_WAKE_LIMIT, isLimitRefusal, limitWaitFloorMs, PROVIDER_RETRY_BASE_MS, providerRetryAt, providerRetryDelayMs, } from "./provider-retry.js";
|
|
13
14
|
import { isStaleExtensionContextError } from "./stale-context.js";
|
|
14
15
|
import { resetContinuationStreak } from "./store.js";
|
|
15
16
|
import { goalStoreRef } from "./store-ref.js";
|
|
@@ -22,6 +23,8 @@ export const GOAL_CONTINUATION_SCHEDULED_EVENT = "goal_continuation_scheduled";
|
|
|
22
23
|
export const GOAL_CONTINUATION_RESUMED_EVENT = "goal_continuation_resumed";
|
|
23
24
|
export const GOAL_CONTINUATION_TIMER_STATE_EVENT = "goal_continuation_timer_state";
|
|
24
25
|
export const GOAL_MONITOR_STALL_EVENT = "goal_monitor_continuation_stall";
|
|
26
|
+
/** A limit outlasted GOAL_LIMIT_WAIT_WAKE_LIMIT re-checks: the goal waits for a sign-in, a block reset or input. */
|
|
27
|
+
const GOAL_LIMIT_WAIT_PARKED_EVENT = "goal_limit_wait_parked";
|
|
25
28
|
/** The one notice a provider-limit park shows: when the goal resumes, in local time and as the exact instant. */
|
|
26
29
|
function providerPauseNotice(retryAt) {
|
|
27
30
|
const local = new Date(retryAt).toLocaleString("en-US", {
|
|
@@ -68,12 +71,51 @@ export class MonitorAwareGoalContinuation {
|
|
|
68
71
|
#pendingProviderRecovery;
|
|
69
72
|
#providerRetryAttempts = 0;
|
|
70
73
|
#providerRetryDelayMs;
|
|
74
|
+
/** The armed provider retry waits out a usage or rate limit, so its wake spends no continuation budget (senpi#4085). */
|
|
75
|
+
#limitWait = false;
|
|
76
|
+
#limitWakes = 0;
|
|
77
|
+
#limitParked = false;
|
|
71
78
|
constructor(pi, isContinuationPending = () => false, markContinuationPending = () => { }, waitTicker) {
|
|
72
79
|
this.#pi = pi;
|
|
73
80
|
this.#isContinuationPending = isContinuationPending;
|
|
74
81
|
this.#markContinuationPending = markContinuationPending;
|
|
75
82
|
this.#waitTicker = waitTicker;
|
|
76
83
|
this.#subscribeToChannelState();
|
|
84
|
+
// A login or an explicit block reset can end the wait early: re-check the provider now (senpi#4085).
|
|
85
|
+
this.#channelStateUnsubscribers.push(subscribeProviderAccountEvents((event) => {
|
|
86
|
+
if (event.type === "accounts_changed" && event.remedy !== undefined)
|
|
87
|
+
this.#wakeLimitWait();
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
#wakeLimitWait() {
|
|
91
|
+
const goal = this.#goal;
|
|
92
|
+
const waiting = this.#scheduledContinuationKind === "providerRetry" || this.#limitParked;
|
|
93
|
+
if (!this.#limitWait || !waiting || goal === null)
|
|
94
|
+
return;
|
|
95
|
+
this.#limitWakes = 0;
|
|
96
|
+
this.#limitParked = false;
|
|
97
|
+
this.#cancelTimer();
|
|
98
|
+
this.#providerRetryDelayMs = 0;
|
|
99
|
+
this.#schedule(goal, "providerRetry");
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Arms the next re-check of a limit wait, never sooner than the provider backoff. A limit that outlasts
|
|
103
|
+
* GOAL_LIMIT_WAIT_WAKE_LIMIT re-checks parks the goal, still active, until a sign-in, a block reset or input.
|
|
104
|
+
*/
|
|
105
|
+
#armLimitWait(goal, delayMs) {
|
|
106
|
+
this.#limitWait = true;
|
|
107
|
+
this.#limitWakes += 1;
|
|
108
|
+
if (this.#limitWakes > GOAL_LIMIT_WAIT_WAKE_LIMIT) {
|
|
109
|
+
this.#limitParked = true;
|
|
110
|
+
this.#publish(GOAL_LIMIT_WAIT_PARKED_EVENT, { goalId: goal.id, wakes: GOAL_LIMIT_WAIT_WAKE_LIMIT });
|
|
111
|
+
if (this.#ctxHasUI()) {
|
|
112
|
+
this.#ctx?.ui.notify(`Goal paused: the provider kept refusing for a usage limit through ${GOAL_LIMIT_WAIT_WAKE_LIMIT} re-checks. It resumes after a sign-in, an account block reset, or your next message.`, "warning");
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
this.#providerRetryDelayMs = Math.max(delayMs, limitWaitFloorMs(this.#providerRetryAttempts));
|
|
117
|
+
this.#schedule(goal, "providerRetry");
|
|
118
|
+
return true;
|
|
77
119
|
}
|
|
78
120
|
start(ctx) {
|
|
79
121
|
this.#cancelTimer();
|
|
@@ -91,6 +133,9 @@ export class MonitorAwareGoalContinuation {
|
|
|
91
133
|
if (options.goal?.id !== this.#goal?.id)
|
|
92
134
|
this.#resetContinuationState();
|
|
93
135
|
this.#providerRetryAttempts = 0;
|
|
136
|
+
this.#limitWait = false;
|
|
137
|
+
this.#limitWakes = 0;
|
|
138
|
+
this.#limitParked = false;
|
|
94
139
|
this.#ctx = options.ctx;
|
|
95
140
|
this.#goal = options.goal;
|
|
96
141
|
this.#lastAgentEndMessages = options.messages;
|
|
@@ -214,9 +259,18 @@ export class MonitorAwareGoalContinuation {
|
|
|
214
259
|
return undefined;
|
|
215
260
|
}
|
|
216
261
|
this.#providerRetryAttempts += 1;
|
|
262
|
+
this.#limitWait = isLimitRefusal(pendingProvider.messages);
|
|
217
263
|
const now = Date.now();
|
|
218
264
|
const retryAt = providerRetryAt(pendingProvider.messages, now);
|
|
219
265
|
const delayMs = providerRetryDelayMs(this.#providerRetryAttempts, retryAt, now);
|
|
266
|
+
if (this.#limitWait) {
|
|
267
|
+
this.#cancelTimer();
|
|
268
|
+
const armed = this.#armLimitWait(pendingProvider.goal, delayMs);
|
|
269
|
+
if (armed && retryAt !== undefined && this.#ctxHasUI(pendingProvider.ctx)) {
|
|
270
|
+
pendingProvider.ctx.ui.notify(providerPauseNotice(retryAt), "info");
|
|
271
|
+
}
|
|
272
|
+
return pendingProvider.goal;
|
|
273
|
+
}
|
|
220
274
|
if (delayMs > 0) {
|
|
221
275
|
this.#cancelTimer();
|
|
222
276
|
this.#providerRetryDelayMs = delayMs;
|
|
@@ -230,17 +284,16 @@ export class MonitorAwareGoalContinuation {
|
|
|
230
284
|
}
|
|
231
285
|
/**
|
|
232
286
|
* senpi#3709: every fallback rung failed, so a continuation now would hit the same limits. Park on
|
|
233
|
-
* the soonest reset the run named
|
|
287
|
+
* the soonest reset the run named; with no known reset, re-check on the provider backoff (senpi#4085).
|
|
234
288
|
*/
|
|
235
289
|
#parkUntilLadderReset(resumeAt) {
|
|
236
290
|
const goal = this.#goal;
|
|
237
291
|
if (goal?.status !== "active")
|
|
238
292
|
return undefined;
|
|
239
293
|
this.#cancelTimer();
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
this.#
|
|
243
|
-
this.#schedule(goal, "providerRetry");
|
|
294
|
+
this.#providerRetryAttempts += 1;
|
|
295
|
+
// No failure named a reset: re-check on the provider backoff (15 s doubling to 30 min) rather than wait for input.
|
|
296
|
+
this.#armLimitWait(goal, resumeAt === undefined ? 0 : exhaustedLadderResumeDelayMs(resumeAt, Date.now()));
|
|
244
297
|
return goal;
|
|
245
298
|
}
|
|
246
299
|
syncGoal(goal) {
|
|
@@ -521,7 +574,10 @@ export class MonitorAwareGoalContinuation {
|
|
|
521
574
|
this.#resetCacheWarmIteration();
|
|
522
575
|
}
|
|
523
576
|
async #admitAndQueue(ctx, goal, path, messages) {
|
|
524
|
-
const input =
|
|
577
|
+
const input = {
|
|
578
|
+
...this.#buildVerdictInput(ctx, goal, path, messages),
|
|
579
|
+
...(path === "providerRecovery" && this.#limitWait ? { limitWait: true } : {}),
|
|
580
|
+
};
|
|
525
581
|
const verdict = evaluateGoalContinuation({ goal, ...input });
|
|
526
582
|
const admittedGoal = await admitAndQueueGoalContinuation(this.#pi, ctx, goal, {
|
|
527
583
|
input,
|
|
@@ -747,6 +803,9 @@ export class MonitorAwareGoalContinuation {
|
|
|
747
803
|
this.#pendingSystemRecovery = undefined;
|
|
748
804
|
this.#pendingProviderRecovery = undefined;
|
|
749
805
|
this.#providerRetryAttempts = 0;
|
|
806
|
+
this.#limitWait = false;
|
|
807
|
+
this.#limitWakes = 0;
|
|
808
|
+
this.#limitParked = false;
|
|
750
809
|
this.#consecutiveLengthRecoveries.clear();
|
|
751
810
|
this.#recentNormalizedOutputHashes = [];
|
|
752
811
|
this.#recentToolEffects = [];
|
|
@@ -7,6 +7,15 @@ export declare const PROVIDER_RETRY_WAKE_CAP_MS: number;
|
|
|
7
7
|
* pool guidance falls back to the earliest future ISO instant in the error only.
|
|
8
8
|
*/
|
|
9
9
|
export declare function providerRetryAt(messages: readonly AgentMessage[], now: number): number | undefined;
|
|
10
|
+
/** Re-checks of a limit that never clears before the goal parks until a sign-in, a block reset or input (senpi#4085). */
|
|
11
|
+
export declare const GOAL_LIMIT_WAIT_WAKE_LIMIT = 48;
|
|
12
|
+
/** The shortest wait before the `attempt`-th limit re-check: the provider backoff, 15 s doubling to 30 min. */
|
|
13
|
+
export declare function limitWaitFloorMs(attempt: number): number;
|
|
14
|
+
/**
|
|
15
|
+
* The turn ended on a provider refusing for a usage or rate limit, not on an outage (senpi#4085). An OpenAI-compatible
|
|
16
|
+
* adapter reports a bare 429 as a status-only diagnostic (`unknown` + 429); that status is still a limit.
|
|
17
|
+
*/
|
|
18
|
+
export declare function isLimitRefusal(messages: readonly AgentMessage[]): boolean;
|
|
10
19
|
/**
|
|
11
20
|
* How long to wait before the `attempt`-th consecutive provider-recovery
|
|
12
21
|
* continuation (senpi#3264). A stated unblock time is the wake; otherwise the
|
|
@@ -31,6 +31,22 @@ export function providerRetryAt(messages, now) {
|
|
|
31
31
|
.filter((at) => Number.isFinite(at) && at > now);
|
|
32
32
|
return instants.length === 0 ? undefined : Math.min(...instants);
|
|
33
33
|
}
|
|
34
|
+
/** Re-checks of a limit that never clears before the goal parks until a sign-in, a block reset or input (senpi#4085). */
|
|
35
|
+
export const GOAL_LIMIT_WAIT_WAKE_LIMIT = 48;
|
|
36
|
+
/** The shortest wait before the `attempt`-th limit re-check: the provider backoff, 15 s doubling to 30 min. */
|
|
37
|
+
export function limitWaitFloorMs(attempt) {
|
|
38
|
+
return Math.min(PROVIDER_RETRY_CAP_MS, PROVIDER_RETRY_BASE_MS * 2 ** (Math.max(1, attempt) - 1));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The turn ended on a provider refusing for a usage or rate limit, not on an outage (senpi#4085). An OpenAI-compatible
|
|
42
|
+
* adapter reports a bare 429 as a status-only diagnostic (`unknown` + 429); that status is still a limit.
|
|
43
|
+
*/
|
|
44
|
+
export function isLimitRefusal(messages) {
|
|
45
|
+
const diagnostic = lastAssistantMessage(messages)?.providerDiagnostic;
|
|
46
|
+
return (diagnostic?.category === "rate_limit" ||
|
|
47
|
+
diagnostic?.category === "quota" ||
|
|
48
|
+
(diagnostic?.category === "unknown" && diagnostic.httpStatus === 429));
|
|
49
|
+
}
|
|
34
50
|
/**
|
|
35
51
|
* How long to wait before the `attempt`-th consecutive provider-recovery
|
|
36
52
|
* continuation (senpi#3264). A stated unblock time is the wake; otherwise the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ExtensionContext } from "../../types.ts";
|
|
2
2
|
import type { MonitorAwareGoalContinuation } from "./monitor-continuation.ts";
|
|
3
|
+
import type { TrailingGoalContinuations } from "./session-start-continuation.ts";
|
|
3
4
|
import type { Goal } from "./types.ts";
|
|
4
5
|
export type ReloadReengagementOutcome = "skipped-inactive" | "backstop-rearmed" | "suppressed-flood" | "continuation-queued";
|
|
5
6
|
/**
|
|
@@ -24,7 +25,7 @@ export declare function reengageGoalAfterReload(options: {
|
|
|
24
25
|
readonly ctx: ExtensionContext;
|
|
25
26
|
readonly goal: Goal;
|
|
26
27
|
readonly monitor: MonitorAwareGoalContinuation;
|
|
27
|
-
readonly
|
|
28
|
-
readonly queueContinuation: () => Promise<void>;
|
|
28
|
+
readonly readTrailingContinuations: () => TrailingGoalContinuations;
|
|
29
|
+
readonly queueContinuation: (limitWait: boolean) => Promise<void>;
|
|
29
30
|
}): Promise<ReloadReengagementOutcome>;
|
|
30
31
|
//# sourceMappingURL=reload-reengagement.d.ts.map
|
|
@@ -30,12 +30,12 @@ export async function reengageGoalAfterReload(options) {
|
|
|
30
30
|
monitor.rearmMonitorBackstop(goal);
|
|
31
31
|
return "backstop-rearmed";
|
|
32
32
|
}
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
35
|
-
ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${
|
|
33
|
+
const trailing = options.readTrailingContinuations();
|
|
34
|
+
if (trailing.counted >= GOAL_CONTINUATION_CAP) {
|
|
35
|
+
ctx.ui.notify(`Goal auto-continuation suppressed for this resumed session (${trailing.counted} historical continuations). Send a message to resume.`, "info");
|
|
36
36
|
return "suppressed-flood";
|
|
37
37
|
}
|
|
38
|
-
await options.queueContinuation();
|
|
38
|
+
await options.queueContinuation(trailing.waitingOutLimit);
|
|
39
39
|
return "continuation-queued";
|
|
40
40
|
}
|
|
41
41
|
//# sourceMappingURL=reload-reengagement.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SessionEntry } from "../../../session-manager.ts";
|
|
2
|
+
import type { ExtensionAPI, ExtensionContext } from "../../types.ts";
|
|
3
|
+
import type { Goal } from "./types.ts";
|
|
4
|
+
export type TrailingGoalContinuations = {
|
|
5
|
+
/** Continuations whose turn ran: the unattended loop the load-time flood guard bounds. */
|
|
6
|
+
readonly counted: number;
|
|
7
|
+
/** The newest continuation was refused for a usage or rate limit, so the goal was waiting that limit out. */
|
|
8
|
+
readonly waitingOutLimit: boolean;
|
|
9
|
+
};
|
|
10
|
+
export type SessionStartContinuationOptions = {
|
|
11
|
+
readonly continuationPending: boolean;
|
|
12
|
+
readonly markContinuationPending: () => void;
|
|
13
|
+
/** The session opened in the middle of a limit wait, so this wake re-checks that limit (senpi#4085). */
|
|
14
|
+
readonly limitWait?: boolean;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Reads the goal continuations queued since the most recent real user message in the branch (mirrors the
|
|
18
|
+
* todo-gate / todo-bridge backward branch read). A real user message is the only reset: the assistant turns in
|
|
19
|
+
* between are exactly the unattended loop the load-time admission guard exists to stop.
|
|
20
|
+
*
|
|
21
|
+
* A continuation the provider refused for a usage or rate limit ran no model turn, so it is not part of that
|
|
22
|
+
* loop (senpi#4085). A goal that waited a limit out for more wakes than the cap is therefore not a flood, and it
|
|
23
|
+
* resumes after a restart or a config reload.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readTrailingGoalContinuations(entries: readonly SessionEntry[]): TrailingGoalContinuations;
|
|
26
|
+
/** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
|
|
27
|
+
export declare function queueGoalContinuation(pi: ExtensionAPI, ctx: ExtensionContext, goal: Goal, options: SessionStartContinuationOptions): Promise<Goal | null>;
|
|
28
|
+
//# sourceMappingURL=session-start-continuation.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
|
|
2
|
+
import { admitAndQueueGoalContinuation, buildCurrentGoalContinuationSignature, isLastTurnStuckOnContextOverflow, lastAssistantText, } from "./lifecycle-helpers.js";
|
|
3
|
+
import { buildContinuationPrompt } from "./prompt.js";
|
|
4
|
+
import { isLimitRefusal } from "./provider-retry.js";
|
|
5
|
+
/**
|
|
6
|
+
* Reads the goal continuations queued since the most recent real user message in the branch (mirrors the
|
|
7
|
+
* todo-gate / todo-bridge backward branch read). A real user message is the only reset: the assistant turns in
|
|
8
|
+
* between are exactly the unattended loop the load-time admission guard exists to stop.
|
|
9
|
+
*
|
|
10
|
+
* A continuation the provider refused for a usage or rate limit ran no model turn, so it is not part of that
|
|
11
|
+
* loop (senpi#4085). A goal that waited a limit out for more wakes than the cap is therefore not a flood, and it
|
|
12
|
+
* resumes after a restart or a config reload.
|
|
13
|
+
*/
|
|
14
|
+
export function readTrailingGoalContinuations(entries) {
|
|
15
|
+
let counted = 0;
|
|
16
|
+
let waitingOutLimit;
|
|
17
|
+
let turn = [];
|
|
18
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
19
|
+
const entry = entries[index];
|
|
20
|
+
if (entry?.type === "message") {
|
|
21
|
+
if (entry.message.role === "user")
|
|
22
|
+
break;
|
|
23
|
+
if (entry.message.role === "assistant")
|
|
24
|
+
turn.push(entry.message);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (entry?.type !== "custom_message" || entry.customType !== GOAL_CONTINUATION_MESSAGE_TYPE)
|
|
28
|
+
continue;
|
|
29
|
+
const refused = wasRefusedForLimit(turn);
|
|
30
|
+
waitingOutLimit ??= refused;
|
|
31
|
+
if (!refused)
|
|
32
|
+
counted += 1;
|
|
33
|
+
turn = [];
|
|
34
|
+
}
|
|
35
|
+
return { counted, waitingOutLimit: waitingOutLimit ?? false };
|
|
36
|
+
}
|
|
37
|
+
function wasRefusedForLimit(turn) {
|
|
38
|
+
return (turn.length > 0 &&
|
|
39
|
+
turn.every((message) => message.stopReason === "error") &&
|
|
40
|
+
turn.some((message) => isLimitRefusal([message])));
|
|
41
|
+
}
|
|
42
|
+
/** Routes startup and resume continuations through the same verdict and delivery accounting as agent-end paths. */
|
|
43
|
+
export async function queueGoalContinuation(pi, ctx, goal, options) {
|
|
44
|
+
const lastAssistant = lastAssistantFromEntries(ctx.sessionManager.getBranch());
|
|
45
|
+
const signature = buildCurrentGoalContinuationSignature(ctx, goal, lastAssistant === undefined ? "" : lastAssistantText([lastAssistant]));
|
|
46
|
+
return admitAndQueueGoalContinuation(pi, ctx, goal, {
|
|
47
|
+
input: {
|
|
48
|
+
isIdle: ctx.isIdle(),
|
|
49
|
+
hasPendingMessages: ctx.hasPendingMessages(),
|
|
50
|
+
path: "sessionStart",
|
|
51
|
+
lastStopReason: undefined,
|
|
52
|
+
lastTurnWasMalformedToolUse: false,
|
|
53
|
+
consecutiveContinuations: goal.consecutiveContinuations ?? 0,
|
|
54
|
+
lastContinuationSignature: goal.lastContinuationSignature,
|
|
55
|
+
currentSignature: signature,
|
|
56
|
+
consecutiveLengthRecoveries: 0,
|
|
57
|
+
recentNormalizedOutputHashes: [],
|
|
58
|
+
toollessContinuationStreak: 0,
|
|
59
|
+
continuationPending: options.continuationPending,
|
|
60
|
+
lastTurnStuckOnContextOverflow: isLastTurnStuckOnContextOverflow(ctx, lastAssistant),
|
|
61
|
+
...(options.limitWait === true ? { limitWait: true } : {}),
|
|
62
|
+
},
|
|
63
|
+
content: () => buildContinuationPrompt(goal, { modelId: ctx.model?.id }),
|
|
64
|
+
markContinuationPending: options.markContinuationPending,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function lastAssistantFromEntries(entries) {
|
|
68
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
69
|
+
const entry = entries[index];
|
|
70
|
+
if (entry?.type === "message" && entry.message.role === "assistant")
|
|
71
|
+
return entry.message;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=session-start-continuation.js.map
|
|
@@ -15,6 +15,7 @@ export declare function clearGoal(ref: GoalStoreRef): Promise<boolean>;
|
|
|
15
15
|
export declare function accountGoalUsage(ref: GoalStoreRef, usage: TokenUsageSnapshot, elapsedSeconds: number, mode?: GoalAccountingMode, expectedGoalId?: string): Promise<Goal | null>;
|
|
16
16
|
export declare function recordContinuationDelivered(ref: GoalStoreRef, signature: string, expectedGoalId?: string, options?: {
|
|
17
17
|
countUnattended?: boolean;
|
|
18
|
+
countStreak?: boolean;
|
|
18
19
|
}): Promise<Goal | null>;
|
|
19
20
|
export declare function resetContinuationStreak(ref: GoalStoreRef, options?: {
|
|
20
21
|
unattended?: boolean;
|
|
@@ -177,7 +177,7 @@ export async function recordContinuationDelivered(ref, signature, expectedGoalId
|
|
|
177
177
|
const counted = options.countUnattended === false ? 0 : 1;
|
|
178
178
|
const next = {
|
|
179
179
|
...goal,
|
|
180
|
-
consecutiveContinuations: (goal.consecutiveContinuations ?? 0) + 1,
|
|
180
|
+
consecutiveContinuations: (goal.consecutiveContinuations ?? 0) + (options.countStreak === false ? 0 : 1),
|
|
181
181
|
unattendedContinuations: (goal.unattendedContinuations ?? 0) + counted,
|
|
182
182
|
noProgressContinuations: (goal.noProgressContinuations ?? 0) + counted,
|
|
183
183
|
lastContinuationSignature: signature,
|
|
@@ -5,6 +5,7 @@ import { readGoal } from "../goal/store.js";
|
|
|
5
5
|
import { goalStoreRef } from "../goal/store-ref.js";
|
|
6
6
|
import { buildHandoffBrief } from "./brief.js";
|
|
7
7
|
import { estimateHandoff, hintText } from "./estimate.js";
|
|
8
|
+
import { createReplacementRelay } from "./replacement-relay.js";
|
|
8
9
|
export const HANDOFF_HINT_ENTRY_TYPE = "handoff-hint";
|
|
9
10
|
export const HANDOFF_BRIEF_MESSAGE_TYPE = "handoff-brief";
|
|
10
11
|
export const HANDOFF_HINT_EVENT = "handoff_hint";
|
|
@@ -51,6 +52,7 @@ const readActiveGoal = (ctx) => readGoal(goalStoreRef(ctx.sessionManager, ctx.cw
|
|
|
51
52
|
export function createHandoffHintExtension(dependencies = {}) {
|
|
52
53
|
const now = dependencies.now ?? Date.now;
|
|
53
54
|
const goalOf = dependencies.readGoal ?? readActiveGoal;
|
|
55
|
+
const acceptances = createReplacementRelay();
|
|
54
56
|
return (pi) => {
|
|
55
57
|
let ctx;
|
|
56
58
|
let timer;
|
|
@@ -168,22 +170,21 @@ export function createHandoffHintExtension(dependencies = {}) {
|
|
|
168
170
|
previousSessionFile: current.sessionManager.getSessionFile(),
|
|
169
171
|
contextTokens: usage?.tokens ?? estimate?.contextTokens ?? 0,
|
|
170
172
|
});
|
|
171
|
-
const accepted = estimate === undefined ? undefined : hintEvent("accepted", current, estimate);
|
|
172
173
|
const sessionId = current.sessionManager.getSessionId();
|
|
174
|
+
const previousSessionFile = current.sessionManager.getSessionFile();
|
|
175
|
+
// The replacement's session_start emits it through the new generation's pi (senpi#4051).
|
|
176
|
+
if (estimate !== undefined)
|
|
177
|
+
acceptances.hold(previousSessionFile, hintEvent("accepted", current, estimate));
|
|
173
178
|
hide(current);
|
|
174
|
-
const
|
|
175
|
-
parentSession:
|
|
179
|
+
const switching = current.newSession({
|
|
180
|
+
parentSession: previousSessionFile,
|
|
176
181
|
withSession: async (next) => {
|
|
177
182
|
await next.sendMessage({ customType: HANDOFF_BRIEF_MESSAGE_TYPE, content: brief, display: true }, { triggerTurn: false });
|
|
178
183
|
},
|
|
179
184
|
});
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
if (accepted !== undefined)
|
|
186
|
-
pi.events?.emit(HANDOFF_HINT_EVENT, accepted);
|
|
185
|
+
const result = await switching.finally(() => acceptances.release(previousSessionFile));
|
|
186
|
+
if (result.cancelled && estimate !== undefined && current.sessionManager.getSessionId() === sessionId)
|
|
187
|
+
render(current, estimate);
|
|
187
188
|
}
|
|
188
189
|
pi.registerCommand(HANDOFF_COMMAND, {
|
|
189
190
|
description: "Start a fresh session seeded with a short handoff brief (`/handoff keep` hides the hint)",
|
|
@@ -207,8 +208,11 @@ export function createHandoffHintExtension(dependencies = {}) {
|
|
|
207
208
|
await startFresh(current);
|
|
208
209
|
},
|
|
209
210
|
});
|
|
210
|
-
pi.on("session_start", (
|
|
211
|
+
pi.on("session_start", (event, next) => {
|
|
211
212
|
ctx = next;
|
|
213
|
+
const accepted = event.reason === "new" ? acceptances.take(event.previousSessionFile) : undefined;
|
|
214
|
+
if (accepted !== undefined)
|
|
215
|
+
pi.events?.emit(HANDOFF_HINT_EVENT, accepted);
|
|
212
216
|
disarm();
|
|
213
217
|
visible = undefined;
|
|
214
218
|
shownThisCrossing = false;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carries a value from a session to the session that replaces it (senpi#4051).
|
|
3
|
+
*
|
|
4
|
+
* `newSession()` retires the old extension generation before the replacement starts, so a captured `pi` cannot emit
|
|
5
|
+
* after the switch, and emitting before it would report a switch that `session_before_switch` may still cancel. The
|
|
6
|
+
* builtin factory outlives both generations in one process, so the value waits here, keyed by the session being
|
|
7
|
+
* replaced, until the replacement's `session_start` takes it through its own live `pi`. The holder releases its key
|
|
8
|
+
* once `newSession()` settles, so a cancelled or failed switch delivers nothing.
|
|
9
|
+
*/
|
|
10
|
+
export interface ReplacementRelay<T> {
|
|
11
|
+
hold(previousSessionFile: string | undefined, value: T): void;
|
|
12
|
+
take(previousSessionFile: string | undefined): T | undefined;
|
|
13
|
+
release(previousSessionFile: string | undefined): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function createReplacementRelay<T>(): ReplacementRelay<T>;
|
|
16
|
+
//# sourceMappingURL=replacement-relay.d.ts.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function createReplacementRelay() {
|
|
2
|
+
const pending = new Map();
|
|
3
|
+
return {
|
|
4
|
+
hold: (previousSessionFile, value) => {
|
|
5
|
+
pending.set(previousSessionFile, value);
|
|
6
|
+
},
|
|
7
|
+
take: (previousSessionFile) => {
|
|
8
|
+
const value = pending.get(previousSessionFile);
|
|
9
|
+
pending.delete(previousSessionFile);
|
|
10
|
+
return value;
|
|
11
|
+
},
|
|
12
|
+
release: (previousSessionFile) => {
|
|
13
|
+
pending.delete(previousSessionFile);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=replacement-relay.js.map
|
|
@@ -85,7 +85,7 @@ ${buildSearchLine(context)}
|
|
|
85
85
|
Route by true intent, not surface form:
|
|
86
86
|
- Information asks (explain, look into, investigate): read the code and report; no edits.
|
|
87
87
|
- Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
|
|
88
|
-
- Change asks (implement, add, fix this error): build, or diagnose and fix
|
|
88
|
+
- Change asks (implement, add, fix this error): build, or diagnose and fix at the cause.
|
|
89
89
|
|
|
90
90
|
Derive intent from the latest user turn alone: a new direction drops the stale plan, and queued steering messages outrank earlier intent.
|
|
91
91
|
|
|
@@ -93,7 +93,7 @@ Derive intent from the latest user turn alone: a new direction drops the stale p
|
|
|
93
93
|
|
|
94
94
|
The request sets the scope, and the scope is the deliverable: deliver all of it and only it. Make routine judgment calls yourself; ask only when different readings would lead to materially different work, and ask after doing everything that does not depend on the answer, through ask_user_question when it is available (waitForAnswer true when the next step depends on the answer). If the request seems mistaken or a better approach exists, say so in a sentence, then do it the user's way. If part of the task is blocked, finish every other part and say exactly what you left out and why.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Change it the way its owner would: build on the code that already does the job instead of adding a parallel copy, fit new behavior into the structure that is there, and leave no duplicate or dead code behind. A defect in the code you are changing is yours to fix now; one elsewhere goes in your summary as a follow-up. Add no helpers or abstractions for hypothetical needs and no defensive checks inside trusted code; validate only at system boundaries. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
|
|
97
97
|
|
|
98
98
|
## Working the Task
|
|
99
99
|
|
|
@@ -107,7 +107,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
|
|
|
107
107
|
|
|
108
108
|
${buildTestDisciplineSection(context.sharedSections)}
|
|
109
109
|
|
|
110
|
-
Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix
|
|
110
|
+
Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix failures your change caused and any that sit in the code you changed; note other pre-existing failures separately.
|
|
111
111
|
|
|
112
112
|
${context.toolSection}
|
|
113
113
|
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
// - Scope, structure ("Say who owns the code"): the guide's ownership sentence
|
|
20
20
|
// opens the structure paragraph and replaces the smallest-change sentence
|
|
21
21
|
// (owner order). Without it, 5.2 acts as a guest: parallel code, duplicates
|
|
22
|
-
// left in place, and nearby bugs only mentioned.
|
|
22
|
+
// left in place, and nearby bugs only mentioned. senpi#6358 carries the same
|
|
23
|
+
// sentence, with what the owner does, into every core.
|
|
23
24
|
// - Working the Task, delegation ("Set expectations for subagents"): the 5.1
|
|
24
25
|
// sentence was a standing invitation to delegate, which the guide says to
|
|
25
26
|
// review first. It now states the cost before the invitation, what a brief
|
|
@@ -67,7 +68,7 @@ ${buildSearchLine(context)}
|
|
|
67
68
|
Route by true intent, not surface form:
|
|
68
69
|
- Information asks (explain, look into, investigate): read the code and report; no edits.
|
|
69
70
|
- Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
|
|
70
|
-
- Change asks (implement, add, fix this error): build, or diagnose and fix
|
|
71
|
+
- Change asks (implement, add, fix this error): build, or diagnose and fix at the cause.
|
|
71
72
|
|
|
72
73
|
Derive intent from the latest user turn alone: a new direction drops the stale plan, and queued steering messages outrank earlier intent.
|
|
73
74
|
|
|
@@ -77,7 +78,7 @@ The request sets the scope, and the scope is the deliverable: deliver all of it
|
|
|
77
78
|
|
|
78
79
|
Errors, timeouts, locked files, empty results, and failing tools are ordinary obstacles, not blockers: diagnose them, then work through them with the access you have (wait and retry a call that is safe to repeat, fix the request, use another tool or source) without asking first. A file marked not to be touched, withheld access, or a safety guardrail is a deliberate blocker: leave it alone and look for another safe way to finish. If part of the task stays blocked, finish every other part and say exactly what you left out and why.
|
|
79
80
|
|
|
80
|
-
This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it.
|
|
81
|
+
This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Change it the way its owner would: build on the code that already does the job instead of adding a parallel copy, fit new behavior into the structure that is there, and leave no duplicate or dead code behind. A defect in the code you are changing is yours to fix now; one elsewhere goes in your summary as a follow-up. Add no helpers or abstractions for hypothetical needs and no defensive checks inside trusted code; validate only at system boundaries. Scratch checks verify and get discarded; commit tests only where the task asks for them or the repository already keeps tests for that kind of change, sized like the neighboring test files. Prefer a surgical edit over rewriting a file when the result would be identical.
|
|
81
82
|
|
|
82
83
|
## Working the Task
|
|
83
84
|
|
|
@@ -91,7 +92,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
|
|
|
91
92
|
|
|
92
93
|
${buildTestDisciplineSection(context.sharedSections)}
|
|
93
94
|
|
|
94
|
-
Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix
|
|
95
|
+
Before reporting progress, audit each claim against a tool result from this session; ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with their output."} Fix failures your change caused and any that sit in the code you changed; note other pre-existing failures separately.
|
|
95
96
|
|
|
96
97
|
${context.toolSection}
|
|
97
98
|
|
|
@@ -59,7 +59,7 @@ ${buildSearchLine(context)}
|
|
|
59
59
|
Route by true intent, not surface form:
|
|
60
60
|
- Information asks (explain, look into, investigate): read the code, report the answer or findings - no edits, no fixes yet.
|
|
61
61
|
- Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
|
|
62
|
-
- Change asks (implement, add, fix this error): build, or diagnose and fix
|
|
62
|
+
- Change asks (implement, add, fix this error): build, or diagnose and fix at the cause, at exactly the asked scope - what fully satisfies an open-ended goal and nothing beyond it; name an ambiguity and resolve it from context when possible.
|
|
63
63
|
|
|
64
64
|
Derive intent from the latest user turn alone: a new direction drops the stale plan; queued steering messages outrank earlier intent. Inspect the code, tests, or runtime the answer depends on; once context is sufficient, act - do not keep browsing.
|
|
65
65
|
|
|
@@ -80,7 +80,7 @@ Tier the scope, never the rigor:
|
|
|
80
80
|
|
|
81
81
|
${buildTestDisciplineSection(context.sharedSections)}
|
|
82
82
|
|
|
83
|
-
"Should pass" is not verification - run the validator. Before reporting progress, audit each claim against a tool result from this session: ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with the output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with the output."} Fix
|
|
83
|
+
"Should pass" is not verification - run the validator. Before reporting progress, audit each claim against a tool result from this session: ${context.surface !== "terminal" ? `report only evidence-backed work and report failing tests with the output. ${APP_UNRUN_CHECK_RULE}` : "report only evidence-backed work, flag the unverified explicitly, and report failing tests with the output."} Fix failures your change caused and any that sit in the code you changed; note other pre-existing failures separately.
|
|
84
84
|
|
|
85
85
|
${context.toolSection}
|
|
86
86
|
|
|
@@ -96,7 +96,7 @@ ${buildHandoffSection({ turnEndRuleStatedElsewhere: true, surface: context.surfa
|
|
|
96
96
|
|
|
97
97
|
## Style
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
This codebase is yours: what it's for and how it's built are your call, and you'll keep building on it. Change it the way its owner would: build on the code that already does the job instead of adding a parallel copy, fit new behavior into the structure that is there, and leave no duplicate or dead code behind. A defect in the code you are changing is yours to fix now; one elsewhere goes in your summary as a follow-up. No helpers or abstractions for hypothetical needs, no defensive checks inside trusted code. Trust framework guarantees; validate only at system boundaries.
|
|
100
100
|
|
|
101
101
|
Act, then report. Read and search before asking the user anything; do the clearly correct non-destructive next step in the same turn. Permission-begging ("Shall I?") is prohibited. Pause only when the work genuinely requires the user - a destructive or irreversible action, a real scope change, or input only they can provide - then ask, through ask_user_question when it is available (waitForAnswer true when the next step depends on the answer), and end the turn rather than ending on a promise; for destructive actions, state the recommended action and stop. Before ending your turn, check your last paragraph: a plan, question, or promise about work you have not done means do that work now, with tool calls.
|
|
102
102
|
|