@code-yeongyu/senpi 2026.9.19-2 → 2026.9.20

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 (171) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/bundle/chunks/{app-server-command-QBJ4GPN7.js → app-server-command-B35FQUTE.js} +1 -1
  3. package/dist/bundle/chunks/{bun-extension-importer-JQZRPNJH.js → bun-extension-importer-HHH4TV4B.js} +3 -3
  4. package/dist/bundle/chunks/{chunk-YRN3PCTM.js → chunk-DQZB4GVV.js} +4 -4
  5. package/dist/bundle/chunks/{chunk-EHSDTQ6I.js → chunk-E5R67F7G.js} +37 -37
  6. package/dist/bundle/chunks/{chunk-5E5TLB76.js → chunk-TWGC3TBR.js} +2 -2
  7. package/dist/bundle/chunks/{chunk-RLRLQRI2.js → chunk-V2ZB62DG.js} +1 -1
  8. package/dist/bundle/chunks/{chunk-3FRTRCR6.js → chunk-YXQG2SUX.js} +2 -2
  9. package/dist/bundle/chunks/{chunk-PLZK7ABD.js → chunk-Z4LHCTJ6.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-3Q4WGKFA.js → chunk-ZMFHNH6U.js} +1 -1
  11. package/dist/bundle/chunks/{cli-main-MLJ2H637.js → cli-main-USVT7JMN.js} +1 -1
  12. package/dist/bundle/chunks/{interactive-host-runtime-AITXUDCT.js → interactive-host-runtime-ELBAZ4LN.js} +1 -1
  13. package/dist/bundle/chunks/{interactive-mode-7C75IXNF.js → interactive-mode-R6225BKI.js} +1 -1
  14. package/dist/bundle/chunks/{list-tips-KTDNGQUK.js → list-tips-Z7T2ACVU.js} +1 -1
  15. package/dist/bundle/chunks/{multi-session-host-2YCJKQOL.js → multi-session-host-QNEBJW2P.js} +3 -3
  16. package/dist/bundle/chunks/{package-manager-cli-4ANSELYT.js → package-manager-cli-4MVJ3JSE.js} +1 -1
  17. package/dist/bundle/chunks/{rpc-mode-OPFQIWFP.js → rpc-mode-CXVDRIBU.js} +1 -1
  18. package/dist/bundle/chunks/{session-picker-DXCITGLS.js → session-picker-6N666KQZ.js} +1 -1
  19. package/dist/bundle/chunks/session-worker.js +66 -66
  20. package/dist/bundle/cli.js +1 -1
  21. package/dist/bundle/index.js +1 -1
  22. package/dist/bundle/rpc-entry.js +1 -1
  23. package/dist/core/agent-session-services.d.ts.map +1 -1
  24. package/dist/core/agent-session-services.js +14 -1
  25. package/dist/core/agent-session-services.js.map +1 -1
  26. package/dist/core/agent-session.d.ts +45 -0
  27. package/dist/core/agent-session.d.ts.map +1 -1
  28. package/dist/core/agent-session.js +233 -11
  29. package/dist/core/agent-session.js.map +1 -1
  30. package/dist/core/extensions/builtin/ask-user/extension.d.ts.map +1 -1
  31. package/dist/core/extensions/builtin/ask-user/extension.js +16 -5
  32. package/dist/core/extensions/builtin/ask-user/extension.js.map +1 -1
  33. package/dist/core/extensions/builtin/ask-user/format.d.ts +5 -0
  34. package/dist/core/extensions/builtin/ask-user/format.d.ts.map +1 -1
  35. package/dist/core/extensions/builtin/ask-user/format.js +4 -0
  36. package/dist/core/extensions/builtin/ask-user/format.js.map +1 -1
  37. package/dist/core/extensions/builtin/ask-user/notify.d.ts +2 -0
  38. package/dist/core/extensions/builtin/ask-user/notify.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/ask-user/notify.js +2 -0
  40. package/dist/core/extensions/builtin/ask-user/notify.js.map +1 -1
  41. package/dist/core/extensions/builtin/ask-user/registry.d.ts +15 -2
  42. package/dist/core/extensions/builtin/ask-user/registry.d.ts.map +1 -1
  43. package/dist/core/extensions/builtin/ask-user/registry.js +13 -0
  44. package/dist/core/extensions/builtin/ask-user/registry.js.map +1 -1
  45. package/dist/core/extensions/builtin/ask-user/render.d.ts +6 -0
  46. package/dist/core/extensions/builtin/ask-user/render.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/ask-user/render.js +9 -0
  48. package/dist/core/extensions/builtin/ask-user/render.js.map +1 -1
  49. package/dist/core/extensions/builtin/ask-user/resume.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/ask-user/resume.js +59 -24
  51. package/dist/core/extensions/builtin/ask-user/resume.js.map +1 -1
  52. package/dist/core/extensions/builtin/ask-user/tool.d.ts +1 -1
  53. package/dist/core/extensions/builtin/ask-user/tool.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/ask-user/tool.js +98 -17
  55. package/dist/core/extensions/builtin/ask-user/tool.js.map +1 -1
  56. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts +18 -0
  57. package/dist/core/extensions/builtin/compaction/model-usability-budget.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/compaction/model-usability-budget.js +16 -8
  59. package/dist/core/extensions/builtin/compaction/model-usability-budget.js.map +1 -1
  60. package/dist/core/extensions/builtin/compaction/switch-admission.d.ts +23 -0
  61. package/dist/core/extensions/builtin/compaction/switch-admission.d.ts.map +1 -0
  62. package/dist/core/extensions/builtin/compaction/switch-admission.js +19 -0
  63. package/dist/core/extensions/builtin/compaction/switch-admission.js.map +1 -0
  64. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/goal/index.js +10 -0
  66. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  67. package/dist/core/extensions/bun-extension-commonjs.d.ts +1 -1
  68. package/dist/core/extensions/bun-extension-commonjs.d.ts.map +1 -1
  69. package/dist/core/extensions/bun-extension-commonjs.js +3 -3
  70. package/dist/core/extensions/bun-extension-commonjs.js.map +1 -1
  71. package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
  72. package/dist/core/extensions/bun-extension-importer.js +4 -1
  73. package/dist/core/extensions/bun-extension-importer.js.map +1 -1
  74. package/dist/core/extensions/bun-extension-registry.d.ts.map +1 -1
  75. package/dist/core/extensions/bun-extension-registry.js +5 -9
  76. package/dist/core/extensions/bun-extension-registry.js.map +1 -1
  77. package/dist/core/resolved-paths-memo.d.ts +31 -0
  78. package/dist/core/resolved-paths-memo.d.ts.map +1 -0
  79. package/dist/core/resolved-paths-memo.js +57 -0
  80. package/dist/core/resolved-paths-memo.js.map +1 -0
  81. package/dist/core/resource-loader.d.ts +4 -1
  82. package/dist/core/resource-loader.d.ts.map +1 -1
  83. package/dist/core/resource-loader.js +25 -12
  84. package/dist/core/resource-loader.js.map +1 -1
  85. package/dist/core/retry-fallback/settings.d.ts +10 -2
  86. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  87. package/dist/core/retry-fallback/settings.js +28 -6
  88. package/dist/core/retry-fallback/settings.js.map +1 -1
  89. package/dist/core/skill-discovery.d.ts.map +1 -1
  90. package/dist/core/skill-discovery.js +11 -1
  91. package/dist/core/skill-discovery.js.map +1 -1
  92. package/dist/main.d.ts +10 -0
  93. package/dist/main.d.ts.map +1 -1
  94. package/dist/main.js +19 -2
  95. package/dist/main.js.map +1 -1
  96. package/dist/modes/interactive/components/ask-user-answer-chip.d.ts +2 -5
  97. package/dist/modes/interactive/components/ask-user-answer-chip.d.ts.map +1 -1
  98. package/dist/modes/interactive/components/ask-user-answer-chip.js +1 -4
  99. package/dist/modes/interactive/components/ask-user-answer-chip.js.map +1 -1
  100. package/dist/modes/interactive/components/assistant-message.d.ts +2 -0
  101. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  102. package/dist/modes/interactive/components/assistant-message.js +8 -0
  103. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  104. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts +1 -0
  105. package/dist/modes/interactive/components/assistant-render-descriptors.d.ts.map +1 -1
  106. package/dist/modes/interactive/components/assistant-render-descriptors.js +4 -0
  107. package/dist/modes/interactive/components/assistant-render-descriptors.js.map +1 -1
  108. package/dist/modes/interactive/components/status-indicator.d.ts +3 -1
  109. package/dist/modes/interactive/components/status-indicator.d.ts.map +1 -1
  110. package/dist/modes/interactive/components/status-indicator.js +15 -2
  111. package/dist/modes/interactive/components/status-indicator.js.map +1 -1
  112. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  113. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  114. package/dist/modes/interactive/interactive-mode.js +81 -6
  115. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  116. package/dist/modes/interactive/provider-error-presentation.d.ts +18 -0
  117. package/dist/modes/interactive/provider-error-presentation.d.ts.map +1 -0
  118. package/dist/modes/interactive/provider-error-presentation.js +100 -0
  119. package/dist/modes/interactive/provider-error-presentation.js.map +1 -0
  120. package/dist/modes/rpc/rpc-types.d.ts +18 -0
  121. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  122. package/dist/modes/rpc/rpc-types.js.map +1 -1
  123. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  124. package/dist/modes/rpc/session-command-router.js +14 -0
  125. package/dist/modes/rpc/session-command-router.js.map +1 -1
  126. package/dist/modes/rpc/session-event-writer.d.ts +9 -1
  127. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  128. package/dist/modes/rpc/session-event-writer.js +21 -0
  129. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  130. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  131. package/dist/modes/rpc/session-registry.js +9 -2
  132. package/dist/modes/rpc/session-registry.js.map +1 -1
  133. package/dist/modes/rpc/session-worker-requests.d.ts +10 -1
  134. package/dist/modes/rpc/session-worker-requests.d.ts.map +1 -1
  135. package/dist/modes/rpc/session-worker-requests.js +8 -2
  136. package/dist/modes/rpc/session-worker-requests.js.map +1 -1
  137. package/dist/modes/rpc/session-worker-requests.test.d.ts +2 -0
  138. package/dist/modes/rpc/session-worker-requests.test.d.ts.map +1 -0
  139. package/dist/modes/rpc/session-worker-requests.test.js +44 -0
  140. package/dist/modes/rpc/session-worker-requests.test.js.map +1 -0
  141. package/docs/rpc.md +7 -0
  142. package/docs/settings.md +1 -1
  143. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
  144. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  145. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  146. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  147. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  148. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  149. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  150. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  151. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  152. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  154. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  155. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  156. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  157. package/node_modules/@earendil-works/pi-tui/dist/native-module-path.d.ts.map +1 -1
  158. package/node_modules/@earendil-works/pi-tui/dist/native-module-path.js +26 -3
  159. package/node_modules/@earendil-works/pi-tui/dist/native-module-path.js.map +1 -1
  160. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
  161. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +13 -6
  162. package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
  163. package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.d.ts.map +1 -1
  164. package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.js +11 -3
  165. package/node_modules/@earendil-works/pi-tui/dist/tui-main-screen.js.map +1 -1
  166. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts +15 -2
  167. package/node_modules/@earendil-works/pi-tui/dist/tui.d.ts.map +1 -1
  168. package/node_modules/@earendil-works/pi-tui/dist/tui.js +45 -2
  169. package/node_modules/@earendil-works/pi-tui/dist/tui.js.map +1 -1
  170. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  171. package/package.json +8 -8
