@oh-my-pi/pi-coding-agent 17.1.0 → 17.1.2
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 +55 -0
- package/dist/cli.js +6325 -4420
- package/dist/types/cli/bench-cli.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +6 -3
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +3 -0
- package/dist/types/launch/broker-list-order.test.d.ts +1 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +10 -2
- package/dist/types/live/protocol.d.ts +1 -1
- package/dist/types/live/transport.d.ts +6 -19
- package/dist/types/live/visualizer.d.ts +8 -11
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -6
- package/dist/types/modes/types.d.ts +9 -5
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -6
- package/dist/types/session/agent-session-types.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +20 -1
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/eval-runner.d.ts +2 -0
- package/dist/types/session/messages.d.ts +2 -0
- package/dist/types/session/session-tools.d.ts +15 -0
- package/dist/types/session/streaming-output.d.ts +8 -0
- package/dist/types/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -0
- package/dist/types/task/executor.d.ts +3 -1
- package/dist/types/task/structured-subagent.d.ts +3 -0
- package/dist/types/task/types.d.ts +11 -11
- package/dist/types/thinking.d.ts +13 -0
- package/dist/types/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- package/dist/types/tools/eval-format/index.d.ts +7 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/julia.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval-format/ruby.d.ts +2 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +7 -0
- package/dist/types/tools/todo.d.ts +4 -1
- package/dist/types/tts/streaming-player.d.ts +10 -43
- package/dist/types/utils/tools-manager.d.ts +1 -2
- package/dist/types/web/search/providers/base.d.ts +16 -0
- package/dist/types/web/search/providers/brave.d.ts +2 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +2 -0
- package/dist/types/web/search/providers/gemini.d.ts +3 -0
- package/dist/types/web/search/providers/jina.d.ts +2 -0
- package/dist/types/web/search/providers/kagi.d.ts +2 -0
- package/dist/types/web/search/providers/kimi.d.ts +2 -0
- package/dist/types/web/search/providers/parallel.d.ts +2 -0
- package/dist/types/web/search/providers/perplexity.d.ts +3 -0
- package/dist/types/web/search/providers/searxng.d.ts +10 -0
- package/dist/types/web/search/providers/tavily.d.ts +8 -0
- package/dist/types/web/search/query.d.ts +190 -0
- package/package.json +12 -12
- package/src/cli/args.ts +1 -0
- package/src/cli/bench-cli.ts +12 -1
- package/src/cli/setup-cli.ts +2 -14
- package/src/cli/web-search-cli.ts +7 -0
- package/src/cli.ts +8 -0
- package/src/config/model-registry.ts +6 -0
- package/src/config/model-resolver.ts +17 -6
- package/src/config/settings-schema.ts +66 -0
- package/src/debug/raw-sse-buffer.ts +157 -30
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/exec/bash-executor.ts +1 -0
- package/src/export/share.ts +4 -3
- package/src/extensibility/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/launch/broker-list-order.test.ts +89 -0
- package/src/launch/broker.ts +49 -8
- package/src/live/attestation.ts +91 -0
- package/src/live/controller.ts +76 -23
- package/src/live/protocol.test.ts +3 -3
- package/src/live/protocol.ts +1 -1
- package/src/live/transport.ts +72 -140
- package/src/live/visualizer.ts +114 -134
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/tool-execution.ts +17 -110
- package/src/modes/controllers/input-controller.ts +55 -47
- package/src/modes/controllers/live-command-controller.ts +82 -5
- package/src/modes/controllers/selector-controller.ts +62 -0
- package/src/modes/interactive-mode.ts +79 -5
- package/src/modes/types.ts +10 -4
- package/src/modes/utils/ui-helpers.ts +7 -8
- package/src/prompts/system/computer-safety.md +14 -0
- package/src/prompts/system/resolve-device-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/computer.md +26 -0
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/task.md +5 -2
- package/src/prompts/tools/web-search.md +2 -0
- package/src/sdk.ts +13 -4
- package/src/session/agent-session-types.ts +9 -0
- package/src/session/agent-session.ts +70 -0
- package/src/session/auth-storage.ts +1 -0
- package/src/session/eval-runner.ts +5 -0
- package/src/session/messages.ts +3 -0
- package/src/session/queued-messages.ts +7 -1
- package/src/session/session-tools.ts +37 -0
- package/src/session/streaming-output.ts +52 -5
- package/src/slash-commands/builtin-registry.ts +165 -9
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +4 -0
- package/src/task/executor.ts +17 -9
- package/src/task/index.ts +14 -34
- package/src/task/structured-subagent.ts +4 -0
- package/src/task/types.ts +15 -13
- package/src/thinking.ts +27 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/ast-edit.ts +4 -1
- package/src/tools/bash-interactive.ts +90 -86
- package/src/tools/bash.ts +13 -0
- package/src/tools/browser/render.ts +2 -1
- package/src/tools/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/eval-format/index.ts +24 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/julia.ts +446 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval-format/ruby.ts +380 -0
- package/src/tools/eval-render.ts +12 -6
- package/src/tools/index.ts +5 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +10 -1
- package/src/tools/todo.ts +58 -6
- package/src/tools/xdev.ts +54 -26
- package/src/tts/streaming-player.ts +81 -340
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- package/src/web/search/index.ts +28 -5
- package/src/web/search/providers/anthropic.ts +62 -4
- package/src/web/search/providers/base.ts +16 -0
- package/src/web/search/providers/brave.ts +30 -3
- package/src/web/search/providers/codex.ts +14 -1
- package/src/web/search/providers/duckduckgo.ts +23 -1
- package/src/web/search/providers/ecosia.ts +5 -1
- package/src/web/search/providers/exa.ts +28 -1
- package/src/web/search/providers/firecrawl.ts +37 -3
- package/src/web/search/providers/gemini.ts +12 -2
- package/src/web/search/providers/google.ts +2 -1
- package/src/web/search/providers/jina.ts +31 -8
- package/src/web/search/providers/kagi.ts +10 -1
- package/src/web/search/providers/kimi.ts +16 -1
- package/src/web/search/providers/mojeek.ts +14 -1
- package/src/web/search/providers/parallel.ts +48 -2
- package/src/web/search/providers/perplexity.ts +94 -6
- package/src/web/search/providers/searxng.ts +145 -9
- package/src/web/search/providers/startpage.ts +8 -2
- package/src/web/search/providers/synthetic.ts +7 -1
- package/src/web/search/providers/tavily.ts +52 -3
- package/src/web/search/providers/tinyfish.ts +6 -1
- package/src/web/search/providers/xai.ts +49 -2
- package/src/web/search/providers/zai.ts +19 -1
- package/src/web/search/query.ts +850 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/live/audio-worklet.txt +0 -59
- package/src/live/browser-runtime.txt +0 -221
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
|
@@ -33,6 +33,7 @@ import { theme } from "../modes/theme/theme";
|
|
|
33
33
|
import type { InteractiveModeContext } from "../modes/types";
|
|
34
34
|
import { extractLastCodeBlock, extractLastCommand } from "../modes/utils/copy-targets";
|
|
35
35
|
import type { AgentSession, FreshSessionResult } from "../session/agent-session";
|
|
36
|
+
import type { SessionOAuthAccountList } from "../session/agent-session-types";
|
|
36
37
|
import { COMPACT_MODES, parseCompactArgs } from "../session/compact-modes";
|
|
37
38
|
import { resolveResumableSession } from "../session/session-listing";
|
|
38
39
|
import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
|
|
@@ -52,6 +53,7 @@ import { createMarketplaceManager } from "./helpers/marketplace-manager";
|
|
|
52
53
|
import { handleMcpAcp } from "./helpers/mcp";
|
|
53
54
|
import { commandConsumed, errorMessage, parseSlashCommand, parseSubcommand, usage } from "./helpers/parse";
|
|
54
55
|
import { describeRedeemOutcome, type ResetUsageAccount, toResetUsageAccounts } from "./helpers/reset-usage";
|
|
56
|
+
import { matchSessionPinAccounts, toSessionPinAccounts } from "./helpers/session-pin";
|
|
55
57
|
import { handleSshAcp } from "./helpers/ssh";
|
|
56
58
|
import { launchStatsDashboard, parseStatsDashboardArgs } from "./helpers/stats-dashboard";
|
|
57
59
|
import { handleTodoAcp } from "./helpers/todo";
|
|
@@ -88,6 +90,26 @@ function formatFastModeStatus(session: AgentSession): string {
|
|
|
88
90
|
return session.isFastModeEnabled() ? "on" : "off";
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/** `/computer status` label for the session-effective `computer.enabled` value. */
|
|
94
|
+
function formatComputerUseStatus(session: AgentSession): string {
|
|
95
|
+
return session.settings.get("computer.enabled") ? "on" : "off";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Apply a session-scoped computer-use toggle: flip the active tool slate first
|
|
100
|
+
* (so a failed enable never leaves a stale settings override), then record the
|
|
101
|
+
* runtime override — never `settings.set`, which would persist to settings.json.
|
|
102
|
+
* Returns the operator feedback line.
|
|
103
|
+
*/
|
|
104
|
+
async function applyComputerUseToggle(session: AgentSession, enable: boolean): Promise<string> {
|
|
105
|
+
const applied = await session.setComputerToolEnabled(enable);
|
|
106
|
+
if (enable && !applied) {
|
|
107
|
+
return "Computer use is unavailable in this session.";
|
|
108
|
+
}
|
|
109
|
+
session.settings.override("computer.enabled", enable);
|
|
110
|
+
return `Computer use ${enable ? "enabled" : "disabled"} for this session.`;
|
|
111
|
+
}
|
|
112
|
+
|
|
91
113
|
const AUTOCOMPLETE_DETAIL_LIMIT = 48;
|
|
92
114
|
|
|
93
115
|
function shortDetail(value: string, limit = AUTOCOMPLETE_DETAIL_LIMIT): string {
|
|
@@ -196,6 +218,74 @@ async function handleUsageResetCommand(
|
|
|
196
218
|
await output(describeRedeemOutcome(outcome, target.label));
|
|
197
219
|
}
|
|
198
220
|
|
|
221
|
+
async function handleSessionPinCommand(
|
|
222
|
+
arg: string,
|
|
223
|
+
session: AgentSession,
|
|
224
|
+
output: SlashCommandRuntime["output"],
|
|
225
|
+
): Promise<void> {
|
|
226
|
+
if (session.isStreaming) {
|
|
227
|
+
await output("Cannot pin an account while the session is streaming.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
let accountList: SessionOAuthAccountList | undefined;
|
|
231
|
+
try {
|
|
232
|
+
accountList = await session.listCurrentProviderOAuthAccounts();
|
|
233
|
+
} catch (error) {
|
|
234
|
+
await output(`Could not load provider accounts: ${errorMessage(error)}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
if (!accountList) {
|
|
238
|
+
await output("Select a model before pinning a provider account.");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const provider = getOAuthProviders().find(candidate => candidate.id === accountList.provider);
|
|
242
|
+
const providerName = provider?.name ?? accountList.provider;
|
|
243
|
+
const accounts = toSessionPinAccounts(accountList.accounts);
|
|
244
|
+
if (accounts.length === 0) {
|
|
245
|
+
const source = session.modelRegistry.authStorage.describeCredentialSource(
|
|
246
|
+
accountList.provider,
|
|
247
|
+
session.sessionId,
|
|
248
|
+
);
|
|
249
|
+
await output(
|
|
250
|
+
source
|
|
251
|
+
? `No stored OAuth accounts for ${providerName}. Current auth comes from ${source}.`
|
|
252
|
+
: `No stored OAuth accounts for ${providerName}. Use /login to add one.`,
|
|
253
|
+
);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const selector = arg.trim();
|
|
258
|
+
if (!selector) {
|
|
259
|
+
const lines = [`OAuth accounts for ${providerName}:`];
|
|
260
|
+
for (const account of accounts) {
|
|
261
|
+
lines.push(`${account.position + 1}. ${account.label}${account.active ? " (active)" : ""}`);
|
|
262
|
+
}
|
|
263
|
+
lines.push("", "Pin one with `/session pin <number|email|account id>`.");
|
|
264
|
+
await output(lines.join("\n"));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const matches = matchSessionPinAccounts(accounts, selector);
|
|
269
|
+
if (matches.length === 0) {
|
|
270
|
+
await output(`No ${providerName} account matches "${selector}".`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (matches.length > 1) {
|
|
274
|
+
await output(
|
|
275
|
+
`"${selector}" matches multiple ${providerName} accounts: ${matches
|
|
276
|
+
.map(account => `${account.position + 1}. ${account.label}`)
|
|
277
|
+
.join(", ")}. Use the account number.`,
|
|
278
|
+
);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const account = matches[0];
|
|
282
|
+
if (!account || !session.pinCurrentProviderOAuthAccount(account.credentialId)) {
|
|
283
|
+
await output(`${account?.label ?? selector} is no longer available to pin.`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
await output(`Pinned ${account.label} to this session for ${providerName}.`);
|
|
287
|
+
}
|
|
288
|
+
|
|
199
289
|
/** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
|
|
200
290
|
function parseShakeMode(args: string): ShakeMode | { error: string } {
|
|
201
291
|
const verb = args.trim().toLowerCase();
|
|
@@ -472,6 +562,49 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
472
562
|
runtime.ctx.editor.setText("");
|
|
473
563
|
},
|
|
474
564
|
},
|
|
565
|
+
{
|
|
566
|
+
name: "computer",
|
|
567
|
+
description: "Toggle the native computer-use tool for this session",
|
|
568
|
+
acpDescription: "Toggle computer use",
|
|
569
|
+
acpInputHint: "[on|off|status]",
|
|
570
|
+
subcommands: [
|
|
571
|
+
{ name: "on", description: "Enable computer use for this session" },
|
|
572
|
+
{ name: "off", description: "Disable computer use for this session" },
|
|
573
|
+
{ name: "status", description: "Show computer use status" },
|
|
574
|
+
],
|
|
575
|
+
allowArgs: true,
|
|
576
|
+
getTuiAutocompleteDescription: runtime => `Computer: ${formatComputerUseStatus(runtime.ctx.session)}`,
|
|
577
|
+
handle: async (command, runtime) => {
|
|
578
|
+
const arg = command.args.trim().toLowerCase();
|
|
579
|
+
if (arg === "status") {
|
|
580
|
+
await runtime.output(`Computer use is ${formatComputerUseStatus(runtime.session)}.`);
|
|
581
|
+
return commandConsumed();
|
|
582
|
+
}
|
|
583
|
+
if (!arg || arg === "toggle" || arg === "on" || arg === "off") {
|
|
584
|
+
const enable = arg === "off" ? false : arg === "on" || !runtime.session.settings.get("computer.enabled");
|
|
585
|
+
await runtime.output(await applyComputerUseToggle(runtime.session, enable));
|
|
586
|
+
return commandConsumed();
|
|
587
|
+
}
|
|
588
|
+
return usage("Usage: /computer [on|off|status]", runtime);
|
|
589
|
+
},
|
|
590
|
+
handleTui: async (command, runtime) => {
|
|
591
|
+
const arg = command.args.trim().toLowerCase();
|
|
592
|
+
if (arg === "status") {
|
|
593
|
+
runtime.ctx.showStatus(`Computer use is ${formatComputerUseStatus(runtime.ctx.session)}.`);
|
|
594
|
+
runtime.ctx.editor.setText("");
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!arg || arg === "toggle" || arg === "on" || arg === "off") {
|
|
598
|
+
const enable =
|
|
599
|
+
arg === "off" ? false : arg === "on" || !runtime.ctx.session.settings.get("computer.enabled");
|
|
600
|
+
runtime.ctx.showStatus(await applyComputerUseToggle(runtime.ctx.session, enable));
|
|
601
|
+
runtime.ctx.editor.setText("");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
runtime.ctx.showStatus("Usage: /computer [on|off|status]");
|
|
605
|
+
runtime.ctx.editor.setText("");
|
|
606
|
+
},
|
|
607
|
+
},
|
|
475
608
|
{
|
|
476
609
|
name: "prewalk",
|
|
477
610
|
description: "Switch to a fast/cheap model at the next action (works even without --prewalk)",
|
|
@@ -981,15 +1114,21 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
981
1114
|
{
|
|
982
1115
|
name: "session",
|
|
983
1116
|
description: "Session management commands",
|
|
984
|
-
acpDescription: "Show session
|
|
985
|
-
acpInputHint: "info|delete",
|
|
1117
|
+
acpDescription: "Show or configure the current session",
|
|
1118
|
+
acpInputHint: "[info|delete|pin [account]]",
|
|
986
1119
|
subcommands: [
|
|
987
1120
|
{ name: "info", description: "Show session info and stats" },
|
|
988
1121
|
{ name: "delete", description: "Delete current session and return to selector" },
|
|
1122
|
+
{
|
|
1123
|
+
name: "pin",
|
|
1124
|
+
description: "Pin the current provider to a stored OAuth account",
|
|
1125
|
+
usage: "[account]",
|
|
1126
|
+
},
|
|
989
1127
|
],
|
|
990
1128
|
allowArgs: true,
|
|
991
1129
|
handle: async (command, runtime) => {
|
|
992
|
-
|
|
1130
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
1131
|
+
if (!verb || (verb === "info" && !rest)) {
|
|
993
1132
|
await runtime.output(
|
|
994
1133
|
[
|
|
995
1134
|
`Session: ${runtime.session.sessionId}`,
|
|
@@ -999,7 +1138,7 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
999
1138
|
);
|
|
1000
1139
|
return commandConsumed();
|
|
1001
1140
|
}
|
|
1002
|
-
if (
|
|
1141
|
+
if (verb === "delete" && !rest) {
|
|
1003
1142
|
if (runtime.session.isStreaming) return usage("Cannot delete the session while streaming.", runtime);
|
|
1004
1143
|
const sessionFile = runtime.sessionManager.getSessionFile();
|
|
1005
1144
|
if (!sessionFile) return usage("No session file to delete (in-memory session).", runtime);
|
|
@@ -1018,17 +1157,34 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
1018
1157
|
);
|
|
1019
1158
|
return commandConsumed();
|
|
1020
1159
|
}
|
|
1021
|
-
|
|
1160
|
+
if (verb === "pin") {
|
|
1161
|
+
await handleSessionPinCommand(rest, runtime.session, runtime.output);
|
|
1162
|
+
return commandConsumed();
|
|
1163
|
+
}
|
|
1164
|
+
return usage("Usage: /session [info|delete|pin [account]]", runtime);
|
|
1022
1165
|
},
|
|
1023
1166
|
handleTui: async (command, runtime) => {
|
|
1024
|
-
const
|
|
1025
|
-
if (
|
|
1167
|
+
const { verb, rest } = parseSubcommand(command.args);
|
|
1168
|
+
if (verb === "delete" && !rest) {
|
|
1026
1169
|
runtime.ctx.editor.setText("");
|
|
1027
1170
|
await runtime.ctx.handleSessionDeleteCommand();
|
|
1028
1171
|
return;
|
|
1029
1172
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1173
|
+
if (verb === "pin") {
|
|
1174
|
+
if (rest) {
|
|
1175
|
+
await handleSessionPinCommand(rest, runtime.ctx.session, text => runtime.ctx.showStatus(text));
|
|
1176
|
+
refreshStatusLine(runtime.ctx);
|
|
1177
|
+
} else {
|
|
1178
|
+
await runtime.ctx.showSessionPinSelector();
|
|
1179
|
+
}
|
|
1180
|
+
runtime.ctx.editor.setText("");
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
if (!verb || (verb === "info" && !rest)) {
|
|
1184
|
+
await runtime.ctx.handleSessionCommand();
|
|
1185
|
+
} else {
|
|
1186
|
+
runtime.ctx.showStatus("Usage: /session [info|delete|pin [account]]");
|
|
1187
|
+
}
|
|
1032
1188
|
runtime.ctx.editor.setText("");
|
|
1033
1189
|
},
|
|
1034
1190
|
},
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { OAuthAccountSummary } from "../../session/auth-storage";
|
|
2
|
+
import { formatActiveAccountLabel } from "./active-oauth-account";
|
|
3
|
+
|
|
4
|
+
/** Stored OAuth account rendered and matched by `/session pin`. */
|
|
5
|
+
export interface SessionPinAccount extends OAuthAccountSummary {
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Add stable user-facing labels to provider account summaries. */
|
|
10
|
+
export function toSessionPinAccounts(accounts: readonly OAuthAccountSummary[]): SessionPinAccount[] {
|
|
11
|
+
return accounts.map(account => {
|
|
12
|
+
const enterpriseUrl = account.enterpriseUrl?.trim();
|
|
13
|
+
return {
|
|
14
|
+
...account,
|
|
15
|
+
label: (formatActiveAccountLabel(account) ?? enterpriseUrl) || `OAuth credential #${account.credentialId}`,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Match a `/session pin` selector by 1-based position or exact account identity. */
|
|
21
|
+
export function matchSessionPinAccounts(accounts: readonly SessionPinAccount[], selector: string): SessionPinAccount[] {
|
|
22
|
+
const wanted = selector.trim().toLowerCase();
|
|
23
|
+
if (!wanted) return [];
|
|
24
|
+
if (wanted === "active") return accounts.filter(account => account.active);
|
|
25
|
+
|
|
26
|
+
if (/^\d+$/.test(wanted)) {
|
|
27
|
+
const position = Number(wanted) - 1;
|
|
28
|
+
const positioned = accounts.find(account => account.position === position);
|
|
29
|
+
if (positioned) return [positioned];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return accounts.filter(account =>
|
|
33
|
+
[
|
|
34
|
+
account.label,
|
|
35
|
+
account.email,
|
|
36
|
+
account.accountId,
|
|
37
|
+
account.projectId,
|
|
38
|
+
account.enterpriseUrl,
|
|
39
|
+
account.orgId,
|
|
40
|
+
account.orgName,
|
|
41
|
+
`OAuth credential #${account.credentialId}`,
|
|
42
|
+
].some(value => value?.trim().toLowerCase() === wanted),
|
|
43
|
+
);
|
|
44
|
+
}
|
package/src/stt/downloader.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { getTinyModelsCacheDir } from "@oh-my-pi/pi-utils";
|
|
|
4
4
|
import { sttClient } from "./asr-client";
|
|
5
5
|
import type { SttProgressStatus } from "./asr-protocol";
|
|
6
6
|
import { resolveSttModelSpec } from "./models";
|
|
7
|
-
import { ensureRecorder } from "./recorder";
|
|
8
7
|
|
|
9
8
|
export interface DownloadProgress {
|
|
10
9
|
stage: string;
|
|
@@ -129,7 +128,6 @@ export async function downloadSttModel(
|
|
|
129
128
|
// ── Public API ─────────────────────────────────────────────────────
|
|
130
129
|
|
|
131
130
|
export async function ensureSTTDependencies(options?: EnsureOptions): Promise<void> {
|
|
132
|
-
await ensureRecorder(progress => options?.onProgress?.(progress), options?.signal);
|
|
133
131
|
await downloadSttModel(
|
|
134
132
|
resolveSttModelSpec(options?.modelName).key,
|
|
135
133
|
progress => {
|
package/src/stt/index.ts
CHANGED
|
@@ -1,22 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import * as path from "node:path";
|
|
4
|
-
import { logger, Snowflake } from "@oh-my-pi/pi-utils";
|
|
1
|
+
import { AudioCapture } from "@oh-my-pi/pi-natives";
|
|
2
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
5
3
|
import { settings } from "../config/settings";
|
|
6
4
|
import { type SttStreamHandle, sttClient } from "./asr-client";
|
|
7
5
|
import { downloadSttModel, isSttModelCached } from "./downloader";
|
|
8
6
|
import { resolveSttModelSpec } from "./models";
|
|
9
|
-
import {
|
|
10
|
-
detectRecorder,
|
|
11
|
-
ensureRecorder,
|
|
12
|
-
type RecordingHandle,
|
|
13
|
-
type StreamingRecordingHandle,
|
|
14
|
-
startRecording,
|
|
15
|
-
startStreamingRecording,
|
|
16
|
-
verifyRecordingFile,
|
|
17
|
-
} from "./recorder";
|
|
18
7
|
import { evaluateSubmitTrigger } from "./submit-trigger";
|
|
19
|
-
import { transcribe } from "./transcriber";
|
|
20
8
|
|
|
21
9
|
export type SttState = "idle" | "recording" | "transcribing";
|
|
22
10
|
|
|
@@ -38,26 +26,34 @@ interface Editor {
|
|
|
38
26
|
deleteBeforeCursor(count: number): void;
|
|
39
27
|
}
|
|
40
28
|
|
|
29
|
+
interface CaptureHandle {
|
|
30
|
+
stop(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type CaptureFactory = (onAudio: (error: Error | null, samples: Float32Array) => void) => CaptureHandle;
|
|
34
|
+
|
|
35
|
+
/** Coordinates native microphone capture with incremental local transcription. */
|
|
41
36
|
export class STTController {
|
|
42
37
|
#state: SttState = "idle";
|
|
43
38
|
#resolvedModelKey: string | null = null;
|
|
44
39
|
#toggling = false;
|
|
45
40
|
#stopAfterStart = false;
|
|
46
41
|
#disposed = false;
|
|
47
|
-
|
|
48
|
-
// Batch (single-shot) capture.
|
|
49
|
-
#recordingHandle: RecordingHandle | null = null;
|
|
50
|
-
#tempFile: string | null = null;
|
|
51
|
-
#transcriptionAbort: AbortController | null = null;
|
|
42
|
+
readonly #createCapture: CaptureFactory;
|
|
52
43
|
|
|
53
44
|
// Live streaming capture.
|
|
54
45
|
#stream: SttStreamHandle | null = null;
|
|
55
|
-
#streamRecorder:
|
|
46
|
+
#streamRecorder: CaptureHandle | null = null;
|
|
56
47
|
#streamEditor: Editor | null = null;
|
|
57
48
|
#streamCommitted = false;
|
|
58
49
|
#streamAbort: AbortController | null = null;
|
|
59
50
|
#streamUtterance = "";
|
|
60
51
|
|
|
52
|
+
/** Creates a controller; tests may replace the hardware capture boundary. */
|
|
53
|
+
constructor(createCapture: CaptureFactory = onAudio => new AudioCapture(16_000, onAudio)) {
|
|
54
|
+
this.#createCapture = createCapture;
|
|
55
|
+
}
|
|
56
|
+
|
|
61
57
|
get state(): SttState {
|
|
62
58
|
return this.#state;
|
|
63
59
|
}
|
|
@@ -79,7 +75,7 @@ export class STTController {
|
|
|
79
75
|
await this.#start(editor, options);
|
|
80
76
|
break;
|
|
81
77
|
case "recording":
|
|
82
|
-
await this.#stop(
|
|
78
|
+
await this.#stop(options);
|
|
83
79
|
break;
|
|
84
80
|
case "transcribing":
|
|
85
81
|
options.showStatus("Transcription in progress...");
|
|
@@ -87,7 +83,7 @@ export class STTController {
|
|
|
87
83
|
}
|
|
88
84
|
if (this.#stopAfterStart && this.#state === "recording") {
|
|
89
85
|
this.#stopAfterStart = false;
|
|
90
|
-
await this.#stop(
|
|
86
|
+
await this.#stop(options);
|
|
91
87
|
} else if (this.#state !== "recording") {
|
|
92
88
|
this.#stopAfterStart = false;
|
|
93
89
|
}
|
|
@@ -103,19 +99,13 @@ export class STTController {
|
|
|
103
99
|
// (with progress) instead of blocking silently at stop.
|
|
104
100
|
if (this.#resolvedModelKey === modelKey) return true;
|
|
105
101
|
try {
|
|
106
|
-
// Only clear the status line
|
|
107
|
-
// fast path
|
|
108
|
-
// unconditional clear would be a stray write.
|
|
102
|
+
// Only clear the status line when preflight emitted progress; the
|
|
103
|
+
// cached-model fast path emits nothing.
|
|
109
104
|
let wroteStatus = false;
|
|
110
105
|
const status = (msg: string): void => {
|
|
111
106
|
wroteStatus = true;
|
|
112
107
|
options.showStatus(msg);
|
|
113
108
|
};
|
|
114
|
-
// A recorder is required to capture audio; startRecording /
|
|
115
|
-
// startStreamingRecording only *detect* a recorder and throw when none
|
|
116
|
-
// exists, so provision one here. Instant when sox/ffmpeg/arecord is on
|
|
117
|
-
// PATH — only a first-run static-ffmpeg download actually blocks.
|
|
118
|
-
await ensureRecorder(p => status(p.stage + (p.percent != null ? ` (${p.percent}%)` : "")));
|
|
119
109
|
// Loading the multi-hundred-MB speech model into the worker is what made
|
|
120
110
|
// the old "Checking STT dependencies…" step slow. Don't pay it before
|
|
121
111
|
// recording: when the weights are already cached, start now and warm the
|
|
@@ -157,30 +147,15 @@ export class STTController {
|
|
|
157
147
|
|
|
158
148
|
async #start(editor: Editor, options: ToggleOptions): Promise<void> {
|
|
159
149
|
if (!(await this.#ensureDeps(options))) return;
|
|
160
|
-
|
|
161
|
-
// PowerShell mci fallback records to a file, so it stays single-shot.
|
|
162
|
-
if (this.#recorderCanStream()) {
|
|
163
|
-
await this.#startStreaming(editor, options);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
await this.#startBatchRecording(options);
|
|
150
|
+
await this.#startStreaming(editor, options);
|
|
167
151
|
}
|
|
168
152
|
|
|
169
|
-
async #stop(
|
|
170
|
-
|
|
171
|
-
await this.#stopStreaming(options);
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
await this.#stopBatch(editor, options);
|
|
153
|
+
async #stop(options: ToggleOptions): Promise<void> {
|
|
154
|
+
await this.#stopStreaming(options);
|
|
175
155
|
}
|
|
176
156
|
|
|
177
157
|
// ── Live streaming ──────────────────────────────────────────────
|
|
178
158
|
|
|
179
|
-
#recorderCanStream(): boolean {
|
|
180
|
-
const recorder = detectRecorder();
|
|
181
|
-
return recorder !== null && recorder.tool !== "powershell";
|
|
182
|
-
}
|
|
183
|
-
|
|
184
159
|
/** Segment text gets a leading space once a prior segment is committed, so
|
|
185
160
|
* phrases join naturally; the first phrase is inserted at the cursor as-is. */
|
|
186
161
|
#prefixed(text: string): string {
|
|
@@ -218,18 +193,38 @@ export class STTController {
|
|
|
218
193
|
},
|
|
219
194
|
});
|
|
220
195
|
this.#stream = stream;
|
|
221
|
-
let recorder:
|
|
196
|
+
let recorder: CaptureHandle;
|
|
222
197
|
try {
|
|
223
|
-
recorder =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
198
|
+
recorder = this.#createCapture((error, samples) => {
|
|
199
|
+
if (this.#disposed || this.#stream !== stream || this.#state !== "recording") return;
|
|
200
|
+
if (error) {
|
|
201
|
+
logger.error("Native microphone capture failed", { error: error.message });
|
|
202
|
+
const activeRecorder = this.#streamRecorder;
|
|
203
|
+
this.#streamRecorder = null;
|
|
204
|
+
try {
|
|
205
|
+
activeRecorder?.stop();
|
|
206
|
+
} catch (cause) {
|
|
207
|
+
logger.debug("stt: microphone cleanup failed", {
|
|
208
|
+
error: cause instanceof Error ? cause.message : String(cause),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
this.#streamAbort?.abort(error);
|
|
212
|
+
stream.cancel();
|
|
213
|
+
this.#streamEditor?.clearVolatileText();
|
|
214
|
+
options.requestRender?.();
|
|
215
|
+
this.#cleanupStream();
|
|
216
|
+
this.#setState("idle", options);
|
|
217
|
+
options.showWarning(error.message);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
stream.pushAudio(samples);
|
|
227
221
|
});
|
|
228
|
-
}
|
|
229
|
-
if (!recorder) {
|
|
222
|
+
} catch (err) {
|
|
230
223
|
stream.cancel();
|
|
231
224
|
this.#cleanupStream();
|
|
232
|
-
|
|
225
|
+
const msg = err instanceof Error ? err.message : "Failed to start microphone capture";
|
|
226
|
+
options.showWarning(msg);
|
|
227
|
+
logger.error("STT recording failed to start", { error: msg });
|
|
233
228
|
return;
|
|
234
229
|
}
|
|
235
230
|
this.#streamRecorder = recorder;
|
|
@@ -247,7 +242,7 @@ export class STTController {
|
|
|
247
242
|
this.#setState("transcribing", options);
|
|
248
243
|
// Stop the mic first so no further audio is fed, then flush the worker.
|
|
249
244
|
try {
|
|
250
|
-
|
|
245
|
+
recorder?.stop();
|
|
251
246
|
} catch (err) {
|
|
252
247
|
logger.debug("stt: streaming recorder stop failed", {
|
|
253
248
|
error: err instanceof Error ? err.message : String(err),
|
|
@@ -306,103 +301,19 @@ export class STTController {
|
|
|
306
301
|
this.#streamUtterance = "";
|
|
307
302
|
}
|
|
308
303
|
|
|
309
|
-
// ── Batch (single-shot) ─────────────────────────────────────────
|
|
310
|
-
|
|
311
|
-
async #startBatchRecording(options: ToggleOptions): Promise<void> {
|
|
312
|
-
const id = Snowflake.next();
|
|
313
|
-
this.#tempFile = path.join(os.tmpdir(), `omp-stt-${id}.wav`);
|
|
314
|
-
try {
|
|
315
|
-
this.#recordingHandle = await startRecording(this.#tempFile);
|
|
316
|
-
this.#setState("recording", options);
|
|
317
|
-
logger.debug("STT recording started", { tempFile: this.#tempFile });
|
|
318
|
-
} catch (err) {
|
|
319
|
-
this.#tempFile = null;
|
|
320
|
-
const msg = err instanceof Error ? err.message : "Failed to start recording";
|
|
321
|
-
options.showWarning(msg);
|
|
322
|
-
logger.error("STT recording failed to start", { error: msg });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async #stopBatch(editor: Editor, options: ToggleOptions): Promise<void> {
|
|
327
|
-
const handle = this.#recordingHandle;
|
|
328
|
-
const tempFile = this.#tempFile;
|
|
329
|
-
this.#recordingHandle = null;
|
|
330
|
-
|
|
331
|
-
if (!handle || !tempFile) {
|
|
332
|
-
this.#setState("idle", options);
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
await handle.stop();
|
|
338
|
-
// Validate the recording produced a usable file
|
|
339
|
-
await verifyRecordingFile(tempFile);
|
|
340
|
-
this.#setState("transcribing", options);
|
|
341
|
-
|
|
342
|
-
const sttSettings = {
|
|
343
|
-
modelName: settings.get("stt.modelName") as string | undefined,
|
|
344
|
-
language: settings.get("stt.language") as string | undefined,
|
|
345
|
-
};
|
|
346
|
-
this.#transcriptionAbort = new AbortController();
|
|
347
|
-
const text = await transcribe(tempFile, { ...sttSettings, signal: this.#transcriptionAbort.signal });
|
|
348
|
-
this.#transcriptionAbort = null;
|
|
349
|
-
if (this.#disposed) return;
|
|
350
|
-
if (text.length > 0) {
|
|
351
|
-
const trigger = settings.get("stt.submitTrigger");
|
|
352
|
-
const { submit, trimTrailing } = evaluateSubmitTrigger(text, trigger);
|
|
353
|
-
const textToInsert = trimTrailing > 0 ? text.slice(0, -trimTrailing) : text;
|
|
354
|
-
if (textToInsert.length > 0) {
|
|
355
|
-
editor.insertText(textToInsert);
|
|
356
|
-
}
|
|
357
|
-
options.showStatus("");
|
|
358
|
-
if (submit) {
|
|
359
|
-
editor.submit();
|
|
360
|
-
}
|
|
361
|
-
} else {
|
|
362
|
-
options.showStatus("No speech detected.");
|
|
363
|
-
}
|
|
364
|
-
if (!this.#disposed) this.#setState("idle", options);
|
|
365
|
-
} catch (err) {
|
|
366
|
-
if (this.#disposed) return;
|
|
367
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
368
|
-
this.#setState("idle", options);
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
const msg = err instanceof Error ? err.message : "Transcription failed";
|
|
372
|
-
options.showWarning(msg);
|
|
373
|
-
logger.error("STT transcription failed", { error: msg });
|
|
374
|
-
this.#setState("idle", options);
|
|
375
|
-
} finally {
|
|
376
|
-
try {
|
|
377
|
-
await fs.rm(tempFile, { force: true });
|
|
378
|
-
} catch {
|
|
379
|
-
// best effort cleanup
|
|
380
|
-
}
|
|
381
|
-
this.#tempFile = null;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
304
|
dispose(): void {
|
|
386
305
|
this.#disposed = true;
|
|
387
|
-
if (this.#transcriptionAbort) {
|
|
388
|
-
this.#transcriptionAbort.abort();
|
|
389
|
-
this.#transcriptionAbort = null;
|
|
390
|
-
}
|
|
391
306
|
if (this.#streamAbort) {
|
|
392
307
|
this.#streamAbort.abort();
|
|
393
308
|
this.#streamAbort = null;
|
|
394
309
|
}
|
|
395
310
|
this.#stream?.cancel();
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
this.#recordingHandle = null;
|
|
401
|
-
}
|
|
402
|
-
if (this.#tempFile) {
|
|
403
|
-
fs.rm(this.#tempFile, { force: true }).catch(() => {});
|
|
404
|
-
this.#tempFile = null;
|
|
311
|
+
try {
|
|
312
|
+
this.#streamRecorder?.stop();
|
|
313
|
+
} catch {
|
|
314
|
+
// best effort cleanup
|
|
405
315
|
}
|
|
316
|
+
this.#cleanupStream();
|
|
406
317
|
this.#state = "idle";
|
|
407
318
|
this.#resolvedModelKey = null;
|
|
408
319
|
}
|
package/src/system-prompt.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { expandAtImports } from "./discovery/at-imports";
|
|
|
17
17
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
18
18
|
import { hasObsidian } from "./internal-urls/vault-protocol";
|
|
19
19
|
import activeRepoContextTemplate from "./prompts/system/active-repo-context.md" with { type: "text" };
|
|
20
|
+
import computerSafetyPrompt from "./prompts/system/computer-safety.md" with { type: "text" };
|
|
20
21
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
21
22
|
import defaultPersonality from "./prompts/system/personalities/default.md" with { type: "text" };
|
|
22
23
|
import friendlyPersonality from "./prompts/system/personalities/friendly.md" with { type: "text" };
|
|
@@ -839,6 +840,9 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
839
840
|
};
|
|
840
841
|
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
|
841
842
|
const systemPrompt = [rendered];
|
|
843
|
+
if (toolNames.includes("computer")) {
|
|
844
|
+
systemPrompt.push(computerSafetyPrompt.trim());
|
|
845
|
+
}
|
|
842
846
|
// Custom prompt templates already render context files and append text; the
|
|
843
847
|
// project footer still carries environment, cwd, workspace, and dir-context.
|
|
844
848
|
const projectPrompt = prompt
|
package/src/task/executor.ts
CHANGED
|
@@ -46,7 +46,7 @@ import type { AuthStorage } from "../session/auth-storage";
|
|
|
46
46
|
import { SKILL_PROMPT_MESSAGE_TYPE, USER_INTERRUPT_LABEL } from "../session/messages";
|
|
47
47
|
import { SessionManager } from "../session/session-manager";
|
|
48
48
|
import { truncateTail } from "../session/streaming-output";
|
|
49
|
-
import type
|
|
49
|
+
import { type ConfiguredThinkingLevel, resolveTaskEffortLevel, type TaskEffort } from "../thinking";
|
|
50
50
|
import type { ContextFileEntry, ToolSession } from "../tools";
|
|
51
51
|
import { resolveEvalBackends } from "../tools/eval-backends";
|
|
52
52
|
import { isIrcEnabled } from "../tools/hub";
|
|
@@ -323,6 +323,8 @@ export interface ExecutorOptions {
|
|
|
323
323
|
*/
|
|
324
324
|
parentActiveModelPattern?: string;
|
|
325
325
|
thinkingLevel?: ConfiguredThinkingLevel;
|
|
326
|
+
/** Caller-requested coarse effort (`lo`/`med`/`hi`); maps onto the resolved model's supported thinking range and wins over {@link thinkingLevel}. */
|
|
327
|
+
effort?: TaskEffort;
|
|
326
328
|
/** Schema used to validate the final structured completion. */
|
|
327
329
|
outputSchema?: unknown;
|
|
328
330
|
/** Enforcement policy for {@link outputSchema}; defaults to legacy permissive behavior. */
|
|
@@ -2644,16 +2646,22 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2644
2646
|
if (model?.contextWindow && model.contextWindow > 0) {
|
|
2645
2647
|
progress.contextWindow = model.contextWindow;
|
|
2646
2648
|
}
|
|
2649
|
+
// Caller-requested coarse effort maps onto the resolved model's
|
|
2650
|
+
// supported range; undefined (no effort, or no controllable effort
|
|
2651
|
+
// surface) falls through to the normal selectors below.
|
|
2652
|
+
const effortLevel = options.effort !== undefined ? resolveTaskEffortLevel(model, options.effort) : undefined;
|
|
2647
2653
|
if (model) {
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2654
|
+
const displayLevel = effortLevel ?? (explicitThinkingLevel ? resolvedThinkingLevel : undefined);
|
|
2655
|
+
progress.resolvedModel =
|
|
2656
|
+
displayLevel !== undefined
|
|
2657
|
+
? formatModelSelectorValue(formatModelStringWithRouting(model), displayLevel)
|
|
2658
|
+
: formatModelStringWithRouting(model);
|
|
2651
2659
|
}
|
|
2652
|
-
// Precedence: explicit `:level` suffix on the resolved
|
|
2653
|
-
// agent-definition default (e.g. task's `auto`) >
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
: (thinkingLevel ?? resolvedThinkingLevel);
|
|
2660
|
+
// Precedence: caller `effort` > explicit `:level` suffix on the resolved
|
|
2661
|
+
// model pattern > agent-definition default (e.g. task's `auto`) >
|
|
2662
|
+
// pattern-derived level.
|
|
2663
|
+
const effectiveThinkingLevel =
|
|
2664
|
+
effortLevel ?? (explicitThinkingLevel ? resolvedThinkingLevel : (thinkingLevel ?? resolvedThinkingLevel));
|
|
2657
2665
|
resolvedAt = performance.now();
|
|
2658
2666
|
// Per-agent prewalk: the agent definition's `prewalk` frontmatter or the
|
|
2659
2667
|
// `task.agentPrewalk` settings override hands the subagent off to a
|