@oh-my-pi/pi-coding-agent 17.1.5 → 17.1.7
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 +54 -0
- package/dist/{CHANGELOG-qs3vd6xf.md → CHANGELOG-5k4dq4g6.md} +54 -0
- package/dist/cli.js +3342 -3274
- 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 +39 -2
- package/dist/types/cursor.d.ts +2 -1
- package/dist/types/extensibility/extensions/runner.d.ts +4 -0
- package/dist/types/extensibility/shared-events.d.ts +18 -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/components/custom-editor.d.ts +5 -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 +8 -6
- package/dist/types/session/agent-session.d.ts +28 -1
- package/dist/types/session/model-controls.d.ts +4 -1
- package/dist/types/session/session-advisors.d.ts +7 -1
- package/dist/types/session/session-tools.d.ts +44 -6
- package/dist/types/session/streaming-output.d.ts +7 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -4
- package/dist/types/session/turn-recovery.d.ts +5 -3
- 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/tools/index.d.ts +8 -3
- package/dist/types/tools/output-meta.d.ts +5 -0
- package/dist/types/tools/read.d.ts +9 -1
- package/dist/types/tools/xdev.d.ts +53 -67
- package/dist/types/utils/cpuprofile.d.ts +51 -0
- package/dist/types/utils/inspect-image-mode.d.ts +29 -0
- package/dist/types/utils/profile-tree.d.ts +47 -0
- package/dist/types/utils/sample-profile.d.ts +67 -0
- 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 +42 -2
- package/src/config/settings.ts +35 -0
- package/src/cursor.ts +4 -3
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-no-local-is-record.md +48 -0
- package/src/eval/py/runner.py +16 -2
- package/src/extensibility/extensions/runner.ts +117 -5
- package/src/extensibility/extensions/types.ts +0 -1
- package/src/extensibility/extensions/wrapper.ts +74 -42
- package/src/extensibility/hooks/tool-wrapper.ts +11 -4
- package/src/extensibility/hooks/types.ts +0 -1
- package/src/extensibility/shared-events.ts +18 -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/custom-editor.ts +39 -16
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +8 -7
- package/src/modes/controllers/command-controller.ts +10 -10
- package/src/modes/controllers/extension-ui-controller.ts +7 -7
- package/src/modes/controllers/selector-controller.ts +7 -2
- 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 +64 -58
- package/src/session/agent-session-types.ts +8 -5
- package/src/session/agent-session.ts +144 -11
- package/src/session/model-controls.ts +48 -12
- package/src/session/session-advisors.ts +30 -14
- package/src/session/session-listing.ts +66 -4
- package/src/session/session-tools.ts +162 -54
- package/src/session/streaming-output.ts +18 -6
- package/src/session/tool-choice-queue.ts +19 -4
- package/src/session/turn-recovery.ts +25 -6
- package/src/slash-commands/builtin-registry.ts +69 -0
- 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/bash.ts +16 -9
- package/src/tools/index.ts +36 -16
- package/src/tools/output-meta.ts +20 -0
- package/src/tools/read.ts +89 -15
- package/src/tools/write.ts +16 -7
- package/src/tools/xdev.ts +198 -210
- package/src/utils/cpuprofile.ts +235 -0
- package/src/utils/inspect-image-mode.ts +39 -0
- package/src/utils/profile-tree.ts +111 -0
- package/src/utils/sample-profile.ts +437 -0
- 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
|
@@ -53,12 +53,17 @@ export interface OutputSummary {
|
|
|
53
53
|
export interface OutputSinkOptions {
|
|
54
54
|
artifactPath?: string;
|
|
55
55
|
artifactId?: string;
|
|
56
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* Total inline body budget (bytes). Default DEFAULT_MAX_BYTES. The head
|
|
58
|
+
* window and rolling tail window share this budget, so a composed
|
|
59
|
+
* `dump()` body never exceeds it (plus the elision marker).
|
|
60
|
+
*/
|
|
57
61
|
spillThreshold?: number;
|
|
58
62
|
/**
|
|
59
63
|
* When > 0, the sink keeps the first `headBytes` of output in addition to
|
|
60
64
|
* the rolling tail window. Output between the two windows is elided
|
|
61
|
-
* (middle elision).
|
|
65
|
+
* (middle elision). Clamped to `spillThreshold / 2` so the tail keeps at
|
|
66
|
+
* least half the inline budget. Default 0 = tail-only behavior.
|
|
62
67
|
*/
|
|
63
68
|
headBytes?: number;
|
|
64
69
|
/**
|
|
@@ -799,7 +804,7 @@ export class OutputSink {
|
|
|
799
804
|
this.#artifactPath = artifactPath;
|
|
800
805
|
this.#artifactId = artifactId;
|
|
801
806
|
this.#spillThreshold = spillThreshold;
|
|
802
|
-
this.#headLimit = Math.max(0, headBytes);
|
|
807
|
+
this.#headLimit = Math.max(0, Math.min(headBytes, Math.floor(spillThreshold / 2)));
|
|
803
808
|
this.#maxColumns = Math.max(0, maxColumns);
|
|
804
809
|
this.#onChunk = onChunk;
|
|
805
810
|
this.#chunkThrottleMs = chunkThrottleMs;
|
|
@@ -969,16 +974,23 @@ export class OutputSink {
|
|
|
969
974
|
return parts.join("");
|
|
970
975
|
}
|
|
971
976
|
|
|
977
|
+
// The rolling tail budget is whatever the head window has not consumed of
|
|
978
|
+
// the inline budget: `spillThreshold - #headBytes`. While the head window
|
|
979
|
+
// fills it shrinks toward `spillThreshold - headLimit`; after `replace()`
|
|
980
|
+
// (head cleared) it grows back to the full threshold. This keeps
|
|
981
|
+
// `head + tail <= spillThreshold`, so the composed dump body fits the
|
|
982
|
+
// inline byte cap by construction.
|
|
983
|
+
|
|
972
984
|
#willOverflow(dataBytes: number): boolean {
|
|
973
985
|
// Triggers file mirroring as soon as the next chunk would push us over
|
|
974
|
-
// the tail budget
|
|
975
|
-
return this.#bufferBytes + dataBytes > this.#spillThreshold;
|
|
986
|
+
// the tail budget — i.e. the first byte that could be lost from memory.
|
|
987
|
+
return this.#bufferBytes + dataBytes > this.#spillThreshold - this.#headBytes;
|
|
976
988
|
}
|
|
977
989
|
|
|
978
990
|
#pushTail(chunk: string, dataBytes: number): void {
|
|
979
991
|
if (dataBytes === 0) return;
|
|
980
992
|
|
|
981
|
-
const threshold = this.#spillThreshold;
|
|
993
|
+
const threshold = Math.max(0, this.#spillThreshold - this.#headBytes);
|
|
982
994
|
const willOverflow = this.#bufferBytes + dataBytes > threshold;
|
|
983
995
|
|
|
984
996
|
if (!willOverflow) {
|
|
@@ -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;
|
|
@@ -110,7 +118,7 @@ export interface TurnRecoveryHost {
|
|
|
110
118
|
waitForSessionMessagePersistence(message: AssistantMessage): Promise<void>;
|
|
111
119
|
appendSessionMessage(message: AssistantMessage): void;
|
|
112
120
|
sessionMessageAlreadyPersisted(message: AssistantMessage): boolean;
|
|
113
|
-
setModelWithProviderSessionReset(model: Model): void
|
|
121
|
+
setModelWithProviderSessionReset(model: Model): Promise<void>;
|
|
114
122
|
resetCurrentResponsesProviderSession(reason: string): void;
|
|
115
123
|
maybeAutoRedeemCodexReset(): Promise<boolean>;
|
|
116
124
|
runAutoCompaction(
|
|
@@ -1011,9 +1019,16 @@ 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
|
-
this.#host.setModelWithProviderSessionReset(candidate);
|
|
1031
|
+
await this.#host.setModelWithProviderSessionReset(candidate);
|
|
1017
1032
|
this.#host.sessionManager.appendModelChange(candidateSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1018
1033
|
this.#host.settings.getStorage()?.recordModelUsage(candidateSelector);
|
|
1019
1034
|
this.#host.setThinkingLevel(nextThinkingLevel);
|
|
@@ -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);
|
|
@@ -1128,7 +1147,7 @@ export class TurnRecovery {
|
|
|
1128
1147
|
const apiKey = await this.#host.modelRegistry.getApiKey(baseModel, this.#host.sessionId());
|
|
1129
1148
|
if (!apiKey) return false;
|
|
1130
1149
|
const baseSelector = formatModelStringWithRouting(baseModel);
|
|
1131
|
-
this.#host.setModelWithProviderSessionReset(baseModel);
|
|
1150
|
+
await this.#host.setModelWithProviderSessionReset(baseModel);
|
|
1132
1151
|
this.#host.sessionManager.appendModelChange(baseSelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1133
1152
|
this.#host.settings.getStorage()?.recordModelUsage(baseSelector);
|
|
1134
1153
|
await this.#host.emitSessionEvent({
|
|
@@ -1182,7 +1201,7 @@ export class TurnRecovery {
|
|
|
1182
1201
|
const thinkingToApply =
|
|
1183
1202
|
currentThinkingLevel === lastAppliedFallbackThinkingLevel ? originalThinkingLevel : currentThinkingLevel;
|
|
1184
1203
|
const primarySelector = formatModelStringWithRouting(primaryModel);
|
|
1185
|
-
this.#host.setModelWithProviderSessionReset(primaryModel);
|
|
1204
|
+
await this.#host.setModelWithProviderSessionReset(primaryModel);
|
|
1186
1205
|
this.#host.sessionManager.appendModelChange(primarySelector, EPHEMERAL_MODEL_CHANGE_ROLE);
|
|
1187
1206
|
this.#host.settings.getStorage()?.recordModelUsage(primarySelector);
|
|
1188
1207
|
this.#host.setThinkingLevel(thinkingToApply);
|
|
@@ -53,6 +53,7 @@ import {
|
|
|
53
53
|
renderChangelogEntries,
|
|
54
54
|
} from "../utils/changelog";
|
|
55
55
|
import { copyToClipboard } from "../utils/clipboard";
|
|
56
|
+
import type { InspectImageMode } from "../utils/inspect-image-mode";
|
|
56
57
|
import { CollabQrCodeComponent } from "./helpers/collab-qrcode";
|
|
57
58
|
import { buildContextReportText } from "./helpers/context-report";
|
|
58
59
|
import { formatDuration } from "./helpers/format";
|
|
@@ -151,6 +152,33 @@ async function applyComputerUseToggle(session: AgentSession, enable: boolean): P
|
|
|
151
152
|
: "Computer use disabled for this session.";
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
/** Session-effective `/vision status` line. */
|
|
156
|
+
function formatVisionStatus(session: AgentSession): string {
|
|
157
|
+
const { mode, active, model } = session.inspectImageState();
|
|
158
|
+
const override = session.getInspectImageModeOverride();
|
|
159
|
+
const modelObj = session.model;
|
|
160
|
+
const capability = modelObj
|
|
161
|
+
? modelObj.input.includes("image")
|
|
162
|
+
? "native image input"
|
|
163
|
+
: "no native image input"
|
|
164
|
+
: "no active model";
|
|
165
|
+
return [
|
|
166
|
+
`inspect_image: ${active ? "active" : "inactive"}`,
|
|
167
|
+
`mode: ${mode}${override ? " (session override)" : ""}`,
|
|
168
|
+
...(override ? [`configured: ${session.settings.get("inspect_image.mode")}`] : []),
|
|
169
|
+
`model: ${model ?? "none"} (${capability})`,
|
|
170
|
+
].join(" · ");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Applies a `/vision` mode for this session and returns the operator feedback line. */
|
|
174
|
+
async function applyVisionMode(session: AgentSession, mode: InspectImageMode): Promise<string> {
|
|
175
|
+
const applied = await session.setInspectImageMode(mode);
|
|
176
|
+
if (!applied) {
|
|
177
|
+
return "inspect_image is unavailable in this session.";
|
|
178
|
+
}
|
|
179
|
+
return `Vision mode: ${mode}. ${formatVisionStatus(session)}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
154
182
|
const AUTOCOMPLETE_DETAIL_LIMIT = 48;
|
|
155
183
|
|
|
156
184
|
function shortDetail(value: string, limit = AUTOCOMPLETE_DETAIL_LIMIT): string {
|
|
@@ -647,6 +675,47 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
647
675
|
runtime.ctx.editor.setText("");
|
|
648
676
|
},
|
|
649
677
|
},
|
|
678
|
+
{
|
|
679
|
+
name: "vision",
|
|
680
|
+
description: "Control the inspect_image vision-delegation tool for this session",
|
|
681
|
+
acpDescription: "Toggle vision delegation",
|
|
682
|
+
acpInputHint: "[on|off|auto|status]",
|
|
683
|
+
subcommands: [
|
|
684
|
+
{ name: "on", description: "Always expose inspect_image this session" },
|
|
685
|
+
{ name: "off", description: "Never expose inspect_image this session" },
|
|
686
|
+
{ name: "auto", description: "Follow inspect_image.mode (auto hides it for vision-capable models)" },
|
|
687
|
+
{ name: "status", description: "Show inspect_image status" },
|
|
688
|
+
],
|
|
689
|
+
allowArgs: true,
|
|
690
|
+
getTuiAutocompleteDescription: runtime => `Vision: ${runtime.ctx.session.inspectImageState().mode}`,
|
|
691
|
+
handle: async (command, runtime) => {
|
|
692
|
+
const arg = command.args.trim().toLowerCase();
|
|
693
|
+
if (arg === "status") {
|
|
694
|
+
await runtime.output(formatVisionStatus(runtime.session));
|
|
695
|
+
return commandConsumed();
|
|
696
|
+
}
|
|
697
|
+
if (arg === "on" || arg === "off" || arg === "auto") {
|
|
698
|
+
await runtime.output(await applyVisionMode(runtime.session, arg));
|
|
699
|
+
return commandConsumed();
|
|
700
|
+
}
|
|
701
|
+
return usage("Usage: /vision [on|off|auto|status]", runtime);
|
|
702
|
+
},
|
|
703
|
+
handleTui: async (command, runtime) => {
|
|
704
|
+
const arg = command.args.trim().toLowerCase();
|
|
705
|
+
if (arg === "status") {
|
|
706
|
+
runtime.ctx.showStatus(formatVisionStatus(runtime.ctx.session));
|
|
707
|
+
runtime.ctx.editor.setText("");
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (arg === "on" || arg === "off" || arg === "auto") {
|
|
711
|
+
runtime.ctx.showStatus(await applyVisionMode(runtime.ctx.session, arg));
|
|
712
|
+
runtime.ctx.editor.setText("");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
runtime.ctx.showStatus("Usage: /vision [on|off|auto|status]");
|
|
716
|
+
runtime.ctx.editor.setText("");
|
|
717
|
+
},
|
|
718
|
+
},
|
|
650
719
|
{
|
|
651
720
|
name: "prewalk",
|
|
652
721
|
description: "Switch to a fast/cheap model at the next action (works even without --prewalk)",
|
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,
|
package/src/task/index.ts
CHANGED
|
@@ -156,27 +156,31 @@ export function formatResultOutputFallback(result: Pick<SingleResult, "output" |
|
|
|
156
156
|
return result.requests > 0 ? `(no output) after ${result.requests} req` : "(no output)";
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
159
|
+
interface TaskDescriptionOptions {
|
|
160
|
+
agents: AgentDefinition[];
|
|
161
|
+
isolationEnabled: boolean;
|
|
162
|
+
applyIsolatedChanges: boolean;
|
|
163
|
+
disabledAgents: string[];
|
|
164
|
+
batchEnabled: boolean;
|
|
165
|
+
effortEnabled: boolean;
|
|
166
|
+
asyncEnabled: boolean;
|
|
167
|
+
ircEnabled: boolean;
|
|
168
|
+
parentSpawns: string;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Render the tool description from a cached agent list and current settings. */
|
|
172
|
+
function renderDescription(options: TaskDescriptionOptions): string {
|
|
173
|
+
const spawnPolicy = resolveSpawnPolicy(options.parentSpawns);
|
|
173
174
|
const spawningDisabled = !spawnPolicy.enabled;
|
|
174
|
-
let filteredAgents =
|
|
175
|
+
let filteredAgents =
|
|
176
|
+
options.disabledAgents.length > 0
|
|
177
|
+
? options.agents.filter(agent => !options.disabledAgents.includes(agent.name))
|
|
178
|
+
: options.agents;
|
|
175
179
|
if (spawningDisabled) {
|
|
176
180
|
filteredAgents = [];
|
|
177
181
|
} else if (spawnPolicy.allowedAgents !== null) {
|
|
178
182
|
const allowed = new Set(spawnPolicy.allowedAgents);
|
|
179
|
-
filteredAgents = filteredAgents.filter(
|
|
183
|
+
filteredAgents = filteredAgents.filter(agent => allowed.has(agent.name));
|
|
180
184
|
}
|
|
181
185
|
const renderedAgents = filteredAgents.map(agent => ({
|
|
182
186
|
name: agent.name,
|
|
@@ -188,12 +192,13 @@ function renderDescription(
|
|
|
188
192
|
agents: renderedAgents,
|
|
189
193
|
spawningDisabled,
|
|
190
194
|
defaultAgent: spawnPolicy.defaultAgent,
|
|
191
|
-
isolationEnabled,
|
|
192
|
-
applyIsolatedChanges,
|
|
193
|
-
batchEnabled,
|
|
194
|
-
|
|
195
|
+
isolationEnabled: options.isolationEnabled,
|
|
196
|
+
applyIsolatedChanges: options.applyIsolatedChanges,
|
|
197
|
+
batchEnabled: options.batchEnabled,
|
|
198
|
+
effortEnabled: options.effortEnabled,
|
|
199
|
+
asyncEnabled: options.asyncEnabled,
|
|
195
200
|
hasBlockingAgents: renderedAgents.some(agent => agent.blocking),
|
|
196
|
-
ircEnabled,
|
|
201
|
+
ircEnabled: options.ircEnabled,
|
|
197
202
|
});
|
|
198
203
|
}
|
|
199
204
|
|
|
@@ -581,28 +586,34 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
|
|
|
581
586
|
const planMode = this.session.getPlanModeState?.()?.enabled === true;
|
|
582
587
|
const isolationEnabled = !planMode && this.session.settings.get("task.isolation.mode") !== "none";
|
|
583
588
|
const defaultAgent = resolveSpawnPolicy(this.session.getSessionSpawns()).defaultAgent;
|
|
584
|
-
return getTaskSchema({
|
|
589
|
+
return getTaskSchema({
|
|
590
|
+
isolationEnabled,
|
|
591
|
+
batchEnabled: this.#isBatchEnabled(),
|
|
592
|
+
effortEnabled: this.session.settings.get("task.enableEffort"),
|
|
593
|
+
defaultAgent,
|
|
594
|
+
});
|
|
585
595
|
}
|
|
586
596
|
|
|
587
597
|
renderCall(args: unknown, options: Parameters<typeof renderTaskCall>[1], theme: Theme) {
|
|
588
598
|
return renderTaskCall(repairTaskParams(args as TaskParams), options, theme);
|
|
589
599
|
}
|
|
590
600
|
|
|
591
|
-
/** Dynamic description that reflects current
|
|
601
|
+
/** Dynamic description that reflects current task settings. */
|
|
592
602
|
get description(): string {
|
|
593
603
|
const disabledAgents = this.session.settings.get("task.disabledAgents") as string[];
|
|
594
604
|
const planMode = this.session.getPlanModeState?.()?.enabled === true;
|
|
595
605
|
const isolationMode = this.session.settings.get("task.isolation.mode");
|
|
596
|
-
return renderDescription(
|
|
597
|
-
this.#discoveredAgents,
|
|
598
|
-
!planMode && isolationMode !== "none",
|
|
599
|
-
this.session.settings.get("task.isolation.apply"),
|
|
606
|
+
return renderDescription({
|
|
607
|
+
agents: this.#discoveredAgents,
|
|
608
|
+
isolationEnabled: !planMode && isolationMode !== "none",
|
|
609
|
+
applyIsolatedChanges: this.session.settings.get("task.isolation.apply"),
|
|
600
610
|
disabledAgents,
|
|
601
|
-
this.#isBatchEnabled(),
|
|
602
|
-
this.session.settings.get("
|
|
603
|
-
|
|
604
|
-
this.session.
|
|
605
|
-
|
|
611
|
+
batchEnabled: this.#isBatchEnabled(),
|
|
612
|
+
effortEnabled: this.session.settings.get("task.enableEffort"),
|
|
613
|
+
asyncEnabled: this.session.settings.get("async.enabled"),
|
|
614
|
+
ircEnabled: isIrcEnabled(this.session.settings, this.session.taskDepth ?? 0),
|
|
615
|
+
parentSpawns: this.session.getSessionSpawns() ?? "*",
|
|
616
|
+
});
|
|
606
617
|
}
|
|
607
618
|
private constructor(
|
|
608
619
|
private readonly session: ToolSession,
|
package/src/task/types.ts
CHANGED
|
@@ -115,7 +115,6 @@ export const taskItemSchema = type({
|
|
|
115
115
|
"name?": "string",
|
|
116
116
|
agent: "string = 'task'",
|
|
117
117
|
task: "string",
|
|
118
|
-
"effort?": effortRule,
|
|
119
118
|
"outputSchema?": outputSchemaInputSchema,
|
|
120
119
|
"schemaMode?": '"permissive" | "strict"',
|
|
121
120
|
"+": "delete",
|
|
@@ -124,7 +123,6 @@ const taskItemSchemaIsolated = type({
|
|
|
124
123
|
"name?": "string",
|
|
125
124
|
agent: "string = 'task'",
|
|
126
125
|
task: "string",
|
|
127
|
-
"effort?": effortRule,
|
|
128
126
|
"outputSchema?": outputSchemaInputSchema,
|
|
129
127
|
"schemaMode?": '"permissive" | "strict"',
|
|
130
128
|
"isolated?": "boolean",
|
|
@@ -153,7 +151,6 @@ export const taskSchema = type({
|
|
|
153
151
|
"name?": "string",
|
|
154
152
|
agent: "string = 'task'",
|
|
155
153
|
task: "string",
|
|
156
|
-
"effort?": effortRule,
|
|
157
154
|
"outputSchema?": outputSchemaInputSchema,
|
|
158
155
|
"schemaMode?": '"permissive" | "strict"',
|
|
159
156
|
"isolated?": "boolean",
|
|
@@ -163,7 +160,6 @@ const taskSchemaNoIsolation = type({
|
|
|
163
160
|
"name?": "string",
|
|
164
161
|
agent: "string = 'task'",
|
|
165
162
|
task: "string",
|
|
166
|
-
"effort?": effortRule,
|
|
167
163
|
"outputSchema?": outputSchemaInputSchema,
|
|
168
164
|
"schemaMode?": '"permissive" | "strict"',
|
|
169
165
|
"+": "delete",
|
|
@@ -200,15 +196,17 @@ function createTaskSchema(options: {
|
|
|
200
196
|
isolationEnabled: boolean;
|
|
201
197
|
batchEnabled: boolean;
|
|
202
198
|
defaultAgent: string;
|
|
199
|
+
effortEnabled: boolean;
|
|
203
200
|
}): BaseType {
|
|
204
201
|
const agent = taskAgentSchemaRule(options.defaultAgent);
|
|
202
|
+
const effortField = options.effortEnabled ? { "effort?": effortRule } : {};
|
|
205
203
|
if (options.batchEnabled) {
|
|
206
204
|
if (options.isolationEnabled) {
|
|
207
205
|
const item = type.raw({
|
|
208
206
|
"name?": "string",
|
|
209
207
|
agent,
|
|
210
208
|
task: "string",
|
|
211
|
-
|
|
209
|
+
...effortField,
|
|
212
210
|
"outputSchema?": outputSchemaInputSchema,
|
|
213
211
|
"schemaMode?": '"permissive" | "strict"',
|
|
214
212
|
"isolated?": "boolean",
|
|
@@ -224,7 +222,7 @@ function createTaskSchema(options: {
|
|
|
224
222
|
"name?": "string",
|
|
225
223
|
agent,
|
|
226
224
|
task: "string",
|
|
227
|
-
|
|
225
|
+
...effortField,
|
|
228
226
|
"outputSchema?": outputSchemaInputSchema,
|
|
229
227
|
"schemaMode?": '"permissive" | "strict"',
|
|
230
228
|
"+": "delete",
|
|
@@ -240,7 +238,7 @@ function createTaskSchema(options: {
|
|
|
240
238
|
"name?": "string",
|
|
241
239
|
agent,
|
|
242
240
|
task: "string",
|
|
243
|
-
|
|
241
|
+
...effortField,
|
|
244
242
|
"outputSchema?": outputSchemaInputSchema,
|
|
245
243
|
"schemaMode?": '"permissive" | "strict"',
|
|
246
244
|
"isolated?": "boolean",
|
|
@@ -251,33 +249,30 @@ function createTaskSchema(options: {
|
|
|
251
249
|
"name?": "string",
|
|
252
250
|
agent,
|
|
253
251
|
task: "string",
|
|
254
|
-
|
|
252
|
+
...effortField,
|
|
255
253
|
"outputSchema?": outputSchemaInputSchema,
|
|
256
254
|
"schemaMode?": '"permissive" | "strict"',
|
|
257
255
|
"+": "delete",
|
|
258
256
|
});
|
|
259
257
|
}
|
|
260
258
|
|
|
261
|
-
|
|
262
|
-
export function getTaskSchema(options: {
|
|
263
|
-
isolationEnabled: boolean;
|
|
264
|
-
batchEnabled: boolean;
|
|
265
|
-
defaultAgent: string;
|
|
266
|
-
}): TaskToolSchemaInstance;
|
|
259
|
+
/** Build the task wire schema for the current settings and spawn policy. */
|
|
267
260
|
export function getTaskSchema(options: {
|
|
268
261
|
isolationEnabled: boolean;
|
|
269
262
|
batchEnabled: boolean;
|
|
263
|
+
effortEnabled?: boolean;
|
|
270
264
|
defaultAgent?: string;
|
|
271
265
|
}): TaskToolSchemaInstance {
|
|
272
266
|
const defaultAgent = options.defaultAgent ?? "task";
|
|
273
|
-
|
|
267
|
+
const effortEnabled = options.effortEnabled ?? false;
|
|
268
|
+
if (defaultAgent === "task" && !effortEnabled) {
|
|
274
269
|
if (options.batchEnabled) return options.isolationEnabled ? taskSchemaBatch : taskSchemaBatchNoIsolation;
|
|
275
270
|
return options.isolationEnabled ? taskSchema : taskSchemaNoIsolation;
|
|
276
271
|
}
|
|
277
|
-
const key = `${options.isolationEnabled ? "iso" : "flat"}:${options.batchEnabled ? "batch" : "single"}:${defaultAgent}`;
|
|
272
|
+
const key = `${options.isolationEnabled ? "iso" : "flat"}:${options.batchEnabled ? "batch" : "single"}:${effortEnabled ? "effort" : "default"}:${defaultAgent}`;
|
|
278
273
|
const cached = taskSchemaCache.get(key);
|
|
279
274
|
if (cached) return cached;
|
|
280
|
-
const schema = createTaskSchema({ ...options, defaultAgent });
|
|
275
|
+
const schema = createTaskSchema({ ...options, effortEnabled, defaultAgent });
|
|
281
276
|
taskSchemaCache.set(key, schema);
|
|
282
277
|
return schema;
|
|
283
278
|
}
|
package/src/thinking.ts
CHANGED
|
@@ -271,19 +271,82 @@ export type TaskEffort = (typeof TASK_EFFORTS)[number];
|
|
|
271
271
|
* at — high, xhigh, or max), `med` = the middle (lower of the two middles for
|
|
272
272
|
* an even-sized range). Without a model, maps over the full canonical range.
|
|
273
273
|
* Returns `undefined` when the model has no controllable effort surface, so
|
|
274
|
-
* callers fall back to their default selector (e.g. `auto`).
|
|
274
|
+
* callers fall back to their default selector (e.g. `auto`). Throws when the
|
|
275
|
+
* configured ceiling is below the model's lowest supported effort.
|
|
275
276
|
*/
|
|
276
|
-
export function resolveTaskEffortLevel(
|
|
277
|
+
export function resolveTaskEffortLevel(
|
|
278
|
+
model: Model | undefined,
|
|
279
|
+
effort: TaskEffort,
|
|
280
|
+
maxEffort?: Effort,
|
|
281
|
+
): Effort | undefined {
|
|
277
282
|
const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS;
|
|
278
283
|
if (supported.length === 0) return undefined;
|
|
284
|
+
let resolved: Effort;
|
|
279
285
|
switch (effort) {
|
|
280
286
|
case "lo":
|
|
281
|
-
|
|
287
|
+
resolved = supported[0];
|
|
288
|
+
break;
|
|
282
289
|
case "med":
|
|
283
|
-
|
|
290
|
+
resolved = supported[(supported.length - 1) >> 1];
|
|
291
|
+
break;
|
|
284
292
|
case "hi":
|
|
285
|
-
|
|
293
|
+
resolved = supported[supported.length - 1];
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
if (maxEffort === undefined) return resolved;
|
|
297
|
+
const maxIndex = THINKING_EFFORTS.indexOf(maxEffort);
|
|
298
|
+
const ceiling = supported.findLast(candidate => THINKING_EFFORTS.indexOf(candidate) <= maxIndex);
|
|
299
|
+
if (ceiling === undefined) {
|
|
300
|
+
const modelName = model ? `${model.provider}/${model.id}` : "Selected model";
|
|
301
|
+
throw new RangeError(`${modelName} has no supported thinking effort at or below task.maxEffort=${maxEffort}`);
|
|
286
302
|
}
|
|
303
|
+
return THINKING_EFFORTS.indexOf(resolved) > THINKING_EFFORTS.indexOf(ceiling) ? ceiling : resolved;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Clamps a concrete thinking selector to a per-session effort ceiling (e.g. a
|
|
308
|
+
* task spawn's `task.maxEffort`-capped effort hint). `off`/`inherit`/
|
|
309
|
+
* `undefined` pass through, as do levels already at or below the ceiling.
|
|
310
|
+
* Levels above it snap to the highest model-supported effort at or below the
|
|
311
|
+
* ceiling. A model whose floor exceeds the ceiling has nothing valid to snap
|
|
312
|
+
* to; the requested level is returned unchanged and the caller is responsible
|
|
313
|
+
* for rejecting or skipping such models (see {@link modelSupportsEffortCeiling}).
|
|
314
|
+
*/
|
|
315
|
+
export function clampThinkingLevelToCeiling(
|
|
316
|
+
model: Model | undefined,
|
|
317
|
+
level: Effort | undefined,
|
|
318
|
+
ceiling: Effort | undefined,
|
|
319
|
+
): Effort | undefined;
|
|
320
|
+
export function clampThinkingLevelToCeiling(
|
|
321
|
+
model: Model | undefined,
|
|
322
|
+
level: ThinkingLevel | undefined,
|
|
323
|
+
ceiling: Effort | undefined,
|
|
324
|
+
): ThinkingLevel | undefined;
|
|
325
|
+
export function clampThinkingLevelToCeiling(
|
|
326
|
+
model: Model | undefined,
|
|
327
|
+
level: ThinkingLevel | undefined,
|
|
328
|
+
ceiling: Effort | undefined,
|
|
329
|
+
): ThinkingLevel | undefined {
|
|
330
|
+
if (ceiling === undefined || level === undefined || level === ThinkingLevel.Off || level === ThinkingLevel.Inherit) {
|
|
331
|
+
return level;
|
|
332
|
+
}
|
|
333
|
+
const maxIndex = THINKING_EFFORTS.indexOf(ceiling);
|
|
334
|
+
if (THINKING_EFFORTS.indexOf(level) <= maxIndex) return level;
|
|
335
|
+
const supported = model ? getSupportedEfforts(model) : THINKING_EFFORTS;
|
|
336
|
+
return supported.findLast(candidate => THINKING_EFFORTS.indexOf(candidate) <= maxIndex) ?? level;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* True when `model` can honor a thinking-effort ceiling: it either has no
|
|
341
|
+
* controllable effort surface (nothing to cap) or supports at least one effort
|
|
342
|
+
* at or below the ceiling. Retry-fallback candidate filtering uses this to
|
|
343
|
+
* skip models whose floor would force the session above the ceiling.
|
|
344
|
+
*/
|
|
345
|
+
export function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boolean {
|
|
346
|
+
const supported = getSupportedEfforts(model);
|
|
347
|
+
if (supported.length === 0) return true;
|
|
348
|
+
const maxIndex = THINKING_EFFORTS.indexOf(ceiling);
|
|
349
|
+
return supported.some(candidate => THINKING_EFFORTS.indexOf(candidate) <= maxIndex);
|
|
287
350
|
}
|
|
288
351
|
|
|
289
352
|
/**
|