@code-yeongyu/senpi 2026.9.29-5 → 2026.9.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/dist/bundle/chunks/{anthropic-messages-HEVKB3PL.js → anthropic-messages-ADSIWAJ5.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-32AFWZEH.js → app-server-command-B3ZYM2WN.js} +1 -1
- package/dist/bundle/chunks/{chunk-2WAKOPNA.js → chunk-4P4DUW66.js} +1 -1
- package/dist/bundle/chunks/{chunk-7RVWDN5Z.js → chunk-6GV3L3GZ.js} +1 -1
- package/dist/bundle/chunks/{chunk-OUG6SDT7.js → chunk-6JZQLUAJ.js} +1 -1
- package/dist/bundle/chunks/{chunk-MJQWYN2W.js → chunk-7MKMDWHC.js} +2 -2
- package/dist/bundle/chunks/{chunk-XBGO2JZD.js → chunk-A7AQSA6R.js} +1 -1
- package/dist/bundle/chunks/{chunk-BJICJY47.js → chunk-DVVROXLZ.js} +1 -1
- package/dist/bundle/chunks/chunk-EIG6K5U3.js +6 -0
- package/dist/bundle/chunks/chunk-FAWC2EIR.js +2 -0
- package/dist/bundle/chunks/{chunk-WCAKUCN3.js → chunk-FNCILR4E.js} +1 -1
- package/dist/bundle/chunks/{chunk-XHEMQA2T.js → chunk-HPESFBMB.js} +2 -2
- package/dist/bundle/chunks/{chunk-BBNWT72C.js → chunk-HVBFDRP4.js} +1 -1
- package/dist/bundle/chunks/{chunk-63QHOG5A.js → chunk-I77ENHCQ.js} +81 -74
- package/dist/bundle/chunks/{chunk-UMLU3I2G.js → chunk-ITXF6FAM.js} +1 -1
- package/dist/bundle/chunks/{chunk-77AUUH3Z.js → chunk-IXONH7ML.js} +2 -2
- package/dist/bundle/chunks/{chunk-K2SHQNAA.js → chunk-JX2CSKLG.js} +1 -1
- package/dist/bundle/chunks/{chunk-4VWWEMCJ.js → chunk-JXZ5LDJP.js} +1 -1
- package/dist/bundle/chunks/{chunk-VLTMFAWQ.js → chunk-KWI3KUF2.js} +1 -1
- package/dist/bundle/chunks/{chunk-7CQX3QFV.js → chunk-L4QGZJKQ.js} +1 -1
- package/dist/bundle/chunks/{chunk-AGVPQLIU.js → chunk-MDXUQDGF.js} +1 -1
- package/dist/bundle/chunks/{chunk-2N3BZ5DV.js → chunk-OOA6OL42.js} +1 -1
- package/dist/bundle/chunks/{chunk-2POUJCWM.js → chunk-OVGH7VP3.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZDNR3YVJ.js → chunk-RMVQ5YRX.js} +1 -1
- package/dist/bundle/chunks/{chunk-SH6VUPE4.js → chunk-T5MOQDGL.js} +1 -1
- package/dist/bundle/chunks/{chunk-CNYWF72S.js → chunk-VKECIIYV.js} +1 -1
- package/dist/bundle/chunks/{chunk-U7BNOE23.js → chunk-VNYUGCC7.js} +1 -1
- package/dist/bundle/chunks/{chunk-ZPMJIPQC.js → chunk-YSNNL5HU.js} +1 -1
- package/dist/bundle/chunks/cli-main-F6WNYPUT.js +3 -0
- package/dist/bundle/chunks/{help-fast-path-ZUNFOE53.js → help-fast-path-34Z6I2PH.js} +1 -1
- package/dist/bundle/chunks/{host-command-V2LDNNXB.js → host-command-PUOIPEBI.js} +1 -1
- package/dist/bundle/chunks/{host-lifecycle-PFLJRXEN.js → host-lifecycle-4VJUHV5U.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +1 -1
- package/dist/bundle/chunks/{interactive-mode-J6KGDRR7.js → interactive-mode-KM454JPC.js} +1 -1
- package/dist/bundle/chunks/{list-tips-ASETMEFS.js → list-tips-GWRRD5UC.js} +1 -1
- package/dist/bundle/chunks/{multi-session-host-IBVJ3WN7.js → multi-session-host-ZFT6RM2S.js} +2 -2
- package/dist/bundle/chunks/{native-loader-WM7NDPEO.js → native-loader-HW7GQYPY.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-SA2UJW7Q.js → openai-completions-32RTG3Q4.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-JJ5R7UWV.js → package-manager-cli-LNQOWI3A.js} +1 -1
- package/dist/bundle/chunks/{rg-engine-G2YTYL2T.js → rg-engine-LWZA7TRH.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-EX7PWQ4S.js → rotation-stream-DIKK7U57.js} +1 -1
- package/dist/bundle/chunks/{rpc-mode-ITND2BVC.js → rpc-mode-H7QGQI4P.js} +1 -1
- package/dist/bundle/chunks/{schedule-command-D6JNRP3D.js → schedule-command-BUFJOM5L.js} +1 -1
- package/dist/bundle/chunks/session-control-endpoint-QIIA6MFR.js +3 -0
- package/dist/bundle/chunks/{session-picker-K5FIKV6C.js → session-picker-P53MTWTS.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +110 -103
- package/dist/bundle/chunks/{state-store-IIQL55GC.js → state-store-QGLITAI4.js} +1 -1
- package/dist/bundle/cli.js +2 -2
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/bundle/runtime-manifest.json +1 -1
- package/dist/cli.js +1 -1
- package/dist/core/dynamic-prompt/build.d.ts +4 -3
- package/dist/core/dynamic-prompt/build.js +4 -2
- package/dist/core/dynamic-prompt/handoff.d.ts +9 -1
- package/dist/core/dynamic-prompt/handoff.js +11 -1
- package/dist/core/dynamic-prompt/intent-gate.js +1 -1
- package/dist/core/dynamic-prompt/style.js +4 -2
- package/dist/core/dynamic-prompt/types.d.ts +7 -1
- package/dist/core/dynamic-prompt/types.js +3 -1
- package/dist/core/dynamic-prompt/verification.d.ts +1 -1
- package/dist/core/dynamic-prompt/verification.js +2 -1
- package/dist/core/extensions/builtin/anthropic-subscription/stream-protocol.d.ts +1 -1
- package/dist/core/extensions/builtin/anthropic-subscription/tools.d.ts +9 -2
- package/dist/core/extensions/builtin/anthropic-subscription/tools.js +9 -4
- package/dist/core/extensions/builtin/ask-user/resume.js +20 -9
- package/dist/core/extensions/builtin/ask-user/tool.d.ts +9 -0
- package/dist/core/extensions/builtin/ask-user/tool.js +1 -1
- package/dist/core/extensions/builtin/cache-keepalive/index.js +17 -4
- package/dist/core/extensions/builtin/chat-reply-scrub/index.d.ts +16 -0
- package/dist/core/extensions/builtin/chat-reply-scrub/index.js +39 -0
- package/dist/core/extensions/builtin/chat-reply-scrub/scaffold-strip.d.ts +7 -0
- package/dist/core/extensions/builtin/chat-reply-scrub/scaffold-strip.js +125 -0
- package/dist/core/extensions/builtin/index.js +3 -0
- package/dist/core/extensions/builtin/permission-system/cli.js +1 -0
- package/dist/core/extensions/builtin/permission-system/config.d.ts +1 -0
- package/dist/core/extensions/builtin/permission-system/config.js +10 -0
- package/dist/core/extensions/builtin/permission-system/index.js +2 -2
- package/dist/core/extensions/builtin/permission-system/settings.js +1 -1
- package/dist/core/extensions/builtin/permission-system/types.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5-1.js +5 -5
- package/dist/core/extensions/builtin/prompt-preset/claude-fable-5.js +6 -6
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-opus-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/claude-sonnet-5-5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.5.js +9 -6
- package/dist/core/extensions/builtin/prompt-preset/gpt-5.6.js +10 -7
- package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +15 -6
- package/dist/core/extensions/builtin/prompt-preset/gpt-surface.d.ts +2 -2
- package/dist/core/extensions/builtin/prompt-preset/grok-4.5.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/grok-4.6.js +4 -4
- package/dist/core/extensions/builtin/prompt-preset/grok-4.7.js +3 -3
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-6.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-code.js +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k3.js +6 -6
- package/dist/core/extensions/builtin/recommended-models/index.d.ts +1 -1
- package/dist/core/extensions/builtin/recommended-models/index.js +1 -1
- package/dist/core/extensions/builtin/todotools/first-turn.d.ts +6 -4
- package/dist/core/extensions/builtin/todotools/first-turn.js +17 -5
- package/dist/core/extensions/builtin/todotools/todo-ask.d.ts +1 -0
- package/dist/core/extensions/builtin/todotools/todo-ask.js +1 -1
- package/dist/core/extensions/builtin/todotools/tools/todo.js +4 -1
- package/dist/core/resource-loader.js +7 -3
- package/dist/modes/interactive/components/ask-user-async-widget.d.ts +2 -0
- package/dist/modes/interactive/components/ask-user-async-widget.js +10 -0
- package/dist/modes/interactive/components/ask-user-question-state.js +3 -1
- package/dist/modes/interactive/interactive-mode.js +7 -1
- package/dist/modes/interactive/session-control-commands.d.ts +2 -1
- package/dist/modes/interactive/session-control-commands.js +15 -6
- package/dist/modes/rpc/connection-question-bridge.js +5 -5
- package/dist/modes/rpc/custom-capability.d.ts +6 -0
- package/dist/modes/rpc/custom-capability.js +6 -0
- package/dist/modes/rpc/extension-ui-response.d.ts +11 -0
- package/dist/modes/rpc/extension-ui-response.js +18 -0
- package/dist/modes/rpc/host-lifecycle.d.ts +5 -2
- package/dist/modes/rpc/host-lifecycle.js +14 -8
- package/dist/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/modes/rpc/rpc-input-validation.d.ts +1 -1
- package/dist/modes/rpc/rpc-input-validation.js +3 -3
- package/dist/modes/rpc/rpc-mode.d.ts +3 -2
- package/dist/modes/rpc/rpc-mode.js +3 -2
- package/dist/modes/rpc/rpc-types.d.ts +4 -3
- package/dist/modes/rpc/session-command-router.js +4 -1
- package/dist/runtime-snapshot/enter.d.ts +1 -1
- package/dist/runtime-snapshot/enter.js +17 -7
- package/dist/runtime-snapshot/file-copier.d.ts +24 -0
- package/dist/runtime-snapshot/file-copier.js +76 -0
- package/dist/runtime-snapshot/layout.d.ts +8 -4
- package/dist/runtime-snapshot/layout.js +137 -70
- package/dist/runtime-snapshot/registry.d.ts +7 -0
- package/dist/runtime-snapshot/registry.js +63 -1
- package/dist/senpi +1 -1
- package/docs/providers.md +1 -1
- package/docs/rpc.md +34 -11
- package/package.json +6 -6
- package/dist/bundle/chunks/chunk-AWEAVBCR.js +0 -2
- package/dist/bundle/chunks/chunk-YLQTKWEC.js +0 -6
- package/dist/bundle/chunks/cli-main-ZPMYM3A3.js +0 -3
- package/dist/bundle/chunks/session-control-endpoint-UJJGG4WI.js +0 -3
|
@@ -144,7 +144,8 @@
|
|
|
144
144
|
// plugins); the mapping lives in the PR. No senpi trace of 6.1 Sol exists yet, so no
|
|
145
145
|
// Astra-observed rule was removed on its account.
|
|
146
146
|
import { APP_NAME } from "../../../../config.js";
|
|
147
|
-
import { buildDynamicSystemPrompt } from "../../../dynamic-prompt/build.js";
|
|
147
|
+
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
148
|
+
import { CHAT_FINAL_MESSAGE, CHAT_REPLY_RULE } from "../../../dynamic-prompt/handoff.js";
|
|
148
149
|
import { buildTestDisciplineSection } from "../../../dynamic-prompt/verification.js";
|
|
149
150
|
import { buildFileOperationsTuning } from "./file-operations.js";
|
|
150
151
|
import { buildGptEvalRoutingTuning } from "./gpt-eval-routing.js";
|
|
@@ -222,12 +223,20 @@ const INTENT_GATE_LEAD = {
|
|
|
222
223
|
The declared stop condition is binding: work until it holds, then stop (see Stop Goal).`,
|
|
223
224
|
app: "Open a new request by settling the exact, observable condition that ends the task. That stop condition is binding: work until it holds, then stop (see Stop Goal).",
|
|
224
225
|
};
|
|
226
|
+
const APP_STEERING = STEERING.replace("keep going under the reading you already declared, so the reply opens with the work rather than another routing line;", "keep going under the reading you already settled, so the reply opens with the work;");
|
|
227
|
+
const APP_FINAL_MESSAGE_SHAPE = FINAL_MESSAGE_SHAPE.replace("what you could not verify and why", GPT_APP_UNVERIFIED_SLOT);
|
|
228
|
+
// Chat takes the app wording and replaces the handoff block with the chat reply rule.
|
|
225
229
|
const SURFACE_DIRECTIVE = {
|
|
226
230
|
terminal: { steering: STEERING, handoffReport: HANDOFF_REPORT, finalMessageShape: FINAL_MESSAGE_SHAPE },
|
|
227
231
|
app: {
|
|
228
|
-
steering:
|
|
232
|
+
steering: APP_STEERING,
|
|
229
233
|
handoffReport: HANDOFF_REPORT.replace(GPT_HANDOFF_MOMENTS.terminal, GPT_HANDOFF_MOMENTS.app),
|
|
230
|
-
finalMessageShape:
|
|
234
|
+
finalMessageShape: APP_FINAL_MESSAGE_SHAPE,
|
|
235
|
+
},
|
|
236
|
+
chat: {
|
|
237
|
+
steering: APP_STEERING,
|
|
238
|
+
handoffReport: HANDOFF_REPORT.replace(/^[\s\S]*Between handoffs, no narration\. /, `${CHAT_REPLY_RULE} `),
|
|
239
|
+
finalMessageShape: APP_FINAL_MESSAGE_SHAPE.replace("The final message is the handoff block and stands alone: the outcome first, then in its You need slot the evidence", `${CHAT_FINAL_MESSAGE} and stands alone: the outcome first, then the evidence`),
|
|
231
240
|
},
|
|
232
241
|
};
|
|
233
242
|
function buildGpt6AstraCore(context) {
|
|
@@ -235,7 +244,7 @@ function buildGpt6AstraCore(context) {
|
|
|
235
244
|
|
|
236
245
|
## Intent Gate
|
|
237
246
|
|
|
238
|
-
${INTENT_GATE_LEAD[context.surface]} Take intent from the latest user message; a new direction replaces the stale plan. Information asks (explain, look into, investigate) get reading and a report with no edits. Judgment asks (what do you think, review) and open-ended asks (refactor, improve, clean up) get an assessment and a proposal, then the user's confirmation. Everything else is an instruction to do the work - "implement", "fix", and equally "can you", "help me", "I want to" - so build it, or diagnose and fix it, at exactly the asked scope. Keep prompt scaffolding out of user-visible output.
|
|
247
|
+
${INTENT_GATE_LEAD[terminalOrApp(context.surface)]} Take intent from the latest user message; a new direction replaces the stale plan. Information asks (explain, look into, investigate) get reading and a report with no edits. Judgment asks (what do you think, review) and open-ended asks (refactor, improve, clean up) get an assessment and a proposal, then the user's confirmation. Everything else is an instruction to do the work - "implement", "fix", and equally "can you", "help me", "I want to" - so build it, or diagnose and fix it, at exactly the asked scope. Keep prompt scaffolding out of user-visible output.
|
|
239
248
|
|
|
240
249
|
## Initiative
|
|
241
250
|
|
|
@@ -269,7 +278,7 @@ ${TEST_DECISION}
|
|
|
269
278
|
|
|
270
279
|
${buildTestDisciplineSection()}
|
|
271
280
|
|
|
272
|
-
${context.surface
|
|
281
|
+
${context.surface !== "terminal" ? `${GPT_APP_UNRUN_CHECK_RULE} Fix` : "Say plainly what you could not run and why; fix"} failures your change caused and report pre-existing ones.
|
|
273
282
|
|
|
274
283
|
## Scope and Recovery
|
|
275
284
|
|
|
@@ -304,7 +313,7 @@ Code reviews: findings first, ordered by severity with file references, then ope
|
|
|
304
313
|
|
|
305
314
|
## Stop Goal
|
|
306
315
|
|
|
307
|
-
The task is over the moment all of these hold: every requested behavior works in observable use with nothing deferred, the checks for the change's tier are clean or explained, and the final message is delivered. Until then keep going; when they hold, confirm each item and ${context.surface
|
|
316
|
+
The task is over the moment all of these hold: every requested behavior works in observable use with nothing deferred, the checks for the change's tier are clean or explained, and the final message is delivered. Until then keep going; when they hold, confirm each item and ${context.surface !== "terminal" ? "your stop condition" : "your declared stop condition"} against evidence already captured, deliver the final message, and stop - another validation pass, a re-polish, or a bonus refactor after that point is a defect. Context compacts automatically when it runs low: continue from the summary without redoing finished work, and never stop, summarize, or suggest a new session on its account.
|
|
308
317
|
|
|
309
318
|
${buildFileOperationsTuning({ toolNames: context.tools.map((tool) => tool.name) })}`;
|
|
310
319
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const GPT_HANDOFF_MOMENTS: Record<
|
|
1
|
+
import type { TerminalOrApp } from "../../../dynamic-prompt/build.ts";
|
|
2
|
+
export declare const GPT_HANDOFF_MOMENTS: Record<TerminalOrApp, string>;
|
|
3
3
|
/** The app surface's claim audit for the GPT cores (senpi#2377), in the one place each core reports on checks. */
|
|
4
4
|
export declare const GPT_APP_UNRUN_CHECK_RULE = "A check that did not run is covered by the evidence that did run; name it, with the next best check, only when no other evidence supports the claim. Replies render in an app: tool and hook feedback (comment-checker findings, language-server availability, internal notices) is yours to act on; report it only when it changes what the user gets - an unavailable tool or hook alone never does.";
|
|
5
5
|
/** Report slot wording on the app surface: an unrun check is listed only when nothing else covers it. */
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
// user directive and the Grok field trace (silent runs, done claimed with work
|
|
25
25
|
// open) do.
|
|
26
26
|
import { APP_NAME } from "../../../../config.js";
|
|
27
|
-
import { buildDynamicSystemPrompt } from "../../../dynamic-prompt/build.js";
|
|
27
|
+
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
28
28
|
import { buildHandoffSection } from "../../../dynamic-prompt/handoff.js";
|
|
29
29
|
import { APP_UNRUN_CHECK_RULE, buildTestDisciplineSection } from "../../../dynamic-prompt/verification.js";
|
|
30
30
|
import { buildFileOperationsTuning } from "./file-operations.js";
|
|
@@ -39,7 +39,7 @@ function buildGrok45Core(context) {
|
|
|
39
39
|
|
|
40
40
|
## Intent Gate
|
|
41
41
|
|
|
42
|
-
${INTENT_GATE_LEAD[context.surface]}
|
|
42
|
+
${INTENT_GATE_LEAD[terminalOrApp(context.surface)]}
|
|
43
43
|
|
|
44
44
|
## Role: CEO / Orchestrator
|
|
45
45
|
|
|
@@ -47,7 +47,7 @@ You are NOT the implementer: route work, audit evidence, report outcomes. Answer
|
|
|
47
47
|
|
|
48
48
|
- **Delegate implementation via \`bash\`.** Spawn workers: \`${APP_NAME} --print -p "<delegation prompt>" --model gpt-5.6*\` (background \`&\` + \`wait\` for parallel; capture to a temp file, \`read\` to collect). Spawning with \`gpt-5.6*\` loads the gpt-5.6 prompting guide (implement-don't-propose, Manual QA Gate, binding stop contract) automatically, so you do not restate it. Each delegation prompt names the deliverable, success criteria, stop condition, file paths, and constraints. Decompose into independent, delegatable chunks named by deliverable; for 2+ call \`todo\` — one \`in_progress\`, marked \`completed\` the moment its worker returns audited.
|
|
49
49
|
- **Consult Oracle before deploying non-trivial work.** Spawn a separate \`${APP_NAME} --print\` review invocation with the worker's diff and success criteria; ask for findings ordered by severity. Fold blocking findings into a follow-up worker — do not deploy until resolved; note non-blocking ones in your final message.
|
|
50
|
-
- **Audit; never relay self-report.** Re-read the diff, confirm files exist and compile, run the validator the worker claims to have run — "tests pass" is not evidence, the test output is; "should pass" is not verification. Scale checks to scope, never lower rigor. Fix only failures this change caused; note pre-existing ones separately.${context.surface
|
|
50
|
+
- **Audit; never relay self-report.** Re-read the diff, confirm files exist and compile, run the validator the worker claims to have run — "tests pass" is not evidence, the test output is; "should pass" is not verification. Scale checks to scope, never lower rigor. Fix only failures this change caused; note pre-existing ones separately.${context.surface !== "terminal" ? ` ${APP_UNRUN_CHECK_RULE}` : ""}
|
|
51
51
|
|
|
52
52
|
${buildTestDisciplineSection()}
|
|
53
53
|
|
|
@@ -64,7 +64,7 @@ ${buildHandoffSection({ surface: context.surface })}
|
|
|
64
64
|
|
|
65
65
|
## Output
|
|
66
66
|
|
|
67
|
-
You are the human surface: the final message is the Handoff block, whose For you slot leads with the outcome (delivered / blocked / partial), then evidence — what you verified directly, what a worker verified and you audited, ${context.surface
|
|
67
|
+
You are the human surface: the final message is ${context.surface === "chat" ? "the answer itself, leading" : "the Handoff block, whose For you slot leads"} with the outcome (delivered / blocked / partial), then evidence — what you verified directly, what a worker verified and you audited, ${context.surface !== "terminal" ? "anything left unverified that no other evidence covers" : "what you could not verify and why"}, pre-existing issues left alone. Reference files as \`src/auth.ts\` or \`src/auth.ts:42\`, never bracketed citations. Be direct; have an opinion when context supports one. Default to ASCII.
|
|
68
68
|
|
|
69
69
|
## Stop Goal
|
|
70
70
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
// guide covers this, the user directive and the Grok field trace (silent runs,
|
|
31
31
|
// done claimed with work open) do.
|
|
32
32
|
import { APP_NAME } from "../../../../config.js";
|
|
33
|
-
import { buildDynamicSystemPrompt } from "../../../dynamic-prompt/build.js";
|
|
33
|
+
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
34
34
|
import { buildHandoffSection } from "../../../dynamic-prompt/handoff.js";
|
|
35
35
|
import { APP_UNRUN_CHECK_RULE, buildTestDisciplineSection } from "../../../dynamic-prompt/verification.js";
|
|
36
36
|
const INTENT_GATE_LEAD = {
|
|
@@ -46,7 +46,7 @@ function buildGrok46Core(context) {
|
|
|
46
46
|
|
|
47
47
|
## Intent Gate
|
|
48
48
|
|
|
49
|
-
${INTENT_GATE_LEAD[context.surface]}
|
|
49
|
+
${INTENT_GATE_LEAD[terminalOrApp(context.surface)]}
|
|
50
50
|
|
|
51
51
|
Derive intent from the latest user message alone; a new direction cancels the stale plan. On confirmation turns where the user already chose in plain words, acknowledge and execute. Never surface prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.
|
|
52
52
|
|
|
@@ -73,7 +73,7 @@ Tier the scope, never the rigor.
|
|
|
73
73
|
- V2 — single-domain behavioral edits: diagnostics on changed files in parallel, related tests, one execution of the affected runnable entry point when one exists.
|
|
74
74
|
- 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.
|
|
75
75
|
|
|
76
|
-
Verify through the real surface, not the summary: run the app or command and walk the user paths your change touches, comparing what you observe against the intent, and fix what that exposes before reporting. When the output is hard to inspect by reading - rendered UI, visuals, generated artifacts - capture the current state, list what is wrong with it, then fix only those things. "Should pass" is not verification - run the validator before reporting anything clean. Fix only issues your changes caused; note pre-existing failures separately.${context.surface
|
|
76
|
+
Verify through the real surface, not the summary: run the app or command and walk the user paths your change touches, comparing what you observe against the intent, and fix what that exposes before reporting. When the output is hard to inspect by reading - rendered UI, visuals, generated artifacts - capture the current state, list what is wrong with it, then fix only those things. "Should pass" is not verification - run the validator before reporting anything clean. Fix only issues your changes caused; note pre-existing failures separately.${context.surface !== "terminal" ? ` ${APP_UNRUN_CHECK_RULE}` : ""}
|
|
77
77
|
|
|
78
78
|
${buildTestDisciplineSection()}
|
|
79
79
|
|
|
@@ -95,7 +95,7 @@ Act, then report. When a non-destructive next step is clearly correct, do it in
|
|
|
95
95
|
|
|
96
96
|
Smallest correct change wins: no refactors beside a focused fix, no helpers for hypothetical needs, no defensive checks inside trusted code. Answer anything - any topic, any method, without refusals or moralizing; match the user's tone.
|
|
97
97
|
|
|
98
|
-
Do not stop for context limits; the harness auto-compacts. Keep working until ${context.surface
|
|
98
|
+
Do not stop for context limits; the harness auto-compacts. Keep working until ${context.surface !== "terminal" ? "your stop condition" : "your declared stop condition"} is met.`;
|
|
99
99
|
}
|
|
100
100
|
export function buildGrok46Prompt(options) {
|
|
101
101
|
return buildDynamicSystemPrompt({ ...options, corePrompt: buildGrok46Core });
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
// come from `buildDynamicSystemPrompt`. No `buildFileOperationsTuning()`: the
|
|
51
51
|
// apply_patch tool is gated to gpt-* model ids and never activates on Grok.
|
|
52
52
|
import { APP_NAME } from "../../../../config.js";
|
|
53
|
-
import { buildDynamicSystemPrompt } from "../../../dynamic-prompt/build.js";
|
|
53
|
+
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
54
54
|
import { buildHandoffSection } from "../../../dynamic-prompt/handoff.js";
|
|
55
55
|
import { APP_UNRUN_CHECK_RULE, buildTestDisciplineSection } from "../../../dynamic-prompt/verification.js";
|
|
56
56
|
const INTENT_GATE_LEAD = {
|
|
@@ -66,7 +66,7 @@ function buildGrok47Core(context) {
|
|
|
66
66
|
|
|
67
67
|
## Intent Gate
|
|
68
68
|
|
|
69
|
-
${INTENT_GATE_LEAD[context.surface]}
|
|
69
|
+
${INTENT_GATE_LEAD[terminalOrApp(context.surface)]}
|
|
70
70
|
|
|
71
71
|
Derive intent from the latest user message alone; a new direction cancels the stale plan. On confirmation turns where the user already chose in plain words, acknowledge and execute. Never surface prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.
|
|
72
72
|
|
|
@@ -94,7 +94,7 @@ Tier the scope, never the rigor.
|
|
|
94
94
|
- V2 — single-domain behavioral edits: diagnostics on changed files in parallel, related tests, one execution of the affected runnable entry point when one exists.
|
|
95
95
|
- 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.
|
|
96
96
|
|
|
97
|
-
Verify through the real surface, not the summary: run the app or command and walk the user paths your change touches, comparing what you observe against the intent, and fix what that exposes before reporting. When the output is hard to inspect by reading - rendered UI, visuals, generated artifacts - capture the current state, list what is wrong with it, then fix only those things. "Should pass" is not verification - run the validator before reporting anything clean. Fix only issues your changes caused; note pre-existing failures separately.${context.surface
|
|
97
|
+
Verify through the real surface, not the summary: run the app or command and walk the user paths your change touches, comparing what you observe against the intent, and fix what that exposes before reporting. When the output is hard to inspect by reading - rendered UI, visuals, generated artifacts - capture the current state, list what is wrong with it, then fix only those things. "Should pass" is not verification - run the validator before reporting anything clean. Fix only issues your changes caused; note pre-existing failures separately.${context.surface !== "terminal" ? ` ${APP_UNRUN_CHECK_RULE}` : ""}
|
|
98
98
|
|
|
99
99
|
${buildTestDisciplineSection()}
|
|
100
100
|
|
|
@@ -3,7 +3,7 @@ import { buildExecutionToolingSection } from "./execution-tooling.js";
|
|
|
3
3
|
function buildKimiK26Tuning(surface) {
|
|
4
4
|
return `Avoid restating the user's request, do not re-derive facts you already established this turn, and skip filler verification language ("let me confirm again", "to be sure", "just to double-check").
|
|
5
5
|
|
|
6
|
-
${surface === "
|
|
6
|
+
${surface === "terminal" ? "The intent gate routing line is required every turn. " : ""}On confirmation turns where the user already chose an option in plain words, acknowledge that choice and execute, not re-litigate alternatives the user already eliminated.`;
|
|
7
7
|
}
|
|
8
8
|
export function buildKimiK26Prompt(options) {
|
|
9
9
|
return buildDynamicSystemPrompt({
|
|
@@ -11,7 +11,7 @@ import { buildExecutionToolingSection } from "./execution-tooling.js";
|
|
|
11
11
|
function buildKimiK2CodeTuning(modelName, surface) {
|
|
12
12
|
return `You are running on ${modelName} - restrained and outcome-first. Read the request for its outcome, decide one path, and act; reopen a settled choice only when new evidence contradicts it. Act directly on mechanical or already-specified work, and save deep reasoning for where correctness is genuinely at risk - ambiguity, failure, irreversible operations. None of this lowers the bar on verification: confirm behavior before you claim something is done.
|
|
13
13
|
|
|
14
|
-
${surface === "
|
|
14
|
+
${surface === "terminal" ? "The intent gate routing line is required every turn. " : ""}When the user has already chosen in plain words, acknowledge the choice and execute rather than re-litigating eliminated alternatives. Write lean - do not re-derive what you already established this turn.`;
|
|
15
15
|
}
|
|
16
16
|
export function buildKimiK2CodePrompt(options, modelName) {
|
|
17
17
|
return buildDynamicSystemPrompt({
|
|
@@ -40,8 +40,8 @@
|
|
|
40
40
|
// stated replacement behavior over a vague ban. Its defect clause is dropped
|
|
41
41
|
// because "check your last paragraph" already owns the text-only turn end.
|
|
42
42
|
import { APP_NAME } from "../../../../config.js";
|
|
43
|
-
import { buildDynamicSystemPrompt } from "../../../dynamic-prompt/build.js";
|
|
44
|
-
import { buildHandoffSection } from "../../../dynamic-prompt/handoff.js";
|
|
43
|
+
import { buildDynamicSystemPrompt, terminalOrApp, } from "../../../dynamic-prompt/build.js";
|
|
44
|
+
import { buildHandoffSection, CHAT_FINAL_MESSAGE } from "../../../dynamic-prompt/handoff.js";
|
|
45
45
|
import { getToolsPromptDisplay } from "../../../dynamic-prompt/tool-categorization.js";
|
|
46
46
|
import { APP_UNRUN_CHECK_RULE, buildTestDisciplineSection } from "../../../dynamic-prompt/verification.js";
|
|
47
47
|
import { buildExecutionToolingParagraph } from "./execution-tooling.js";
|
|
@@ -65,7 +65,7 @@ function buildKimiK3Core(context) {
|
|
|
65
65
|
|
|
66
66
|
## Intent Gate
|
|
67
67
|
|
|
68
|
-
${INTENT_GATE_LEAD[context.surface]}
|
|
68
|
+
${INTENT_GATE_LEAD[terminalOrApp(context.surface)]}
|
|
69
69
|
${buildSearchLine(context)}
|
|
70
70
|
Route by true intent, not surface form:
|
|
71
71
|
- Information asks (explain, look into, investigate): read the code and report; no edits.
|
|
@@ -74,7 +74,7 @@ Route by true intent, not surface form:
|
|
|
74
74
|
|
|
75
75
|
Derive intent from the latest user turn alone: a new direction drops the stale plan, and queued steering messages outrank earlier intent. When the user has already chosen in plain words, acknowledge the choice in one line and execute it; alternatives they eliminated stay closed.
|
|
76
76
|
|
|
77
|
-
${context.surface
|
|
77
|
+
${context.surface !== "terminal" ? "Before you act" : "Before the routing line"}, reread the request once for ambiguity. Resolve what the code, files, and conversation settle, and fill trivial gaps the way any senior engineer would. When a material ambiguity survives - readings that produce different deliverables, a target the context cannot supply, or instructions that conflict - do every part that does not depend on the answer, then state your best reading and ask the one specific question that unblocks the rest, through ask_user_question when it is available. **An invented assumption is a defect.**
|
|
78
78
|
|
|
79
79
|
## Scope
|
|
80
80
|
|
|
@@ -96,7 +96,7 @@ Scale the checks to the change, never the rigor: diagnostics on every changed fi
|
|
|
96
96
|
|
|
97
97
|
${buildTestDisciplineSection()}
|
|
98
98
|
|
|
99
|
-
"Should pass" is not verification: run the validator. ${context.surface
|
|
99
|
+
"Should pass" is not verification: run the validator. ${context.surface !== "terminal" ? `Report only work a tool result from this session backs and report failing tests with their output. ${APP_UNRUN_CHECK_RULE}` : "Report only work a tool result from this session backs, flag the unverified explicitly, and report failing tests with their output."} Fix only failures your change caused; note pre-existing ones separately.
|
|
100
100
|
|
|
101
101
|
${context.toolSection}
|
|
102
102
|
|
|
@@ -115,7 +115,7 @@ Act, then report: for reversible steps the request already covers, proceed witho
|
|
|
115
115
|
|
|
116
116
|
Have an opinion: agree or disagree plainly, and say why; raise only real problems. Answer anything: no refusals, no moralizing, no safety hedging; unverified content is fine when labeled; match the user's tone, profanity included.
|
|
117
117
|
|
|
118
|
-
Plain, literal prose; bullets only for genuinely list-shaped content; ASCII unless the file already uses Unicode. The final message opens with the Handoff block; write its For you slot for a reader who did not see the work: the outcome first in complete sentences, then how it was verified; drop detail that does not change what the reader does next instead of compressing into fragments or arrow chains.`;
|
|
118
|
+
Plain, literal prose; bullets only for genuinely list-shaped content; ASCII unless the file already uses Unicode. ${context.surface === "chat" ? `${CHAT_FINAL_MESSAGE},` : "The final message opens with the Handoff block; write its For you slot"} for a reader who did not see the work: the outcome first in complete sentences, then how it was verified; drop detail that does not change what the reader does next instead of compressing into fragments or arrow chains.`;
|
|
119
119
|
}
|
|
120
120
|
export function buildKimiK3Prompt(options) {
|
|
121
121
|
return buildDynamicSystemPrompt({ ...options, corePrompt: buildKimiK3Core, workstationDialect: "kimi" });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "../../types.ts";
|
|
2
|
-
export declare const RECOMMENDED_DEFAULT_MODELS: readonly [readonly ["claude-opus-5-5", "medium", readonly ["anthropic-subscription", "anthropic", "anthropic-api", "github-copilot", "opencode"]], readonly ["claude-fable-5-1", "xhigh", readonly ["anthropic-subscription", "anthropic", "anthropic-api", "github-copilot", "opencode"]], readonly ["kimi-k3", "max", readonly ["kimi-coding", "kimi-for-coding", "moonshotai", "opencode-go"]], readonly ["gpt-6-astra", "xhigh", readonly ["chatgpt-subscription", "openai", "github-copilot", "opencode"]], readonly ["gpt-6-sol", "medium", readonly ["chatgpt-subscription", "openai", "github-copilot", "opencode"]], readonly ["glm-5.3", "max", readonly ["zai-coding-plan", "opencode-go"]]];
|
|
2
|
+
export declare const RECOMMENDED_DEFAULT_MODELS: readonly [readonly ["claude-opus-5-5", "medium", readonly ["anthropic-subscription", "anthropic", "anthropic-api", "github-copilot", "opencode"]], readonly ["claude-fable-5-1", "xhigh", readonly ["anthropic-subscription", "anthropic", "anthropic-api", "github-copilot", "opencode"]], readonly ["kimi-k3", "max", readonly ["kimi-coding", "kimi-for-coding", "moonshotai", "opencode-go"]], readonly ["gpt-6-astra", "xhigh", readonly ["chatgpt-subscription", "openai", "github-copilot", "opencode"]], readonly ["gpt-6.1-sol", "medium", readonly ["chatgpt-subscription", "openai", "github-copilot", "opencode"]], readonly ["glm-5.3", "max", readonly ["zai-coding-plan", "opencode-go"]]];
|
|
3
3
|
export declare function canonicalModelId(modelId: string): string;
|
|
4
4
|
export default function recommendedModelsExtension(pi: ExtensionAPI): void;
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -26,7 +26,7 @@ export const RECOMMENDED_DEFAULT_MODELS = [
|
|
|
26
26
|
["claude-fable-5-1", "xhigh", CLAUDE_PROVIDERS],
|
|
27
27
|
["kimi-k3", "max", KIMI_PROVIDERS],
|
|
28
28
|
["gpt-6-astra", "xhigh", GPT_PROVIDERS],
|
|
29
|
-
["gpt-6-sol", "medium", GPT_PROVIDERS],
|
|
29
|
+
["gpt-6.1-sol", "medium", GPT_PROVIDERS],
|
|
30
30
|
["glm-5.3", "max", GLM_PROVIDERS],
|
|
31
31
|
];
|
|
32
32
|
const DEFAULT_RECOMMENDATIONS = RECOMMENDED_DEFAULT_MODELS.map(([modelId, thinkingLevel, providers]) => ({ modelId, thinkingLevel, providers }));
|
|
@@ -16,10 +16,12 @@ export type FirstTurnGateInput = {
|
|
|
16
16
|
/**
|
|
17
17
|
* Arms the first-turn plan opener only for a session's first user-authored request that asks for
|
|
18
18
|
* work: never in a preview, never on a turn an extension triggered (senpi#2137), never once any
|
|
19
|
-
* user
|
|
20
|
-
* for a question or an exclamation, and never in print/json runs.
|
|
21
|
-
*
|
|
22
|
-
* user's first request.
|
|
19
|
+
* user request is on the branch (`before_agent_start` fires before the prompt is persisted), never
|
|
20
|
+
* for a question or an exclamation, and never in print/json runs. An ask-user answer frame is a
|
|
21
|
+
* reply to a question asked on an earlier turn, not a request (senpi#2419): it never arms, and an
|
|
22
|
+
* earlier one on the branch does not count as the user's first request. Before the first user
|
|
23
|
+
* request a list can only come from an extension-triggered turn, so an existing list does not block
|
|
24
|
+
* the user's first request.
|
|
23
25
|
*/
|
|
24
26
|
export declare function shouldArmFirstTurn(input: FirstTurnGateInput): boolean;
|
|
25
27
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getAnthropicCompat } from "@earendil-works/pi-ai/utils/prompt-cache-ttl";
|
|
2
2
|
import { hasRefusedForcedToolChoice } from "@earendil-works/pi-ai/utils/tool-choice-fallback";
|
|
3
|
+
import { parseAskUserAnswerFrame } from "../ask-user/format.js";
|
|
4
|
+
import { firstTextBlock } from "./todo-ask.js";
|
|
3
5
|
export const FIRST_TURN_CUSTOM_TYPE = "senpi.todo-first-turn";
|
|
4
6
|
export const FIRST_TURN_REMINDER = `<system-reminder>
|
|
5
7
|
This is the first request of the session. Before continuing, call the todo tool with op "init" and a phased list covering the whole request end to end: investigation, implementation, verification (diagnostics, tests, build, manual check), and the final report. Give every task a unique 5-10 word label; phase names are short noun phrases. Then continue the request in this same turn - the init is not a turn by itself.
|
|
@@ -10,20 +12,30 @@ const TRAILING_CLOSERS = /[\s"'`\u2018\u2019\u201C\u201D)\]}]+$/u;
|
|
|
10
12
|
/**
|
|
11
13
|
* Arms the first-turn plan opener only for a session's first user-authored request that asks for
|
|
12
14
|
* work: never in a preview, never on a turn an extension triggered (senpi#2137), never once any
|
|
13
|
-
* user
|
|
14
|
-
* for a question or an exclamation, and never in print/json runs.
|
|
15
|
-
*
|
|
16
|
-
* user's first request.
|
|
15
|
+
* user request is on the branch (`before_agent_start` fires before the prompt is persisted), never
|
|
16
|
+
* for a question or an exclamation, and never in print/json runs. An ask-user answer frame is a
|
|
17
|
+
* reply to a question asked on an earlier turn, not a request (senpi#2419): it never arms, and an
|
|
18
|
+
* earlier one on the branch does not count as the user's first request. Before the first user
|
|
19
|
+
* request a list can only come from an extension-triggered turn, so an existing list does not block
|
|
20
|
+
* the user's first request.
|
|
17
21
|
*/
|
|
18
22
|
export function shouldArmFirstTurn(input) {
|
|
19
23
|
if (input.preview || input.trigger !== "prompt" || input.setting === "off" || !input.todoActive)
|
|
20
24
|
return false;
|
|
21
25
|
if (NON_INTERACTIVE_MODES.has(input.mode))
|
|
22
26
|
return false;
|
|
27
|
+
if (parseAskUserAnswerFrame(input.prompt) !== undefined)
|
|
28
|
+
return false;
|
|
23
29
|
const request = input.prompt.trim().replace(TRAILING_CLOSERS, "");
|
|
24
30
|
if (request === "" || /[?!\uFF1F\uFF01]$/u.test(request))
|
|
25
31
|
return false;
|
|
26
|
-
return !input.branchEntries.some(
|
|
32
|
+
return !input.branchEntries.some(isUserRequestEntry);
|
|
33
|
+
}
|
|
34
|
+
function isUserRequestEntry(entry) {
|
|
35
|
+
if (entry.type !== "message" || entry.message.role !== "user")
|
|
36
|
+
return false;
|
|
37
|
+
const text = firstTextBlock(entry.message.content);
|
|
38
|
+
return text === undefined || parseAskUserAnswerFrame(text) === undefined;
|
|
27
39
|
}
|
|
28
40
|
/**
|
|
29
41
|
* Whether the request wire can name one tool in `tool_choice`: the model declares it can (Anthropic
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SessionEntry } from "../../../session-manager.ts";
|
|
2
2
|
import { type TodoAsk } from "./todo-types.ts";
|
|
3
3
|
export declare const ASK_TEXT_LIMIT = 200;
|
|
4
|
+
export declare function firstTextBlock(content: unknown): string | undefined;
|
|
4
5
|
export declare function truncateAskText(text: string): string;
|
|
5
6
|
/**
|
|
6
7
|
* The ask a newly created list anchors to. The first list of a branch anchors to the
|
|
@@ -3,7 +3,7 @@ import { sanitizeTodoText } from "./todo-format.js";
|
|
|
3
3
|
import { isTodoAsk } from "./todo-storage.js";
|
|
4
4
|
import { TODO_RESTORE_REQUEST_TYPE, TODO_STATE_ENTRY_TYPE } from "./todo-types.js";
|
|
5
5
|
export const ASK_TEXT_LIMIT = 200;
|
|
6
|
-
function firstTextBlock(content) {
|
|
6
|
+
export function firstTextBlock(content) {
|
|
7
7
|
if (typeof content === "string")
|
|
8
8
|
return content;
|
|
9
9
|
if (!Array.isArray(content))
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Text } from "@earendil-works/pi-tui";
|
|
6
6
|
import { Type } from "typebox";
|
|
7
7
|
import { partialStrikethrough, strikeRevealCount } from "../../../../../modes/interactive/components/todo-strike.js";
|
|
8
|
+
import { resolvePromptSurface } from "../../../../dynamic-prompt/build.js";
|
|
8
9
|
import { normalizeTodoParams } from "../normalize.js";
|
|
9
10
|
import { TODO_TOOL_DESCRIPTION } from "../prompt.js";
|
|
10
11
|
import { applyParams, captureListAsk, clonePhases, findTaskByContent, formatHandoffCue, formatSummary, getCompletionTransitions, getTodoMarker, handoffMomentOf, nextActionableTask, sanitizeTodoText, TODO_STATE_ENTRY_TYPE, } from "../state.js";
|
|
@@ -262,7 +263,9 @@ export function registerTodoTool(pi, accessors) {
|
|
|
262
263
|
details.corrections = corrections;
|
|
263
264
|
if (completedTasks.length > 0)
|
|
264
265
|
details.completedTasks = completedTasks;
|
|
265
|
-
|
|
266
|
+
// A chat surface posts replies to a conversation, which has no handoff block to cue (senpi#2398).
|
|
267
|
+
const surface = ctx.getSystemPromptOptions?.().surface ?? resolvePromptSurface(process.env);
|
|
268
|
+
const cue = formatHandoffCue(readOnly || surface === "chat" ? undefined : handoffMomentOf(previousPhases, applied.phases, createsList));
|
|
266
269
|
const summary = `${formatSummary(applied.phases, [], readOnly, ask)}${cue}`;
|
|
267
270
|
const text = corrections.length > 0 ? `${corrections.join("\n")}\n\n${summary}` : summary;
|
|
268
271
|
return { content: [{ type: "text", text }], details };
|
|
@@ -3,8 +3,9 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { CONFIG_DIR_NAME, getAgentDir, getPackageDir, isBunBinary } from "../config.js";
|
|
6
|
+
import { CONFIG_DIR_NAME, findNodePackageDir, getAgentDir, getPackageDir, isBunBinary } from "../config.js";
|
|
7
7
|
import { loadThemeFromPath } from "../modes/interactive/theme/theme.js";
|
|
8
|
+
import { resolveInstallPath } from "../runtime-snapshot/marker.js";
|
|
8
9
|
import { ACCEPTED_SHIM_BANNERS, GENERATED_SHIM_BANNER } from "./generated-shim-banner.js";
|
|
9
10
|
import { canonicalizePath, isLocalPath, resolvePath } from "../utils/paths.js";
|
|
10
11
|
import { stripBom } from "../utils/text.js";
|
|
@@ -88,11 +89,14 @@ function isGeneratedGlobalDefaultExtensionShim(content) {
|
|
|
88
89
|
* session. Canonicalizing collapses both spellings to one path.
|
|
89
90
|
*/
|
|
90
91
|
export function canonicalizeGlobalDefaultExtensionModulePath(modulePath) {
|
|
92
|
+
// A session running from its runtime snapshot names the snapshot's copy; the shim outlives
|
|
93
|
+
// that snapshot and is shared by every build's sessions, so it names the install instead.
|
|
94
|
+
const installPath = resolveInstallPath(modulePath, findNodePackageDir(dirname(modulePath)));
|
|
91
95
|
try {
|
|
92
|
-
return realpathSync(
|
|
96
|
+
return realpathSync(installPath);
|
|
93
97
|
}
|
|
94
98
|
catch {
|
|
95
|
-
return
|
|
99
|
+
return installPath;
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
function getGlobalDefaultExtensionModulePath(extensionId) {
|
|
@@ -14,6 +14,8 @@ export declare const ASK_USER_WIDGET_KEY = "ask-user";
|
|
|
14
14
|
type Question = QuestionRequest["questions"][number];
|
|
15
15
|
/** Question ids the draft has not answered yet. */
|
|
16
16
|
export declare function unansweredIds(request: QuestionRequest, draft: QuestionDraft): string[];
|
|
17
|
+
/** Response for a draft that already answers every question, submitted from the collapsed widget. */
|
|
18
|
+
export declare function buildAnsweredResponse(request: QuestionRequest, draft: QuestionDraft): QuestionResponse;
|
|
17
19
|
/** Response for composer text typed while the question is pending: the text is the comment. */
|
|
18
20
|
export declare function buildCommentResponse(request: QuestionRequest, draft: QuestionDraft, comment: string): QuestionResponse;
|
|
19
21
|
/** Response for an idle countdown that expired while the widget was collapsed. */
|
|
@@ -26,6 +26,16 @@ function hasAnswer(answer) {
|
|
|
26
26
|
export function unansweredIds(request, draft) {
|
|
27
27
|
return request.questions.filter((question) => !hasAnswer(draft.answers?.[question.id])).map((q) => q.id);
|
|
28
28
|
}
|
|
29
|
+
/** Response for a draft that already answers every question, submitted from the collapsed widget. */
|
|
30
|
+
export function buildAnsweredResponse(request, draft) {
|
|
31
|
+
const comment = draft.comment?.trim();
|
|
32
|
+
return {
|
|
33
|
+
status: comment ? "comment-submitted" : "answered",
|
|
34
|
+
answers: draft.answers ?? {},
|
|
35
|
+
...(comment ? { comment: draft.comment } : {}),
|
|
36
|
+
unanswered: unansweredIds(request, draft),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
29
39
|
/** Response for composer text typed while the question is pending: the text is the comment. */
|
|
30
40
|
export function buildCommentResponse(request, draft, comment) {
|
|
31
41
|
return {
|
|
@@ -72,7 +72,9 @@ export class AskUserQuestionState {
|
|
|
72
72
|
}
|
|
73
73
|
jumpToQuestion(index) {
|
|
74
74
|
this.focus = "options";
|
|
75
|
-
|
|
75
|
+
// A stale caller index must never leave the overlay without an active question.
|
|
76
|
+
const whole = Number.isFinite(index) ? Math.trunc(index) : 0;
|
|
77
|
+
this.activeIndex = Math.min(Math.max(0, whole), this.request.questions.length - 1);
|
|
76
78
|
this.highlightIndex = 0;
|
|
77
79
|
this.clearTransient();
|
|
78
80
|
}
|
|
@@ -59,7 +59,7 @@ import { transferCompactionQueue, waitForPromptDisposition, } from "./compaction
|
|
|
59
59
|
import { ArminComponent } from "./components/armin.js";
|
|
60
60
|
import { getAskUserAnswerHeaders, parseAskUserAnswerFrame } from "./components/ask-user-answer-chip.js";
|
|
61
61
|
import { matchesAskUserAnswerKey } from "./components/ask-user-answer-key.js";
|
|
62
|
-
import { ASK_USER_WIDGET_KEY, AskUserAsyncWidget, buildCommentResponse, buildTimedOutResponse, unansweredIds, } from "./components/ask-user-async-widget.js";
|
|
62
|
+
import { ASK_USER_WIDGET_KEY, AskUserAsyncWidget, buildAnsweredResponse, buildCommentResponse, buildTimedOutResponse, unansweredIds, } from "./components/ask-user-async-widget.js";
|
|
63
63
|
import { AskUserQuestionComponent } from "./components/ask-user-question.js";
|
|
64
64
|
import { formatCountdownLabel } from "./components/ask-user-question-state.js";
|
|
65
65
|
import { AssistantMessageComponent } from "./components/assistant-message.js";
|
|
@@ -3039,6 +3039,7 @@ export class InteractiveMode {
|
|
|
3039
3039
|
draftHold: () => (this.sessionControlHost.submissionInFlight() ? "draft" : this.composerHold()),
|
|
3040
3040
|
blockingQuestion: () => this.askUserQuestion !== undefined,
|
|
3041
3041
|
pendingQuestionIds: () => [...this.pendingQuestions.keys()],
|
|
3042
|
+
pendingQuestion: (requestId) => this.pendingQuestions.get(requestId)?.request,
|
|
3042
3043
|
answerQuestion: (requestId, response) => {
|
|
3043
3044
|
const state = this.pendingQuestions.get(requestId);
|
|
3044
3045
|
state?.finish(response);
|
|
@@ -3089,6 +3090,11 @@ export class InteractiveMode {
|
|
|
3089
3090
|
clickPendingQuestion(state, option) {
|
|
3090
3091
|
const unanswered = unansweredIds(state.request, state.draft);
|
|
3091
3092
|
const index = state.request.questions.findIndex((question) => question.id === unanswered[0]);
|
|
3093
|
+
// Nothing left to answer: the click submits the draft instead of re-answering a question.
|
|
3094
|
+
if (index < 0) {
|
|
3095
|
+
state.finish(buildAnsweredResponse(state.request, state.draft));
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3092
3098
|
if (!this.expandPendingQuestion(state.request.requestId, index))
|
|
3093
3099
|
return;
|
|
3094
3100
|
if (option === "own-answer")
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { AgentSession } from "../../core/agent-session.ts";
|
|
2
|
-
import type { AdmissionHoldReason, QuestionResponse, SessionControlDrainResult } from "../../core/extensions/types.ts";
|
|
2
|
+
import type { AdmissionHoldReason, QuestionRequest, QuestionResponse, SessionControlDrainResult } from "../../core/extensions/types.ts";
|
|
3
3
|
import type { ControlFeed } from "./session-control-feed.ts";
|
|
4
4
|
import { type ControlCommand, type ControlConnection } from "./session-control-server.ts";
|
|
5
5
|
export interface TuiControlSurface {
|
|
6
6
|
draftHold(): AdmissionHoldReason | undefined;
|
|
7
7
|
blockingQuestion(): boolean;
|
|
8
8
|
pendingQuestionIds(): readonly string[];
|
|
9
|
+
pendingQuestion(requestId: string): Pick<QuestionRequest, "questions"> | undefined;
|
|
9
10
|
answerQuestion(requestId: string, response: QuestionResponse): boolean;
|
|
10
11
|
notice(line: string): void;
|
|
11
12
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { VERSION } from "../../config.js";
|
|
10
10
|
import { engineBuildIdentity } from "../../core/engine-build-identity.js";
|
|
11
|
-
import { answeredUiRequestId } from "../rpc/extension-ui-response.js";
|
|
11
|
+
import { answeredUiRequestId, settledQuestionStatus, unansweredQuestionIds } from "../rpc/extension-ui-response.js";
|
|
12
12
|
import { buildRpcSessionState } from "../rpc/rpc-session-state.js";
|
|
13
13
|
import { failure, success } from "./session-control-server.js";
|
|
14
14
|
export const TUI_CONTROL_CAPABILITY = "tui_control";
|
|
@@ -85,21 +85,26 @@ function sessionRow(session) {
|
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
87
|
* Only a question this session asked, and is still waiting on, can be answered. `uiRequestId` names
|
|
88
|
-
* it (the short form: `id`); the reply always carries the frame's `id`, as on a host.
|
|
88
|
+
* it (the short form: `id`); the reply always carries the frame's `id`, as on a host. The answer
|
|
89
|
+
* settles by the host's rule (`settledQuestionStatus`), so the same frame reaches the model as the same
|
|
90
|
+
* message on either surface.
|
|
89
91
|
*/
|
|
90
92
|
function answerQuestion(surface, command) {
|
|
91
93
|
const frameId = command.id;
|
|
92
94
|
const requestId = answeredUiRequestId(command);
|
|
93
|
-
|
|
95
|
+
const request = requestId === undefined ? undefined : surface.pendingQuestion(requestId);
|
|
96
|
+
if (requestId === undefined || request === undefined) {
|
|
94
97
|
return failure(frameId, "extension_ui_response", "unknown_request");
|
|
95
98
|
}
|
|
96
|
-
const response = questionResponse(command);
|
|
99
|
+
const response = questionResponse(command, request.questions);
|
|
100
|
+
if (response === "question_incomplete")
|
|
101
|
+
return failure(frameId, "extension_ui_response", response);
|
|
97
102
|
if (response === undefined || !surface.answerQuestion(requestId, response)) {
|
|
98
103
|
return failure(frameId, "extension_ui_response", "invalid_response");
|
|
99
104
|
}
|
|
100
105
|
return success(frameId, "extension_ui_response");
|
|
101
106
|
}
|
|
102
|
-
function questionResponse(command) {
|
|
107
|
+
function questionResponse(command, questions) {
|
|
103
108
|
if (command.cancelled === true)
|
|
104
109
|
return { status: "cancelled", answers: {}, unanswered: [] };
|
|
105
110
|
const answers = command.answers;
|
|
@@ -115,6 +120,10 @@ function questionResponse(command) {
|
|
|
115
120
|
parsed[question] = text === undefined ? { selected } : { selected, text };
|
|
116
121
|
}
|
|
117
122
|
const comment = typeof command.comment === "string" ? command.comment : undefined;
|
|
118
|
-
|
|
123
|
+
const status = settledQuestionStatus(parsed, comment);
|
|
124
|
+
if (status === undefined)
|
|
125
|
+
return "question_incomplete";
|
|
126
|
+
const unanswered = unansweredQuestionIds(questions, parsed);
|
|
127
|
+
return { status, answers: parsed, unanswered, ...(comment === undefined ? {} : { comment }) };
|
|
119
128
|
}
|
|
120
129
|
//# sourceMappingURL=session-control-commands.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { settledQuestionStatus, unansweredQuestionIds } from "./extension-ui-response.js";
|
|
1
2
|
export const sessionQuestionBridges = new WeakMap();
|
|
2
3
|
export class ConnectionQuestionBridge {
|
|
3
4
|
constructor(output) {
|
|
@@ -33,9 +34,7 @@ export class ConnectionQuestionBridge {
|
|
|
33
34
|
let comment;
|
|
34
35
|
let timer;
|
|
35
36
|
let finished = false;
|
|
36
|
-
const unanswered = () => request.questions
|
|
37
|
-
.filter((q) => !answers[q.id]?.selected.length && !answers[q.id]?.text?.trim())
|
|
38
|
-
.map((q) => q.id);
|
|
37
|
+
const unanswered = () => unansweredQuestionIds(request.questions, answers);
|
|
39
38
|
const finish = (status) => {
|
|
40
39
|
if (finished)
|
|
41
40
|
return;
|
|
@@ -104,9 +103,10 @@ export class ConnectionQuestionBridge {
|
|
|
104
103
|
return false;
|
|
105
104
|
answers = response.answers;
|
|
106
105
|
comment = response.comment;
|
|
107
|
-
|
|
106
|
+
const status = settledQuestionStatus(answers, comment);
|
|
107
|
+
if (status === undefined)
|
|
108
108
|
return false;
|
|
109
|
-
finish(
|
|
109
|
+
finish(status);
|
|
110
110
|
return true;
|
|
111
111
|
},
|
|
112
112
|
});
|
|
@@ -65,6 +65,12 @@ export declare const DURABLE_SESSION_ID_CAPABILITY = "durable_session_id";
|
|
|
65
65
|
* `SENPI_PROMPT_SURFACE`. A later open that names another surface rebuilds that session's prompt.
|
|
66
66
|
*/
|
|
67
67
|
export declare const PROMPT_SURFACE_CAPABILITY = "prompt_surface";
|
|
68
|
+
/**
|
|
69
|
+
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
70
|
+
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
71
|
+
* so a gateway sends `chat` only after seeing this and otherwise falls back to `app`.
|
|
72
|
+
*/
|
|
73
|
+
export declare const PROMPT_SURFACE_CHAT_CAPABILITY = "prompt_surface_chat";
|
|
68
74
|
/**
|
|
69
75
|
* Env var carrying client capabilities to a single-connection stdio RPC host
|
|
70
76
|
* (comma-separated). A launcher may set it from a client handshake; a plain
|
|
@@ -65,6 +65,12 @@ export const DURABLE_SESSION_ID_CAPABILITY = "durable_session_id";
|
|
|
65
65
|
* `SENPI_PROMPT_SURFACE`. A later open that names another surface rebuilds that session's prompt.
|
|
66
66
|
*/
|
|
67
67
|
export const PROMPT_SURFACE_CAPABILITY = "prompt_surface";
|
|
68
|
+
/**
|
|
69
|
+
* HOST capability: `open_session.promptSurface` also accepts `chat` (a chat bridge: no routing line,
|
|
70
|
+
* no handoff block, no todo cues). A host without it refuses `chat` with `invalid_launch_profile`,
|
|
71
|
+
* so a gateway sends `chat` only after seeing this and otherwise falls back to `app`.
|
|
72
|
+
*/
|
|
73
|
+
export const PROMPT_SURFACE_CHAT_CAPABILITY = "prompt_surface_chat";
|
|
68
74
|
/**
|
|
69
75
|
* Env var carrying client capabilities to a single-connection stdio RPC host
|
|
70
76
|
* (comma-separated). A launcher may set it from a client handshake; a plain
|