@oh-my-pi/pi-coding-agent 17.1.5 → 17.1.6
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 +31 -0
- package/dist/{CHANGELOG-qs3vd6xf.md → CHANGELOG-x9zt79k8.md} +31 -0
- package/dist/cli.js +2989 -2978
- package/dist/types/capability/index.d.ts +1 -1
- package/dist/types/capability/types.d.ts +23 -1
- package/dist/types/config/settings-schema.d.ts +23 -1
- package/dist/types/internal-urls/mcp-protocol.d.ts +3 -2
- package/dist/types/internal-urls/parse.d.ts +12 -0
- package/dist/types/internal-urls/router.d.ts +6 -0
- package/dist/types/internal-urls/types.d.ts +6 -0
- package/dist/types/lsp/config.d.ts +1 -0
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +5 -0
- package/dist/types/mcp/tool-bridge.d.ts +13 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -0
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session-types.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/model-controls.d.ts +3 -0
- package/dist/types/session/session-advisors.d.ts +7 -1
- package/dist/types/session/tool-choice-queue.d.ts +6 -4
- package/dist/types/session/turn-recovery.d.ts +4 -2
- package/dist/types/task/index.d.ts +1 -1
- package/dist/types/task/types.d.ts +3 -11
- package/dist/types/thinking.d.ts +21 -2
- package/dist/types/utils/title-generator.d.ts +17 -16
- package/package.json +12 -12
- package/src/capability/index.ts +43 -12
- package/src/capability/mcp.ts +21 -0
- package/src/capability/types.ts +20 -1
- package/src/cli/read-cli.ts +44 -2
- package/src/config/settings-schema.ts +27 -1
- package/src/eval/py/runner.py +16 -2
- package/src/extensibility/extensions/runner.ts +91 -5
- package/src/extensibility/extensions/types.ts +0 -1
- package/src/extensibility/hooks/types.ts +0 -1
- package/src/internal-urls/mcp-protocol.ts +17 -3
- package/src/internal-urls/parse.ts +31 -0
- package/src/internal-urls/router.ts +24 -4
- package/src/internal-urls/types.ts +6 -0
- package/src/live/transport.ts +2 -2
- package/src/lsp/client.ts +2 -2
- package/src/lsp/config.ts +4 -0
- package/src/lsp/types.ts +2 -0
- package/src/mcp/config.ts +26 -14
- package/src/mcp/manager.ts +26 -9
- package/src/mcp/tool-bridge.ts +52 -1
- package/src/memories/index.ts +25 -6
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/controllers/command-controller.ts +10 -10
- package/src/modes/controllers/extension-ui-controller.ts +3 -3
- package/src/modes/rpc/rpc-mode.ts +60 -47
- package/src/prompts/steering/user-interjection.md +2 -5
- package/src/prompts/tools/task.md +4 -2
- package/src/sdk.ts +17 -8
- package/src/session/agent-session-types.ts +3 -0
- package/src/session/agent-session.ts +21 -4
- package/src/session/model-controls.ts +43 -7
- package/src/session/session-advisors.ts +30 -14
- package/src/session/session-tools.ts +4 -2
- package/src/session/tool-choice-queue.ts +19 -4
- package/src/session/turn-recovery.ts +21 -2
- package/src/task/executor.ts +11 -3
- package/src/task/index.ts +43 -32
- package/src/task/types.ts +12 -17
- package/src/thinking.ts +68 -5
- package/src/tools/read.ts +2 -2
- package/src/utils/title-generator.ts +88 -34
- package/dist/types/advisor/__tests__/advisor.test.d.ts +0 -1
- package/dist/types/advisor/__tests__/config.test.d.ts +0 -1
- package/dist/types/advisor/__tests__/emission-guard.test.d.ts +0 -1
- package/dist/types/cli/__tests__/auth-gateway-catalog.test.d.ts +0 -1
- package/dist/types/cli/update-cli.test.d.ts +0 -1
- package/dist/types/config/__tests__/model-registry.test.d.ts +0 -1
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/bridge-timeout.test.d.ts +0 -1
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/completion-bridge.test.d.ts +0 -1
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +0 -1
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +0 -1
- package/dist/types/eval/__tests__/js-context-manager.test.d.ts +0 -1
- package/dist/types/eval/__tests__/julia-prelude.test.d.ts +0 -1
- package/dist/types/eval/__tests__/kernel-spawn.test.d.ts +0 -1
- package/dist/types/eval/__tests__/prelude-agent.test.d.ts +0 -1
- package/dist/types/eval/__tests__/process-entry-import.test.d.ts +0 -1
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +0 -1
- package/dist/types/eval/py/__tests__/runner-shell-output.test.d.ts +0 -1
- package/dist/types/hindsight/client.test.d.ts +0 -1
- package/dist/types/internal-urls/__tests__/agent-protocol-nested.test.d.ts +0 -1
- package/dist/types/internal-urls/__tests__/ssh-protocol.test.d.ts +0 -1
- package/dist/types/launch/broker-list-order.test.d.ts +0 -1
- package/dist/types/launch/broker-output-snapshot.test.d.ts +0 -1
- package/dist/types/launch/protocol.test.d.ts +0 -1
- package/dist/types/launch/spawn-options.test.d.ts +0 -1
- package/dist/types/launch/terminal-output.test.d.ts +0 -1
- package/dist/types/live/protocol.test.d.ts +0 -1
- package/dist/types/mcp/config-writer.test.d.ts +0 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +0 -1
- package/dist/types/mcp/smithery-registry.test.d.ts +0 -1
- package/dist/types/mcp/transports/stdio.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/dynamic-border.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/move-overlay.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/pause-screen.test.d.ts +0 -1
- package/dist/types/modes/components/__tests__/skill-message.test.d.ts +0 -1
- package/dist/types/modes/components/custom-editor-plugin-ctor.test.d.ts +0 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +0 -1
- package/dist/types/modes/components/login-dialog.test.d.ts +0 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +0 -1
- package/dist/types/modes/components/status-line/component.test.d.ts +0 -1
- package/dist/types/modes/components/tool-execution.test.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.test.d.ts +0 -1
- package/dist/types/modes/noninteractive-dispose.test.d.ts +0 -1
- package/dist/types/modes/print-mode.test.d.ts +0 -1
- package/dist/types/modes/session-teardown.test.d.ts +0 -1
- package/dist/types/modes/theme/mermaid-rendering.test.d.ts +0 -1
- package/dist/types/modes/utils/transcript-render-helpers.test.d.ts +0 -1
- package/dist/types/modes/warp-events.test.d.ts +0 -1
- package/dist/types/plan-mode/approved-plan-prompt.test.d.ts +0 -1
- package/dist/types/plan-mode/model-transition.test.d.ts +0 -1
- package/dist/types/plan-mode/reentry-prompt.test.d.ts +0 -1
- package/dist/types/session/agent-session-error-log.test.d.ts +0 -1
- package/dist/types/session/blob-store.test.d.ts +0 -1
- package/dist/types/session/messages.test.d.ts +0 -1
- package/dist/types/session/session-context.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/connection-manager-args.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/connection-manager-timeout.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/file-transfer-posix-guard.test.d.ts +0 -1
- package/dist/types/ssh/__tests__/sshfs-mount.test.d.ts +0 -1
- package/dist/types/system-prompt.test.d.ts +0 -1
- package/dist/types/task/render.test.d.ts +0 -1
- package/dist/types/task/spawn-policy.test.d.ts +0 -1
- package/dist/types/tools/__tests__/eval-description.test.d.ts +0 -1
- package/dist/types/tools/__tests__/glob.test.d.ts +0 -1
- package/dist/types/tools/__tests__/json-tree.test.d.ts +0 -1
- package/dist/types/tools/__tests__/vibe-render.test.d.ts +0 -1
- package/dist/types/tools/hub/launch-compat.test.d.ts +0 -1
- package/dist/types/vibe/__tests__/token-rate.test.d.ts +0 -1
- package/src/advisor/__tests__/advisor.test.ts +0 -4889
- package/src/advisor/__tests__/config.test.ts +0 -349
- package/src/advisor/__tests__/emission-guard.test.ts +0 -147
- package/src/cli/__tests__/auth-gateway-catalog.test.ts +0 -111
- package/src/cli/update-cli.test.ts +0 -28
- package/src/config/__tests__/model-registry.test.ts +0 -182
- package/src/eval/__tests__/agent-bridge.test.ts +0 -1509
- package/src/eval/__tests__/bridge-timeout.test.ts +0 -170
- package/src/eval/__tests__/budget-bridge.test.ts +0 -80
- package/src/eval/__tests__/completion-bridge.test.ts +0 -412
- package/src/eval/__tests__/helpers-local-roots.test.ts +0 -55
- package/src/eval/__tests__/idle-timeout.test.ts +0 -80
- package/src/eval/__tests__/js-context-manager.test.ts +0 -456
- package/src/eval/__tests__/julia-prelude.test.ts +0 -66
- package/src/eval/__tests__/kernel-spawn.test.ts +0 -115
- package/src/eval/__tests__/prelude-agent.test.ts +0 -156
- package/src/eval/__tests__/process-entry-import.test.ts +0 -137
- package/src/eval/py/__tests__/prelude.test.ts +0 -104
- package/src/eval/py/__tests__/runner-shell-output.test.ts +0 -157
- package/src/hindsight/client.test.ts +0 -75
- package/src/internal-urls/__tests__/agent-protocol-nested.test.ts +0 -141
- package/src/internal-urls/__tests__/ssh-protocol.test.ts +0 -331
- package/src/launch/broker-list-order.test.ts +0 -89
- package/src/launch/broker-output-snapshot.test.ts +0 -126
- package/src/launch/protocol.test.ts +0 -59
- package/src/launch/spawn-options.test.ts +0 -31
- package/src/launch/terminal-output.test.ts +0 -107
- package/src/live/protocol.test.ts +0 -140
- package/src/mcp/config-writer.test.ts +0 -43
- package/src/mcp/smithery-auth.test.ts +0 -29
- package/src/mcp/smithery-registry.test.ts +0 -51
- package/src/mcp/transports/stdio.test.ts +0 -427
- package/src/modes/components/__tests__/dynamic-border.test.ts +0 -55
- package/src/modes/components/__tests__/move-overlay.test.ts +0 -252
- package/src/modes/components/__tests__/pause-screen.test.ts +0 -143
- package/src/modes/components/__tests__/skill-message.test.ts +0 -94
- package/src/modes/components/custom-editor-plugin-ctor.test.ts +0 -36
- package/src/modes/components/custom-editor.test.ts +0 -510
- package/src/modes/components/login-dialog.test.ts +0 -56
- package/src/modes/components/status-line/component.jj-cache.test.ts +0 -229
- package/src/modes/components/status-line/component.test.ts +0 -84
- package/src/modes/components/tool-execution.test.ts +0 -162
- package/src/modes/controllers/extension-ui-controller.test.ts +0 -250
- package/src/modes/noninteractive-dispose.test.ts +0 -73
- package/src/modes/print-mode.test.ts +0 -71
- package/src/modes/session-teardown.test.ts +0 -219
- package/src/modes/theme/mermaid-rendering.test.ts +0 -53
- package/src/modes/utils/transcript-render-helpers.test.ts +0 -38
- package/src/modes/warp-events.test.ts +0 -794
- package/src/plan-mode/approved-plan-prompt.test.ts +0 -36
- package/src/plan-mode/model-transition.test.ts +0 -60
- package/src/plan-mode/reentry-prompt.test.ts +0 -41
- package/src/session/agent-session-error-log.test.ts +0 -59
- package/src/session/blob-store.test.ts +0 -56
- package/src/session/messages.test.ts +0 -282
- package/src/session/session-context.test.ts +0 -384
- package/src/ssh/__tests__/connection-manager-args.test.ts +0 -191
- package/src/ssh/__tests__/connection-manager-timeout.test.ts +0 -61
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +0 -105
- package/src/ssh/__tests__/sshfs-mount.test.ts +0 -13
- package/src/system-prompt.test.ts +0 -236
- package/src/task/render.test.ts +0 -290
- package/src/task/spawn-policy.test.ts +0 -62
- package/src/tools/__tests__/eval-description.test.ts +0 -18
- package/src/tools/__tests__/glob.test.ts +0 -37
- package/src/tools/__tests__/json-tree.test.ts +0 -35
- package/src/tools/__tests__/vibe-render.test.ts +0 -210
- package/src/tools/hub/launch-compat.test.ts +0 -40
- package/src/vibe/__tests__/token-rate.test.ts +0 -96
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Agent, AgentMessage, AgentTool, StreamFn, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type {
|
|
3
3
|
Context,
|
|
4
|
+
Effort,
|
|
4
5
|
ImageContent,
|
|
5
6
|
Message,
|
|
6
7
|
MessageAttribution,
|
|
@@ -105,6 +106,8 @@ export interface AgentSessionConfig {
|
|
|
105
106
|
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
106
107
|
/** Initial session thinking selector. */
|
|
107
108
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
109
|
+
/** Hard ceiling on the session's thinking effort (e.g. a task spawn's `task.maxEffort`-capped hint); every later change, including retry-fallback recovery, is re-clamped to it. */
|
|
110
|
+
thinkingLevelCeiling?: Effort;
|
|
108
111
|
/** Retry chain ownership when startup selected one of its fallback entries. */
|
|
109
112
|
initialRetryFallback?: InitialRetryFallbackState;
|
|
110
113
|
/** Prewalk from the starting model to a fast/cheap target after implementation begins. */
|
|
@@ -911,6 +911,7 @@ export class AgentSession {
|
|
|
911
911
|
this.#models = new ModelControls(modelControlsHost, {
|
|
912
912
|
scopedModels: config.scopedModels,
|
|
913
913
|
thinkingLevel: config.thinkingLevel,
|
|
914
|
+
thinkingLevelCeiling: config.thinkingLevelCeiling,
|
|
914
915
|
serviceTierByFamily: config.serviceTierByFamily,
|
|
915
916
|
});
|
|
916
917
|
|
|
@@ -928,6 +929,7 @@ export class AgentSession {
|
|
|
928
929
|
thinkingLevel: () => this.thinkingLevel,
|
|
929
930
|
configuredThinkingLevel: () => this.configuredThinkingLevel(),
|
|
930
931
|
setThinkingLevel: level => this.setThinkingLevel(level),
|
|
932
|
+
thinkingLevelCeiling: () => this.#models.thinkingLevelCeiling,
|
|
931
933
|
isDisposed: () => this.#isDisposed,
|
|
932
934
|
isStreaming: () => this.isStreaming,
|
|
933
935
|
isCompacting: () => this.isCompacting,
|
|
@@ -1471,7 +1473,7 @@ export class AgentSession {
|
|
|
1471
1473
|
|
|
1472
1474
|
this.#toolChoiceQueue.pushSequence([forced, "none"], {
|
|
1473
1475
|
label: "user-force",
|
|
1474
|
-
onRejected:
|
|
1476
|
+
onRejected: info => (info.reason === "unavailable" ? "drop_sequence" : "requeue"),
|
|
1475
1477
|
});
|
|
1476
1478
|
}
|
|
1477
1479
|
|
|
@@ -4222,6 +4224,7 @@ export class AgentSession {
|
|
|
4222
4224
|
|
|
4223
4225
|
/** Drop mutable tool decisions and directives owned by the previous logical session. */
|
|
4224
4226
|
#clearSessionScopedToolState(): void {
|
|
4227
|
+
this.agent.clearDeferredToolDirectives();
|
|
4225
4228
|
this.#toolChoiceQueue.clear();
|
|
4226
4229
|
this.#tools.clearAcpPermissionDecisions();
|
|
4227
4230
|
}
|
|
@@ -5901,6 +5904,10 @@ export class AgentSession {
|
|
|
5901
5904
|
additionalDirectories: this.settings.get("workspace.additionalDirectories"),
|
|
5902
5905
|
});
|
|
5903
5906
|
this.#bash.markSessionTransition(bashTransition);
|
|
5907
|
+
// The new session owns the transcript from here, so the previous
|
|
5908
|
+
// conversation's advisor spend is retired with it. Clearing at the commit
|
|
5909
|
+
// point keeps the status line honest even if a later step below throws.
|
|
5910
|
+
this.#advisors.clearCost();
|
|
5904
5911
|
sessionTransitioned = true;
|
|
5905
5912
|
} finally {
|
|
5906
5913
|
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
@@ -6323,7 +6330,7 @@ export class AgentSession {
|
|
|
6323
6330
|
activeMessages.splice(0, activeMessages.length, ...sessionContext.messages);
|
|
6324
6331
|
}
|
|
6325
6332
|
this.agent.replaceMessages(activeMessages ?? sessionContext.messages);
|
|
6326
|
-
this.#advisors.resetSessionState();
|
|
6333
|
+
this.#advisors.resetSessionState({ preserveCost: true });
|
|
6327
6334
|
this.#todo.syncFromBranch();
|
|
6328
6335
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
6329
6336
|
this.#checkpointState = undefined;
|
|
@@ -6941,7 +6948,7 @@ export class AgentSession {
|
|
|
6941
6948
|
}
|
|
6942
6949
|
|
|
6943
6950
|
this.agent.replaceMessages(sessionContext.messages);
|
|
6944
|
-
this.#advisors.resetSessionState();
|
|
6951
|
+
this.#advisors.resetSessionState({ preserveCost: true });
|
|
6945
6952
|
this.#todo.syncFromBranch();
|
|
6946
6953
|
if (switchingToDifferentSession) {
|
|
6947
6954
|
this.#closeAllProviderSessions("session switch");
|
|
@@ -7050,6 +7057,9 @@ export class AgentSession {
|
|
|
7050
7057
|
error: String(refreshErr),
|
|
7051
7058
|
});
|
|
7052
7059
|
}
|
|
7060
|
+
// Only a committed switch retires the previous conversation's advisor spend:
|
|
7061
|
+
// an earlier clear would be lost work if any step above rolled the switch back.
|
|
7062
|
+
if (switchingToDifferentSession) this.#advisors.clearCost();
|
|
7053
7063
|
this.#bash.finishSessionTransition(bashTransition, true);
|
|
7054
7064
|
return true;
|
|
7055
7065
|
} catch (error) {
|
|
@@ -7148,6 +7158,7 @@ export class AgentSession {
|
|
|
7148
7158
|
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
7149
7159
|
}
|
|
7150
7160
|
this.#bash.markSessionTransition(bashTransition);
|
|
7161
|
+
this.#advisors.clearCost();
|
|
7151
7162
|
sessionTransitioned = true;
|
|
7152
7163
|
} finally {
|
|
7153
7164
|
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
@@ -7245,6 +7256,7 @@ export class AgentSession {
|
|
|
7245
7256
|
try {
|
|
7246
7257
|
this.sessionManager.createBranchedSession(leafId);
|
|
7247
7258
|
this.#bash.markSessionTransition(bashTransition);
|
|
7259
|
+
this.#advisors.clearCost();
|
|
7248
7260
|
sessionTransitioned = true;
|
|
7249
7261
|
} finally {
|
|
7250
7262
|
this.#bash.finishSessionTransition(bashTransition, sessionTransitioned);
|
|
@@ -7574,7 +7586,7 @@ export class AgentSession {
|
|
|
7574
7586
|
const displayContext = deobfuscateSessionContext(stateContext, this.#obfuscator);
|
|
7575
7587
|
this.agent.replaceMessages(displayContext.messages);
|
|
7576
7588
|
this.#rehydrateCheckpointRewindState();
|
|
7577
|
-
this.#advisors.resetSessionState();
|
|
7589
|
+
this.#advisors.resetSessionState({ preserveCost: true });
|
|
7578
7590
|
this.#todo.syncFromBranch();
|
|
7579
7591
|
this.#closeCodexProviderSessionsForHistoryRewrite();
|
|
7580
7592
|
|
|
@@ -8205,6 +8217,11 @@ export class AgentSession {
|
|
|
8205
8217
|
getAdvisorStatusOverview(): { configured: boolean; advisors: { name: string; status: AdvisorRuntimeStatus }[] } {
|
|
8206
8218
|
return this.#advisors.getAdvisorStatusOverview();
|
|
8207
8219
|
}
|
|
8220
|
+
|
|
8221
|
+
/** Return cumulative cost recorded for the current session's advisor activity. */
|
|
8222
|
+
getAdvisorCost(): number {
|
|
8223
|
+
return this.#advisors.getAdvisorCost();
|
|
8224
|
+
}
|
|
8208
8225
|
/**
|
|
8209
8226
|
* Return structured advisor stats for the status command and TUI panel.
|
|
8210
8227
|
*/
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
AUTO_THINKING,
|
|
28
28
|
type ConfiguredThinkingLevel,
|
|
29
29
|
clampAutoThinkingEffort,
|
|
30
|
+
clampThinkingLevelToCeiling,
|
|
30
31
|
resolveProvisionalAutoLevel,
|
|
31
32
|
resolveThinkingLevelForModel,
|
|
32
33
|
shouldDisableReasoning,
|
|
@@ -65,6 +66,8 @@ export class ModelControls {
|
|
|
65
66
|
readonly #host: ModelControlsHost;
|
|
66
67
|
#scopedModels: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
67
68
|
#thinkingLevel: ThinkingLevel | undefined;
|
|
69
|
+
/** Hard per-session effort ceiling (e.g. a task spawn's `task.maxEffort` cap); recovery paths re-clamp to it. */
|
|
70
|
+
readonly #thinkingLevelCeiling: Effort | undefined;
|
|
68
71
|
#autoThinking = false;
|
|
69
72
|
#autoResolvedLevel: Effort | undefined;
|
|
70
73
|
#serviceTierByFamily: ServiceTierByFamily;
|
|
@@ -74,18 +77,28 @@ export class ModelControls {
|
|
|
74
77
|
options: {
|
|
75
78
|
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
76
79
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
80
|
+
thinkingLevelCeiling?: Effort;
|
|
77
81
|
serviceTierByFamily?: ServiceTierByFamily;
|
|
78
82
|
},
|
|
79
83
|
) {
|
|
80
84
|
this.#host = host;
|
|
81
85
|
this.#scopedModels = options.scopedModels ?? [];
|
|
82
86
|
this.#serviceTierByFamily = options.serviceTierByFamily ?? {};
|
|
87
|
+
this.#thinkingLevelCeiling = options.thinkingLevelCeiling;
|
|
83
88
|
if (options.thinkingLevel === AUTO_THINKING) {
|
|
84
89
|
// Keep auto pending until the first turn while exposing a valid wire effort.
|
|
85
90
|
this.#autoThinking = true;
|
|
86
|
-
this.#thinkingLevel =
|
|
91
|
+
this.#thinkingLevel = clampThinkingLevelToCeiling(
|
|
92
|
+
this.#model,
|
|
93
|
+
resolveProvisionalAutoLevel(this.#model),
|
|
94
|
+
this.#thinkingLevelCeiling,
|
|
95
|
+
);
|
|
87
96
|
} else {
|
|
88
|
-
this.#thinkingLevel =
|
|
97
|
+
this.#thinkingLevel = clampThinkingLevelToCeiling(
|
|
98
|
+
this.#model,
|
|
99
|
+
options.thinkingLevel,
|
|
100
|
+
this.#thinkingLevelCeiling,
|
|
101
|
+
);
|
|
89
102
|
}
|
|
90
103
|
this.#applyThinkingLevelToAgent(this.#thinkingLevel);
|
|
91
104
|
}
|
|
@@ -99,6 +112,11 @@ export class ModelControls {
|
|
|
99
112
|
return this.#thinkingLevel;
|
|
100
113
|
}
|
|
101
114
|
|
|
115
|
+
/** Hard per-session effort ceiling every thinking-level change is clamped to. */
|
|
116
|
+
get thinkingLevelCeiling(): Effort | undefined {
|
|
117
|
+
return this.#thinkingLevelCeiling;
|
|
118
|
+
}
|
|
119
|
+
|
|
102
120
|
/** Configured selector, preserving `auto` while classification is active. */
|
|
103
121
|
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined {
|
|
104
122
|
return this.#autoThinking ? AUTO_THINKING : this.#thinkingLevel;
|
|
@@ -130,8 +148,15 @@ export class ModelControls {
|
|
|
130
148
|
this.#autoResolvedLevel = undefined;
|
|
131
149
|
this.#thinkingLevel =
|
|
132
150
|
level === AUTO_THINKING
|
|
133
|
-
?
|
|
134
|
-
|
|
151
|
+
? clampThinkingLevelToCeiling(
|
|
152
|
+
this.#model,
|
|
153
|
+
resolveProvisionalAutoLevel(this.#model),
|
|
154
|
+
this.#thinkingLevelCeiling,
|
|
155
|
+
)
|
|
156
|
+
: resolveThinkingLevelForModel(
|
|
157
|
+
this.#model,
|
|
158
|
+
clampThinkingLevelToCeiling(this.#model, level, this.#thinkingLevelCeiling),
|
|
159
|
+
);
|
|
135
160
|
this.#applyThinkingLevelToAgent(this.#thinkingLevel);
|
|
136
161
|
}
|
|
137
162
|
|
|
@@ -469,7 +494,11 @@ export class ModelControls {
|
|
|
469
494
|
*/
|
|
470
495
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined, persist: boolean = false): void {
|
|
471
496
|
if (level === AUTO_THINKING) {
|
|
472
|
-
const provisional =
|
|
497
|
+
const provisional = clampThinkingLevelToCeiling(
|
|
498
|
+
this.#model,
|
|
499
|
+
resolveProvisionalAutoLevel(this.#model),
|
|
500
|
+
this.#thinkingLevelCeiling,
|
|
501
|
+
);
|
|
473
502
|
const wasAuto = this.#autoThinking;
|
|
474
503
|
const previousLevel = this.#thinkingLevel;
|
|
475
504
|
this.#autoThinking = true;
|
|
@@ -493,7 +522,10 @@ export class ModelControls {
|
|
|
493
522
|
const wasAuto = this.#autoThinking;
|
|
494
523
|
this.#autoThinking = false;
|
|
495
524
|
this.#autoResolvedLevel = undefined;
|
|
496
|
-
const effectiveLevel = resolveThinkingLevelForModel(
|
|
525
|
+
const effectiveLevel = resolveThinkingLevelForModel(
|
|
526
|
+
this.#model,
|
|
527
|
+
clampThinkingLevelToCeiling(this.#model, level, this.#thinkingLevelCeiling),
|
|
528
|
+
);
|
|
497
529
|
// Leaving auto must persist even when the resolved effort is unchanged (e.g.
|
|
498
530
|
// auto resolved to medium, then the user pins medium): otherwise the latest
|
|
499
531
|
// session entry keeps `configured: "auto"` and resume re-enables auto.
|
|
@@ -591,7 +623,11 @@ export class ModelControls {
|
|
|
591
623
|
// Drop the result if the turn was aborted/superseded while classifying.
|
|
592
624
|
if (this.#host.promptGeneration() !== generation || !this.#autoThinking) return;
|
|
593
625
|
|
|
594
|
-
const effort =
|
|
626
|
+
const effort = clampThinkingLevelToCeiling(
|
|
627
|
+
model,
|
|
628
|
+
resolved ?? resolveProvisionalAutoLevel(model),
|
|
629
|
+
this.#thinkingLevelCeiling,
|
|
630
|
+
);
|
|
595
631
|
if (effort === undefined) return;
|
|
596
632
|
const shouldPersistResolution = this.#thinkingLevel !== effort;
|
|
597
633
|
this.#autoResolvedLevel = effort;
|
|
@@ -254,6 +254,7 @@ export class SessionAdvisors {
|
|
|
254
254
|
#advisorConfigs: AdvisorConfig[] | undefined;
|
|
255
255
|
#advisorStatuses = new Map<string, { name: string; status: AdvisorRuntimeStatus }>();
|
|
256
256
|
#advisorProviderSessionIds = new Map<string, string>();
|
|
257
|
+
#advisorCosts = new Map<string, number>();
|
|
257
258
|
#advisorRecorderClosed: Promise<void> = Promise.resolve();
|
|
258
259
|
#advisorAutoResumeSuppressed = false;
|
|
259
260
|
#preserveAdvisorAdvice = false;
|
|
@@ -326,8 +327,13 @@ export class SessionAdvisors {
|
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
/** Re-primes advisor transcript views across a conversation boundary. */
|
|
329
|
-
resetSessionState(): void {
|
|
330
|
-
this.#resetAdvisorSessionState();
|
|
330
|
+
resetSessionState(options: { preserveCost?: boolean } = {}): void {
|
|
331
|
+
this.#resetAdvisorSessionState(options.preserveCost === true);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Drop the recorded spend once a conversation boundary has committed. */
|
|
335
|
+
clearCost(): void {
|
|
336
|
+
this.#advisorCosts.clear();
|
|
331
337
|
}
|
|
332
338
|
|
|
333
339
|
/**
|
|
@@ -451,7 +457,8 @@ export class SessionAdvisors {
|
|
|
451
457
|
* agent steer/follow-up queues, and preserved cards deferred to the next turn —
|
|
452
458
|
* so none of them inject into the new conversation.
|
|
453
459
|
*/
|
|
454
|
-
#resetAdvisorSessionState(): void {
|
|
460
|
+
#resetAdvisorSessionState(preserveCost: boolean): void {
|
|
461
|
+
if (!preserveCost) this.#advisorCosts.clear();
|
|
455
462
|
// Mute the recorder across the re-prime: AdvisorRuntime.reset() aborts the advisor
|
|
456
463
|
// loop, and that abort can emit an `aborted` message_end we must not attribute to
|
|
457
464
|
// either session's transcript. Detach, reset, then re-attach the live agent's feed.
|
|
@@ -959,8 +966,7 @@ export class SessionAdvisors {
|
|
|
959
966
|
.catch(err => logger.debug("advisor delivery failed", { err: String(err) }));
|
|
960
967
|
}
|
|
961
968
|
|
|
962
|
-
/** Re-prime every advisor's transcript view
|
|
963
|
-
* session-level latch reset {@link #resetAdvisorSessionState} performs. */
|
|
969
|
+
/** Re-prime every advisor's transcript view after an in-conversation history rewrite. */
|
|
964
970
|
#resetAllAdvisorRuntimes(): void {
|
|
965
971
|
for (const a of this.#advisors) a.runtime.reset();
|
|
966
972
|
}
|
|
@@ -985,12 +991,18 @@ export class SessionAdvisors {
|
|
|
985
991
|
this.#advisorYieldQueueUnsubscribe = undefined;
|
|
986
992
|
}
|
|
987
993
|
|
|
994
|
+
#recordAdvisorCost(advisor: ActiveAdvisor, message: AssistantMessage): void {
|
|
995
|
+
this.#advisorCosts.set(advisor.slug, (this.#advisorCosts.get(advisor.slug) ?? 0) + message.usage.cost.total);
|
|
996
|
+
}
|
|
997
|
+
|
|
988
998
|
/** Subscribe the advisor agent's finalized messages into the transcript recorder.
|
|
989
999
|
* Idempotent-by-replacement: callers detach the prior feed first. Kept separate
|
|
990
1000
|
* so the re-prime path can mute the feed across an abort-driven reset. */
|
|
991
1001
|
#attachAdvisorRecorderFeed(advisor: ActiveAdvisor): void {
|
|
992
1002
|
advisor.agentUnsubscribe = advisor.agent.subscribe(event => {
|
|
993
|
-
if (event.type
|
|
1003
|
+
if (event.type !== "message_end") return;
|
|
1004
|
+
if (event.message.role === "assistant") this.#recordAdvisorCost(advisor, event.message);
|
|
1005
|
+
advisor.recorder.record(event.message);
|
|
994
1006
|
});
|
|
995
1007
|
}
|
|
996
1008
|
|
|
@@ -1507,6 +1519,13 @@ export class SessionAdvisors {
|
|
|
1507
1519
|
}));
|
|
1508
1520
|
return { configured: this.#advisorEnabled, advisors };
|
|
1509
1521
|
}
|
|
1522
|
+
|
|
1523
|
+
/** Return cumulative advisor cost recorded for the current session. */
|
|
1524
|
+
getAdvisorCost(): number {
|
|
1525
|
+
let cost = 0;
|
|
1526
|
+
for (const advisorCost of this.#advisorCosts.values()) cost += advisorCost;
|
|
1527
|
+
return cost;
|
|
1528
|
+
}
|
|
1510
1529
|
/**
|
|
1511
1530
|
* Return structured advisor stats for the status command and TUI panel.
|
|
1512
1531
|
*/
|
|
@@ -1530,12 +1549,13 @@ export class SessionAdvisors {
|
|
|
1530
1549
|
contextWindow: 0,
|
|
1531
1550
|
contextTokens: 0,
|
|
1532
1551
|
tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
1533
|
-
cost: 0,
|
|
1552
|
+
cost: this.#advisorCosts.get(slug) ?? 0,
|
|
1534
1553
|
messages: { user: 0, assistant: 0, total: 0 },
|
|
1535
1554
|
});
|
|
1536
1555
|
}
|
|
1537
1556
|
}
|
|
1538
1557
|
const active = liveAdvisors.length > 0;
|
|
1558
|
+
const cost = this.getAdvisorCost();
|
|
1539
1559
|
if (liveAdvisors.length === 0) {
|
|
1540
1560
|
return {
|
|
1541
1561
|
configured,
|
|
@@ -1543,14 +1563,13 @@ export class SessionAdvisors {
|
|
|
1543
1563
|
contextWindow: 0,
|
|
1544
1564
|
contextTokens: 0,
|
|
1545
1565
|
tokens: { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
1546
|
-
cost
|
|
1566
|
+
cost,
|
|
1547
1567
|
messages: { user: 0, assistant: 0, total: 0 },
|
|
1548
1568
|
advisors: roster,
|
|
1549
1569
|
};
|
|
1550
1570
|
}
|
|
1551
1571
|
const tokens = { input: 0, output: 0, reasoning: 0, cacheRead: 0, cacheWrite: 0, total: 0 };
|
|
1552
1572
|
const messages = { user: 0, assistant: 0, total: 0 };
|
|
1553
|
-
let cost = 0;
|
|
1554
1573
|
let contextTokens = 0;
|
|
1555
1574
|
for (const a of liveAdvisors) {
|
|
1556
1575
|
tokens.input += a.tokens.input;
|
|
@@ -1562,7 +1581,6 @@ export class SessionAdvisors {
|
|
|
1562
1581
|
messages.user += a.messages.user;
|
|
1563
1582
|
messages.assistant += a.messages.assistant;
|
|
1564
1583
|
messages.total += a.messages.total;
|
|
1565
|
-
cost += a.cost;
|
|
1566
1584
|
contextTokens += a.contextTokens;
|
|
1567
1585
|
}
|
|
1568
1586
|
// Single-advisor displays read the top-level model/window directly; surface the
|
|
@@ -1591,7 +1609,6 @@ export class SessionAdvisors {
|
|
|
1591
1609
|
let cacheRead = 0;
|
|
1592
1610
|
let cacheWrite = 0;
|
|
1593
1611
|
let totalTokens = 0;
|
|
1594
|
-
let cost = 0;
|
|
1595
1612
|
let user = 0;
|
|
1596
1613
|
let assistant = 0;
|
|
1597
1614
|
for (const message of messages) {
|
|
@@ -1605,7 +1622,6 @@ export class SessionAdvisors {
|
|
|
1605
1622
|
cacheRead += assistantMsg.usage.cacheRead;
|
|
1606
1623
|
cacheWrite += assistantMsg.usage.cacheWrite;
|
|
1607
1624
|
totalTokens += assistantMsg.usage.totalTokens;
|
|
1608
|
-
cost += assistantMsg.usage.cost.total;
|
|
1609
1625
|
}
|
|
1610
1626
|
}
|
|
1611
1627
|
return {
|
|
@@ -1619,7 +1635,7 @@ export class SessionAdvisors {
|
|
|
1619
1635
|
contextWindow: model.contextWindow ?? 0,
|
|
1620
1636
|
contextTokens,
|
|
1621
1637
|
tokens: { input, output, reasoning, cacheRead, cacheWrite, total: totalTokens },
|
|
1622
|
-
cost,
|
|
1638
|
+
cost: this.#advisorCosts.get(advisor.slug) ?? 0,
|
|
1623
1639
|
messages: { user, assistant, total: messages.length },
|
|
1624
1640
|
sessionId: advisor.agent.sessionId,
|
|
1625
1641
|
};
|
|
@@ -1649,7 +1665,7 @@ export class SessionAdvisors {
|
|
|
1649
1665
|
const spendParts = [`${s.tokens.input.toLocaleString()} input`, `${s.tokens.output.toLocaleString()} output`];
|
|
1650
1666
|
if (s.tokens.cacheRead > 0) spendParts.push(`${s.tokens.cacheRead.toLocaleString()} cache read`);
|
|
1651
1667
|
if (s.tokens.cacheWrite > 0) spendParts.push(`${s.tokens.cacheWrite.toLocaleString()} cache write`);
|
|
1652
|
-
const spendLine = `Spend: ${spendParts.join(", ")}, $${
|
|
1668
|
+
const spendLine = `Spend: ${spendParts.join(", ")}, $${stats.cost.toFixed(4)}`;
|
|
1653
1669
|
if (!s.model || s.status !== "running") return `Advisor "${s.name}" is ${s.status.replace("_", " ")}.`;
|
|
1654
1670
|
return `Advisor is enabled (${s.model.provider}/${s.model.id}). ${contextLine}. ${spendLine}.`;
|
|
1655
1671
|
}
|
|
@@ -11,6 +11,7 @@ import type { ExtensionRunner } from "../extensibility/extensions";
|
|
|
11
11
|
import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
12
12
|
import { loadSkills, type Skill, type SkillWarning, setActiveSkills } from "../extensibility/skills";
|
|
13
13
|
import { type LocalProtocolOptions, XD_URL_PREFIX } from "../internal-urls";
|
|
14
|
+
import { deduplicateMCPToolsByName } from "../mcp/tool-bridge";
|
|
14
15
|
import { resolveMemoryBackend } from "../memory-backend/resolve";
|
|
15
16
|
import { MEMORY_BACKEND_TOOL_NAMES } from "../memory-backend/tool-names";
|
|
16
17
|
import type { MemoryBackendStartOptions } from "../memory-backend/types";
|
|
@@ -1040,7 +1041,8 @@ export class SessionTools {
|
|
|
1040
1041
|
});
|
|
1041
1042
|
|
|
1042
1043
|
const extensionRunner = this.#host.extensionRunner();
|
|
1043
|
-
|
|
1044
|
+
const uniqueMcpTools = deduplicateMCPToolsByName(mcpTools);
|
|
1045
|
+
for (const customTool of uniqueMcpTools) {
|
|
1044
1046
|
const wrapped = wrapToolWithMetaNotice(CustomToolAdapter.wrap(customTool, getCustomToolContext) as AgentTool);
|
|
1045
1047
|
const finalTool = (
|
|
1046
1048
|
extensionRunner ? new ExtensionToolWrapper(wrapped, extensionRunner) : wrapped
|
|
@@ -1050,7 +1052,7 @@ export class SessionTools {
|
|
|
1050
1052
|
|
|
1051
1053
|
// Every connected MCP tool is selected; centralized repartitioning owns
|
|
1052
1054
|
// presentation pins and write-transport activation/removal.
|
|
1053
|
-
const nextActive = [...new Set([...this.#getActiveNonMCPToolNames(), ...
|
|
1055
|
+
const nextActive = [...new Set([...this.#getActiveNonMCPToolNames(), ...uniqueMcpTools.map(tool => tool.name)])];
|
|
1054
1056
|
try {
|
|
1055
1057
|
await this.applyActiveToolsByName(nextActive);
|
|
1056
1058
|
if (this.#host.isDisposed()) restorePreviousMcpTools();
|
|
@@ -13,15 +13,15 @@ export interface RejectInfo {
|
|
|
13
13
|
reason: "aborted" | "error" | "cleared" | "removed" | "unavailable" | "not_invoked";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
export type RejectOutcome = "requeue" | "drop";
|
|
16
|
+
/** Controls whether rejection replays a yield, drops it, or drops its remaining sequence. */
|
|
17
|
+
export type RejectOutcome = "requeue" | "drop" | "drop_sequence";
|
|
18
18
|
|
|
19
19
|
export interface DirectiveCallbacks {
|
|
20
20
|
/** Fires when the yield completed; onInvoked directives require the requested tool to run first. */
|
|
21
21
|
onResolved?: (info: ResolveInfo) => void;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Return "requeue" to replay the same value, or "drop_sequence" to discard
|
|
24
|
+
* its directive including later yields. Default: drop the rejected yield.
|
|
25
25
|
*/
|
|
26
26
|
onRejected?: (info: RejectInfo) => RejectOutcome | undefined;
|
|
27
27
|
/**
|
|
@@ -39,6 +39,8 @@ export interface ToolChoiceDirective {
|
|
|
39
39
|
/** Stable label for targeted removal and debugging (e.g. "user-force"). */
|
|
40
40
|
label: string;
|
|
41
41
|
callbacks: DirectiveCallbacks;
|
|
42
|
+
/** Original multi-yield directive retained across one-yield replays. */
|
|
43
|
+
sequenceRoot?: ToolChoiceDirective;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export interface PushOptions {
|
|
@@ -83,6 +85,12 @@ interface PendingInvoker {
|
|
|
83
85
|
|
|
84
86
|
export class ToolChoiceQueue {
|
|
85
87
|
#queue: ToolChoiceDirective[] = [];
|
|
88
|
+
/**
|
|
89
|
+
* In-flight yield awaiting resolve()/reject(). May outlive the run that
|
|
90
|
+
* claimed it: a pre-model gate stop ends the run without a `turn_end`, and
|
|
91
|
+
* the claim is finalized later — by the next admitted turn's `turn_end`,
|
|
92
|
+
* the abort safety net, or an `unavailable` rejection on redeem.
|
|
93
|
+
*/
|
|
86
94
|
#inFlight: InFlight | undefined;
|
|
87
95
|
/**
|
|
88
96
|
* Label of the directive whose last yield was resolved this turn.
|
|
@@ -178,6 +186,12 @@ export class ToolChoiceQueue {
|
|
|
178
186
|
reason,
|
|
179
187
|
});
|
|
180
188
|
|
|
189
|
+
if (outcome === "drop_sequence") {
|
|
190
|
+
const root = inFlight.directive.sequenceRoot ?? inFlight.directive;
|
|
191
|
+
this.#queue = this.#queue.filter(directive => directive !== root && directive.sequenceRoot !== root);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
181
195
|
if (outcome === "requeue") {
|
|
182
196
|
// Re-queue only the lost yield, not the rest of the sequence. Carry forward
|
|
183
197
|
// callbacks so the replayed yield still executes and finalizes correctly,
|
|
@@ -185,6 +199,7 @@ export class ToolChoiceQueue {
|
|
|
185
199
|
this.#queue.unshift({
|
|
186
200
|
generator: onceGen(inFlight.yielded),
|
|
187
201
|
label: `${inFlight.directive.label}-requeued`,
|
|
202
|
+
sequenceRoot: inFlight.directive.sequenceRoot ?? inFlight.directive,
|
|
188
203
|
callbacks: {
|
|
189
204
|
onResolved: inFlight.directive.callbacks.onResolved,
|
|
190
205
|
onInvoked: inFlight.directive.callbacks.onInvoked,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
AssistantRetryRecovery,
|
|
12
12
|
AssistantRetryRecoveryKind,
|
|
13
13
|
CodexCompactionContext,
|
|
14
|
+
Effort,
|
|
14
15
|
Model,
|
|
15
16
|
TextContent,
|
|
16
17
|
ToolChoice,
|
|
@@ -27,7 +28,12 @@ import type { RecoveredRetryError } from "../extensibility/shared-events";
|
|
|
27
28
|
import emptyStopRetryTemplate from "../prompts/system/empty-stop-retry.md" with { type: "text" };
|
|
28
29
|
import thinkingLoopRedirectTemplate from "../prompts/system/thinking-loop-redirect.md" with { type: "text" };
|
|
29
30
|
import unexpectedStopRetryTemplate from "../prompts/system/unexpected-stop-retry.md" with { type: "text" };
|
|
30
|
-
import
|
|
31
|
+
import {
|
|
32
|
+
AUTO_THINKING,
|
|
33
|
+
type ConfiguredThinkingLevel,
|
|
34
|
+
clampThinkingLevelToCeiling,
|
|
35
|
+
modelSupportsEffortCeiling,
|
|
36
|
+
} from "../thinking";
|
|
31
37
|
import type { AgentSessionEvent } from "./agent-session-events";
|
|
32
38
|
import type { InitialRetryFallbackState } from "./agent-session-types";
|
|
33
39
|
import { isEmptyErrorTurn } from "./messages";
|
|
@@ -98,6 +104,8 @@ export interface TurnRecoveryHost {
|
|
|
98
104
|
thinkingLevel(): ThinkingLevel | undefined;
|
|
99
105
|
configuredThinkingLevel(): ConfiguredThinkingLevel | undefined;
|
|
100
106
|
setThinkingLevel(level: ConfiguredThinkingLevel | undefined): void;
|
|
107
|
+
/** Hard per-session effort ceiling; fallback recovery must never raise thinking above it. */
|
|
108
|
+
thinkingLevelCeiling(): Effort | undefined;
|
|
101
109
|
isDisposed(): boolean;
|
|
102
110
|
isStreaming(): boolean;
|
|
103
111
|
isCompacting(): boolean;
|
|
@@ -1011,7 +1019,14 @@ export class TurnRecovery {
|
|
|
1011
1019
|
// Capture the configured selector (auto-aware) so a fallback chain preserves
|
|
1012
1020
|
// `auto` instead of collapsing it to the level it resolved to this turn.
|
|
1013
1021
|
const currentThinkingLevel = this.#host.configuredThinkingLevel();
|
|
1014
|
-
const
|
|
1022
|
+
const requestedThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;
|
|
1023
|
+
// A fallback selector's explicit level (or the carried level after the
|
|
1024
|
+
// replacement model's floor clamp) must never exceed the session's
|
|
1025
|
+
// per-spawn effort ceiling.
|
|
1026
|
+
const nextThinkingLevel =
|
|
1027
|
+
requestedThinkingLevel === AUTO_THINKING
|
|
1028
|
+
? requestedThinkingLevel
|
|
1029
|
+
: clampThinkingLevelToCeiling(candidate, requestedThinkingLevel, this.#host.thinkingLevelCeiling());
|
|
1015
1030
|
const candidateSelector = formatModelStringWithRouting(candidate);
|
|
1016
1031
|
this.#host.setModelWithProviderSessionReset(candidate);
|
|
1017
1032
|
this.#host.sessionManager.appendModelChange(candidateSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
@@ -1041,11 +1056,15 @@ export class TurnRecovery {
|
|
|
1041
1056
|
const role = this.#activeRetryFallback?.role ?? this.resolveRetryFallbackRole(currentSelector);
|
|
1042
1057
|
if (!role) return false;
|
|
1043
1058
|
|
|
1059
|
+
const ceiling = this.#host.thinkingLevelCeiling();
|
|
1044
1060
|
for (const selector of this.findRetryFallbackCandidates(role, currentSelector)) {
|
|
1045
1061
|
if (this.isRetryFallbackSelectorSuppressed(selector)) continue;
|
|
1046
1062
|
const resolved = resolveModelOverride([selector.raw], this.#host.modelRegistry, this.#host.settings);
|
|
1047
1063
|
const candidate = resolved.model ?? this.#host.modelRegistry.find(selector.provider, selector.id);
|
|
1048
1064
|
if (!candidate) continue;
|
|
1065
|
+
// A candidate whose effort floor exceeds the per-spawn ceiling would be
|
|
1066
|
+
// clamped UP past the cap by its model floor — skip it entirely.
|
|
1067
|
+
if (ceiling !== undefined && !modelSupportsEffortCeiling(candidate, ceiling)) continue;
|
|
1049
1068
|
const apiKey = await this.#host.modelRegistry.getApiKey(candidate, this.#host.sessionId());
|
|
1050
1069
|
if (!apiKey) continue;
|
|
1051
1070
|
await this.applyRetryFallbackCandidate(role, selector, currentSelector, options);
|
package/src/task/executor.ts
CHANGED
|
@@ -2647,9 +2647,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2647
2647
|
progress.contextWindow = model.contextWindow;
|
|
2648
2648
|
}
|
|
2649
2649
|
// Caller-requested coarse effort maps onto the resolved model's
|
|
2650
|
-
// supported range
|
|
2651
|
-
//
|
|
2652
|
-
|
|
2650
|
+
// supported range, then respects the operator-configured ceiling.
|
|
2651
|
+
// Undefined (no effort, or no controllable effort surface) falls
|
|
2652
|
+
// through to the normal selectors below.
|
|
2653
|
+
// The ceiling outlives initial resolution: it rides into the session so
|
|
2654
|
+
// retry-fallback recovery can never clamp effort back up past it.
|
|
2655
|
+
const spawnEffortCeiling = options.effort !== undefined ? settings.get("task.maxEffort") : undefined;
|
|
2656
|
+
const effortLevel =
|
|
2657
|
+
options.effort !== undefined
|
|
2658
|
+
? resolveTaskEffortLevel(model, options.effort, spawnEffortCeiling)
|
|
2659
|
+
: undefined;
|
|
2653
2660
|
if (model) {
|
|
2654
2661
|
const displayLevel = effortLevel ?? (explicitThinkingLevel ? resolvedThinkingLevel : undefined);
|
|
2655
2662
|
progress.resolvedModel =
|
|
@@ -2773,6 +2780,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2773
2780
|
modelPatternDefaultFallbackChain:
|
|
2774
2781
|
model || modelOverride === undefined ? undefined : defaultRetryFallbackChain,
|
|
2775
2782
|
thinkingLevel: effectiveThinkingLevel,
|
|
2783
|
+
thinkingLevelCeiling: spawnEffortCeiling,
|
|
2776
2784
|
toolNames,
|
|
2777
2785
|
outputSchema,
|
|
2778
2786
|
outputSchemaMode: options.outputSchemaMode,
|