@oh-my-pi/pi-coding-agent 17.2.12 → 17.2.14
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 +63 -0
- package/dist/{CHANGELOG-k9ghy5sn.md → CHANGELOG-za420td8.md} +63 -0
- package/dist/cli.js +3658 -3627
- package/dist/types/advisor/delta-split.d.ts +24 -0
- package/dist/types/advisor/runtime.d.ts +2 -2
- package/dist/types/async/job-manager.d.ts +8 -1
- package/dist/types/cli/update-cli.d.ts +53 -1
- package/dist/types/config/keybindings.d.ts +10 -0
- package/dist/types/config/model-resolver.d.ts +15 -2
- package/dist/types/config/settings-schema.d.ts +14 -0
- package/dist/types/discovery/agents-md.d.ts +10 -1
- package/dist/types/eval/runner-cache.d.ts +12 -0
- package/dist/types/extensibility/extensions/runner.d.ts +12 -3
- package/dist/types/extensibility/extensions/types.d.ts +15 -4
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +4 -1
- package/dist/types/lib/xai-http.d.ts +0 -1
- package/dist/types/mcp/tool-bridge.d.ts +8 -5
- package/dist/types/modes/components/agent-hub-renderer.d.ts +6 -1
- package/dist/types/modes/components/status-line/types.d.ts +4 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -4
- package/dist/types/modes/interactive-mode.d.ts +17 -8
- package/dist/types/modes/types.d.ts +7 -10
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +1 -1
- package/dist/types/session/agent-session-types.d.ts +4 -0
- package/dist/types/session/agent-session.d.ts +24 -3
- package/dist/types/session/messages.d.ts +20 -0
- package/dist/types/session/retry-fallback-chains.d.ts +13 -0
- package/dist/types/session/session-advisors.d.ts +1 -1
- package/dist/types/session/session-history-format.d.ts +10 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-tools.d.ts +35 -5
- package/dist/types/session/turn-recovery.d.ts +34 -5
- package/dist/types/slash-commands/types.d.ts +5 -1
- package/dist/types/task/executor.d.ts +1 -1
- package/dist/types/tools/approval.d.ts +7 -0
- package/dist/types/tools/builtin-names.d.ts +1 -2
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/think.d.ts +38 -0
- package/dist/types/tools/todo.d.ts +14 -15
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/utils/local-date.d.ts +2 -0
- package/dist/types/vibe/runtime.d.ts +1 -1
- package/dist/types/web/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/brave.d.ts +8 -3
- package/dist/types/web/search/providers/codex.d.ts +6 -0
- package/dist/types/web/search/providers/firecrawl.d.ts +3 -2
- package/dist/types/web/search/providers/jina.d.ts +3 -3
- package/dist/types/web/search/providers/parallel.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +4 -0
- package/dist/types/web/search/providers/tinyfish.d.ts +2 -0
- package/package.json +13 -13
- package/src/advisor/delta-split.ts +98 -0
- package/src/advisor/runtime.ts +321 -69
- package/src/async/job-manager.ts +14 -3
- package/src/cli/gallery-fixtures/agentic.ts +16 -0
- package/src/cli/plugin-cli.ts +30 -2
- package/src/cli/update-cli.ts +259 -24
- package/src/config/keybindings.ts +52 -9
- package/src/config/model-resolver.ts +19 -3
- package/src/config/settings-schema.ts +16 -0
- package/src/cursor.ts +10 -5
- package/src/discovery/agents-md.ts +61 -23
- package/src/eval/jl/kernel.ts +2 -20
- package/src/eval/py/kernel.ts +2 -20
- package/src/eval/rb/kernel.ts +2 -20
- package/src/eval/runner-cache.ts +41 -0
- package/src/exec/non-interactive-env.ts +14 -3
- package/src/extensibility/extensions/loader.ts +5 -2
- package/src/extensibility/extensions/runner.ts +184 -66
- package/src/extensibility/extensions/types.ts +26 -2
- package/src/extensibility/extensions/wrapper.ts +13 -7
- package/src/extensibility/plugins/marketplace/manager.ts +6 -2
- package/src/hindsight/client.ts +1 -1
- package/src/lib/xai-http.ts +0 -4
- package/src/lsp/client.ts +2 -0
- package/src/lsp/servers.ts +1 -1
- package/src/mcp/tool-bridge.ts +15 -6
- package/src/modes/components/agent-hub-renderer.ts +9 -3
- package/src/modes/components/agent-hub.ts +2 -1
- package/src/modes/components/status-line/component.ts +58 -6
- package/src/modes/components/status-line/segments.ts +12 -1
- package/src/modes/components/status-line/types.ts +1 -0
- package/src/modes/components/user-message.ts +20 -5
- package/src/modes/controllers/event-controller.ts +48 -0
- package/src/modes/controllers/extension-ui-controller.ts +14 -7
- package/src/modes/controllers/input-controller.ts +25 -6
- package/src/modes/controllers/selector-controller.ts +5 -0
- package/src/modes/interactive-mode.ts +315 -129
- package/src/modes/rpc/rpc-frame.ts +13 -5
- package/src/modes/theme/tui-adapters.ts +4 -5
- package/src/modes/types.ts +13 -7
- package/src/modes/utils/hotkeys-markdown.ts +10 -6
- package/src/prompts/system/system-prompt.md +10 -1
- package/src/registry/persisted-agents.ts +43 -8
- package/src/sdk.ts +146 -9
- package/src/session/agent-session-types.ts +4 -0
- package/src/session/agent-session.ts +91 -10
- package/src/session/messages.ts +98 -28
- package/src/session/retry-fallback-chains.ts +14 -0
- package/src/session/session-advisors.ts +32 -15
- package/src/session/session-history-format.ts +15 -1
- package/src/session/session-maintenance.ts +8 -8
- package/src/session/session-manager.ts +6 -2
- package/src/session/session-tools.ts +355 -183
- package/src/session/turn-recovery.ts +225 -47
- package/src/slash-commands/builtin-modes.ts +41 -12
- package/src/slash-commands/types.ts +5 -1
- package/src/task/executor.ts +92 -45
- package/src/task/structured-subagent.ts +5 -5
- package/src/tools/approval.ts +44 -10
- package/src/tools/builtin-names.ts +1 -2
- package/src/tools/fetch.ts +21 -2
- package/src/tools/image-gen.ts +6 -8
- package/src/tools/index.ts +8 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/think.ts +62 -0
- package/src/tools/todo.ts +70 -26
- package/src/tools/tts.ts +3 -2
- package/src/tools/write.ts +7 -3
- package/src/utils/local-date.ts +13 -0
- package/src/utils/tools-manager.ts +2 -2
- package/src/vibe/runtime.ts +22 -14
- package/src/web/kagi.ts +91 -34
- package/src/web/parallel.ts +11 -2
- package/src/web/scrapers/crates-io.ts +2 -2
- package/src/web/scrapers/discogs.ts +2 -2
- package/src/web/scrapers/docs-rs.ts +2 -2
- package/src/web/scrapers/github.ts +2 -2
- package/src/web/scrapers/musicbrainz.ts +1 -2
- package/src/web/scrapers/pubmed.ts +2 -2
- package/src/web/scrapers/sec-edgar.ts +2 -2
- package/src/web/search/providers/brave.ts +121 -46
- package/src/web/search/providers/codex.ts +88 -12
- package/src/web/search/providers/exa.ts +45 -10
- package/src/web/search/providers/firecrawl.ts +53 -11
- package/src/web/search/providers/gemini.ts +139 -27
- package/src/web/search/providers/jina.ts +48 -25
- package/src/web/search/providers/parallel.ts +23 -9
- package/src/web/search/providers/perplexity.ts +24 -7
- package/src/web/search/providers/searxng.ts +77 -1
- package/src/web/search/providers/tavily.ts +23 -22
- package/src/web/search/providers/tinyfish.ts +44 -10
- package/src/web/search/providers/xai.ts +85 -14
package/src/task/executor.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type { AgentEvent, AgentIdentity, AgentMessage, AgentTelemetryConfig } fr
|
|
|
9
9
|
import { recordHandoff, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
|
|
10
10
|
import type { Api, Model, ServiceTierByFamily, Usage } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import { logger, popLoopPhase, prompt, pushLoopPhase, untilAborted } from "@oh-my-pi/pi-utils";
|
|
12
|
-
import { AsyncJobManager } from "../async";
|
|
12
|
+
import { ASYNC_JOB_MANAGER_SHUTDOWN_REASON, AsyncJobManager } from "../async";
|
|
13
13
|
import type { Rule } from "../capability/rule";
|
|
14
14
|
import { ModelRegistry } from "../config/model-registry";
|
|
15
15
|
import {
|
|
@@ -189,19 +189,24 @@ function resolveSubagentRetryFallbackCandidates(
|
|
|
189
189
|
* Chain a single-model subagent inherits when its own model patterns supply no
|
|
190
190
|
* fallbacks of their own. The child is pinned to a `subagent:<id>` role whose
|
|
191
191
|
* chain shadows every configured role chain (see
|
|
192
|
-
* {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol
|
|
193
|
-
* MUST inherit that role's chain —
|
|
194
|
-
* child onto the `default` role's
|
|
195
|
-
* inheriting `default`: they carry no role
|
|
196
|
-
* be assigned the same model must not
|
|
192
|
+
* {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol`,
|
|
193
|
+
* the bundled `task` agent's `@task`) MUST inherit that role's chain —
|
|
194
|
+
* otherwise the pin silently re-routes the child onto the `default` role's
|
|
195
|
+
* chain. Explicit model selectors keep inheriting `default`: they carry no role
|
|
196
|
+
* identity, and a role that happens to be assigned the same model must not
|
|
197
|
+
* capture the child's fallback routing.
|
|
198
|
+
*
|
|
199
|
+
* Spawn paths preserve the pre-expansion alias as `modelRole` because their
|
|
200
|
+
* model patterns are already expanded. Direct callers may still supply an
|
|
201
|
+
* unexpanded alias through `modelOverride` or `agent.model`; retain that
|
|
202
|
+
* existing path by deriving the role only when no preserved role was supplied.
|
|
197
203
|
*/
|
|
198
204
|
function resolveSubagentInheritedRetryFallbackChain(
|
|
199
205
|
settings: Settings,
|
|
200
206
|
modelRegistry: ModelRegistry,
|
|
201
|
-
|
|
207
|
+
role: string | undefined,
|
|
202
208
|
): string[] | undefined {
|
|
203
209
|
const configuredChains = settings.get("retry.fallbackChains");
|
|
204
|
-
const role = resolveExplicitModelRole(modelPatterns, settings);
|
|
205
210
|
// An explicitly emptied role chain means "no fallbacks", not "inherit
|
|
206
211
|
// default" — mirrors expandDefaultRetryFallbackChains.
|
|
207
212
|
const fallbackChain = (role !== undefined ? configuredChains?.[role] : undefined) ?? configuredChains?.default;
|
|
@@ -895,7 +900,7 @@ export function createSubagentSettings(
|
|
|
895
900
|
);
|
|
896
901
|
}
|
|
897
902
|
|
|
898
|
-
export type AbortReason = "signal" | "terminate" | "timeout" | "budget";
|
|
903
|
+
export type AbortReason = "signal" | "shutdown" | "terminate" | "timeout" | "budget";
|
|
899
904
|
|
|
900
905
|
const MAX_YIELD_TOOL_ERRORS = 6;
|
|
901
906
|
|
|
@@ -1095,7 +1100,21 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1095
1100
|
budgetLimitExceeded = true;
|
|
1096
1101
|
}
|
|
1097
1102
|
if (abortSent) {
|
|
1098
|
-
|
|
1103
|
+
// Shutdown is a superseding external abort: a process teardown that
|
|
1104
|
+
// races a self-inflicted budget hard-abort must still follow the
|
|
1105
|
+
// shutdown release path (dispose + unregister) instead of the
|
|
1106
|
+
// budget-resumable path, which would leave the subagent adopted and
|
|
1107
|
+
// alive past AgentLifecycleManager.dispose(). Genuine kills
|
|
1108
|
+
// (signal/timeout/terminate) already dispose terminally, and shutdown
|
|
1109
|
+
// is never downgraded back to signal.
|
|
1110
|
+
if (reason === "shutdown" && abortReason === "budget") {
|
|
1111
|
+
abortReason = "shutdown";
|
|
1112
|
+
} else if (
|
|
1113
|
+
reason === "signal" &&
|
|
1114
|
+
abortReason !== "signal" &&
|
|
1115
|
+
abortReason !== "timeout" &&
|
|
1116
|
+
abortReason !== "shutdown"
|
|
1117
|
+
) {
|
|
1099
1118
|
abortReason = "signal";
|
|
1100
1119
|
}
|
|
1101
1120
|
return;
|
|
@@ -1158,7 +1177,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1158
1177
|
signal.addEventListener(
|
|
1159
1178
|
"abort",
|
|
1160
1179
|
() => {
|
|
1161
|
-
if (!resolved) requestAbort("signal");
|
|
1180
|
+
if (!resolved) requestAbort(signal.reason === ASYNC_JOB_MANAGER_SHUTDOWN_REASON ? "shutdown" : "signal");
|
|
1162
1181
|
},
|
|
1163
1182
|
{ once: true, signal: listenerSignal },
|
|
1164
1183
|
);
|
|
@@ -1183,6 +1202,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1183
1202
|
}
|
|
1184
1203
|
|
|
1185
1204
|
const resolveSignalAbortReason = (): string => {
|
|
1205
|
+
if (signal?.reason === ASYNC_JOB_MANAGER_SHUTDOWN_REASON) return "Async job manager shutdown";
|
|
1186
1206
|
const reason = signal?.reason;
|
|
1187
1207
|
if (reason instanceof Error) {
|
|
1188
1208
|
const message = reason.message.trim();
|
|
@@ -1659,15 +1679,28 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1659
1679
|
};
|
|
1660
1680
|
|
|
1661
1681
|
const attach = (session: AgentSession): (() => void) => {
|
|
1662
|
-
|
|
1682
|
+
// The session owns attribution: it knows which model produced its output
|
|
1683
|
+
// and withholds an armed-but-unproven fallback. Re-deriving that here from
|
|
1684
|
+
// the event stream got it wrong twice over — the stream also carries
|
|
1685
|
+
// advisor turns running on a different model, and a routing switch was
|
|
1686
|
+
// read as evidence the target had served.
|
|
1687
|
+
const publishServingModel = (): void => {
|
|
1688
|
+
const serving = session.servingModel;
|
|
1689
|
+
if (!serving) return;
|
|
1690
|
+
const isFallback = serving.isFallback;
|
|
1691
|
+
if (
|
|
1692
|
+
serving.selector === progress.resolvedModel &&
|
|
1693
|
+
(progress.resolvedModelIsFallback ?? false) === isFallback
|
|
1694
|
+
) {
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
progress.resolvedModel = serving.selector;
|
|
1698
|
+
progress.resolvedModelIsFallback = isFallback;
|
|
1699
|
+
scheduleProgress(true);
|
|
1700
|
+
};
|
|
1663
1701
|
return session.subscribe(event => {
|
|
1664
1702
|
emitSubagentEvent(event);
|
|
1665
|
-
|
|
1666
|
-
if (nextModel && nextModel !== activeModel) {
|
|
1667
|
-
activeModel = nextModel;
|
|
1668
|
-
progress.resolvedModel = nextModel;
|
|
1669
|
-
scheduleProgress(true);
|
|
1670
|
-
}
|
|
1703
|
+
publishServingModel();
|
|
1671
1704
|
if (event.type === "auto_retry_start") {
|
|
1672
1705
|
progress.retryState = {
|
|
1673
1706
|
attempt: event.attempt,
|
|
@@ -1707,18 +1740,6 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1707
1740
|
popLoopPhase();
|
|
1708
1741
|
}
|
|
1709
1742
|
}
|
|
1710
|
-
if (event.type === "retry_fallback_applied") {
|
|
1711
|
-
progress.resolvedModel = event.to;
|
|
1712
|
-
progress.resolvedModelIsFallback = true;
|
|
1713
|
-
scheduleProgress(true);
|
|
1714
|
-
return;
|
|
1715
|
-
}
|
|
1716
|
-
if (event.type === "retry_fallback_succeeded") {
|
|
1717
|
-
progress.resolvedModel = event.model;
|
|
1718
|
-
progress.resolvedModelIsFallback = true;
|
|
1719
|
-
scheduleProgress(true);
|
|
1720
|
-
return;
|
|
1721
|
-
}
|
|
1722
1743
|
});
|
|
1723
1744
|
};
|
|
1724
1745
|
|
|
@@ -1751,7 +1772,11 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1751
1772
|
runtimeLimitExceeded: () => runtimeLimitExceeded,
|
|
1752
1773
|
terminalError: () => terminalError,
|
|
1753
1774
|
hasExplicitAbortReason: () =>
|
|
1754
|
-
abortReason === "signal" ||
|
|
1775
|
+
abortReason === "signal" ||
|
|
1776
|
+
abortReason === "shutdown" ||
|
|
1777
|
+
runtimeLimitExceeded ||
|
|
1778
|
+
budgetLimitExceeded ||
|
|
1779
|
+
budgetStopRequested,
|
|
1755
1780
|
budgetStopRequested: () => budgetStopRequested,
|
|
1756
1781
|
waitForBudgetStop: () => budgetStopAbortPromise ?? Promise.resolve(),
|
|
1757
1782
|
yieldInvalidatedByAsync: () => yieldInvalidatedByAsync,
|
|
@@ -1777,7 +1802,11 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
|
|
|
1777
1802
|
// the lifecycle can park the agent as resumable instead of killing it.
|
|
1778
1803
|
abortKind: () => abortReason ?? (budgetStopRequested ? "budget" : undefined),
|
|
1779
1804
|
isAbortedRun: () =>
|
|
1780
|
-
abortReason === "signal" ||
|
|
1805
|
+
abortReason === "signal" ||
|
|
1806
|
+
abortReason === "shutdown" ||
|
|
1807
|
+
runtimeLimitExceeded ||
|
|
1808
|
+
budgetLimitExceeded ||
|
|
1809
|
+
abortReason === undefined,
|
|
1781
1810
|
requestAbort,
|
|
1782
1811
|
failWithError,
|
|
1783
1812
|
abortActiveSession,
|
|
@@ -2416,23 +2445,37 @@ export async function finalizeSubagentLifecycle(args: {
|
|
|
2416
2445
|
}
|
|
2417
2446
|
};
|
|
2418
2447
|
|
|
2419
|
-
// A budget abort leaves a consistent session with its transcript on disk
|
|
2420
|
-
//
|
|
2448
|
+
// A budget abort leaves a consistent session with its transcript on disk.
|
|
2449
|
+
// Manager shutdown also preserves the transcript, but disposes and unregisters
|
|
2450
|
+
// the process-local session. Caller signals, wall-clock timeouts, and internal
|
|
2421
2451
|
// terminations are genuine kills and stay terminal.
|
|
2422
2452
|
const resumableAbort =
|
|
2423
2453
|
args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
|
|
2424
2454
|
if (args.aborted && !resumableAbort) {
|
|
2425
2455
|
if (ref && ownsRef) {
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2456
|
+
if (args.abortKind === "shutdown") {
|
|
2457
|
+
try {
|
|
2458
|
+
await AgentLifecycleManager.global().release(args.id, ref);
|
|
2459
|
+
} catch (error) {
|
|
2460
|
+
logger.warn("runSubagent: failed to release session during manager shutdown", {
|
|
2461
|
+
id: args.id,
|
|
2462
|
+
error: String(error),
|
|
2463
|
+
});
|
|
2464
|
+
await disposeSession();
|
|
2465
|
+
registry.unregister(args.id, ref);
|
|
2466
|
+
}
|
|
2467
|
+
} else {
|
|
2468
|
+
// Route hard kills through the lifecycle owner so the terminal
|
|
2469
|
+
// decision is durable and a restart cannot rediscover the transcript
|
|
2470
|
+
// as a revivable parked agent.
|
|
2471
|
+
try {
|
|
2472
|
+
await AgentLifecycleManager.global().release(args.id, ref, { tombstone: true });
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
logger.warn("runSubagent: failed to persist kill tombstone", { id: args.id, error: String(error) });
|
|
2475
|
+
registry.setStatus(args.id, "aborted", ref);
|
|
2476
|
+
registry.detachSession(args.id, ref);
|
|
2477
|
+
await disposeSession();
|
|
2478
|
+
}
|
|
2436
2479
|
}
|
|
2437
2480
|
} else {
|
|
2438
2481
|
await disposeSession();
|
|
@@ -2838,7 +2881,11 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
2838
2881
|
const configuredModelPatterns = resolveConfiguredModelPatterns(modelPatterns, settings);
|
|
2839
2882
|
const inheritedRetryFallbackChain =
|
|
2840
2883
|
configuredModelPatterns.length === 1
|
|
2841
|
-
? resolveSubagentInheritedRetryFallbackChain(
|
|
2884
|
+
? resolveSubagentInheritedRetryFallbackChain(
|
|
2885
|
+
subagentSettings,
|
|
2886
|
+
modelRegistry,
|
|
2887
|
+
modelRole ?? resolveExplicitModelRole(modelPatterns, subagentSettings),
|
|
2888
|
+
)
|
|
2842
2889
|
: undefined;
|
|
2843
2890
|
const {
|
|
2844
2891
|
model,
|
|
@@ -8,7 +8,7 @@ import * as fs from "node:fs/promises";
|
|
|
8
8
|
import * as os from "node:os";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { $env, prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
11
|
-
import {
|
|
11
|
+
import { resolveAgentModelSelection } from "../config/model-resolver";
|
|
12
12
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
13
13
|
import { registerArtifactsDir } from "../internal-urls/registry-helpers";
|
|
14
14
|
import { MCPManager } from "../mcp/manager";
|
|
@@ -288,10 +288,10 @@ export async function resolveEffectiveSubagentPolicy(
|
|
|
288
288
|
activeModelPattern: parentActiveModelPattern,
|
|
289
289
|
fallbackModelPattern: request.session.getModelString?.(),
|
|
290
290
|
};
|
|
291
|
-
//
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
const modelOverride =
|
|
291
|
+
// Role identity and patterns come from one call so they cannot be derived
|
|
292
|
+
// from different sources: the expansion below discards the alias, and the
|
|
293
|
+
// child's inherited retry-fallback chain is keyed off the role.
|
|
294
|
+
const { patterns: modelOverride, role: modelRole } = resolveAgentModelSelection(modelResolution);
|
|
295
295
|
const isolationMode = request.session.settings.get("task.isolation.mode");
|
|
296
296
|
const isIsolated = request.isolation?.requested === true;
|
|
297
297
|
if (isIsolated && isolationMode === "none") {
|
package/src/tools/approval.ts
CHANGED
|
@@ -21,6 +21,8 @@ export interface ResolvedApproval {
|
|
|
21
21
|
reason?: string;
|
|
22
22
|
override: boolean;
|
|
23
23
|
source?: "tool" | "user" | "mode";
|
|
24
|
+
/** User-policy key that produced `source: "user"` (defaults to the tool name). */
|
|
25
|
+
policyKey?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const POLICY_VALUES: ReadonlySet<ApprovalPolicy> = new Set(["allow", "deny", "prompt"]);
|
|
@@ -61,11 +63,14 @@ function normalizeDecision(value: unknown): Omit<ResolvedApproval, "policy"> & {
|
|
|
61
63
|
const tier = isToolTier(record.tier) ? record.tier : "exec";
|
|
62
64
|
const reason = typeof record.reason === "string" && record.reason.length > 0 ? record.reason : undefined;
|
|
63
65
|
const policy = normalizePolicy(record.policy);
|
|
66
|
+
const policyKey =
|
|
67
|
+
typeof record.policyKey === "string" && record.policyKey.length > 0 ? record.policyKey : undefined;
|
|
64
68
|
return {
|
|
65
69
|
tier,
|
|
66
70
|
override: record.override === true,
|
|
67
71
|
...(policy ? { policy } : {}),
|
|
68
72
|
...(reason ? { reason } : {}),
|
|
73
|
+
...(policyKey ? { policyKey } : {}),
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
76
|
|
|
@@ -101,6 +106,11 @@ function modeApprovesTier(mode: ApprovalMode, tier: ToolTier): boolean {
|
|
|
101
106
|
*
|
|
102
107
|
* Resolution order:
|
|
103
108
|
* 1. Tool `approval(args)` decision, defaulting to tier "exec" when omitted.
|
|
109
|
+
* A decision may carry a `policyKey` — `tools.approval.<policyKey>` is then
|
|
110
|
+
* the user override consulted instead of `tools.approval.<tool.name>`, with
|
|
111
|
+
* the invoking tool's own policy as the fallback when the user set none for
|
|
112
|
+
* the keyed sub-tool (e.g. an `xd://` device dispatch without a device
|
|
113
|
+
* policy still honors `tools.approval.write`).
|
|
104
114
|
* 2. User per-tool override, if set and valid.
|
|
105
115
|
* 3. Active mode tier comparison.
|
|
106
116
|
*
|
|
@@ -114,7 +124,14 @@ export function resolveApproval(
|
|
|
114
124
|
userConfig: Record<string, unknown> = {},
|
|
115
125
|
): ResolvedApproval {
|
|
116
126
|
const decision = getToolDecision(tool, args);
|
|
117
|
-
const
|
|
127
|
+
const policyKey = decision.policyKey ?? tool.name;
|
|
128
|
+
const userPolicy = Object.hasOwn(userConfig, policyKey) ? normalizePolicy(userConfig[policyKey]) : undefined;
|
|
129
|
+
const fallbackPolicy =
|
|
130
|
+
policyKey !== tool.name && userPolicy === undefined && Object.hasOwn(userConfig, tool.name)
|
|
131
|
+
? normalizePolicy(userConfig[tool.name])
|
|
132
|
+
: undefined;
|
|
133
|
+
const effectiveUserPolicy = userPolicy ?? fallbackPolicy;
|
|
134
|
+
const userPolicyKey = userPolicy !== undefined ? policyKey : tool.name;
|
|
118
135
|
|
|
119
136
|
if (decision.policy === "deny") {
|
|
120
137
|
return {
|
|
@@ -122,11 +139,18 @@ export function resolveApproval(
|
|
|
122
139
|
tier: decision.tier,
|
|
123
140
|
override: decision.override,
|
|
124
141
|
source: "tool",
|
|
142
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
125
143
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
126
144
|
};
|
|
127
145
|
}
|
|
128
|
-
if (
|
|
129
|
-
return {
|
|
146
|
+
if (effectiveUserPolicy === "deny") {
|
|
147
|
+
return {
|
|
148
|
+
policy: "deny",
|
|
149
|
+
tier: decision.tier,
|
|
150
|
+
override: decision.override,
|
|
151
|
+
source: "user",
|
|
152
|
+
policyKey: userPolicyKey,
|
|
153
|
+
};
|
|
130
154
|
}
|
|
131
155
|
|
|
132
156
|
if (mode === "yolo") {
|
|
@@ -136,14 +160,16 @@ export function resolveApproval(
|
|
|
136
160
|
tier: decision.tier,
|
|
137
161
|
override: false,
|
|
138
162
|
source: "tool",
|
|
163
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
139
164
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
140
165
|
};
|
|
141
166
|
}
|
|
142
167
|
return {
|
|
143
|
-
policy:
|
|
168
|
+
policy: effectiveUserPolicy ?? "allow",
|
|
144
169
|
tier: decision.tier,
|
|
145
170
|
override: false,
|
|
146
|
-
source:
|
|
171
|
+
source: effectiveUserPolicy ? "user" : "mode",
|
|
172
|
+
...(effectiveUserPolicy ? { policyKey: userPolicyKey } : {}),
|
|
147
173
|
};
|
|
148
174
|
}
|
|
149
175
|
|
|
@@ -153,6 +179,7 @@ export function resolveApproval(
|
|
|
153
179
|
tier: decision.tier,
|
|
154
180
|
override: true,
|
|
155
181
|
source: "tool",
|
|
182
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
156
183
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
157
184
|
};
|
|
158
185
|
}
|
|
@@ -163,12 +190,19 @@ export function resolveApproval(
|
|
|
163
190
|
tier: decision.tier,
|
|
164
191
|
override: false,
|
|
165
192
|
source: "tool",
|
|
193
|
+
...(decision.policyKey ? { policyKey: decision.policyKey } : {}),
|
|
166
194
|
...(decision.reason ? { reason: decision.reason } : {}),
|
|
167
195
|
};
|
|
168
196
|
}
|
|
169
197
|
|
|
170
|
-
if (
|
|
171
|
-
return {
|
|
198
|
+
if (effectiveUserPolicy) {
|
|
199
|
+
return {
|
|
200
|
+
policy: effectiveUserPolicy,
|
|
201
|
+
tier: decision.tier,
|
|
202
|
+
override: false,
|
|
203
|
+
source: "user",
|
|
204
|
+
policyKey: userPolicyKey,
|
|
205
|
+
};
|
|
172
206
|
}
|
|
173
207
|
|
|
174
208
|
if (modeApprovesTier(mode, decision.tier)) {
|
|
@@ -196,15 +230,15 @@ export function requiresApproval(
|
|
|
196
230
|
mode: ApprovalMode,
|
|
197
231
|
userConfig: Record<string, unknown> = {},
|
|
198
232
|
): { required: boolean; reason?: string } {
|
|
199
|
-
const { policy, reason, source } = resolveApproval(tool, args, mode, userConfig);
|
|
233
|
+
const { policy, reason, source, policyKey } = resolveApproval(tool, args, mode, userConfig);
|
|
200
234
|
|
|
201
235
|
if (policy === "deny") {
|
|
202
236
|
if (source === "tool") {
|
|
203
237
|
throw new Error(`Tool "${tool.name}" is blocked by tool policy.${reason ? `\nReason: ${reason}` : ""}`);
|
|
204
238
|
}
|
|
205
239
|
throw new Error(
|
|
206
|
-
`Tool "${tool.name}" is blocked by user policy.\n` +
|
|
207
|
-
`To allow: remove "tools.approval.${tool.name}: deny" from config.`,
|
|
240
|
+
`Tool "${policyKey ?? tool.name}" is blocked by user policy.\n` +
|
|
241
|
+
`To allow: remove "tools.approval.${policyKey ?? tool.name}: deny" from config.`,
|
|
208
242
|
);
|
|
209
243
|
}
|
|
210
244
|
|
|
@@ -32,8 +32,7 @@ export const BUILTIN_TOOL_NAMES = [
|
|
|
32
32
|
|
|
33
33
|
export type BuiltinToolName = (typeof BUILTIN_TOOL_NAMES)[number];
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
export const HIDDEN_TOOL_NAMES = ["yield", "goal"] as const;
|
|
35
|
+
export const HIDDEN_TOOL_NAMES = ["yield", "goal", "think"] as const;
|
|
37
36
|
|
|
38
37
|
export type HiddenToolName = (typeof HIDDEN_TOOL_NAMES)[number];
|
|
39
38
|
|
package/src/tools/fetch.ts
CHANGED
|
@@ -576,6 +576,19 @@ async function parseFeedToMarkdown(content: string, maxItems = 10): Promise<stri
|
|
|
576
576
|
* local fallback renderers (trafilatura, lynx, native). See #1449.
|
|
577
577
|
*/
|
|
578
578
|
const REMOTE_READER_MAX_MS = 10_000;
|
|
579
|
+
const JINA_MARKDOWN_MARKER = "Markdown Content:";
|
|
580
|
+
const JINA_READER_MAX_BYTES = 2 * 1024 * 1024;
|
|
581
|
+
|
|
582
|
+
function parseJinaReaderContent(responseBody: string): string | null {
|
|
583
|
+
const markerStart = responseBody.indexOf(JINA_MARKDOWN_MARKER);
|
|
584
|
+
if (markerStart < 0) return null;
|
|
585
|
+
|
|
586
|
+
const content = responseBody.slice(markerStart + JINA_MARKDOWN_MARKER.length).trim();
|
|
587
|
+
if (content.length < 100 || content.startsWith("Loading...") || content.startsWith("Please enable JavaScript")) {
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
return content;
|
|
591
|
+
}
|
|
579
592
|
|
|
580
593
|
/** Reader backends for {@link renderHtmlToText}, in default priority order. */
|
|
581
594
|
export type FetchProvider = "native" | "trafilatura" | "lynx" | "parallel" | "jina";
|
|
@@ -653,10 +666,16 @@ export async function renderHtmlToText(
|
|
|
653
666
|
},
|
|
654
667
|
jina: async () => {
|
|
655
668
|
const response = await fetchImpl(`https://r.jina.ai/${url}`, {
|
|
656
|
-
headers: {
|
|
669
|
+
headers: {
|
|
670
|
+
Accept: "text/markdown",
|
|
671
|
+
"X-No-Cache": "true",
|
|
672
|
+
},
|
|
657
673
|
signal: remoteSignal(),
|
|
658
674
|
});
|
|
659
|
-
|
|
675
|
+
if (!response.ok) return null;
|
|
676
|
+
const contentLength = Number(response.headers.get("content-length"));
|
|
677
|
+
if (Number.isFinite(contentLength) && contentLength > JINA_READER_MAX_BYTES) return null;
|
|
678
|
+
return parseJinaReaderContent(await response.text());
|
|
660
679
|
},
|
|
661
680
|
};
|
|
662
681
|
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { type } from "@oh-my-pi/omptype";
|
|
4
|
-
import { type ApiKey, type FetchImpl, getEnvApiKey, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
4
|
+
import { type ApiKey, type FetchImpl, getEnvApiKey, getOpenRouterHeaders, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { ProviderHttpError } from "@oh-my-pi/pi-ai/error";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_BASE_URL,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
ptree,
|
|
20
20
|
readSseJson,
|
|
21
21
|
Snowflake,
|
|
22
|
+
USER_AGENT,
|
|
22
23
|
untilAborted,
|
|
23
24
|
} from "@oh-my-pi/pi-utils";
|
|
24
|
-
import packageJson from "../../package.json" with { type: "json" };
|
|
25
25
|
import { isAuthenticated, type ModelRegistry } from "../config/model-registry";
|
|
26
26
|
import { settings } from "../config/settings";
|
|
27
27
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
28
|
-
import {
|
|
28
|
+
import { resolveXAIHttpCredentials } from "../lib/xai-http";
|
|
29
29
|
import imageGenDescription from "../prompts/tools/image-gen.md" with { type: "text" };
|
|
30
30
|
import { AUTO_IMAGE_PROVIDER_ORDER, type ImageProvider, isImageProviderId } from "./image-providers";
|
|
31
31
|
import { resolveReadPath } from "./path-utils";
|
|
@@ -897,7 +897,7 @@ function buildOpenAIImageHeaders(model: Model, apiKey: string, sessionId: string
|
|
|
897
897
|
}
|
|
898
898
|
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
|
899
899
|
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
|
900
|
-
headers.set("User-Agent",
|
|
900
|
+
headers.set("User-Agent", USER_AGENT);
|
|
901
901
|
if (sessionId) {
|
|
902
902
|
headers.set(OPENAI_HEADERS.CONVERSATION_ID, sessionId);
|
|
903
903
|
headers.set(OPENAI_HEADERS.SESSION_ID, sessionId);
|
|
@@ -1389,7 +1389,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1389
1389
|
headers: {
|
|
1390
1390
|
Authorization: `Bearer ${key}`,
|
|
1391
1391
|
"Content-Type": "application/json",
|
|
1392
|
-
"User-Agent":
|
|
1392
|
+
"User-Agent": USER_AGENT,
|
|
1393
1393
|
},
|
|
1394
1394
|
body: JSON.stringify(xaiBody),
|
|
1395
1395
|
signal: requestSignal,
|
|
@@ -1479,9 +1479,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1479
1479
|
headers: {
|
|
1480
1480
|
"Content-Type": "application/json",
|
|
1481
1481
|
Authorization: `Bearer ${key}`,
|
|
1482
|
-
|
|
1483
|
-
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1484
|
-
"X-OpenRouter-Categories": "cli-agent",
|
|
1482
|
+
...getOpenRouterHeaders(),
|
|
1485
1483
|
},
|
|
1486
1484
|
body: JSON.stringify(requestBody),
|
|
1487
1485
|
signal: requestSignal,
|
package/src/tools/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ import { wrapToolWithMetaNotice } from "./output-meta";
|
|
|
62
62
|
import { ReadTool } from "./read";
|
|
63
63
|
import type { PlanProposalHandler } from "./resolve";
|
|
64
64
|
import { SecurityScanTool } from "./security-scan";
|
|
65
|
+
import { ThinkTool } from "./think";
|
|
65
66
|
import { type TodoPhase, TodoTool } from "./todo";
|
|
66
67
|
import { WriteTool } from "./write";
|
|
67
68
|
import { isMountableUnderXdev, type XdevState } from "./xdev";
|
|
@@ -102,6 +103,7 @@ export * from "./report-tool-issue";
|
|
|
102
103
|
export * from "./resolve";
|
|
103
104
|
export * from "./review";
|
|
104
105
|
export * from "./security-scan";
|
|
106
|
+
export * from "./think";
|
|
105
107
|
export * from "./todo";
|
|
106
108
|
export * from "./tts";
|
|
107
109
|
export * from "./vibe";
|
|
@@ -444,6 +446,7 @@ export const BUILTIN_TOOLS: Record<BuiltinToolName, ToolFactory> = {
|
|
|
444
446
|
};
|
|
445
447
|
|
|
446
448
|
export const HIDDEN_TOOLS: Record<HiddenToolName, ToolFactory> = {
|
|
449
|
+
think: () => new ThinkTool(),
|
|
447
450
|
yield: s => new YieldTool(s),
|
|
448
451
|
goal: s => new GoalTool(s),
|
|
449
452
|
};
|
|
@@ -562,6 +565,9 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
562
565
|
if (session.settings.get("memory.backend") === "mnemopi" && !requestedTools.includes("memory_edit")) {
|
|
563
566
|
requestedTools.push("memory_edit");
|
|
564
567
|
}
|
|
568
|
+
if (session.settings.get("externalThinking") && !requestedTools.includes("think")) {
|
|
569
|
+
requestedTools.push("think");
|
|
570
|
+
}
|
|
565
571
|
// Auto-learn tools are gated by `autolearn.enabled` but, like the memory
|
|
566
572
|
// tools above, must also be force-included into an explicit requestedTools
|
|
567
573
|
// list so a restricted top-level session whose controller/guidance is
|
|
@@ -604,6 +610,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
604
610
|
if (name === "inspect_image") return isInspectImageToolActive(session);
|
|
605
611
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
606
612
|
if (name === "security_scan") return session.settings.get("security.enabled");
|
|
613
|
+
if (name === "think") return session.settings.get("externalThinking");
|
|
607
614
|
if (name === "ask") return session.settings.get("ask.enabled");
|
|
608
615
|
if (name === "browser") return session.settings.get("browser.enabled");
|
|
609
616
|
if (name === "computer") return session.settings.get("computer.enabled");
|
|
@@ -650,6 +657,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
650
657
|
...Object.entries(BUILTIN_TOOLS)
|
|
651
658
|
.filter(([name]) => isToolAllowed(name))
|
|
652
659
|
.map(([name, factory]) => [name, factory] as const),
|
|
660
|
+
...(session.settings.get("externalThinking") ? ([["think", HIDDEN_TOOLS.think]] as const) : []),
|
|
653
661
|
...(includeYield ? ([["yield", HIDDEN_TOOLS.yield]] as const) : []),
|
|
654
662
|
...(goalModeActive ? ([["goal", HIDDEN_TOOLS.goal]] as const) : []),
|
|
655
663
|
];
|
package/src/tools/renderers.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
|
27
27
|
import { recallToolRenderer, reflectToolRenderer, retainToolRenderer } from "./memory-render";
|
|
28
28
|
import { readToolRenderer } from "./read";
|
|
29
29
|
import { resolveRenderer } from "./resolve";
|
|
30
|
+
import { thinkToolRenderer } from "./think";
|
|
30
31
|
import { todoToolRenderer } from "./todo";
|
|
31
32
|
import { createVibeToolRenderer } from "./vibe";
|
|
32
33
|
import { writeToolRenderer } from "./write";
|
|
@@ -115,6 +116,7 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
115
116
|
get task(): ToolRenderer {
|
|
116
117
|
return taskToolRenderer as ToolRenderer;
|
|
117
118
|
},
|
|
119
|
+
think: thinkToolRenderer as ToolRenderer,
|
|
118
120
|
todo: todoToolRenderer as ToolRenderer,
|
|
119
121
|
github: githubToolRenderer as ToolRenderer,
|
|
120
122
|
goal: goalToolRenderer as ToolRenderer,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type } from "@oh-my-pi/omptype";
|
|
2
|
+
import type { AgentTool, AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
3
|
+
import { type Component, Markdown } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
5
|
+
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
6
|
+
|
|
7
|
+
const thinkSchema = type({
|
|
8
|
+
thoughts: type("string").describe("private scratchpad reasoning to retain before the next response"),
|
|
9
|
+
"+": "reject",
|
|
10
|
+
}).describe("record private intermediate reasoning before answering");
|
|
11
|
+
|
|
12
|
+
type ThinkParams = typeof thinkSchema.infer;
|
|
13
|
+
|
|
14
|
+
export type ThinkRenderArgs = {
|
|
15
|
+
thoughts?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const thinkToolRenderer = {
|
|
19
|
+
inline: true,
|
|
20
|
+
renderCall(args: ThinkRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
21
|
+
const thoughts =
|
|
22
|
+
typeof args === "object" && args !== null && "thoughts" in args && typeof args.thoughts === "string"
|
|
23
|
+
? args.thoughts
|
|
24
|
+
: "";
|
|
25
|
+
return new Markdown(thoughts, 1, 0, getMarkdownTheme(), {
|
|
26
|
+
color: (text: string) => uiTheme.fg("thinkingText", text),
|
|
27
|
+
italic: true,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
renderResult(): Component {
|
|
31
|
+
return undefined as unknown as Component;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
interface ThinkToolDetails {
|
|
36
|
+
recorded: true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Records private intermediate reasoning while native GPT reasoning is disabled. */
|
|
40
|
+
export class ThinkTool implements AgentTool<typeof thinkSchema, ThinkToolDetails> {
|
|
41
|
+
readonly name = "think";
|
|
42
|
+
readonly approval = "read" as const;
|
|
43
|
+
readonly label = "Think";
|
|
44
|
+
readonly summary = "Record private intermediate reasoning before answering";
|
|
45
|
+
readonly description =
|
|
46
|
+
"Use this private scratchpad to plan, derive, or check work before answering. Record only materially new reasoning. The user does not see this tool activity.";
|
|
47
|
+
readonly parameters = thinkSchema;
|
|
48
|
+
readonly strict = true;
|
|
49
|
+
readonly intent = "omit" as const;
|
|
50
|
+
|
|
51
|
+
async execute(_toolCallId: string, _params: ThinkParams): Promise<AgentToolResult<ThinkToolDetails>> {
|
|
52
|
+
return {
|
|
53
|
+
content: [
|
|
54
|
+
{
|
|
55
|
+
type: "text",
|
|
56
|
+
text: "------",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
details: { recorded: true },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|