@@ -1,9 +1,9 @@
1
1
  import { WAKE_SOURCE_STATE_EVENT } from "../monitor-state-event.js";
2
2
  import { TOOL_NAMES } from "./family.js";
3
3
  import { formatResultDetails, formatResultText, formatUserMessage } from "./format.js";
4
- import { ASK_USER_ASKED_EVENT, ASK_USER_QUESTION_ENTRY, emitAskUserNotification, } from "./notify.js";
4
+ import { ASK_USER_ASKED_EVENT, ASK_USER_QUESTION_ENTRY, ASK_USER_SETTLEMENT_ENTRY, emitAskUserNotification, } from "./notify.js";
5
5
  import { createPendingQuestion } from "./pending.js";
6
- import { getPendingQuestions, registerPendingQuestion } from "./registry.js";
6
+ import { getPendingQuestions, queueQuestionOutcome, registerPendingQuestion, } from "./registry.js";
7
7
  import { renderCall, renderResult } from "./render.js";
8
8
  import { AskUserSchemaError, CLAUDE_PARAMS, CODEX_PARAMS, toCanonical, } from "./schema.js";
9
9
  const REASK = "The user did not answer the previous question this turn; continue without asking again.";
@@ -42,7 +42,11 @@ function emitWake(pi, sessionId) {
42
42
  };
43
43
  pi.events.emit(WAKE_SOURCE_STATE_EVENT, event);
44
44
  }
