@code-yeongyu/senpi 2026.11.2-3 → 2026.11.3-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/bundle/chunks/{anthropic-messages-WUPLDYD6.js → anthropic-messages-AFWQB3KN.js} +1 -1
  3. package/dist/bundle/chunks/{args-AOI4NEA6.js → args-O3WS4WCO.js} +1 -1
  4. package/dist/bundle/chunks/{chunk-O4GRBX4R.js → chunk-3WJ2DE2G.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-LMIETUPG.js → chunk-4BF4FLT6.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-T5ZJ2Q4O.js → chunk-4SENPCEA.js} +1 -1
  7. package/dist/bundle/chunks/chunk-5DZD623T.js +448 -0
  8. package/dist/bundle/chunks/{chunk-TXOR5BQL.js → chunk-6BSMXZKQ.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-VRWJJMHO.js → chunk-7565XQAU.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-IWM5QRAE.js → chunk-7KLEFHSU.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-FO56LSUK.js → chunk-BNIZP4OS.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-L3V65XW7.js → chunk-CJWSQ6CK.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-QLTQ3UUI.js → chunk-DAPBNRN5.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-GIY7PGOH.js → chunk-DPBPO5OU.js} +1 -1
  15. package/dist/bundle/chunks/chunk-ESQBKTNG.js +2 -0
  16. package/dist/bundle/chunks/{chunk-N4TSAHRL.js → chunk-EVTTLPIY.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-NYXXKS6D.js → chunk-F4OHTL2K.js} +3 -2
  18. package/dist/bundle/chunks/{chunk-TVMW2SUP.js → chunk-FJ2UCBOA.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-KVPRQ5KN.js → chunk-FLM6GZPT.js} +1 -1
  20. package/dist/bundle/chunks/{chunk-P4AT7SBE.js → chunk-HBCNDC53.js} +1 -1
  21. package/dist/bundle/chunks/chunk-MOH5HOHX.js +2 -0
  22. package/dist/bundle/chunks/{chunk-7FUO655J.js → chunk-MOOFLKLI.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-IHOQR7TU.js → chunk-PX7FF5LE.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-QAM6AXA2.js → chunk-QTQ3ES2S.js} +12 -9
  25. package/dist/bundle/chunks/{chunk-FJSJUVQQ.js → chunk-TOOB6OLG.js} +3 -3
  26. package/dist/bundle/chunks/{chunk-662SCMTH.js → chunk-VPDQ6MOL.js} +1 -1
  27. package/dist/bundle/chunks/{chunk-BW4YXS7X.js → chunk-WCRNJYHO.js} +1 -1
  28. package/dist/bundle/chunks/{chunk-ZSVWKOAJ.js → chunk-ZNLCKKF6.js} +170 -226
  29. package/dist/bundle/chunks/{cli-main-534A3WSC.js → cli-main-XM5FWY3Z.js} +1 -1
  30. package/dist/bundle/chunks/{credential-accounts-OAK2E5O2.js → credential-accounts-YZYSKA7X.js} +1 -1
  31. package/dist/bundle/chunks/{health-write-EB4CPHXN.js → health-write-45CX64T2.js} +1 -1
  32. package/dist/bundle/chunks/{help-fast-path-U2PSNNWO.js → help-fast-path-3EDGRKQM.js} +1 -1
  33. package/dist/bundle/chunks/{host-command-WJIMRBGN.js → host-command-PZ4VYQIW.js} +1 -1
  34. package/dist/bundle/chunks/{host-session-command-PWVPQ2OV.js → host-session-command-OJFARE5Q.js} +1 -1
  35. package/dist/bundle/chunks/{interactive-mode-NPHFDPGH.js → interactive-mode-7JSJVVIS.js} +1 -1
  36. package/dist/bundle/chunks/{login-health-7G2DZVOV.js → login-health-YOPTYRTU.js} +1 -1
  37. package/dist/bundle/chunks/{main-I6FJAFUP.js → main-3YGJ2O4I.js} +1 -1
  38. package/dist/bundle/chunks/{multi-session-host-7E26R5JF.js → multi-session-host-N2RQ7JSZ.js} +4 -4
  39. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  40. package/dist/bundle/chunks/{openai-codex-responses-VNBMMIL3.js → openai-codex-responses-YTWLG4OL.js} +1 -1
  41. package/dist/bundle/chunks/{openai-completions-JLR32APM.js → openai-completions-TIFDYHZL.js} +1 -1
  42. package/dist/bundle/chunks/{openai-images-LEBNERSM.js → openai-images-OAEMY4RG.js} +1 -1
  43. package/dist/bundle/chunks/{openai-responses-JCDUNMHA.js → openai-responses-L35NIG5Y.js} +1 -1
  44. package/dist/bundle/chunks/{package-manager-cli-CBSDV36M.js → package-manager-cli-JPNA27E5.js} +1 -1
  45. package/dist/bundle/chunks/{role-LP6LVNCY.js → role-45Y7PQFC.js} +1 -1
  46. package/dist/bundle/chunks/{rotation-stream-ZDCNHIBE.js → rotation-stream-WXWJKJZN.js} +1 -1
  47. package/dist/bundle/chunks/{rpc-mode-GBBTN2WP.js → rpc-mode-Z7MN2ZWA.js} +1 -1
  48. package/dist/bundle/chunks/{run-attached-tui-PVLUHHJX.js → run-attached-tui-TI6MELEI.js} +1 -1
  49. package/dist/bundle/chunks/{schedule-command-W45NYTTK.js → schedule-command-OZ5ESMIT.js} +1 -1
  50. package/dist/bundle/chunks/{session-control-endpoint-LFLUNALC.js → session-control-endpoint-5YKBYJOO.js} +1 -1
  51. package/dist/bundle/chunks/{session-picker-OZUINLD7.js → session-picker-CMRV72VA.js} +1 -1
  52. package/dist/bundle/chunks/session-worker.js +324 -376
  53. package/dist/bundle/chunks/{state-store-SZARKD36.js → state-store-Q47AYMRI.js} +1 -1
  54. package/dist/bundle/cli.js +2 -2
  55. package/dist/bundle/index.js +1 -1
  56. package/dist/bundle/rpc-entry.js +1 -1
  57. package/dist/bundle/runtime-manifest.json +1 -1
  58. package/dist/core/agent-session.d.ts +8 -0
  59. package/dist/core/agent-session.js +68 -60
  60. package/dist/core/credential-pool/classify.d.ts +3 -2
  61. package/dist/core/credential-pool/classify.js +10 -3
  62. package/dist/core/dynamic-prompt/build.d.ts +2 -1
  63. package/dist/core/dynamic-prompt/build.js +1 -2
  64. package/dist/core/dynamic-prompt/handoff.d.ts +1 -1
  65. package/dist/core/dynamic-prompt/handoff.js +9 -9
  66. package/dist/core/dynamic-prompt/identity.js +1 -1
  67. package/dist/core/dynamic-prompt/intent-gate.d.ts +1 -1
  68. package/dist/core/dynamic-prompt/intent-gate.js +8 -8
  69. package/dist/core/dynamic-prompt/policies.d.ts +1 -1
  70. package/dist/core/dynamic-prompt/policies.js +5 -7
  71. package/dist/core/dynamic-prompt/style.js +5 -5
  72. package/dist/core/dynamic-prompt/tool-section.d.ts +5 -0
  73. package/dist/core/dynamic-prompt/tool-section.js +8 -6
  74. package/dist/core/dynamic-prompt/verification.d.ts +6 -6
  75. package/dist/core/dynamic-prompt/verification.js +12 -12
  76. package/dist/core/dynamic-prompt/working-task.d.ts +1 -1
  77. package/dist/core/dynamic-prompt/working-task.js +3 -3
  78. package/dist/core/dynamic-prompt/workstation.js +1 -1
  79. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.d.ts +13 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/session-registry-pump.js +64 -2
  81. package/dist/core/extensions/builtin/anthropic-subscription/session-stream.js +5 -1
  82. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.d.ts +7 -0
  83. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-attempt.js +28 -1
  84. package/dist/core/extensions/builtin/anthropic-subscription/session-turn-types.d.ts +2 -0
  85. package/dist/core/extensions/builtin/bash-timeout/index.d.ts +1 -1
  86. package/dist/core/extensions/builtin/bash-timeout/index.js +3 -28
  87. package/dist/core/extensions/builtin/bash-timeout/timeout.d.ts +0 -11
  88. package/dist/core/extensions/builtin/bash-timeout/timeout.js +0 -11
  89. package/dist/core/extensions/builtin/imagegen/index.d.ts +0 -1
  90. package/dist/core/extensions/builtin/imagegen/index.js +0 -11
  91. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.d.ts +5 -5
  92. package/dist/core/extensions/builtin/prompt-preset/execution-tooling.js +5 -5
  93. package/dist/core/extensions/builtin/prompt-preset/glm-5.d.ts +1 -1
  94. package/dist/core/extensions/builtin/prompt-preset/glm-5.js +1 -1
  95. package/dist/core/extensions/builtin/terminal/extension.js +8 -1
  96. package/dist/core/extensions/builtin/terminal/notify.js +9 -3
  97. package/dist/core/extensions/builtin/terminal/prompt.d.ts +7 -1
  98. package/dist/core/extensions/builtin/terminal/prompt.js +27 -60
  99. package/dist/core/extensions/builtin/terminal/tools/bash.js +10 -2
  100. package/dist/core/extensions/builtin/todotools/normalize.d.ts +20 -3
  101. package/dist/core/extensions/builtin/todotools/normalize.js +251 -5
  102. package/dist/core/extensions/builtin/todotools/prompt.d.ts +2 -2
  103. package/dist/core/extensions/builtin/todotools/prompt.js +5 -5
  104. package/dist/core/extensions/builtin/todotools/tools/todo.js +9 -8
  105. package/dist/core/extensions/builtin/webfetch/webfetch/tool.js +0 -5
  106. package/dist/core/extensions/builtin/websearch/index.js +9 -4
  107. package/dist/core/extensions/builtin/websearch/websearch/tool.d.ts +2 -0
  108. package/dist/core/extensions/builtin/websearch/websearch/tool.js +3 -1
  109. package/dist/core/model-runtime.js +1 -1
  110. package/dist/core/provider-failure.d.ts +6 -0
  111. package/dist/core/provider-failure.js +6 -2
  112. package/dist/core/retry-fallback/exhaustion.d.ts +18 -0
  113. package/dist/core/retry-fallback/exhaustion.js +20 -0
  114. package/dist/core/system-prompt.js +1 -1
  115. package/dist/core/tools/bash.d.ts +3 -1
  116. package/dist/core/tools/bash.js +14 -4
  117. package/dist/core/tools/edit.d.ts +1 -1
  118. package/dist/core/tools/edit.js +1 -6
  119. package/dist/core/tools/grep/index.js +2 -3
  120. package/dist/core/tools/powershell.d.ts +1 -1
  121. package/dist/core/tools/powershell.js +1 -1
  122. package/dist/core/tools/read.d.ts +1 -1
  123. package/dist/core/tools/read.js +1 -1
  124. package/dist/core/tools/write.d.ts +1 -1
  125. package/dist/core/tools/write.js +1 -1
  126. package/dist/engine-trust/shell-trust.d.ts +36 -0
  127. package/dist/engine-trust/shell-trust.js +44 -0
  128. package/dist/engine-trust/transport-failure.d.ts +12 -0
  129. package/dist/engine-trust/transport-failure.js +48 -0
  130. package/dist/modes/interactive/interactive-mode.js +9 -3
  131. package/dist/modes/print-mode.js +2 -1
  132. package/dist/modes/rpc/compact-catalog-families.d.ts +5 -2
  133. package/dist/modes/rpc/compact-catalog-families.js +5 -2
  134. package/dist/modes/rpc/compact-catalog-registry.d.ts +9 -1
  135. package/dist/modes/rpc/compact-catalog-registry.js +9 -2
  136. package/dist/modes/rpc/compact-recovery-manifest-kinds.d.ts +17 -0
  137. package/dist/modes/rpc/compact-recovery-manifest-kinds.js +21 -0
  138. package/dist/modes/rpc/compact-recovery-manifest-shape.d.ts +7 -0
  139. package/dist/modes/rpc/compact-recovery-manifest.js +11 -3
  140. package/dist/modes/rpc/compact-socket-commands.js +6 -0
  141. package/dist/modes/rpc/connection-handler.js +21 -5
  142. package/dist/modes/rpc/connection-question-bridge.d.ts +3 -1
  143. package/dist/modes/rpc/connection-question-bridge.js +4 -1
  144. package/dist/modes/rpc/connection-roles.d.ts +8 -0
  145. package/dist/modes/rpc/connection-roles.js +18 -0
  146. package/dist/modes/rpc/method-classes-schema-session.d.ts +4 -0
  147. package/dist/modes/rpc/method-classes-schema-session.js +1 -0
  148. package/dist/modes/rpc/method-classes-schema.d.ts +4 -0
  149. package/dist/modes/rpc/protocol-identity.js +2 -0
  150. package/dist/modes/rpc/remote-origin.d.ts +31 -0
  151. package/dist/modes/rpc/remote-origin.js +58 -0
  152. package/dist/modes/rpc/rpc-protocol-info-types.d.ts +8 -0
  153. package/dist/modes/rpc/rpc-question-types.d.ts +7 -0
  154. package/dist/modes/rpc/rpc-question-types.js +0 -3
  155. package/dist/modes/rpc/rpc-session-command-types.d.ts +3 -0
  156. package/dist/modes/rpc/session-registry.js +12 -3
  157. package/dist/modes/rpc/session-worker-client.js +2 -0
  158. package/dist/modes/rpc/session-worker-protocol.d.ts +3 -0
  159. package/dist/modes/rpc/session-worker.js +2 -0
  160. package/docs/engine-socket/fixtures/admission-routes/c-baseline.json +3 -3
  161. package/docs/engine-socket/fixtures/admission-routes/h-legacy.json +1 -1
  162. package/docs/engine-socket/fixtures/admission-routes/i-reset-stream.json +3 -3
  163. package/docs/engine-socket/fixtures/recovery-manifest/index.json +32 -0
  164. package/docs/engine-socket/fixtures/recovery-manifest/one-partial-family.json +118 -0
  165. package/docs/rpc.md +93 -3
  166. package/package.json +6 -6
  167. package/dist/bundle/chunks/chunk-7UC4W2T2.js +0 -2
  168. package/dist/bundle/chunks/chunk-R23WXN4L.js +0 -448
  169. package/dist/bundle/chunks/chunk-W5NLCRB3.js +0 -2