45
- export function startQuestion(pi, ctx, request, signal, state, variant, { resuming = false } = {}) {
45
+ export function startQuestion(initialPi, initialCtx, request, signal, initialState, variant, { resuming = false } = {}) {
46
+ let pi = initialPi;
47
+ let ctx = initialCtx;
48
+ let state = initialState;
49
+ let notify = ctx.ui.notify.bind(ctx.ui);
46
50
  const question = ctx.ui.question;
47
51
  if (!question && !resuming)
48
52
  throw new Error("Question UI is unavailable");
@@ -55,10 +59,22 @@ export function startQuestion(pi, ctx, request, signal, state, variant, { resumi
55
59
  answers: {},
56
60
  unanswered: request.questions.map((question) => question.id),
57
61
  });
58
- const controller = new AbortController();
62
+ let controller = new AbortController();
63
+ let attached = true;
64
+ let ownerBound = true;
65
+ let generation = 0;
59
66
  const completion = Promise.withResolvers();
60
67
  let settled = false;
61
68
  let unregister = () => { };
69
+ const publish = (ownerPi, ownerCtx, response) => {
70
+ if (!request.waitForAnswer)
71
+ ownerPi.appendEntry(ASK_USER_SETTLEMENT_ENTRY, {
72
+ requestId: request.requestId,
73
+ status: response.status,
74
+ });
75
+ if (!request.waitForAnswer || resuming)
76
+ deliverAnswer(ownerPi, ownerCtx, request, response, variant);
77
+ };
62
78
  const finish = (response) => {
63
79
  if (settled)
64
80
  return;
@@ -70,6 +86,15 @@ export function startQuestion(pi, ctx, request, signal, state, variant, { resumi
70
86
  if (!request.waitForAnswer)
71
87
  emitWake(pi, sessionId);
72
88
  pi.events.emit("herdr:blocked", { active: false, id: request.requestId });
89
+ if (ownerBound)
90
+ publish(pi, ctx, response);
91
+ else {
92
+ // The old API is invalid after reload. End the request at its deadline,
93
+ // notify the still-live UI, and deliver through the next bound runner.
94
+ queueQuestionOutcome(sessionId, (nextPi, nextCtx) => publish(nextPi, nextCtx, response));
95
+ if (response.status !== "cancelled")
96
+ notify(`Question ${request.requestId} ended (${response.status}) while reload was incomplete. Its outcome will reach the model when the session starts again.`, "error");
97
+ }
73
98
  completion.resolve(response);
74
99
  // This extension owns the authoritative idle timer (pending.ts), so a UI
75
100
  // bridge that is still waiting learns the outcome only from this abort.
@@ -84,12 +109,42 @@ export function startQuestion(pi, ctx, request, signal, state, variant, { resumi
84
109
  idleTimeoutMs: request.timeoutMs,
85
110
  onTimeout: finish,
86
111
  });
87
- const cancel = (message) => {
88
- const response = pending.cancel();
112
+ const cancel = (message, reportDetachedLoss = false) => {
113
+ const lost = !attached && reportDetachedLoss;
114
+ const response = pending.cancel(lost ? "orphaned-after-restart" : "cancelled");
89
115
  finish(message ? { ...response, comment: message } : response);
116
+ if (lost)
117
+ notify(message ?? "The pending question could not be restored.", "error");
90
118
  };
91
119
  const abort = () => cancel();
92
- unregister = registerPendingQuestion(sessionId, { request, pending, completion: completion.promise, cancel });
120
+ unregister = registerPendingQuestion(sessionId, {
121
+ request,
122
+ pending,
123
+ completion: completion.promise,
124
+ cancel,
125
+ detach() {
126
+ if (!attached || settled)
127
+ return;
128
+ attached = false;
129
+ ownerBound = false;
130
+ generation++;
131
+ controller.abort("reload");
132
+ },
133
+ rebind(nextPi, nextCtx, nextState) {
134
+ pi = nextPi;
135
+ ctx = nextCtx;
136
+ state = nextState;
137
+ notify = ctx.ui.notify.bind(ctx.ui);
138
+ ownerBound = true;
139
+ },
140
+ reattach() {
141
+ if (attached || settled)
142
+ return;
143
+ attached = true;
144
+ controller = new AbortController();
145
+ attach();
146
+ },
147
+ });
93
148
  pi.appendEntry(ASK_USER_QUESTION_ENTRY, {
94
149
  requestId: request.requestId,
95
150
  headers: request.questions.map((question) => question.header),
@@ -141,27 +196,53 @@ export function startQuestion(pi, ctx, request, signal, state, variant, { resumi
141
196
  if (settled)
142
197
  return;
143
198
  const message = `Question UI failed: ${error instanceof Error ? error.message : String(error)}`;
144
- if (resuming) {
199
+ if (resuming || generation > 0) {
145
200
  accept({ ...orphaned(), comment: message });
201
+ ctx.ui.notify(message, "error");
146
202
  return;
147
203
  }
148
204
  cancel(message);
149
205
  ctx.ui.notify(message, "error");
150
206
  };
151
- if (signal?.aborted)
152
- abort();
153
- else if (!question)
154
- accept(orphaned());
155
- else {
207
+ const attach = () => {
208
+ const currentGeneration = generation;
209
+ const currentQuestion = ctx.ui.question;
210
+ if (!currentQuestion) {
211
+ accept(orphaned());
212
+ if (generation > 0)
213
+ ctx.ui.notify("The pending question could not be restored: question UI is unavailable.", "error");
214
+ return;
215
+ }
156
216
  try {
157
- question.call(ctx.ui, request, opts).then(accept, fail);
217
+ currentQuestion
218
+ .call(ctx.ui, request, {
219
+ ...opts,
220
+ signal: controller.signal,
221
+ // The first attachment owns the whole budget; a re-attachment inherits what the
222
+ // authoritative idle timer has left, so a reload cannot hand the question a fresh one.
223
+ timeout: currentGeneration === 0 ? request.timeoutMs : pending.remainingMs(Date.now()),
224
+ initialDraft: draft,
225
+ onProgress: (progress) => {
226
+ if (attached && generation === currentGeneration)
227
+ opts.onProgress(progress);
228
+ },
229
+ })
230
+ .then((response) => {
231
+ if (attached && generation === currentGeneration)
232
+ accept(response);
233
+ }, (error) => {
234
+ if (attached && generation === currentGeneration)
235
+ fail(error);
236
+ });
158
237
  }
159
238
  catch (error) {
160
239
  fail(error);
161
240
  }
162
- }
163
- if (!request.waitForAnswer && !resuming)
164
- void completion.promise.then((response) => deliverAnswer(pi, ctx, request, response, variant));
241
+ };
242
+ if (signal?.aborted)
243
+ abort();
244
+ else
245
+ attach();
165
246
  return completion.promise;
166
247
  }
167
248
  export function createAskUserTool(variant, pi, state) {
@@ -1 +1 @@
1
- {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/ask-user/tool.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"../../types.ts\";\nimport { WAKE_SOURCE_STATE_EVENT, type WakeSourceStateEvent } from \"../monitor-state-event.ts\";\nimport { TOOL_NAMES } from \"./family.ts\";\nimport { formatResultDetails, formatResultText, formatUserMessage } from \"./format.ts\";\nimport {\n\tASK_USER_ASKED_EVENT,\n\tASK_USER_QUESTION_ENTRY,\n\ttype AskUserAskedEvent,\n\ttype AskUserQuestionEntry,\n\temitAskUserNotification,\n} from \"./notify.ts\";\nimport { createPendingQuestion } from \"./pending.ts\";\nimport { getPendingQuestions, type QuestionDialogOptions, registerPendingQuestion } from \"./registry.ts\";\nimport { renderCall, renderResult } from \"./render.ts\";\nimport {\n\tAskUserSchemaError,\n\ttype AskUserVariant,\n\tCLAUDE_PARAMS,\n\tCODEX_PARAMS,\n\ttype QuestionRequest,\n\ttype QuestionResponse,\n\ttoCanonical,\n} from \"./schema.ts\";\n\nexport interface AskUserState {\n\ttimedOut: boolean;\n\tunavailable: boolean;\n}\nconst REASK = \"The user did not answer the previous question this turn; continue without asking again.\";\nfunction result(\n\tvariant: AskUserVariant,\n\tresponse: QuestionResponse,\n\trequest: QuestionRequest,\n\ttext?: string,\n): Awaited<ReturnType<ToolDefinition[\"execute\"]>> {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: text ?? formatResultText(variant, response, request.questions) }],\n\t\tdetails: formatResultDetails(variant, response, request.questions),\n\t};\n}\n/**\n * Deliver a settled async answer as one framed user message. The extension owns\n * this so every surface (interactive TUI, RPC, app-server) only has to RESOLVE\n * the question; a surface that delivered on its own would send it twice. It\n * steers into a running turn and follows up when idle - a follow-up always\n * triggers a turn, which is what wakes the model after a timeout. A cancelled\n * question stays silent: it was dismissed, superseded, or aborted.\n */\nfunction deliverAnswer(\n\tpi: Pick<ExtensionAPI, \"sendUserMessage\" | \"events\">,\n\tctx: ExtensionContext,\n\trequest: QuestionRequest,\n\tresponse: QuestionResponse,\n\tvariant: \"codex\" | \"claude\",\n): void {\n\tif (response.status === \"cancelled\") return;\n\tpi.sendUserMessage(formatUserMessage(response, request.requestId, request.questions), {\n\t\tdeliverAs: ctx.isIdle() ? \"followUp\" : \"steer\",\n\t});\n\tvoid emitAskUserNotification(pi, ctx, request, response, variant);\n}\nfunction emitWake(pi: Pick<ExtensionAPI, \"events\">, sessionId: string) {\n\tconst entries = getPendingQuestions(sessionId).filter((e) => !e.request.waitForAnswer);\n\tconst event: WakeSourceStateEvent = {\n\t\tsource: \"ask-user\",\n\t\tactiveCount: entries.length,\n\t\titems: entries.map((e) => ({\n\t\t\tid: e.request.requestId,\n\t\t\tdeadlineAtMs: e.pending.deadlineAtMs,\n\t\t\tdescription: e.request.questions.map((q) => q.header).join(\", \"),\n\t\t})),\n\t};\n\tpi.events.emit(WAKE_SOURCE_STATE_EVENT, event);\n}\nexport function startQuestion(\n\tpi: Pick<ExtensionAPI, \"sendUserMessage\" | \"events\" | \"appendEntry\">,\n\tctx: ExtensionContext,\n\trequest: QuestionRequest,\n\tsignal: AbortSignal | undefined,\n\tstate: AskUserState,\n\tvariant: \"codex\" | \"claude\",\n\t{ resuming = false }: { resuming?: boolean } = {},\n) {\n\tconst question = ctx.ui.question;\n\tif (!question && !resuming) throw new Error(\"Question UI is unavailable\");\n\tconst sessionId = ctx.sessionManager.getSessionId();\n\tconst existing = getPendingQuestions(sessionId).find((entry) => entry.request.requestId === request.requestId);\n\tif (existing) return existing.completion;\n\tconst orphaned = (): QuestionResponse => ({\n\t\tstatus: \"orphaned-after-restart\",\n\t\tanswers: {},\n\t\tunanswered: request.questions.map((question) => question.id),\n\t});\n\tconst controller = new AbortController();\n\tconst completion = Promise.withResolvers<QuestionResponse>();\n\tlet settled = false;\n\tlet unregister = () => {};\n\tconst finish = (response: QuestionResponse) => {\n\t\tif (settled) return;\n\t\tsettled = true;\n\t\tif (response.status === \"timed_out\") state.timedOut = true;\n\t\tunregister();\n\t\tsignal?.removeEventListener(\"abort\", abort);\n\t\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\t\tpi.events.emit(\"herdr:blocked\", { active: false, id: request.requestId });\n\t\tcompletion.resolve(response);\n\t\t// This extension owns the authoritative idle timer (pending.ts), so a UI\n\t\t// bridge that is still waiting learns the outcome only from this abort.\n\t\t// Carry the resolved status as the abort reason: a bare abort reads as a\n\t\t// dismissal, and the RPC bridge would broadcast a timeout as \"cancelled\".\n\t\tconst abortReason: QuestionResponse[\"status\"] = response.status;\n\t\tcontroller.abort(abortReason);\n\t};\n\tconst pending = createPendingQuestion({\n\t\trequest,\n\t\tnow: Date.now,\n\t\tidleTimeoutMs: request.timeoutMs,\n\t\tonTimeout: finish,\n\t});\n\tconst cancel = (message?: string) => {\n\t\tconst response = pending.cancel();\n\t\tfinish(message ? { ...response, comment: message } : response);\n\t};\n\tconst abort = () => cancel();\n\tunregister = registerPendingQuestion(sessionId, { request, pending, completion: completion.promise, cancel });\n\tpi.appendEntry<AskUserQuestionEntry>(ASK_USER_QUESTION_ENTRY, {\n\t\trequestId: request.requestId,\n\t\theaders: request.questions.map((question) => question.header),\n\t});\n\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\tpi.events.emit(ASK_USER_ASKED_EVENT, { ctx, request, variant } satisfies AskUserAskedEvent);\n\tconst first = request.questions[0];\n\tpi.events.emit(\"herdr:blocked\", {\n\t\tactive: true,\n\t\tlabel: first ? `${first.header} — ${first.question}` : \"Question\",\n\t\tid: request.requestId,\n\t});\n\tsignal?.addEventListener(\"abort\", abort, { once: true });\n\tlet draft: { answers: QuestionResponse[\"answers\"]; comment?: string } = { answers: {} };\n\tconst opts: QuestionDialogOptions = {\n\t\ttimeout: request.timeoutMs,\n\t\tsignal: controller.signal,\n\t\tdeliver: request.waitForAnswer ? \"tool-result\" : \"user-message\",\n\t\thardDeadlineAtMs: Date.now() + 7_200_000,\n\t\tgetDeadlineAtMs: () => pending.deadlineAtMs,\n\t\tonProgress: (progress) => {\n\t\t\tdraft = { ...draft, ...progress, answers: progress.answers ?? draft.answers };\n\t\t\tpending.touch(draft);\n\t\t\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\t\t},\n\t};\n\tconst accept = (response: QuestionResponse) => {\n\t\tif (settled) return;\n\t\tpending.touch({ answers: response.answers, comment: response.comment });\n\t\tif (response.status === \"timed_out\") {\n\t\t\tfinish(response);\n\t\t\tpending.timeout();\n\t\t\treturn;\n\t\t}\n\t\tif (response.status === \"answered\" || response.status === \"comment-submitted\") {\n\t\t\t// The UI owns partial submission validation; retain its response verbatim.\n\t\t\tpending.submit(response.answers, response.comment);\n\t\t\tif (pending.state === \"pending\") pending.cancel();\n\t\t} else pending.cancel(response.status);\n\t\tfinish(response);\n\t};\n\tconst fail = (error: unknown) => {\n\t\tif (settled) return;\n\t\tconst message = `Question UI failed: ${error instanceof Error ? error.message : String(error)}`;\n\t\tif (resuming) {\n\t\t\taccept({ ...orphaned(), comment: message });\n\t\t\treturn;\n\t\t}\n\t\tcancel(message);\n\t\tctx.ui.notify(message, \"error\");\n\t};\n\tif (signal?.aborted) abort();\n\telse if (!question) accept(orphaned());\n\telse {\n\t\ttry {\n\t\t\tquestion.call(ctx.ui, request, opts).then(accept, fail);\n\t\t} catch (error: unknown) {\n\t\t\tfail(error);\n\t\t}\n\t}\n\tif (!request.waitForAnswer && !resuming)\n\t\tvoid completion.promise.then((response) => deliverAnswer(pi, ctx, request, response, variant));\n\treturn completion.promise;\n}\nexport function createAskUserTool(variant: AskUserVariant, pi: ExtensionAPI, state: AskUserState): ToolDefinition {\n\tconst flag = variant === \"codex\" ? \"wait_for_answer\" : \"waitForAnswer\";\n\tconst opener =\n\t\tvariant === \"codex\"\n\t\t\t? \"Request user input for one to three short questions.\"\n\t\t\t: \"Ask the user one to four concise questions.\";\n\treturn {\n\t\tname: TOOL_NAMES[variant],\n\t\tlabel: \"Ask user\",\n\t\texposure: \"direct\",\n\t\tallowLazyActivation: false,\n\t\tparameters: variant === \"codex\" ? CODEX_PARAMS : CLAUDE_PARAMS,\n\t\tpromptSnippet: \"Ask a material question, explicitly choosing whether to wait or receive the answer later.\",\n\t\tdescription: `${opener} Set ${flag} true to pause here until the user answers (the answer returns as this tool's result, or a timeout result after 30 idle minutes); set ${flag} false to keep working while the question stays open (the answer arrives later as a user message). Use it only when the answer materially changes the work; if it returns no answers, continue with best judgment instead of asking again. Never use it for permission requests; ask those directly in your message. Unavailable to subagents.${variant === \"claude\" ? \" Users will always be able to type a free-text answer or one comment covering everything; do not add an Other option.\" : \"\"} Call this tool directly, never from inside an eval cell (a cell that waits on the user would hold the kernel).`,\n\t\tprepareArguments(args) {\n\t\t\ttoCanonical(variant, args);\n\t\t\treturn args;\n\t\t},\n\t\tasync execute(toolCallId, params, signal, _onUpdate, ctx) {\n\t\t\tlet request: QuestionRequest;\n\t\t\ttry {\n\t\t\t\trequest = toCanonical(variant, params, {\n\t\t\t\t\trequestId: toolCallId,\n\t\t\t\t\ttimeoutMs: (ctx.getAskUserSettings?.().timeoutMinutes ?? 30) * 60_000,\n\t\t\t\t});\n\t\t\t} catch (error: unknown) {\n\t\t\t\tif (!(error instanceof AskUserSchemaError)) throw error;\n\t\t\t\treturn { content: [{ type: \"text\", text: error.message }], details: { status: \"unavailable\" } };\n\t\t\t}\n\t\t\tconst unavailable: QuestionResponse = {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tanswers: {},\n\t\t\t\tunanswered: request.questions.map((q) => q.id),\n\t\t\t};\n\t\t\tif (state.timedOut) return result(variant, unavailable, request, REASK);\n\t\t\tif (\n\t\t\t\tstate.unavailable ||\n\t\t\t\tctx.getAskUserSettings?.().enabled === false ||\n\t\t\t\tpi.getFlag(\"no-ask-user\") === true ||\n\t\t\t\tctx.mode === \"print\" ||\n\t\t\t\tctx.mode === \"json\" ||\n\t\t\t\t!ctx.ui.question\n\t\t\t) {\n\t\t\t\tstate.unavailable = true;\n\t\t\t\tpi.setActiveTools(pi.getActiveTools().filter((name) => !Object.values(TOOL_NAMES).includes(name)));\n\t\t\t\treturn result(variant, unavailable, request);\n\t\t\t}\n\t\t\tconst completion = startQuestion(pi, ctx, request, signal, state, variant);\n\t\t\tif (!request.waitForAnswer)\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Question accepted; the answer will arrive as a user message.\" }],\n\t\t\t\t\tdetails: { accepted: true, requestId: request.requestId, status: \"pending\" },\n\t\t\t\t};\n\t\t\tconst response = await completion;\n\t\t\tif (response.status !== \"cancelled\") {\n\t\t\t\tvoid emitAskUserNotification(pi, ctx, request, response, variant);\n\t\t\t}\n\t\t\treturn result(\n\t\t\t\tvariant,\n\t\t\t\tresponse,\n\t\t\t\trequest,\n\t\t\t\tresponse.status === \"cancelled\" && response.comment ? response.comment : undefined,\n\t\t\t);\n\t\t},\n\t\trenderCall,\n\t\trenderResult,\n\t};\n}\n"]}
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/ask-user/tool.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionContext, ToolDefinition } from \"../../types.ts\";\nimport { WAKE_SOURCE_STATE_EVENT, type WakeSourceStateEvent } from \"../monitor-state-event.ts\";\nimport { TOOL_NAMES } from \"./family.ts\";\nimport { formatResultDetails, formatResultText, formatUserMessage } from \"./format.ts\";\nimport {\n\tASK_USER_ASKED_EVENT,\n\tASK_USER_QUESTION_ENTRY,\n\tASK_USER_SETTLEMENT_ENTRY,\n\ttype AskUserAskedEvent,\n\ttype AskUserQuestionEntry,\n\temitAskUserNotification,\n} from \"./notify.ts\";\nimport { createPendingQuestion } from \"./pending.ts\";\nimport {\n\tgetPendingQuestions,\n\ttype QuestionDialogOptions,\n\tqueueQuestionOutcome,\n\tregisterPendingQuestion,\n} from \"./registry.ts\";\nimport { renderCall, renderResult } from \"./render.ts\";\nimport {\n\tAskUserSchemaError,\n\ttype AskUserVariant,\n\tCLAUDE_PARAMS,\n\tCODEX_PARAMS,\n\ttype QuestionRequest,\n\ttype QuestionResponse,\n\ttoCanonical,\n} from \"./schema.ts\";\n\nexport interface AskUserState {\n\ttimedOut: boolean;\n\tunavailable: boolean;\n}\nconst REASK = \"The user did not answer the previous question this turn; continue without asking again.\";\nfunction result(\n\tvariant: AskUserVariant,\n\tresponse: QuestionResponse,\n\trequest: QuestionRequest,\n\ttext?: string,\n): Awaited<ReturnType<ToolDefinition[\"execute\"]>> {\n\treturn {\n\t\tcontent: [{ type: \"text\", text: text ?? formatResultText(variant, response, request.questions) }],\n\t\tdetails: formatResultDetails(variant, response, request.questions),\n\t};\n}\n/**\n * Deliver a settled async answer as one framed user message. The extension owns\n * this so every surface (interactive TUI, RPC, app-server) only has to RESOLVE\n * the question; a surface that delivered on its own would send it twice. It\n * steers into a running turn and follows up when idle - a follow-up always\n * triggers a turn, which is what wakes the model after a timeout. A cancelled\n * question stays silent: it was dismissed, superseded, or aborted.\n */\nfunction deliverAnswer(\n\tpi: Pick<ExtensionAPI, \"sendUserMessage\" | \"events\">,\n\tctx: ExtensionContext,\n\trequest: QuestionRequest,\n\tresponse: QuestionResponse,\n\tvariant: \"codex\" | \"claude\",\n): void {\n\tif (response.status === \"cancelled\") return;\n\tpi.sendUserMessage(formatUserMessage(response, request.requestId, request.questions), {\n\t\tdeliverAs: ctx.isIdle() ? \"followUp\" : \"steer\",\n\t});\n\tvoid emitAskUserNotification(pi, ctx, request, response, variant);\n}\nfunction emitWake(pi: Pick<ExtensionAPI, \"events\">, sessionId: string) {\n\tconst entries = getPendingQuestions(sessionId).filter((e) => !e.request.waitForAnswer);\n\tconst event: WakeSourceStateEvent = {\n\t\tsource: \"ask-user\",\n\t\tactiveCount: entries.length,\n\t\titems: entries.map((e) => ({\n\t\t\tid: e.request.requestId,\n\t\t\tdeadlineAtMs: e.pending.deadlineAtMs,\n\t\t\tdescription: e.request.questions.map((q) => q.header).join(\", \"),\n\t\t})),\n\t};\n\tpi.events.emit(WAKE_SOURCE_STATE_EVENT, event);\n}\nexport function startQuestion(\n\tinitialPi: Pick<ExtensionAPI, \"sendUserMessage\" | \"events\" | \"appendEntry\">,\n\tinitialCtx: ExtensionContext,\n\trequest: QuestionRequest,\n\tsignal: AbortSignal | undefined,\n\tinitialState: AskUserState,\n\tvariant: \"codex\" | \"claude\",\n\t{ resuming = false }: { resuming?: boolean } = {},\n) {\n\tlet pi = initialPi;\n\tlet ctx = initialCtx;\n\tlet state = initialState;\n\tlet notify = ctx.ui.notify.bind(ctx.ui);\n\tconst question = ctx.ui.question;\n\tif (!question && !resuming) throw new Error(\"Question UI is unavailable\");\n\tconst sessionId = ctx.sessionManager.getSessionId();\n\tconst existing = getPendingQuestions(sessionId).find((entry) => entry.request.requestId === request.requestId);\n\tif (existing) return existing.completion;\n\tconst orphaned = (): QuestionResponse => ({\n\t\tstatus: \"orphaned-after-restart\",\n\t\tanswers: {},\n\t\tunanswered: request.questions.map((question) => question.id),\n\t});\n\tlet controller = new AbortController();\n\tlet attached = true;\n\tlet ownerBound = true;\n\tlet generation = 0;\n\tconst completion = Promise.withResolvers<QuestionResponse>();\n\tlet settled = false;\n\tlet unregister = () => {};\n\tconst publish = (\n\t\townerPi: Pick<ExtensionAPI, \"sendUserMessage\" | \"events\" | \"appendEntry\">,\n\t\townerCtx: ExtensionContext,\n\t\tresponse: QuestionResponse,\n\t) => {\n\t\tif (!request.waitForAnswer)\n\t\t\townerPi.appendEntry(ASK_USER_SETTLEMENT_ENTRY, {\n\t\t\t\trequestId: request.requestId,\n\t\t\t\tstatus: response.status,\n\t\t\t});\n\t\tif (!request.waitForAnswer || resuming) deliverAnswer(ownerPi, ownerCtx, request, response, variant);\n\t};\n\tconst finish = (response: QuestionResponse) => {\n\t\tif (settled) return;\n\t\tsettled = true;\n\t\tif (response.status === \"timed_out\") state.timedOut = true;\n\t\tunregister();\n\t\tsignal?.removeEventListener(\"abort\", abort);\n\t\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\t\tpi.events.emit(\"herdr:blocked\", { active: false, id: request.requestId });\n\t\tif (ownerBound) publish(pi, ctx, response);\n\t\telse {\n\t\t\t// The old API is invalid after reload. End the request at its deadline,\n\t\t\t// notify the still-live UI, and deliver through the next bound runner.\n\t\t\tqueueQuestionOutcome(sessionId, (nextPi, nextCtx) => publish(nextPi, nextCtx, response));\n\t\t\tif (response.status !== \"cancelled\")\n\t\t\t\tnotify(\n\t\t\t\t\t`Question ${request.requestId} ended (${response.status}) while reload was incomplete. Its outcome will reach the model when the session starts again.`,\n\t\t\t\t\t\"error\",\n\t\t\t\t);\n\t\t}\n\t\tcompletion.resolve(response);\n\t\t// This extension owns the authoritative idle timer (pending.ts), so a UI\n\t\t// bridge that is still waiting learns the outcome only from this abort.\n\t\t// Carry the resolved status as the abort reason: a bare abort reads as a\n\t\t// dismissal, and the RPC bridge would broadcast a timeout as \"cancelled\".\n\t\tconst abortReason: QuestionResponse[\"status\"] = response.status;\n\t\tcontroller.abort(abortReason);\n\t};\n\tconst pending = createPendingQuestion({\n\t\trequest,\n\t\tnow: Date.now,\n\t\tidleTimeoutMs: request.timeoutMs,\n\t\tonTimeout: finish,\n\t});\n\tconst cancel = (message?: string, reportDetachedLoss = false) => {\n\t\tconst lost = !attached && reportDetachedLoss;\n\t\tconst response = pending.cancel(lost ? \"orphaned-after-restart\" : \"cancelled\");\n\t\tfinish(message ? { ...response, comment: message } : response);\n\t\tif (lost) notify(message ?? \"The pending question could not be restored.\", \"error\");\n\t};\n\tconst abort = () => cancel();\n\tunregister = registerPendingQuestion(sessionId, {\n\t\trequest,\n\t\tpending,\n\t\tcompletion: completion.promise,\n\t\tcancel,\n\t\tdetach() {\n\t\t\tif (!attached || settled) return;\n\t\t\tattached = false;\n\t\t\townerBound = false;\n\t\t\tgeneration++;\n\t\t\tcontroller.abort(\"reload\");\n\t\t},\n\t\trebind(nextPi, nextCtx, nextState) {\n\t\t\tpi = nextPi;\n\t\t\tctx = nextCtx;\n\t\t\tstate = nextState;\n\t\t\tnotify = ctx.ui.notify.bind(ctx.ui);\n\t\t\townerBound = true;\n\t\t},\n\t\treattach() {\n\t\t\tif (attached || settled) return;\n\t\t\tattached = true;\n\t\t\tcontroller = new AbortController();\n\t\t\tattach();\n\t\t},\n\t});\n\tpi.appendEntry<AskUserQuestionEntry>(ASK_USER_QUESTION_ENTRY, {\n\t\trequestId: request.requestId,\n\t\theaders: request.questions.map((question) => question.header),\n\t});\n\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\tpi.events.emit(ASK_USER_ASKED_EVENT, { ctx, request, variant } satisfies AskUserAskedEvent);\n\tconst first = request.questions[0];\n\tpi.events.emit(\"herdr:blocked\", {\n\t\tactive: true,\n\t\tlabel: first ? `${first.header} — ${first.question}` : \"Question\",\n\t\tid: request.requestId,\n\t});\n\tsignal?.addEventListener(\"abort\", abort, { once: true });\n\tlet draft: { answers: QuestionResponse[\"answers\"]; comment?: string } = { answers: {} };\n\tconst opts: QuestionDialogOptions = {\n\t\ttimeout: request.timeoutMs,\n\t\tsignal: controller.signal,\n\t\tdeliver: request.waitForAnswer ? \"tool-result\" : \"user-message\",\n\t\thardDeadlineAtMs: Date.now() + 7_200_000,\n\t\tgetDeadlineAtMs: () => pending.deadlineAtMs,\n\t\tonProgress: (progress) => {\n\t\t\tdraft = { ...draft, ...progress, answers: progress.answers ?? draft.answers };\n\t\t\tpending.touch(draft);\n\t\t\tif (!request.waitForAnswer) emitWake(pi, sessionId);\n\t\t},\n\t};\n\tconst accept = (response: QuestionResponse) => {\n\t\tif (settled) return;\n\t\tpending.touch({ answers: response.answers, comment: response.comment });\n\t\tif (response.status === \"timed_out\") {\n\t\t\tfinish(response);\n\t\t\tpending.timeout();\n\t\t\treturn;\n\t\t}\n\t\tif (response.status === \"answered\" || response.status === \"comment-submitted\") {\n\t\t\t// The UI owns partial submission validation; retain its response verbatim.\n\t\t\tpending.submit(response.answers, response.comment);\n\t\t\tif (pending.state === \"pending\") pending.cancel();\n\t\t} else pending.cancel(response.status);\n\t\tfinish(response);\n\t};\n\tconst fail = (error: unknown) => {\n\t\tif (settled) return;\n\t\tconst message = `Question UI failed: ${error instanceof Error ? error.message : String(error)}`;\n\t\tif (resuming || generation > 0) {\n\t\t\taccept({ ...orphaned(), comment: message });\n\t\t\tctx.ui.notify(message, \"error\");\n\t\t\treturn;\n\t\t}\n\t\tcancel(message);\n\t\tctx.ui.notify(message, \"error\");\n\t};\n\tconst attach = () => {\n\t\tconst currentGeneration = generation;\n\t\tconst currentQuestion = ctx.ui.question;\n\t\tif (!currentQuestion) {\n\t\t\taccept(orphaned());\n\t\t\tif (generation > 0)\n\t\t\t\tctx.ui.notify(\"The pending question could not be restored: question UI is unavailable.\", \"error\");\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tcurrentQuestion\n\t\t\t\t.call(ctx.ui, request, {\n\t\t\t\t\t...opts,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t// The first attachment owns the whole budget; a re-attachment inherits what the\n\t\t\t\t\t// authoritative idle timer has left, so a reload cannot hand the question a fresh one.\n\t\t\t\t\ttimeout: currentGeneration === 0 ? request.timeoutMs : pending.remainingMs(Date.now()),\n\t\t\t\t\tinitialDraft: draft,\n\t\t\t\t\tonProgress: (progress) => {\n\t\t\t\t\t\tif (attached && generation === currentGeneration) opts.onProgress(progress);\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\t.then(\n\t\t\t\t\t(response) => {\n\t\t\t\t\t\tif (attached && generation === currentGeneration) accept(response);\n\t\t\t\t\t},\n\t\t\t\t\t(error: unknown) => {\n\t\t\t\t\t\tif (attached && generation === currentGeneration) fail(error);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t} catch (error: unknown) {\n\t\t\tfail(error);\n\t\t}\n\t};\n\tif (signal?.aborted) abort();\n\telse attach();\n\treturn completion.promise;\n}\nexport function createAskUserTool(variant: AskUserVariant, pi: ExtensionAPI, state: AskUserState): ToolDefinition {\n\tconst flag = variant === \"codex\" ? \"wait_for_answer\" : \"waitForAnswer\";\n\tconst opener =\n\t\tvariant === \"codex\"\n\t\t\t? \"Request user input for one to three short questions.\"\n\t\t\t: \"Ask the user one to four concise questions.\";\n\treturn {\n\t\tname: TOOL_NAMES[variant],\n\t\tlabel: \"Ask user\",\n\t\texposure: \"direct\",\n\t\tallowLazyActivation: false,\n\t\tparameters: variant === \"codex\" ? CODEX_PARAMS : CLAUDE_PARAMS,\n\t\tpromptSnippet: \"Ask a material question, explicitly choosing whether to wait or receive the answer later.\",\n\t\tdescription: `${opener} Set ${flag} true to pause here until the user answers (the answer returns as this tool's result, or a timeout result after 30 idle minutes); set ${flag} false to keep working while the question stays open (the answer arrives later as a user message). Use it only when the answer materially changes the work; if it returns no answers, continue with best judgment instead of asking again. Never use it for permission requests; ask those directly in your message. Unavailable to subagents.${variant === \"claude\" ? \" Users will always be able to type a free-text answer or one comment covering everything; do not add an Other option.\" : \"\"} Call this tool directly, never from inside an eval cell (a cell that waits on the user would hold the kernel).`,\n\t\tprepareArguments(args) {\n\t\t\ttoCanonical(variant, args);\n\t\t\treturn args;\n\t\t},\n\t\tasync execute(toolCallId, params, signal, _onUpdate, ctx) {\n\t\t\tlet request: QuestionRequest;\n\t\t\ttry {\n\t\t\t\trequest = toCanonical(variant, params, {\n\t\t\t\t\trequestId: toolCallId,\n\t\t\t\t\ttimeoutMs: (ctx.getAskUserSettings?.().timeoutMinutes ?? 30) * 60_000,\n\t\t\t\t});\n\t\t\t} catch (error: unknown) {\n\t\t\t\tif (!(error instanceof AskUserSchemaError)) throw error;\n\t\t\t\treturn { content: [{ type: \"text\", text: error.message }], details: { status: \"unavailable\" } };\n\t\t\t}\n\t\t\tconst unavailable: QuestionResponse = {\n\t\t\t\tstatus: \"unavailable\",\n\t\t\t\tanswers: {},\n\t\t\t\tunanswered: request.questions.map((q) => q.id),\n\t\t\t};\n\t\t\tif (state.timedOut) return result(variant, unavailable, request, REASK);\n\t\t\tif (\n\t\t\t\tstate.unavailable ||\n\t\t\t\tctx.getAskUserSettings?.().enabled === false ||\n\t\t\t\tpi.getFlag(\"no-ask-user\") === true ||\n\t\t\t\tctx.mode === \"print\" ||\n\t\t\t\tctx.mode === \"json\" ||\n\t\t\t\t!ctx.ui.question\n\t\t\t) {\n\t\t\t\tstate.unavailable = true;\n\t\t\t\tpi.setActiveTools(pi.getActiveTools().filter((name) => !Object.values(TOOL_NAMES).includes(name)));\n\t\t\t\treturn result(variant, unavailable, request);\n\t\t\t}\n\t\t\tconst completion = startQuestion(pi, ctx, request, signal, state, variant);\n\t\t\tif (!request.waitForAnswer)\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: \"Question accepted; the answer will arrive as a user message.\" }],\n\t\t\t\t\tdetails: { accepted: true, requestId: request.requestId, status: \"pending\" },\n\t\t\t\t};\n\t\t\tconst response = await completion;\n\t\t\tif (response.status !== \"cancelled\") {\n\t\t\t\tvoid emitAskUserNotification(pi, ctx, request, response, variant);\n\t\t\t}\n\t\t\treturn result(\n\t\t\t\tvariant,\n\t\t\t\tresponse,\n\t\t\t\trequest,\n\t\t\t\tresponse.status === \"cancelled\" && response.comment ? response.comment : undefined,\n\t\t\t);\n\t\t},\n\t\trenderCall,\n\t\trenderResult,\n\t};\n}\n"]}
@@ -1,6 +1,19 @@
1
1
  import { type Api, type Model, type Tool } from "@earendil-works/pi-ai";
2
2
  import { type CompactionPreparation } from "../../../compaction/index.ts";
3
3
  export type ModelUsabilityAdmission = "start" | "resume" | "switch";
4
+ /**
5
+ * Whether a model can serve this session, and at what cost (#1873).
6
+ *
7
+ * - `fits-now` — the assembled budget already fits the window.
8
+ * - `fits-after-compaction` — it does not fit now, but the window can hold the
9
+ * fixed overhead plus a reduced transcript, so reducing the transcript makes
10
+ * the model usable. This is a capability statement, not a permission: whether
11
+ * the session may reduce (and whether it summarizes or slices) stays with the
12
+ * caller, which is the split `sdk.ts` already makes on resume.
13
+ * - `impossible` — the fixed overhead leaves no room for any transcript, so no
14
+ * amount of reduction helps and refusing is the only correct answer.
15
+ */
16
+ export type ModelUsabilityVerdict = "fits-now" | "fits-after-compaction" | "impossible";
4
17
  export interface ModelUsabilityBudgetProjection {
5
18
  readonly model: string;
6
19
  readonly contextWindow: number;
@@ -16,6 +29,11 @@ export interface ModelUsabilityBudgetProjection {
16
29
  readonly shortfallTokens: number;
17
30
  readonly usable: boolean;
18
31
  readonly admission: ModelUsabilityAdmission;
32
+ readonly verdict: ModelUsabilityVerdict;
33
+ /** Overhead plus the post-compaction keep-recent floor; the `impossible` boundary. */
34
+ readonly postCompactionRequiredTokens: number;
35
+ /** Live context plus the overhead a summarization request itself must carry. */
36
+ readonly compactionRequiredTokens: number;
19
37
  }
20
38
  export interface ModelUsabilityBudgetInput<TApi extends Api> {
21
39
  readonly model: Model<TApi>;
@@ -1 +1 @@
1
- {"version":3,"file":"model-usability-budget.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/model-usability-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAyB,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,KAAK,qBAAqB,EAA+B,MAAM,8BAA8B,CAAC;AAqBvG,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpE,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;CAC5C;AAED,MAAM,WAAW,yBAAyB,CAAC,IAAI,SAAS,GAAG;IAC1D,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACvD,2FAA2F;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,uBAAuB,CAAC;CAC7C;AAkBD,wBAAgB,2BAA2B,CAAC,IAAI,SAAS,GAAG,EAC3D,KAAK,EAAE,yBAAyB,CAAC,IAAI,CAAC,GACpC,8BAA8B,CA8EhC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC;IAEpD,YAAY,UAAU,EAAE,8BAA8B,EAWrD;CACD"}
1
+ {"version":3,"file":"model-usability-budget.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/model-usability-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAyB,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,KAAK,qBAAqB,EAA+B,MAAM,8BAA8B,CAAC;AAqBvG,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEpE;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,uBAAuB,GAAG,YAAY,CAAC;AAExF,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,uBAAuB,CAAC;IAC5C,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,sFAAsF;IACtF,QAAQ,CAAC,4BAA4B,EAAE,MAAM,CAAC;IAC9C,gFAAgF;IAChF,QAAQ,CAAC,wBAAwB,EAAE,MAAM,CAAC;CAC1C;AAED,MAAM,WAAW,yBAAyB,CAAC,IAAI,SAAS,GAAG;IAC1D,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC;IAChC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACvD,2FAA2F;IAC3F,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAC1C,QAAQ,CAAC,SAAS,CAAC,EAAE,uBAAuB,CAAC;CAC7C;AAkBD,wBAAgB,2BAA2B,CAAC,IAAI,SAAS,GAAG,EAC3D,KAAK,EAAE,yBAAyB,CAAC,IAAI,CAAC,GACpC,8BAA8B,CAsFhC;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC;IAEpD,YAAY,UAAU,EAAE,8BAA8B,EAWrD;CACD"}
@@ -49,18 +49,23 @@ export function projectModelUsabilityBudget(input) {
49
49
  safetyMargin.tokens;
50
50
  const uncompactedRequiredTokens = liveContextTokens + baseRequiredTokens;
51
51
  const admission = input.admission ?? (liveContextTokens > 0 ? "switch" : "start");
52
+ // #1873: both reduction geometries are projected for every admission, so a
53
+ // caller can tell "needs a smaller transcript" from "can never serve" without
54
+ // re-deriving the arithmetic. Only the `usable` relaxation below stays scoped
55
+ // to resume, keeping the admission contract other callers already depend on.
56
+ const compactionRequiredTokens = liveContextTokens + systemPromptTokens + activeToolSchemaTokens + compactionReserveTokens + safetyMargin.tokens;
57
+ const keepRecentSetting = input.compaction.keepRecentTokens ?? DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
58
+ const effectiveKeepRecentTokens = computeEffectiveKeepRecentTokens(keepRecentSetting, input.model.contextWindow, baseThresholdRatioForWindow(input.model.contextWindow));
59
+ const postCompactionRequiredTokens = effectiveKeepRecentTokens + baseRequiredTokens;
52
60
  let requiredTokens = uncompactedRequiredTokens;
53
61
  let shortfallTokens = Math.max(0, requiredTokens - input.model.contextWindow);
54
62
  let usable = shortfallTokens === 0;
63
+ const verdict = usable
64
+ ? "fits-now"
65
+ : postCompactionRequiredTokens <= input.model.contextWindow
66
+ ? "fits-after-compaction"
67
+ : "impossible";
55
68
  if (!usable && admission === "resume" && input.compaction.enabled && !includeSpeculationLead) {
56
- const compactionRequiredTokens = liveContextTokens +
57
- systemPromptTokens +
58
- activeToolSchemaTokens +
59
- compactionReserveTokens +
60
- safetyMargin.tokens;
61
- const keepRecentSetting = input.compaction.keepRecentTokens ?? DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
62
- const effectiveKeepRecentTokens = computeEffectiveKeepRecentTokens(keepRecentSetting, input.model.contextWindow, baseThresholdRatioForWindow(input.model.contextWindow));
63
- const postCompactionRequiredTokens = effectiveKeepRecentTokens + baseRequiredTokens;
64
69
  if (compactionRequiredTokens <= input.model.contextWindow &&
65
70
  postCompactionRequiredTokens <= input.model.contextWindow) {
66
71
  requiredTokens = Math.max(compactionRequiredTokens, postCompactionRequiredTokens);
@@ -83,6 +88,9 @@ export function projectModelUsabilityBudget(input) {
83
88
  shortfallTokens,
84
89
  usable: shortfallTokens === 0,
85
90
  admission,
91
+ verdict,
92
+ postCompactionRequiredTokens,
93
+ compactionRequiredTokens,
86
94
  };
87
95
  }
88
96
  export class ModelUsabilityBudgetError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"model-usability-budget.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/model-usability-budget.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Api, estimateContextTokens, type Model, type Tool } from \"@earendil-works/pi-ai\";\nimport { type CompactionPreparation, DEFAULT_COMPACTION_SETTINGS } from \"../../../compaction/index.ts\";\nimport { getPromptContextWindow } from \"./extension-wiring.ts\";\nimport { resolveCompactionGeometry } from \"./orchestration.ts\";\nimport { baseThresholdRatioForWindow, computeEffectiveKeepRecentTokens } from \"./policy.ts\";\n\ninterface ModelSafetyMarginProfile {\n\treadonly id: string;\n\treadonly tokens: number;\n\treadonly providers?: readonly string[];\n\treadonly familyMarkers?: readonly string[];\n}\n\n/** Measured conversation runway by shipped prompt family, rounded up to a 4K token boundary. */\nconst MODEL_SAFETY_MARGIN_PROFILES: readonly ModelSafetyMarginProfile[] = [\n\t{ id: \"anthropic\", tokens: 16_384, providers: [\"anthropic\"], familyMarkers: [\"claude\"] },\n\t{ id: \"openai-reasoning\", tokens: 16_384, providers: [\"openai\"], familyMarkers: [\"gpt-5\", \"o1\", \"o3\", \"o4\"] },\n\t{ id: \"google\", tokens: 12_288, providers: [\"google\"], familyMarkers: [\"gemini\"] },\n\t{ id: \"deepseek\", tokens: 12_288, providers: [\"deepseek\"], familyMarkers: [\"deepseek\"] },\n\t{ id: \"default\", tokens: 8_192 },\n];\n\nexport type ModelUsabilityAdmission = \"start\" | \"resume\" | \"switch\";\n\nexport interface ModelUsabilityBudgetProjection {\n\treadonly model: string;\n\treadonly contextWindow: number;\n\treadonly liveContextTokens: number;\n\treadonly systemPromptTokens: number;\n\treadonly activeToolSchemaTokens: number;\n\treadonly outputReserveTokens: number;\n\treadonly compactionReserveTokens: number;\n\treadonly speculationLeadTokens: number;\n\treadonly safetyMarginTokens: number;\n\treadonly safetyMarginProfile: string;\n\treadonly requiredTokens: number;\n\treadonly shortfallTokens: number;\n\treadonly usable: boolean;\n\treadonly admission: ModelUsabilityAdmission;\n}\n\nexport interface ModelUsabilityBudgetInput<TApi extends Api> {\n\treadonly model: Model<TApi>;\n\treadonly systemPrompt: string;\n\treadonly tools: readonly Tool[];\n\treadonly liveContextTokens?: number;\n\treadonly compaction: CompactionPreparation[\"settings\"];\n\t/** Defaults to true. Resume/startup must pass false so speculation can run after admit. */\n\treadonly includeSpeculationLead?: boolean;\n\treadonly admission?: ModelUsabilityAdmission;\n}\n\nfunction matchesFamilyMarker(modelId: string, marker: string): boolean {\n\tconst escapedMarker = marker.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\treturn new RegExp(`(?:^|[/.:_-])${escapedMarker}(?=$|[^a-z0-9])`).test(modelId.toLowerCase());\n}\n\nfunction resolveSafetyMarginProfile<TApi extends Api>(model: Model<TApi>): ModelSafetyMarginProfile {\n\treturn (\n\t\tMODEL_SAFETY_MARGIN_PROFILES.find(\n\t\t\t(profile) =>\n\t\t\t\tprofile.id !== \"default\" &&\n\t\t\t\t(profile.providers?.includes(model.provider) === true ||\n\t\t\t\t\tprofile.familyMarkers?.some((marker) => matchesFamilyMarker(model.id, marker)) === true),\n\t\t) ?? MODEL_SAFETY_MARGIN_PROFILES[MODEL_SAFETY_MARGIN_PROFILES.length - 1]\n\t);\n}\n\nexport function projectModelUsabilityBudget<TApi extends Api>(\n\tinput: ModelUsabilityBudgetInput<TApi>,\n): ModelUsabilityBudgetProjection {\n\tconst liveContextTokens = input.liveContextTokens ?? 0;\n\tconst systemPromptTokens = estimateContextTokens({\n\t\tsystemPrompt: input.systemPrompt,\n\t\tmessages: [],\n\t\ttools: [],\n\t}).tokens;\n\tconst promptAndToolsTokens = estimateContextTokens({\n\t\tsystemPrompt: input.systemPrompt,\n\t\tmessages: [],\n\t\ttools: [...input.tools],\n\t}).tokens;\n\tconst activeToolSchemaTokens = promptAndToolsTokens - systemPromptTokens;\n\tconst outputReserveTokens =\n\t\tinput.model.contextWindow - getPromptContextWindow(input.model.contextWindow, input.model.maxTokens);\n\tconst geometry = resolveCompactionGeometry({ contextWindow: input.model.contextWindow, settings: input.compaction });\n\tconst compactionReserveTokens = input.compaction.enabled ? geometry.reserveTokens : 0;\n\tconst includeSpeculationLead = input.includeSpeculationLead !== false;\n\tconst speculationLeadTokens =\n\t\tincludeSpeculationLead && input.compaction.enabled && input.compaction.speculativeEnabled !== false\n\t\t\t? geometry.leadTokens\n\t\t\t: 0;\n\tconst safetyMargin = resolveSafetyMarginProfile(input.model);\n\tconst baseRequiredTokens =\n\t\tsystemPromptTokens +\n\t\tactiveToolSchemaTokens +\n\t\toutputReserveTokens +\n\t\tcompactionReserveTokens +\n\t\tspeculationLeadTokens +\n\t\tsafetyMargin.tokens;\n\tconst uncompactedRequiredTokens = liveContextTokens + baseRequiredTokens;\n\tconst admission = input.admission ?? (liveContextTokens > 0 ? \"switch\" : \"start\");\n\n\tlet requiredTokens = uncompactedRequiredTokens;\n\tlet shortfallTokens = Math.max(0, requiredTokens - input.model.contextWindow);\n\tlet usable = shortfallTokens === 0;\n\n\tif (!usable && admission === \"resume\" && input.compaction.enabled && !includeSpeculationLead) {\n\t\tconst compactionRequiredTokens =\n\t\t\tliveContextTokens +\n\t\t\tsystemPromptTokens +\n\t\t\tactiveToolSchemaTokens +\n\t\t\tcompactionReserveTokens +\n\t\t\tsafetyMargin.tokens;\n\t\tconst keepRecentSetting = input.compaction.keepRecentTokens ?? DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;\n\t\tconst effectiveKeepRecentTokens = computeEffectiveKeepRecentTokens(\n\t\t\tkeepRecentSetting,\n\t\t\tinput.model.contextWindow,\n\t\t\tbaseThresholdRatioForWindow(input.model.contextWindow),\n\t\t);\n\t\tconst postCompactionRequiredTokens = effectiveKeepRecentTokens + baseRequiredTokens;\n\n\t\tif (\n\t\t\tcompactionRequiredTokens <= input.model.contextWindow &&\n\t\t\tpostCompactionRequiredTokens <= input.model.contextWindow\n\t\t) {\n\t\t\trequiredTokens = Math.max(compactionRequiredTokens, postCompactionRequiredTokens);\n\t\t\tshortfallTokens = 0;\n\t\t\tusable = true;\n\t\t}\n\t}\n\n\treturn {\n\t\tmodel: `${input.model.provider}/${input.model.id}`,\n\t\tcontextWindow: input.model.contextWindow,\n\t\tliveContextTokens,\n\t\tsystemPromptTokens,\n\t\tactiveToolSchemaTokens,\n\t\toutputReserveTokens,\n\t\tcompactionReserveTokens,\n\t\tspeculationLeadTokens,\n\t\tsafetyMarginTokens: safetyMargin.tokens,\n\t\tsafetyMarginProfile: safetyMargin.id,\n\t\trequiredTokens,\n\t\tshortfallTokens,\n\t\tusable: shortfallTokens === 0,\n\t\tadmission,\n\t};\n}\n\nexport class ModelUsabilityBudgetError extends Error {\n\treadonly projection: ModelUsabilityBudgetProjection;\n\n\tconstructor(projection: ModelUsabilityBudgetProjection) {\n\t\tconst breakdown = `system prompt ${projection.systemPromptTokens}, active tool schemas ${projection.activeToolSchemaTokens}, output reserve ${projection.outputReserveTokens}, compaction reserve ${projection.compactionReserveTokens}, speculation lead ${projection.speculationLeadTokens}, safety margin ${projection.safetyMarginTokens} [${projection.safetyMarginProfile}]`;\n\t\tconst message =\n\t\t\tprojection.admission === \"resume\"\n\t\t\t\t? `Model \"${projection.model}\" cannot resume: target context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token requirement (live context ${projection.liveContextTokens}, ${breakdown}).`\n\t\t\t\t: projection.admission === \"switch\"\n\t\t\t\t\t? `Model \"${projection.model}\" cannot switch: target context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token requirement (live context ${projection.liveContextTokens}, ${breakdown}). Compact the session, then revalidate and retry the model switch.`\n\t\t\t\t\t: `Model \"${projection.model}\" cannot start: context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token minimum (${breakdown}).`;\n\t\tsuper(message);\n\t\tthis.name = \"ModelUsabilityBudgetError\";\n\t\tthis.projection = projection;\n\t}\n}\n"]}
1
+ {"version":3,"file":"model-usability-budget.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/model-usability-budget.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Api, estimateContextTokens, type Model, type Tool } from \"@earendil-works/pi-ai\";\nimport { type CompactionPreparation, DEFAULT_COMPACTION_SETTINGS } from \"../../../compaction/index.ts\";\nimport { getPromptContextWindow } from \"./extension-wiring.ts\";\nimport { resolveCompactionGeometry } from \"./orchestration.ts\";\nimport { baseThresholdRatioForWindow, computeEffectiveKeepRecentTokens } from \"./policy.ts\";\n\ninterface ModelSafetyMarginProfile {\n\treadonly id: string;\n\treadonly tokens: number;\n\treadonly providers?: readonly string[];\n\treadonly familyMarkers?: readonly string[];\n}\n\n/** Measured conversation runway by shipped prompt family, rounded up to a 4K token boundary. */\nconst MODEL_SAFETY_MARGIN_PROFILES: readonly ModelSafetyMarginProfile[] = [\n\t{ id: \"anthropic\", tokens: 16_384, providers: [\"anthropic\"], familyMarkers: [\"claude\"] },\n\t{ id: \"openai-reasoning\", tokens: 16_384, providers: [\"openai\"], familyMarkers: [\"gpt-5\", \"o1\", \"o3\", \"o4\"] },\n\t{ id: \"google\", tokens: 12_288, providers: [\"google\"], familyMarkers: [\"gemini\"] },\n\t{ id: \"deepseek\", tokens: 12_288, providers: [\"deepseek\"], familyMarkers: [\"deepseek\"] },\n\t{ id: \"default\", tokens: 8_192 },\n];\n\nexport type ModelUsabilityAdmission = \"start\" | \"resume\" | \"switch\";\n\n/**\n * Whether a model can serve this session, and at what cost (#1873).\n *\n * - `fits-now` — the assembled budget already fits the window.\n * - `fits-after-compaction` — it does not fit now, but the window can hold the\n * fixed overhead plus a reduced transcript, so reducing the transcript makes\n * the model usable. This is a capability statement, not a permission: whether\n * the session may reduce (and whether it summarizes or slices) stays with the\n * caller, which is the split `sdk.ts` already makes on resume.\n * - `impossible` — the fixed overhead leaves no room for any transcript, so no\n * amount of reduction helps and refusing is the only correct answer.\n */\nexport type ModelUsabilityVerdict = \"fits-now\" | \"fits-after-compaction\" | \"impossible\";\n\nexport interface ModelUsabilityBudgetProjection {\n\treadonly model: string;\n\treadonly contextWindow: number;\n\treadonly liveContextTokens: number;\n\treadonly systemPromptTokens: number;\n\treadonly activeToolSchemaTokens: number;\n\treadonly outputReserveTokens: number;\n\treadonly compactionReserveTokens: number;\n\treadonly speculationLeadTokens: number;\n\treadonly safetyMarginTokens: number;\n\treadonly safetyMarginProfile: string;\n\treadonly requiredTokens: number;\n\treadonly shortfallTokens: number;\n\treadonly usable: boolean;\n\treadonly admission: ModelUsabilityAdmission;\n\treadonly verdict: ModelUsabilityVerdict;\n\t/** Overhead plus the post-compaction keep-recent floor; the `impossible` boundary. */\n\treadonly postCompactionRequiredTokens: number;\n\t/** Live context plus the overhead a summarization request itself must carry. */\n\treadonly compactionRequiredTokens: number;\n}\n\nexport interface ModelUsabilityBudgetInput<TApi extends Api> {\n\treadonly model: Model<TApi>;\n\treadonly systemPrompt: string;\n\treadonly tools: readonly Tool[];\n\treadonly liveContextTokens?: number;\n\treadonly compaction: CompactionPreparation[\"settings\"];\n\t/** Defaults to true. Resume/startup must pass false so speculation can run after admit. */\n\treadonly includeSpeculationLead?: boolean;\n\treadonly admission?: ModelUsabilityAdmission;\n}\n\nfunction matchesFamilyMarker(modelId: string, marker: string): boolean {\n\tconst escapedMarker = marker.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n\treturn new RegExp(`(?:^|[/.:_-])${escapedMarker}(?=$|[^a-z0-9])`).test(modelId.toLowerCase());\n}\n\nfunction resolveSafetyMarginProfile<TApi extends Api>(model: Model<TApi>): ModelSafetyMarginProfile {\n\treturn (\n\t\tMODEL_SAFETY_MARGIN_PROFILES.find(\n\t\t\t(profile) =>\n\t\t\t\tprofile.id !== \"default\" &&\n\t\t\t\t(profile.providers?.includes(model.provider) === true ||\n\t\t\t\t\tprofile.familyMarkers?.some((marker) => matchesFamilyMarker(model.id, marker)) === true),\n\t\t) ?? MODEL_SAFETY_MARGIN_PROFILES[MODEL_SAFETY_MARGIN_PROFILES.length - 1]\n\t);\n}\n\nexport function projectModelUsabilityBudget<TApi extends Api>(\n\tinput: ModelUsabilityBudgetInput<TApi>,\n): ModelUsabilityBudgetProjection {\n\tconst liveContextTokens = input.liveContextTokens ?? 0;\n\tconst systemPromptTokens = estimateContextTokens({\n\t\tsystemPrompt: input.systemPrompt,\n\t\tmessages: [],\n\t\ttools: [],\n\t}).tokens;\n\tconst promptAndToolsTokens = estimateContextTokens({\n\t\tsystemPrompt: input.systemPrompt,\n\t\tmessages: [],\n\t\ttools: [...input.tools],\n\t}).tokens;\n\tconst activeToolSchemaTokens = promptAndToolsTokens - systemPromptTokens;\n\tconst outputReserveTokens =\n\t\tinput.model.contextWindow - getPromptContextWindow(input.model.contextWindow, input.model.maxTokens);\n\tconst geometry = resolveCompactionGeometry({ contextWindow: input.model.contextWindow, settings: input.compaction });\n\tconst compactionReserveTokens = input.compaction.enabled ? geometry.reserveTokens : 0;\n\tconst includeSpeculationLead = input.includeSpeculationLead !== false;\n\tconst speculationLeadTokens =\n\t\tincludeSpeculationLead && input.compaction.enabled && input.compaction.speculativeEnabled !== false\n\t\t\t? geometry.leadTokens\n\t\t\t: 0;\n\tconst safetyMargin = resolveSafetyMarginProfile(input.model);\n\tconst baseRequiredTokens =\n\t\tsystemPromptTokens +\n\t\tactiveToolSchemaTokens +\n\t\toutputReserveTokens +\n\t\tcompactionReserveTokens +\n\t\tspeculationLeadTokens +\n\t\tsafetyMargin.tokens;\n\tconst uncompactedRequiredTokens = liveContextTokens + baseRequiredTokens;\n\tconst admission = input.admission ?? (liveContextTokens > 0 ? \"switch\" : \"start\");\n\n\t// #1873: both reduction geometries are projected for every admission, so a\n\t// caller can tell \"needs a smaller transcript\" from \"can never serve\" without\n\t// re-deriving the arithmetic. Only the `usable` relaxation below stays scoped\n\t// to resume, keeping the admission contract other callers already depend on.\n\tconst compactionRequiredTokens =\n\t\tliveContextTokens + systemPromptTokens + activeToolSchemaTokens + compactionReserveTokens + safetyMargin.tokens;\n\tconst keepRecentSetting = input.compaction.keepRecentTokens ?? DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;\n\tconst effectiveKeepRecentTokens = computeEffectiveKeepRecentTokens(\n\t\tkeepRecentSetting,\n\t\tinput.model.contextWindow,\n\t\tbaseThresholdRatioForWindow(input.model.contextWindow),\n\t);\n\tconst postCompactionRequiredTokens = effectiveKeepRecentTokens + baseRequiredTokens;\n\n\tlet requiredTokens = uncompactedRequiredTokens;\n\tlet shortfallTokens = Math.max(0, requiredTokens - input.model.contextWindow);\n\tlet usable = shortfallTokens === 0;\n\tconst verdict: ModelUsabilityVerdict = usable\n\t\t? \"fits-now\"\n\t\t: postCompactionRequiredTokens <= input.model.contextWindow\n\t\t\t? \"fits-after-compaction\"\n\t\t\t: \"impossible\";\n\n\tif (!usable && admission === \"resume\" && input.compaction.enabled && !includeSpeculationLead) {\n\t\tif (\n\t\t\tcompactionRequiredTokens <= input.model.contextWindow &&\n\t\t\tpostCompactionRequiredTokens <= input.model.contextWindow\n\t\t) {\n\t\t\trequiredTokens = Math.max(compactionRequiredTokens, postCompactionRequiredTokens);\n\t\t\tshortfallTokens = 0;\n\t\t\tusable = true;\n\t\t}\n\t}\n\n\treturn {\n\t\tmodel: `${input.model.provider}/${input.model.id}`,\n\t\tcontextWindow: input.model.contextWindow,\n\t\tliveContextTokens,\n\t\tsystemPromptTokens,\n\t\tactiveToolSchemaTokens,\n\t\toutputReserveTokens,\n\t\tcompactionReserveTokens,\n\t\tspeculationLeadTokens,\n\t\tsafetyMarginTokens: safetyMargin.tokens,\n\t\tsafetyMarginProfile: safetyMargin.id,\n\t\trequiredTokens,\n\t\tshortfallTokens,\n\t\tusable: shortfallTokens === 0,\n\t\tadmission,\n\t\tverdict,\n\t\tpostCompactionRequiredTokens,\n\t\tcompactionRequiredTokens,\n\t};\n}\n\nexport class ModelUsabilityBudgetError extends Error {\n\treadonly projection: ModelUsabilityBudgetProjection;\n\n\tconstructor(projection: ModelUsabilityBudgetProjection) {\n\t\tconst breakdown = `system prompt ${projection.systemPromptTokens}, active tool schemas ${projection.activeToolSchemaTokens}, output reserve ${projection.outputReserveTokens}, compaction reserve ${projection.compactionReserveTokens}, speculation lead ${projection.speculationLeadTokens}, safety margin ${projection.safetyMarginTokens} [${projection.safetyMarginProfile}]`;\n\t\tconst message =\n\t\t\tprojection.admission === \"resume\"\n\t\t\t\t? `Model \"${projection.model}\" cannot resume: target context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token requirement (live context ${projection.liveContextTokens}, ${breakdown}).`\n\t\t\t\t: projection.admission === \"switch\"\n\t\t\t\t\t? `Model \"${projection.model}\" cannot switch: target context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token requirement (live context ${projection.liveContextTokens}, ${breakdown}). Compact the session, then revalidate and retry the model switch.`\n\t\t\t\t\t: `Model \"${projection.model}\" cannot start: context window ${projection.contextWindow} tokens is ${projection.shortfallTokens} tokens short of the ${projection.requiredTokens}-token minimum (${breakdown}).`;\n\t\tsuper(message);\n\t\tthis.name = \"ModelUsabilityBudgetError\";\n\t\tthis.projection = projection;\n\t}\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import type { Api, Model } from "@earendil-works/pi-ai";
2
+ import type { ModelUsabilityBudgetProjection } from "./model-usability-budget.ts";
3
+ export interface PendingModelSwitch {
4
+ readonly model: Model<Api>;
5
+ readonly projection: ModelUsabilityBudgetProjection;
6
+ readonly persistDefault: boolean;
7
+ readonly notice: string;
8
+ }
9
+ export declare function createPendingModelSwitch(input: {
10
+ model: Model<Api>;
11
+ projection: ModelUsabilityBudgetProjection;
12
+ persistDefault: boolean;
13
+ }): PendingModelSwitch;
14
+ /**
15
+ * The keep-recent size the PENDING model's window is designed around, recovered
16
+ * from the projection's own post-compaction geometry. The deferred compaction
17
+ * aims here while the summary request is still issued by the model that can hold
18
+ * the transcript today, so geometry and summarizer come from different models on
19
+ * purpose: aiming at the current model's geometry, or merely at whatever still
20
+ * fits, leaves no room for the summary the compaction is about to add.
21
+ */
22
+ export declare function pendingSwitchKeepRecentTokens(projection: ModelUsabilityBudgetProjection): number;
23
+ //# sourceMappingURL=switch-admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-admission.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/switch-admission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,6BAA6B,CAAC;AAElF,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC;IACpD,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC/C,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,UAAU,EAAE,8BAA8B,CAAC;IAC3C,cAAc,EAAE,OAAO,CAAC;CACxB,GAAG,kBAAkB,CAKrB;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAAC,UAAU,EAAE,8BAA8B,GAAG,MAAM,CAGhG"}
@@ -0,0 +1,19 @@
1
+ export function createPendingModelSwitch(input) {
2
+ return {
3
+ ...input,
4
+ notice: `${input.model.id} needs ${input.projection.shortfallTokens} fewer tokens than this conversation holds. It is compacted on your next message, and the switch applies after that.`,
5
+ };
6
+ }
7
+ /**
8
+ * The keep-recent size the PENDING model's window is designed around, recovered
9
+ * from the projection's own post-compaction geometry. The deferred compaction
10
+ * aims here while the summary request is still issued by the model that can hold
11
+ * the transcript today, so geometry and summarizer come from different models on
12
+ * purpose: aiming at the current model's geometry, or merely at whatever still
13
+ * fits, leaves no room for the summary the compaction is about to add.
14
+ */
15
+ export function pendingSwitchKeepRecentTokens(projection) {
16
+ const overheadTokens = projection.requiredTokens - projection.liveContextTokens;
17
+ return Math.max(1, projection.postCompactionRequiredTokens - overheadTokens);
18
+ }
19
+ //# sourceMappingURL=switch-admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"switch-admission.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/compaction/switch-admission.ts"],"names":[],"mappings":"AAUA,MAAM,UAAU,wBAAwB,CAAC,KAIxC;IACA,OAAO;QACN,GAAG,KAAK;QACR,MAAM,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,KAAK,CAAC,UAAU,CAAC,eAAe,sHAAsH;KACzL,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,6BAA6B,CAAC,UAA0C;IACvF,MAAM,cAAc,GAAG,UAAU,CAAC,cAAc,GAAG,UAAU,CAAC,iBAAiB,CAAC;IAChF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,4BAA4B,GAAG,cAAc,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { ModelUsabilityBudgetProjection } from \"./model-usability-budget.ts\";\n\nexport interface PendingModelSwitch {\n\treadonly model: Model<Api>;\n\treadonly projection: ModelUsabilityBudgetProjection;\n\treadonly persistDefault: boolean;\n\treadonly notice: string;\n}\n\nexport function createPendingModelSwitch(input: {\n\tmodel: Model<Api>;\n\tprojection: ModelUsabilityBudgetProjection;\n\tpersistDefault: boolean;\n}): PendingModelSwitch {\n\treturn {\n\t\t...input,\n\t\tnotice: `${input.model.id} needs ${input.projection.shortfallTokens} fewer tokens than this conversation holds. It is compacted on your next message, and the switch applies after that.`,\n\t};\n}\n\n/**\n * The keep-recent size the PENDING model's window is designed around, recovered\n * from the projection's own post-compaction geometry. The deferred compaction\n * aims here while the summary request is still issued by the model that can hold\n * the transcript today, so geometry and summarizer come from different models on\n * purpose: aiming at the current model's geometry, or merely at whatever still\n * fits, leaves no room for the summary the compaction is about to add.\n */\nexport function pendingSwitchKeepRecentTokens(projection: ModelUsabilityBudgetProjection): number {\n\tconst overheadTokens = projection.requiredTokens - projection.liveContextTokens;\n\treturn Math.max(1, projection.postCompactionRequiredTokens - overheadTokens);\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAgCrE,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA8X5D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/goal/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAgCrE,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAsY5D"}
@@ -1,3 +1,4 @@
1
+ import { MANUAL_CONTINUE_CUSTOM_TYPE } from "../../../manual-continue.js";
1
2
  import { GOAL_CONTINUATION_MESSAGE_TYPE } from "../../../messages.js";
2
3
  import { continueGoalAfterAgentEnd } from "./agent-end-continuation.js";
3
4
  import { GOAL_CACHE_WARMUP_ENTRY_TYPE } from "./cache-warm.js";
@@ -186,6 +187,15 @@ export default function goalExtension(pi) {
186
187
  agentGoalAccounting = null;
187
188
  }
188
189
  });
190
+ pi.on("message_start", async (event, ctx) => {
191
+ if (event.message.role !== "custom" || event.message.customType !== MANUAL_CONTINUE_CUSTOM_TYPE)
192
+ return;
193
+ const ref = goalStoreRef(ctx);
194
+ const goal = await readGoal(ref);
195
+ if (goal?.status !== "blocked")
196
+ return;
197
+ syncContinuationGoal(ctx, await updateGoal(ref, { status: "active" }, "user"));
198
+ });
189
199
  pi.on("message_end", async (event) => {
190
200
  turnUsage.noteMessageEnd(event.message);
191
201
  });