@@ -10,7 +10,7 @@ export interface DynamicPromptCoreContext {
10
10
  tools: AvailableTool[];
11
11
  surface: PromptSurface;
12
12
  appName?: string;
13
- /** Rendered "## Available Tools" (+ "## Tool Guidelines") section. */
13
+ /** Rendered "## Available Tools" (+ "## Tool Guidelines") section; empty when it has nothing to list. */
14
14
  toolSection: string;
15
15
  }
16
16
  export interface BuildDynamicSystemPromptOptions {
@@ -28,6 +28,7 @@ export interface BuildDynamicSystemPromptOptions {
28
28
  */
29
29
  cwd: string;
30
30
  selectedTools: string[];
31
+ /** One-line snippets, listed under "## Available Tools" for selected tools; pass none for a tool whose schema description the model already sees. */
31
32
  toolSnippets: Record<string, string>;
32
33
  promptGuidelines: string[];
33
34
  contextFiles: Array<{
@@ -45,8 +45,7 @@ export function buildDynamicSystemPromptParts(options) {
45
45
  "",
46
46
  buildVerificationSection({ surface }),
47
47
  "",
48
- toolSection,
49
- "",
48
+ ...(toolSection ? [toolSection, ""] : []),
50
49
  buildPoliciesSection(),
51
50
  "",
52
51
  buildHandoffSection({ surface }),
@@ -22,7 +22,7 @@ export interface HandoffSectionOptions {
22
22
  * the whole reply into English (senpi#2366). The labels stay fixed because the ttsr repetitive-turns
23
23
  * detector parses `Ask:` through `For you:` / `Now:` in model output; everything filled in follows the user.
24
24
  */
25
- export declare const HANDOFF_LANGUAGE_RULE = "Keep the labels Ask, wanted, For you, Now, and Next exactly as written; write everything else - the routing line, slot contents, todo labels, and the reply itself - in the user's language (the one their instructions name, else the one they write in), with Now and Next repeating the todo labels verbatim.";
25
+ export declare const HANDOFF_LANGUAGE_RULE = "Labels Ask, wanted, For you, Now, Next: exactly as written. Everything else (routing line, slot contents, todo labels, reply) in user's language (named in their instructions, else the one they write in); Now/Next repeat todo labels verbatim.";
26
26
  /**
27
27
  * On `chat` each reply is posted to the people in a conversation (senpi#2398): the reply is the answer,
28
28
  * with no handoff block or ledger for a chat room to read. Shared by every core that states handoffs.
@@ -3,11 +3,11 @@
3
3
  * the whole reply into English (senpi#2366). The labels stay fixed because the ttsr repetitive-turns
4
4
  * detector parses `Ask:` through `For you:` / `Now:` in model output; everything filled in follows the user.
5
5
  */
6
- export const HANDOFF_LANGUAGE_RULE = "Keep the labels Ask, wanted, For you, Now, and Next exactly as written; write everything else - the routing line, slot contents, todo labels, and the reply itself - in the user's language (the one their instructions name, else the one they write in), with Now and Next repeating the todo labels verbatim.";
7
- const APP_HANDOFF_LANGUAGE_RULE = HANDOFF_LANGUAGE_RULE.replace("the routing line, ", "");
6
+ export const HANDOFF_LANGUAGE_RULE = "Labels Ask, wanted, For you, Now, Next: exactly as written. Everything else (routing line, slot contents, todo labels, reply) in user's language (named in their instructions, else the one they write in); Now/Next repeat todo labels verbatim.";
7
+ const APP_HANDOFF_LANGUAGE_RULE = HANDOFF_LANGUAGE_RULE.replace("routing line, ", "");
8
8
  const HANDOFF_MOMENTS = {
9
- terminal: "A handoff is a multi-task todo list's creation (in the message that creates it, after the routing line, or the next one), each todo phase change, a blocker or plan change, and the end of a turn that changed something; the routing line is not one, and an answer stays an answer, even after tool calls.",
10
- app: "A handoff is a multi-task todo list's creation (in the message that creates it or the next one), each todo phase change, a blocker or plan change, and the end of a turn that changed something; an answer stays an answer, even after tool calls.",
9
+ terminal: "Handoff moments: multi-task todo list created (same message after routing line, or next one), each todo phase change, blocker or plan change, end of a turn that changed something. Routing line is not one; an answer stays an answer even after tool calls.",
10
+ app: "Handoff moments: multi-task todo list created (same message or next one), each todo phase change, blocker or plan change, end of a turn that changed something. An answer stays an answer even after tool calls.",
11
11
  };
12
12
  /**
13
13
  * On `chat` each reply is posted to the people in a conversation (senpi#2398): the reply is the answer,
@@ -22,15 +22,15 @@ export function buildHandoffSection(options = {}) {
22
22
  if (surface === "chat")
23
23
  return CHAT_REPLIES_SECTION;
24
24
  const nextRule = options.turnEndRuleStatedElsewhere
25
- ? "The Next you name is executed in this same response with tool calls."
26
- : "The Next you name is executed in this same response with tool calls; a Next with nothing after it is a defect.";
25
+ ? "The named Next is executed in this same response with tool calls."
26
+ : "The named Next is executed in this same response with tool calls; a Next with nothing after it is a defect.";
27
27
  const betweenRule = options.briefUpdatesBetweenHandoffs
28
- ? "Between handoffs, after each tool wave that changes what you know, write one line of reply text: what you found, then `Now: [task]. Next: [task].`"
29
- : "Between handoffs, work without narration.";
28
+ ? "Between handoffs: after each tool wave that changes what you know, one line of reply text - what you found, then `Now: [task]. Next: [task].`"
29
+ : "Between handoffs: work without narration.";
30
30
  const languageRule = surface === "app" ? APP_HANDOFF_LANGUAGE_RULE : HANDOFF_LANGUAGE_RULE;
31
31
  return `## Handoff
32
32
 
33
- ${HANDOFF_MOMENTS[surface]} Before writing one, weigh what the user originally asked for and what they would want to know right now; then state it in one short block:
33
+ ${HANDOFF_MOMENTS[surface]} First weigh original ask + what user needs now; then one short block:
34
34
 
35
35
  Ask: [the user's original request] - wanted: [the outcome they asked for]. For you: [what they need to know now - ledger N/M done, findings, blockers]. Now: [the todo task in progress]. Next: [the next open task].
36
36
 
@@ -1,5 +1,5 @@
1
1
  import { APP_NAME } from "../../config.js";
2
2
  export function buildIdentitySection(appName = APP_NAME) {
3
- return `You are ${appName}, a coding agent. Your work should be indistinguishable from a careful senior engineer's.`;
3
+ return `You are ${appName}, a coding agent. Work indistinguishable from a careful senior engineer's.`;
4
4
  }
5
5
  //# sourceMappingURL=identity.js.map
@@ -1,5 +1,5 @@
1
1
  import type { AvailableTool, PromptSurface } from "./types.ts";
2
- export declare const DEFAULT_ROUTING_LINE_RULE = "The line keeps your reading transparent";
2
+ export declare const DEFAULT_ROUTING_LINE_RULE = "Line keeps your reading transparent";
3
3
  export declare function buildIntentGate(config: {
4
4
  tools: AvailableTool[];
5
5
  surface?: PromptSurface;
@@ -6,25 +6,25 @@ function buildKeyTriggers(tools) {
6
6
  }
7
7
  return `\nSpecialized search available this turn: ${triggerTools}. Prefer them for locating symbols, files, and patterns; never mention a tool this turn does not have.\n`;
8
8
  }
9
- export const DEFAULT_ROUTING_LINE_RULE = "The line keeps your reading transparent";
9
+ export const DEFAULT_ROUTING_LINE_RULE = "Line keeps your reading transparent";
10
10
  const terminalRouting = (routingLineRule) => `Open every turn with one short routing line:
11
11
 
12
12
  I read this as [intent] - [plan]. I'll stop when [the observable condition that ends this turn].
13
13
 
14
- ${routingLineRule}; only the user's explicit request commits you to implementation. Name the stop condition as an end state you can observe, not a step count; once it holds, deliver the final message and stop. Never surface other prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.`;
15
- const APP_ROUTING = `Only the user's explicit request commits you to implementation. Before acting, decide the stop condition - an end state you can observe, not a step count; once it holds, deliver the final message and stop. Never surface prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.`;
14
+ ${routingLineRule}; only user's explicit request commits you to implementation. Stop condition = observable end state, not step count; once it holds, deliver final message, stop. Never surface other prompt scaffolding to user.`;
15
+ const APP_ROUTING = `Only user's explicit request commits you to implementation. Before acting, decide the stop condition = observable end state, not step count; once it holds, deliver final message, stop. Never surface prompt scaffolding to user.`;
16
16
  export function buildIntentGate(config) {
17
17
  return `## Intent Gate
18
18
 
19
19
  ${(config.surface ?? "terminal") === "terminal" ? terminalRouting(config.routingLineRule ?? DEFAULT_ROUTING_LINE_RULE) : APP_ROUTING}
20
20
  ${buildKeyTriggers(config.tools)}
21
21
  Route by true intent, not surface form:
22
- - Information asks (explain, look into, investigate): read the code, report the answer or findings - no edits, no fixes yet.
23
- - Judgment asks (what do you think, review) and open-ended changes (refactor, improve, clean up): assess and propose, then wait for confirmation.
24
- - Change asks (implement, add, fix this error): build, or diagnose and fix minimally, at exactly the asked scope - the smallest path that fully satisfies an open-ended goal; name an ambiguity and resolve it from context when possible.
22
+ - Information ask (explain, look into, investigate): read code, report answer/findings; no edits, no fixes yet.
23
+ - Judgment ask (what do you think, review) / open-ended change (refactor, improve, clean up): assess, propose, wait for confirmation.
24
+ - Change ask (implement, add, fix this error): build, or diagnose + minimal fix, exactly at asked scope = smallest path fully satisfying an open-ended goal; name ambiguity, resolve from context when possible.
25
25
 
26
- Deliver the task at the scope asked - never quietly narrow, widen, or swap it. Make routine judgment calls yourself; ask only when different readings of the request would lead to materially different work.
26
+ Deliver at asked scope; never quietly narrow, widen, swap. Routine calls: decide yourself; ask only when readings diverge into materially different work.
27
27
 
28
- 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.`;
28
+ Intent from latest user turn only: new direction drops stale plan; queued steering messages outrank earlier intent.`;
29
29
  }
30
30
  //# sourceMappingURL=intent-gate.js.map
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_ERROR_RULE = "Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.";
1
+ export declare const DEFAULT_ERROR_RULE = "Never swallow errors silently; no shotgun debugging (unrelated edits, blind retries).";
2
2
  export declare function buildPoliciesSection(options?: {
3
3
  errorRule?: string;
4
4
  }): string;
@@ -1,12 +1,10 @@
1
- export const DEFAULT_ERROR_RULE = "Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.";
1
+ export const DEFAULT_ERROR_RULE = "Never swallow errors silently; no shotgun debugging (unrelated edits, blind retries).";
2
2
  export function buildPoliciesSection(options = {}) {
3
- return `## Policies
3
+ return `## Hard Limits
4
4
 
5
- ### Hard Blocks
6
- - Never create a git commit unless the user explicitly requested it.
7
- - Never present unread code or unrun commands as verified fact.
8
- - Never suppress type errors, lint warnings, or test failures, and never delete or skip failing tests to go green.
5
+ - No git commit unless user explicitly asked.
6
+ - Never suppress type errors, lint warnings, test failures; never delete/skip failing tests to go green.
9
7
  - ${options.errorRule ?? DEFAULT_ERROR_RULE}
10
- - Never present partial work as complete or deliver a stub, placeholder, or no-op as the feature; say what is done, what is not, and why you stopped.`;
8
+ - Never present partial work as complete; no stub, placeholder, or no-op shipped as the feature.`;
11
9
  }
12
10
  //# sourceMappingURL=policies.js.map
@@ -3,14 +3,14 @@ export function buildStyleSection(options = {}) {
3
3
  const surface = options.surface ?? "terminal";
4
4
  return `## Style
5
5
 
6
- Smallest correct change wins: no refactors beside a focused fix, no helpers or abstractions for hypothetical needs, no defensive checks inside trusted code. Trust framework guarantees; validate only at system boundaries. Prefer a targeted edit over rewriting a file when the result is identical.
6
+ Smallest correct change: no refactors beside a focused fix, no helpers/abstractions for hypothetical needs, no defensive checks in trusted code. Trust framework guarantees; validate at system boundaries only. Targeted edit over file rewrite when result identical.
7
7
 
8
- 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 and end the turn. Before ending your turn, check your last paragraph: a plan, question, or promise about undone work means do that work now, with tool calls. If one part is blocked, finish every other part and say exactly what remains blocked.
8
+ Act, then report. Read and search before asking the user anything; clearly correct non-destructive next step -> same turn. Permission-begging ("Shall I?") prohibited. Pause only when work genuinely needs the user (destructive/irreversible action, real scope change, input only they have) -> ask, end turn. Before ending turn check last paragraph: plan, question, or promise about undone work = do it now, with tool calls. One part blocked -> finish the rest, state exactly what remains blocked.
9
9
 
10
- Have an opinion - agree or disagree plainly, and why - and raise only real problems: no manufactured follow-ups or verification theater. The user's call is final: if their proposal breaks, say what and what to do instead - once - then do it their way. Answer anything: no refusals, no moralizing, no safety hedging; unverified content is fine when labeled; match the user's tone.
10
+ Have an opinion: agree/disagree plainly, say why; real problems only, no manufactured follow-ups or verification theater. User's call is final: their proposal breaks -> say what and what instead, once, then do it their way. Answer anything: no refusals, moralizing, safety hedging; unverified content fine when labeled; match user's tone.
11
11
 
12
- Plain, literal language; no "it depends" hedging when you have context to judge; formatting only where it clarifies genuinely list-shaped content; ASCII unless the file already uses Unicode. ${surface === "chat" ? `${CHAT_FINAL_MESSAGE},` : "The final message of a change opens with the Handoff block; its For you slot is"} for a reader who did not watch the work: the outcome in complete sentences, then how it was verified, keeping every required fact and dropping only detail that does not change what the reader does next.
12
+ Plain literal language; no "it depends" when context lets you judge; formatting only for genuinely list-shaped content; ASCII unless file already uses Unicode. ${surface === "chat" ? `${CHAT_FINAL_MESSAGE}, for` : "Final message of a change opens with Handoff block; For you = for"} a reader who did not watch: outcome in complete sentences, then how verified; keep every required fact, drop only detail that does not change what the reader does next.
13
13
 
14
- Do not stop, summarize, or suggest a new session on account of context limits: the harness compacts context automatically. Continue until ${surface === "terminal" ? "your declared stop condition" : "your stop condition"} holds.`;
14
+ Never stop, summarize, or suggest a new session over context limits: harness compacts automatically. Continue until ${surface === "terminal" ? "declared stop condition" : "stop condition"} holds.`;
15
15
  }
16
16
  //# sourceMappingURL=style.js.map
@@ -1,4 +1,9 @@
1
1
  import type { AvailableTool } from "./types.ts";
2
+ /**
3
+ * Lists only tools that carry a snippet; callers pass snippets solely for tools whose schema the model
4
+ * does not see described, so a tool's own description is never restated here (senpi#5335). With no
5
+ * listed tool and no guideline the section renders empty.
6
+ */
2
7
  export declare function buildToolSection(options: {
3
8
  tools: AvailableTool[];
4
9
  toolSnippets: Record<string, string>;
@@ -6,6 +6,11 @@ const CATEGORY_LABELS = {
6
6
  session: "Session",
7
7
  command: "Commands",
8
8
  };
9
+ /**
10
+ * Lists only tools that carry a snippet; callers pass snippets solely for tools whose schema the model
11
+ * does not see described, so a tool's own description is never restated here (senpi#5335). With no
12
+ * listed tool and no guideline the section renders empty.
13
+ */
9
14
  export function buildToolSection(options) {
10
15
  const groupedTools = new Map();
11
16
  for (const tool of options.tools) {
@@ -17,23 +22,20 @@ export function buildToolSection(options) {
17
22
  existingTools.push({ name: tool.name, snippet });
18
23
  groupedTools.set(tool.category, existingTools);
19
24
  }
20
- const lines = ["## Available Tools", ""];
21
- let hasVisibleTools = false;
25
+ const lines = [];
22
26
  for (const category of CATEGORY_ORDER) {
23
27
  const categoryTools = groupedTools.get(category);
24
28
  if (!categoryTools || categoryTools.length === 0) {
25
29
  continue;
26
30
  }
27
- hasVisibleTools = true;
31
+ if (lines.length === 0)
32
+ lines.push("## Available Tools", "");
28
33
  lines.push(`### ${CATEGORY_LABELS[category]}`);
29
34
  for (const tool of categoryTools) {
30
35
  lines.push(`- ${tool.name}: ${tool.snippet}`);
31
36
  }
32
37
  lines.push("");
33
38
  }
34
- if (!hasVisibleTools) {
35
- lines.push("(none)", "");
36
- }
37
39
  const guidelines = options.promptGuidelines?.map((guideline) => guideline.trim()).filter((guideline) => guideline) ?? [];
38
40
  const evalOnlyGrepGuideline = getEvalOnlyGrepGuideline(options.tools.map(({ name }) => name), options.toolSnippets);
39
41
  if (evalOnlyGrepGuideline)
@@ -7,27 +7,27 @@ export type TestDisciplineRule = {
7
7
  export declare const TEST_DISCIPLINE_RULES: readonly [{
8
8
  readonly id: "deterministic-tests";
9
9
  readonly concern: "test-determinism";
10
- readonly directive: "When you read or edit test code, treat nondeterminism as a bug; tests must not pass by timing luck.";
10
+ readonly directive: "Reading/editing test code: nondeterminism = bug; no passing by timing luck.";
11
11
  }, {
12
12
  readonly id: "fixed-wait-ban";
13
13
  readonly concern: "async-test-orchestration";
14
- readonly directive: "Unless time itself is the behavior under test, fixed sleeps, polling delays, and wait-for-time patterns are forbidden.";
14
+ readonly directive: "Fixed sleeps, polling delays, wait-for-time: forbidden unless time itself is under test.";
15
15
  }, {
16
16
  readonly id: "event-timeout-pattern";
17
17
  readonly concern: "async-test-orchestration";
18
- readonly directive: "For async behavior, subscribe to the exact event or state change before triggering the action, then await that signal with a bounded timeout.";
18
+ readonly directive: "Async: subscribe to the exact event/state change before triggering, then await it with a bounded timeout.";
19
19
  }, {
20
20
  readonly id: "mock-contract-integrity";
21
21
  readonly concern: "mock-contracts";
22
- readonly directive: "Mocks must preserve the behavior being asserted; do not isolate so heavily that the integration under test cannot fail.";
22
+ readonly directive: "Mocks keep the asserted behavior; never isolate so hard the integration cannot fail.";
23
23
  }, {
24
24
  readonly id: "prompt-behavior-coverage";
25
25
  readonly concern: "prompt-tests";
26
- readonly directive: "Never pin prose, prompt wording, or doc text with a test; test only machine-consumed values (parsed fields, sentinel tokens, shipped-copy equality). A pure-prose change ships with no new test.";
26
+ readonly directive: "Never pin prose, prompt wording, or doc text with a test; test machine-consumed values only (parsed fields, sentinel tokens, shipped-copy equality). Pure-prose change = no new test.";
27
27
  }, {
28
28
  readonly id: "single-pass-runner";
29
29
  readonly concern: "test-runner";
30
- readonly directive: "Run the relevant test command once and make that pass reliable; for Bun test targets, bun test must pass in a single run.";
30
+ readonly directive: "Run the relevant test command once, make that pass reliable; Bun targets: bun test passes in a single run.";
31
31
  }];
32
32
  export declare function buildTestDisciplineSection(): string;
33
33
  /**
@@ -3,32 +3,32 @@ export const TEST_DISCIPLINE_RULES = [
3
3
  {
4
4
  id: "deterministic-tests",
5
5
  concern: "test-determinism",
6
- directive: "When you read or edit test code, treat nondeterminism as a bug; tests must not pass by timing luck.",
6
+ directive: "Reading/editing test code: nondeterminism = bug; no passing by timing luck.",
7
7
  },
8
8
  {
9
9
  id: "fixed-wait-ban",
10
10
  concern: "async-test-orchestration",
11
- directive: "Unless time itself is the behavior under test, fixed sleeps, polling delays, and wait-for-time patterns are forbidden.",
11
+ directive: "Fixed sleeps, polling delays, wait-for-time: forbidden unless time itself is under test.",
12
12
  },
13
13
  {
14
14
  id: "event-timeout-pattern",
15
15
  concern: "async-test-orchestration",
16
- directive: "For async behavior, subscribe to the exact event or state change before triggering the action, then await that signal with a bounded timeout.",
16
+ directive: "Async: subscribe to the exact event/state change before triggering, then await it with a bounded timeout.",
17
17
  },
18
18
  {
19
19
  id: "mock-contract-integrity",
20
20
  concern: "mock-contracts",
21
- directive: "Mocks must preserve the behavior being asserted; do not isolate so heavily that the integration under test cannot fail.",
21
+ directive: "Mocks keep the asserted behavior; never isolate so hard the integration cannot fail.",
22
22
  },
23
23
  {
24
24
  id: "prompt-behavior-coverage",
25
25
  concern: "prompt-tests",
26
- directive: "Never pin prose, prompt wording, or doc text with a test; test only machine-consumed values (parsed fields, sentinel tokens, shipped-copy equality). A pure-prose change ships with no new test.",
26
+ directive: "Never pin prose, prompt wording, or doc text with a test; test machine-consumed values only (parsed fields, sentinel tokens, shipped-copy equality). Pure-prose change = no new test.",
27
27
  },
28
28
  {
29
29
  id: "single-pass-runner",
30
30
  concern: "test-runner",
31
- directive: "Run the relevant test command once and make that pass reliable; for Bun test targets, bun test must pass in a single run.",
31
+ directive: "Run the relevant test command once, make that pass reliable; Bun targets: bun test passes in a single run.",
32
32
  },
33
33
  ];
34
34
  export function buildTestDisciplineSection() {
@@ -44,20 +44,20 @@ export function buildTestDisciplineSection() {
44
44
  */
45
45
  export const APP_UNRUN_CHECK_RULE = "A check that did not run is covered by the evidence that did run; name it only when no other evidence supports the claim. Replies render in an app, so tool and hook feedback (comment-checker findings, language-server availability, internal notices) is for you to act on: it reaches the user only when it changes what they get, and an unavailable tool or hook never does by itself.";
46
46
  const CLAIM_AUDIT = {
47
- terminal: "report only evidence-backed work, flag the unverified explicitly, and report failing tests with the output.",
48
- app: `report only evidence-backed work and report failing tests with the output. ${APP_UNRUN_CHECK_RULE}`,
47
+ terminal: "evidence-backed work only, flag unverified explicitly, failing tests with output.",
48
+ app: `evidence-backed work only, failing tests with output. ${APP_UNRUN_CHECK_RULE}`,
49
49
  };
50
50
  export function buildVerificationSection(options = {}) {
51
51
  return `## Verification
52
52
 
53
53
  Tier the scope, never the rigor.
54
54
 
55
- - V1 (single-file non-behavioral edits): diagnostics on that file. Done.
56
- - V2 (single-domain behavioral edits): diagnostics on changed files in parallel, related tests, one execution of the affected runnable entry point when one exists.
57
- - V3 (multi-file or cross-cutting work): diagnostics on every changed file, related tests, build, manual exercise of user-visible behavior through its real surface.
55
+ - V1 single-file non-behavioral edit: diagnostics on that file. Done.
56
+ - V2 single-domain behavioral edit: diagnostics on changed files in parallel + related tests + one run of affected runnable entry point if any.
57
+ - V3 multi-file / cross-cutting: diagnostics on every changed file + related tests + build + manual exercise of user-visible behavior through its real surface.
58
58
 
59
59
  ${buildTestDisciplineSection()}
60
60
 
61
- "Should pass" is not verification - run the validator. Before reporting progress, audit each claim against a tool result from this session: ${CLAIM_AUDIT[terminalOrApp(options.surface ?? "terminal")]} Fix only issues your changes caused; note pre-existing failures separately.`;
61
+ Before reporting progress: audit each claim against a tool result from this session; ${CLAIM_AUDIT[terminalOrApp(options.surface ?? "terminal")]} Fix only issues your changes caused; note pre-existing failures separately.`;
62
62
  }
63
63
  //# sourceMappingURL=verification.js.map
@@ -1,4 +1,4 @@
1
- export declare const DEFAULT_WORKING_TASK_OPENING = "Fire independent tool calls as one parallel wave - reads, searches, listings, diagnostics - and bias toward breadth when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; stale assumptions cost the turn. Edits and result-dependent calls go one at a time, each compared with the state you meant to produce; when the result must be seen rather than read, render after each change and look before the next. Never fill missing parameters with placeholders.";
1
+ export declare const DEFAULT_WORKING_TASK_OPENING = "Independent calls (reads, searches, listings, diagnostics) -> one parallel wave; thin context -> go wide now, pull anything loosely relevant, not serially later. Edits + result-dependent calls -> one at a time, each compared with intended state; result must be seen -> render after each change, look before next. Never fill missing parameters with placeholders.";
2
2
  export declare function buildWorkingTaskSection(options?: {
3
3
  opening?: string;
4
4
  }): string;
@@ -1,11 +1,11 @@
1
- export const DEFAULT_WORKING_TASK_OPENING = "Fire independent tool calls as one parallel wave - reads, searches, listings, diagnostics - and bias toward breadth when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; stale assumptions cost the turn. Edits and result-dependent calls go one at a time, each compared with the state you meant to produce; when the result must be seen rather than read, render after each change and look before the next. Never fill missing parameters with placeholders.";
1
+ export const DEFAULT_WORKING_TASK_OPENING = "Independent calls (reads, searches, listings, diagnostics) -> one parallel wave; thin context -> go wide now, pull anything loosely relevant, not serially later. Edits + result-dependent calls -> one at a time, each compared with intended state; result must be seen -> render after each change, look before next. Never fill missing parameters with placeholders.";
2
2
  export function buildWorkingTaskSection(options = {}) {
3
3
  return `## Working the Task
4
4
 
5
5
  ${options.opening ?? DEFAULT_WORKING_TASK_OPENING}
6
6
 
7
- Memory of file contents is unreliable - read before claiming, re-read before editing. Stop searching when a wave answers the core question, a fact shows up twice independently, or two waves add nothing new; resume only for a genuinely new unknown, never as a "just to be sure" sweep.
7
+ Memory of file contents unreliable: read before claiming, re-read before editing. Stop searching when a wave answers the core question, a fact appears twice independently, or two waves add nothing; resume only for a genuinely new unknown, never a "just to be sure" sweep.
8
8
 
9
- Make one reasonable plan and execute it; reopen it only when new evidence contradicts it. Do not re-derive facts already established in the conversation or re-litigate decisions the user has made. When weighing a choice, give a recommendation, not a survey. When a delegation tool is available, hand sizeable independent tracks to subagents and keep working while they run; keep work you can finish in a few calls yourself.`;
9
+ One reasonable plan, execute; reopen only when new evidence contradicts it. Don't re-derive established facts or re-litigate user decisions. Weighing a choice -> recommendation, not survey. Delegation tool available: hand sizeable independent tracks to subagents, keep working meanwhile; few-call work -> yourself.`;
10
10
  }
11
11
  //# sourceMappingURL=working-task.js.map
@@ -131,7 +131,7 @@ ${executors} on THIS workstation. Match commands, paths, package managers, and p
131
131
  codex: (executors) => `${executors} on this workstation; match commands, paths, and parallelism to it. Written code may target other platforms: executed code runs here.`,
132
132
  kimi: (executors) => `${executors} on this workstation: choose commands, paths, and parallelism that fit it. When writing code for another target platform, keep the code portable; only the execution happens here.`,
133
133
  gemini: (executors) => `${executors} on this workstation; match commands, paths, package managers, and parallel pool sizes to it. Code you write may target another machine; code you run always runs here.`,
134
- default: (executors) => `**EXECUTION HAPPENS HERE.** ${executors} on THIS machine: you MUST match commands, paths, package managers, and parallel pool sizes to the workstation above. Code you WRITE may target a different machine; anything you RUN executes HERE.`,
134
+ default: (executors) => `**EXECUTION HAPPENS HERE.** ${executors} on THIS machine: match commands, paths, package managers, parallel pool sizes to the workstation above. Code you WRITE may target another machine; anything you RUN executes HERE.`,
135
135
  };
136
136
  /** Render the `<workstation>` facts block plus the dialect-tuned execution-context instruction. */
137
137
  export function buildWorkstationSection(options) {
@@ -5,6 +5,13 @@ export type { SessionTurnResult } from "./session-turn-types.ts";
5
5
  export declare const DEFAULT_PRE_REPLAY_MAX_MESSAGES = 64;
6
6
  export declare const DEFAULT_PRE_REPLAY_MAX_BYTES: number;
7
7
  export declare const SESSION_TURN_ABORT_GRACE_MS = 1000;
8
+ /**
9
+ * How long a turn submitted while the previous turn of the same session is still active waits for that turn to
10
+ * settle before the submission is refused. The common case settles within the abort grace above: the agent loop
11
+ * starts the next turn (a steer, a follow-up, a monitor notification) right after it interrupted or finished
12
+ * consuming the previous one, while the pump still waits for the SDK's terminal result of that turn.
13
+ */
14
+ export declare const SESSION_TURN_ADMISSION_WAIT_MS = 10000;
8
15
  export interface SessionTurnRequest {
9
16
  message: SDKUserMessage["message"];
10
17
  signal?: AbortSignal;
@@ -16,5 +23,11 @@ export declare class ConcurrentSessionTurnAdmissionError extends Error {
16
23
  readonly code = "claude_sdk_oauth_concurrent_turn_admission";
17
24
  constructor(sessionId: string);
18
25
  }
26
+ /**
27
+ * Waits for `settled`, bounded by SESSION_TURN_ADMISSION_WAIT_MS: "admitted" once it settles, "aborted" when
28
+ * `signal` aborts first, and a ConcurrentSessionTurnAdmissionError when the bound passes, so a stuck pump cannot
29
+ * hold the session's callers forever.
30
+ */
31
+ export declare function waitForTurnAdmission(sessionId: string, settled: Promise<unknown>, signal: AbortSignal | undefined, schedule?: (callback: () => void, delayMs: number) => () => void): Promise<"admitted" | "aborted">;
19
32
  export declare function submitSessionTurn(registry: AnthropicSubscriptionSessionRegistry, entry: AnthropicSubscriptionSessionEntry, request: SessionTurnRequest, limits?: PreReplayBufferLimits): Promise<SessionTurnResult>;
20
33
  //# sourceMappingURL=session-registry-pump.d.ts.map
@@ -8,6 +8,13 @@ import { SessionTurnAttributionError } from "./session-turn-types.js";
8
8
  export const DEFAULT_PRE_REPLAY_MAX_MESSAGES = 64;
9
9
  export const DEFAULT_PRE_REPLAY_MAX_BYTES = 256 * 1024;
10
10
  export const SESSION_TURN_ABORT_GRACE_MS = 1_000;
11
+ /**
12
+ * How long a turn submitted while the previous turn of the same session is still active waits for that turn to
13
+ * settle before the submission is refused. The common case settles within the abort grace above: the agent loop
14
+ * starts the next turn (a steer, a follow-up, a monitor notification) right after it interrupted or finished
15
+ * consuming the previous one, while the pump still waits for the SDK's terminal result of that turn.
16
+ */
17
+ export const SESSION_TURN_ADMISSION_WAIT_MS = 10_000;
11
18
  export class ConcurrentSessionTurnAdmissionError extends Error {
12
19
  constructor(sessionId) {
13
20
  super(`Concurrent Anthropic Subscription turn admission for session ${sessionId}`);
@@ -154,12 +161,66 @@ async function runPump(registry, entry) {
154
161
  failTurn(registry, entry, error instanceof Error ? error : new Error(String(error)));
155
162
  }
156
163
  }
164
+ /**
165
+ * Waits for `settled`, bounded by SESSION_TURN_ADMISSION_WAIT_MS: "admitted" once it settles, "aborted" when
166
+ * `signal` aborts first, and a ConcurrentSessionTurnAdmissionError when the bound passes, so a stuck pump cannot
167
+ * hold the session's callers forever.
168
+ */
169
+ export function waitForTurnAdmission(sessionId, settled, signal, schedule = scheduleAbort) {
170
+ return new Promise((resolve, reject) => {
171
+ let done = false;
172
+ let cancelTimer;
173
+ const finish = (run) => {
174
+ if (done)
175
+ return;
176
+ done = true;
177
+ cancelTimer?.();
178
+ signal?.removeEventListener("abort", onAbort);
179
+ run();
180
+ };
181
+ const onAbort = () => finish(() => resolve("aborted"));
182
+ if (signal?.aborted) {
183
+ onAbort();
184
+ return;
185
+ }
186
+ // The scheduler may fire synchronously (tests inject one); `done` then stops every later step.
187
+ cancelTimer = schedule(() => finish(() => reject(new ConcurrentSessionTurnAdmissionError(sessionId))), SESSION_TURN_ADMISSION_WAIT_MS);
188
+ if (done)
189
+ return;
190
+ signal?.addEventListener("abort", onAbort, { once: true });
191
+ const admitted = () => finish(() => resolve("admitted"));
192
+ void settled.then(admitted, admitted);
193
+ });
194
+ }
195
+ /**
196
+ * A turn submitted while the session's previous turn is still active waits for that turn to settle, bounded by
197
+ * SESSION_TURN_ADMISSION_WAIT_MS, then submits. One SDK session runs one turn at a time, so the second request is
198
+ * logically after the first; refusing it (the behavior before this guard) surfaced as a provider failure and moved
199
+ * the session to a fallback model for no reason of the provider's. An abort of the waiting request resolves it as
200
+ * an aborted turn; a previous turn that does not settle in time keeps the refusal.
201
+ */
202
+ async function admitAfter(registry, entry, active, settled, request, limits) {
203
+ const generation = entry.generation;
204
+ const outcome = await waitForTurnAdmission(entry.senpiSessionId, settled, request.signal, request.scheduleAbort);
205
+ if (outcome === "aborted")
206
+ return { uuid: createSessionUuid(), messages: [], aborted: true };
207
+ if (!registry.isCurrentGeneration(entry.senpiSessionId, generation)) {
208
+ throw new Error("Anthropic Subscription session closed while a turn waited for admission");
209
+ }
210
+ if (currentTurn(entry) === active)
211
+ entry.activeTurn = null;
212
+ return submitSessionTurn(registry, entry, request, limits);
213
+ }
157
214
  export function submitSessionTurn(registry, entry, request, limits = {
158
215
  maxMessages: DEFAULT_PRE_REPLAY_MAX_MESSAGES,
159
216
  maxBytes: DEFAULT_PRE_REPLAY_MAX_BYTES,
160
217
  }) {
161
- if (currentTurn(entry))
162
- throw new ConcurrentSessionTurnAdmissionError(entry.senpiSessionId);
218
+ const active = currentTurn(entry);
219
+ if (active) {
220
+ if (!active.settled)
221
+ throw new ConcurrentSessionTurnAdmissionError(entry.senpiSessionId);
222
+ return admitAfter(registry, entry, active, active.settled, request, limits);
223
+ }
163
224
  if (entry.state === "STARTING")
164
225
  transitionToIdleSynced(entry);
165
226
  transitionToTurnWaiting(entry);
@@ -199,6 +260,7 @@ export function submitSessionTurn(registry, entry, request, limits = {
199
260
  limits,
200
261
  };
201
262
  });
263
+ turn.settled = promise.then(() => undefined, () => undefined);
202
264
  entry.activeTurn = turn;
203
265
  if (!entry.pumpTask)
204
266
  entry.pumpTask = runPump(registry, entry);
@@ -12,7 +12,7 @@ import { bindingFromEntry, bindingInvalidationReason, getBinding } from "./sessi
12
12
  import { closeSession, getOrCreateSession, getSession, isIdleExpired, } from "./session-registry.js";
13
13
  import { admitRestoredBinding } from "./session-restored-admission.js";
14
14
  import { buildDeltaPromptBlocks, configFingerprint, sentHashesForEntry, sentMessageHashes, sentMessages, } from "./session-sync.js";
15
- import { createSessionTurnAttempt } from "./session-turn-attempt.js";
15
+ import { createSessionTurnAttempt, waitForActiveTurn } from "./session-turn-attempt.js";
16
16
  function userMessage(content) {
17
17
  return { role: "user", content };
18
18
  }
@@ -46,6 +46,10 @@ async function createResidentAttempt(input, auth) {
46
46
  const sessionId = input.streamOptions.sessionId;
47
47
  const messages = sentMessages(input.context);
48
48
  const hashes = sentMessageHashes(messages);
49
+ // Continuity and the delta are read from the settled previous turn, never from one still in flight.
50
+ const pending = getSession(sessionId);
51
+ if (pending)
52
+ await waitForActiveTurn(pending, input.streamOptions.signal);
49
53
  const existing = getSession(sessionId);
50
54
  const fingerprint = configFingerprint(auth.options, input.context, auth.authLane, auth.accountName);
51
55
  const residentHashes = existing ? (sentHashesForEntry(existing) ?? hashes) : hashes;
@@ -4,6 +4,13 @@ import { type AnthropicSubscriptionSessionEntry } from "./session-registry.ts";
4
4
  type StagedContinuityDecision = {
5
5
  emit(): void;
6
6
  };
7
+ /**
8
+ * Holds a new attempt until the attempt running the entry's previous turn finished and recorded the sent stream
9
+ * (`recordSyncedStream`), bounded and abortable like the pump's own admission wait. The caller decides
10
+ * continuity and the delta only after this, so the delta starts after the earlier turn's message instead of
11
+ * sending it again (senpi#5015 review).
12
+ */
13
+ export declare function waitForActiveTurn(entry: AnthropicSubscriptionSessionEntry, signal: AbortSignal | undefined): Promise<void>;
7
14
  /** Claude Code's wording for a fork point absent from its transcript; the captured id is the dead one. */
8
15
  export declare const RESUME_MESSAGE_MISSING: RegExp;
9
16
  export declare function createSessionTurnAttempt(entry: AnthropicSubscriptionSessionEntry, message: SDKUserMessage["message"], hashes: readonly string[], signal: AbortSignal | undefined, staged: StagedContinuityDecision,
@@ -2,8 +2,24 @@ import { BoundedAsyncQueue, SESSION_STREAM_QUEUE_CAPACITY } from "./bounded-queu
2
2
  import { sdkResultFailure } from "./errors.js";
3
3
  import { bindingFromEntry, forgetBinding, rememberBinding } from "./session-reattach.js";
4
4
  import { closeSession, isCurrentGeneration, sessionRegistry, } from "./session-registry.js";
5
- import { submitSessionTurn } from "./session-registry-pump.js";
5
+ import { submitSessionTurn, waitForTurnAdmission } from "./session-registry-pump.js";
6
6
  import { recordSyncedStream, sentHashPrefixDigest } from "./session-sync.js";
7
+ /** Pending while the attempt that submitted the entry's latest turn has not yet recorded what that turn sent. */
8
+ const attemptBookkeeping = new WeakMap();
9
+ /**
10
+ * Holds a new attempt until the attempt running the entry's previous turn finished and recorded the sent stream
11
+ * (`recordSyncedStream`), bounded and abortable like the pump's own admission wait. The caller decides
12
+ * continuity and the delta only after this, so the delta starts after the earlier turn's message instead of
13
+ * sending it again (senpi#5015 review).
14
+ */
15
+ export async function waitForActiveTurn(entry, signal) {
16
+ const pending = attemptBookkeeping.get(entry);
17
+ if (!pending)
18
+ return;
19
+ if ((await waitForTurnAdmission(entry.senpiSessionId, pending, signal)) === "aborted") {
20
+ throw new Error("Anthropic Subscription turn aborted while waiting for the previous turn");
21
+ }
22
+ }
7
23
  function successfulTurn(messages) {
8
24
  return messages.some((message) => message.type === "result" && message.subtype === "success" && sdkResultFailure(message) === undefined);
9
25
  }
@@ -91,6 +107,16 @@ servedLineage) {
91
107
  return {
92
108
  messages: (async function* () {
93
109
  const queue = new BoundedAsyncQueue(SESSION_STREAM_QUEUE_CAPACITY);
110
+ let markBookkept;
111
+ const bookkeeping = new Promise((resolve) => {
112
+ markBookkept = resolve;
113
+ });
114
+ attemptBookkeeping.set(entry, bookkeeping);
115
+ const bookkept = () => {
116
+ if (attemptBookkeeping.get(entry) === bookkeeping)
117
+ attemptBookkeeping.delete(entry);
118
+ markBookkept();
119
+ };
94
120
  const completion = submitSessionTurn(sessionRegistry, entry, {
95
121
  message,
96
122
  signal,
@@ -147,6 +173,7 @@ servedLineage) {
147
173
  finally {
148
174
  // A consumer that stops reading early must not leave the pump waiting for room.
149
175
  queue.close();
176
+ bookkept();
150
177
  }
151
178
  })(),
152
179
  discard: () => {
@@ -23,6 +23,8 @@ export interface ActiveTurn {
23
23
  resolve: (result: SessionTurnResult) => void;
24
24
  reject: (error: Error) => void;
25
25
  limits: PreReplayBufferLimits;
26
+ /** Settles (never rejects) once the turn resolved or rejected; a turn submitted behind it waits here. */
27
+ settled?: Promise<void>;
26
28
  }
27
29
  export interface PreReplayBufferLimits {
28
30
  maxMessages: number;
@@ -1,5 +1,5 @@
1
1
  import type { ExtensionAPI } from "../../types.ts";
2
2
  export type { BashTimeoutDefaults, BashToolInputLike } from "./timeout.ts";
3
- export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, BASH_MAX_TIMEOUT_SECONDS, buildBashTimeoutPrompt, resolveBashTimeoutDefaults, } from "./timeout.ts";
3
+ export { applyBashTimeout, BASH_DEFAULT_TIMEOUT_SECONDS, resolveBashTimeoutDefaults } from "./timeout.ts";
4
4
  export default function bashTimeoutExtension(pi: ExtensionAPI): void;
5
5
  //# sourceMappingURL=index.d.ts.map