@gajae-code/coding-agent 0.5.0 → 0.5.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 +36 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +26 -0
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/list-models.d.ts +6 -0
- package/dist/types/cli/setup-cli.d.ts +8 -1
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/setup.d.ts +7 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +29 -0
- package/dist/types/config/model-registry.d.ts +4 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +62 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/defaults/gjc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +25 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/gjc-grok-cli.d.ts +5 -0
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +103 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +112 -0
- package/dist/types/gjc-runtime/gc-render.d.ts +6 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/gjc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +64 -2
- package/dist/types/gjc-runtime/team-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-common.d.ts +11 -0
- package/dist/types/gjc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +13 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +29 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +7 -0
- package/dist/types/harness-control-plane/storage.d.ts +20 -0
- package/dist/types/modes/components/hook-selector.d.ts +7 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +72 -2
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +13 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +10 -0
- package/dist/types/modes/theme/defaults/index.d.ts +302 -0
- package/dist/types/modes/theme/theme.d.ts +1 -0
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/blob-store.d.ts +39 -3
- package/dist/types/session/history-storage.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +10 -1
- package/dist/types/setup/credential-import.d.ts +79 -0
- package/dist/types/skill-state/workflow-hud.d.ts +14 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/render.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +15 -1
- package/dist/types/tools/subagent-render.d.ts +7 -1
- package/dist/types/tools/subagent.d.ts +27 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +5 -0
- package/dist/types/web/search/index.d.ts +4 -4
- package/dist/types/web/search/provider.d.ts +16 -20
- package/dist/types/web/search/providers/base.d.ts +2 -1
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +14 -2
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +52 -0
- package/src/cli/args.ts +5 -0
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/fast-help.ts +2 -0
- package/src/cli/list-models.ts +13 -1
- package/src/cli/setup-cli.ts +138 -3
- package/src/cli.ts +1 -0
- package/src/commands/gc.ts +22 -0
- package/src/commands/harness.ts +7 -3
- package/src/commands/setup.ts +5 -1
- package/src/commands/ultragoal.ts +3 -1
- package/src/config/file-lock-gc.ts +193 -0
- package/src/config/file-lock.ts +66 -10
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +39 -30
- package/src/config/model-registry.ts +21 -1
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +62 -0
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/server.ts +459 -3
- package/src/defaults/gjc/agent.models.grok-cli.yml +36 -0
- package/src/defaults/gjc/extensions/grok-build/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-build/package.json +7 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/biome.json +39 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/package.json +8 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/index.ts +1 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +155 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +361 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/billing.ts +57 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +99 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +50 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/usage.ts +56 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +36 -0
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/errors.ts +44 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +131 -113
- package/src/defaults/gjc/skills/deep-interview/lateral-review-panel.md +49 -0
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +30 -8
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/defaults/gjc-grok-cli.ts +22 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +128 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +457 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +18 -26
- package/src/gjc-runtime/deep-interview-state.ts +324 -0
- package/src/gjc-runtime/gc-render.ts +70 -0
- package/src/gjc-runtime/gc-runtime.ts +403 -0
- package/src/gjc-runtime/launch-tmux.ts +3 -4
- package/src/gjc-runtime/ledger-event-renderer.ts +164 -0
- package/src/gjc-runtime/ralplan-runtime.ts +232 -19
- package/src/gjc-runtime/state-renderer.ts +12 -3
- package/src/gjc-runtime/state-runtime.ts +48 -30
- package/src/gjc-runtime/state-writer.ts +254 -7
- package/src/gjc-runtime/team-gc.ts +49 -0
- package/src/gjc-runtime/team-runtime.ts +179 -2
- package/src/gjc-runtime/tmux-common.ts +14 -0
- package/src/gjc-runtime/tmux-gc.ts +177 -0
- package/src/gjc-runtime/tmux-sessions.ts +49 -1
- package/src/gjc-runtime/ultragoal-guard.ts +155 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1239 -31
- package/src/gjc-runtime/workflow-manifest.generated.json +44 -0
- package/src/gjc-runtime/workflow-manifest.ts +12 -0
- package/src/harness-control-plane/gc-adapter.ts +184 -0
- package/src/harness-control-plane/owner.ts +14 -2
- package/src/harness-control-plane/rpc-adapter.ts +1 -1
- package/src/harness-control-plane/storage.ts +70 -0
- package/src/hooks/skill-state.ts +121 -2
- package/src/internal-urls/docs-index.generated.ts +22 -12
- package/src/lsp/defaults.json +1 -0
- package/src/main.ts +18 -3
- package/src/modes/acp/acp-agent.ts +4 -2
- package/src/modes/bridge/bridge-mode.ts +2 -1
- package/src/modes/components/history-search.ts +5 -2
- package/src/modes/components/hook-selector.ts +19 -0
- package/src/modes/components/model-selector.ts +51 -8
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/controllers/command-controller.ts +25 -6
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/selector-controller.ts +81 -1
- package/src/modes/interactive-mode.ts +11 -1
- package/src/modes/rpc/rpc-mode.ts +266 -34
- package/src/modes/shared/agent-wire/command-dispatch.ts +281 -261
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +30 -1
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +3 -0
- package/src/modes/shared/agent-wire/session-registry.ts +109 -0
- package/src/modes/shared/agent-wire/unattended-action-policy.ts +24 -0
- package/src/modes/shared/agent-wire/unattended-run-controller.ts +23 -3
- package/src/modes/shared/agent-wire/unattended-session.ts +32 -2
- package/src/modes/theme/defaults/claude-code.json +100 -0
- package/src/modes/theme/defaults/codex.json +100 -0
- package/src/modes/theme/defaults/index.ts +6 -0
- package/src/modes/theme/defaults/opencode.json +102 -0
- package/src/modes/theme/theme.ts +2 -2
- package/src/modes/types.ts +1 -1
- package/src/prompts/agents/executor.md +5 -2
- package/src/sdk.ts +29 -4
- package/src/session/agent-session.ts +99 -19
- package/src/session/blob-store.ts +59 -3
- package/src/session/history-storage.ts +32 -11
- package/src/session/session-manager.ts +72 -20
- package/src/setup/credential-import.ts +429 -0
- package/src/setup/hermes/templates/operator-instructions.v1.md +7 -1
- package/src/skill-state/deep-interview-mutation-guard.ts +2 -1
- package/src/skill-state/workflow-hud.ts +106 -10
- package/src/slash-commands/builtin-registry.ts +3 -2
- package/src/task/executor.ts +16 -1
- package/src/task/render.ts +18 -7
- package/src/tools/ask.ts +59 -2
- package/src/tools/cron.ts +1 -1
- package/src/tools/job.ts +3 -2
- package/src/tools/monitor.ts +36 -1
- package/src/tools/subagent-render.ts +128 -29
- package/src/tools/subagent.ts +173 -9
- package/src/tools/ultragoal-ask-guard.ts +39 -0
- package/src/web/search/index.ts +25 -25
- package/src/web/search/provider.ts +178 -87
- package/src/web/search/providers/base.ts +2 -1
- package/src/web/search/providers/openai-compatible.ts +151 -0
- package/src/web/search/types.ts +47 -22
|
@@ -12,7 +12,7 @@ import { Text } from "@gajae-code/tui";
|
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import type { Theme } from "../modes/theme/theme";
|
|
14
14
|
import { renderSubagentLiveProgress } from "../task/render";
|
|
15
|
-
import { Ellipsis, Hasher,
|
|
15
|
+
import { Ellipsis, Hasher, renderStatusLine } from "../tui";
|
|
16
16
|
import {
|
|
17
17
|
formatDuration,
|
|
18
18
|
formatStatusIcon,
|
|
@@ -21,12 +21,87 @@ import {
|
|
|
21
21
|
type ToolUIStatus,
|
|
22
22
|
truncateToWidth,
|
|
23
23
|
} from "./render-utils";
|
|
24
|
-
import type
|
|
24
|
+
import { type SubagentSnapshot, type SubagentToolDetails, subagentAwaitRenderedStateSignature } from "./subagent";
|
|
25
25
|
|
|
26
26
|
const PREVIEW_LINES_COLLAPSED = 1;
|
|
27
27
|
const PREVIEW_LINES_EXPANDED = 4;
|
|
28
28
|
const PREVIEW_LINE_WIDTH = 80;
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Bounded, content-addressed cache for each subagent's heavy body lines (the
|
|
32
|
+
* indented receipt fields + `renderSubagentLiveProgress` -> `renderAgentProgress`
|
|
33
|
+
* output). It is module-level so it survives the built-in renderer recreating the
|
|
34
|
+
* result component on every partial update (`tool-execution.ts` clears the content
|
|
35
|
+
* box and re-invokes `renderResult`), which a per-component `let cached` cannot.
|
|
36
|
+
*
|
|
37
|
+
* The cached body is a PURE function of its key: the per-subagent rendered-state
|
|
38
|
+
* signature (reused from the producer; excludes time-derived churn), expanded
|
|
39
|
+
* state, width, and the actual Theme instance identity. `spinnerFrame` and all
|
|
40
|
+
* wall-clock displays are deliberately kept OUT of the cached body — the animated
|
|
41
|
+
* spinner and the fresh duration live in the cheap per-subagent status line, and
|
|
42
|
+
* `renderSubagentLiveProgress` is invoked with `staticTime` so current-tool elapsed
|
|
43
|
+
* and retry countdowns are never baked into cached lines.
|
|
44
|
+
*/
|
|
45
|
+
const SUBAGENT_BODY_CACHE_MAX = 128;
|
|
46
|
+
const subagentBodyCache = new Map<bigint, string[]>();
|
|
47
|
+
let subagentBodyRenderCount = 0;
|
|
48
|
+
|
|
49
|
+
// Stable identity per Theme instance so a theme change (preview, symbol preset,
|
|
50
|
+
// color-blind reload, custom-theme reload, or in-memory swap) never reuses stale
|
|
51
|
+
// ANSI/glyph strings — distinct Theme objects get distinct ids even when the theme
|
|
52
|
+
// name is unchanged (e.g. the "<in-memory>" name).
|
|
53
|
+
const themeIdentity = new WeakMap<Theme, number>();
|
|
54
|
+
let nextThemeId = 1;
|
|
55
|
+
function themeIdentityId(theme: Theme): number {
|
|
56
|
+
let id = themeIdentity.get(theme);
|
|
57
|
+
if (id === undefined) {
|
|
58
|
+
id = nextThemeId++;
|
|
59
|
+
themeIdentity.set(theme, id);
|
|
60
|
+
}
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Test-only seam (PR3 deterministic cache-hit assertions). */
|
|
65
|
+
export const subagentBodyCacheTestHooks = {
|
|
66
|
+
get bodyRenders(): number {
|
|
67
|
+
return subagentBodyRenderCount;
|
|
68
|
+
},
|
|
69
|
+
get size(): number {
|
|
70
|
+
return subagentBodyCache.size;
|
|
71
|
+
},
|
|
72
|
+
reset(): void {
|
|
73
|
+
subagentBodyRenderCount = 0;
|
|
74
|
+
subagentBodyCache.clear();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function renderCachedSubagentBody(
|
|
79
|
+
snapshot: SubagentSnapshot,
|
|
80
|
+
signature: string,
|
|
81
|
+
expanded: boolean,
|
|
82
|
+
width: number,
|
|
83
|
+
theme: Theme,
|
|
84
|
+
): string[] {
|
|
85
|
+
const key = new Hasher().str(signature).bool(expanded).u32(width).u32(themeIdentityId(theme)).digest();
|
|
86
|
+
const hit = subagentBodyCache.get(key);
|
|
87
|
+
if (hit) {
|
|
88
|
+
// Refresh LRU recency.
|
|
89
|
+
subagentBodyCache.delete(key);
|
|
90
|
+
subagentBodyCache.set(key, hit);
|
|
91
|
+
return hit;
|
|
92
|
+
}
|
|
93
|
+
const lines = renderSubagentSnapshotBody(snapshot, expanded, theme).map(line =>
|
|
94
|
+
line.length > 0 ? truncateToWidth(line, width, Ellipsis.Omit) : "",
|
|
95
|
+
);
|
|
96
|
+
subagentBodyRenderCount += 1;
|
|
97
|
+
subagentBodyCache.set(key, lines);
|
|
98
|
+
if (subagentBodyCache.size > SUBAGENT_BODY_CACHE_MAX) {
|
|
99
|
+
const oldest = subagentBodyCache.keys().next().value;
|
|
100
|
+
if (oldest !== undefined) subagentBodyCache.delete(oldest);
|
|
101
|
+
}
|
|
102
|
+
return lines;
|
|
103
|
+
}
|
|
104
|
+
|
|
30
105
|
function statusIconKind(status: SubagentSnapshot["status"]): ToolUIStatus {
|
|
31
106
|
switch (status) {
|
|
32
107
|
case "completed":
|
|
@@ -44,13 +119,10 @@ function statusIconKind(status: SubagentSnapshot["status"]): ToolUIStatus {
|
|
|
44
119
|
}
|
|
45
120
|
}
|
|
46
121
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
spinnerFrame: number | undefined,
|
|
52
|
-
): string[] {
|
|
53
|
-
const lines: string[] = [];
|
|
122
|
+
// Cheap, dynamic per-subagent status line: the spinner may animate and the duration
|
|
123
|
+
// is the snapshot's own (fresh) value, so this line is rebuilt every frame and is
|
|
124
|
+
// NOT part of the cached body.
|
|
125
|
+
function renderSubagentStatusLine(snapshot: SubagentSnapshot, theme: Theme, spinnerFrame: number | undefined): string {
|
|
54
126
|
const icon = formatStatusIcon(
|
|
55
127
|
statusIconKind(snapshot.status),
|
|
56
128
|
theme,
|
|
@@ -59,13 +131,29 @@ function renderSubagentSnapshot(
|
|
|
59
131
|
const id = theme.fg("muted", snapshot.id);
|
|
60
132
|
const status = theme.fg("dim", snapshot.status);
|
|
61
133
|
const duration = theme.fg("dim", formatDuration(snapshot.durationMs));
|
|
62
|
-
|
|
134
|
+
return `${icon} ${id} ${status} ${duration}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Heavy, cacheable per-subagent body: a pure function of (snapshot content, expanded,
|
|
138
|
+
// theme). No spinner frame and no wall-clock displays leak in (live progress uses
|
|
139
|
+
// `staticTime`), so the module body cache can never serve stale or frozen-ticking lines.
|
|
140
|
+
function renderSubagentSnapshotBody(snapshot: SubagentSnapshot, expanded: boolean, theme: Theme): string[] {
|
|
141
|
+
const lines: string[] = [];
|
|
63
142
|
|
|
64
143
|
// Static receipt fields (parity with the markdown content for non-await actions).
|
|
65
144
|
if (snapshot.jobId !== snapshot.id) lines.push(` ${theme.fg("dim", `Job: ${snapshot.jobId}`)}`);
|
|
66
145
|
if (snapshot.agent && snapshot.agent !== "unknown") {
|
|
67
146
|
lines.push(` ${theme.fg("dim", `Agent: ${snapshot.agent} (${snapshot.agentSource})`)}`);
|
|
68
147
|
}
|
|
148
|
+
if (snapshot.effectiveModel) {
|
|
149
|
+
if (snapshot.modelFellBack && snapshot.requestedModel) {
|
|
150
|
+
lines.push(
|
|
151
|
+
` ${theme.fg("warning", `Model: ${snapshot.effectiveModel} (requested ${snapshot.requestedModel}, fell back — no credentials)`)}`,
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
lines.push(` ${theme.fg("dim", `Model: ${snapshot.effectiveModel}`)}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
69
157
|
if (snapshot.description) lines.push(` ${theme.fg("dim", `Description: ${snapshot.description}`)}`);
|
|
70
158
|
if (snapshot.outputRef) lines.push(` ${theme.fg("dim", `Output: ${snapshot.outputRef}`)}`);
|
|
71
159
|
if (snapshot.assignment) {
|
|
@@ -73,13 +161,13 @@ function renderSubagentSnapshot(
|
|
|
73
161
|
for (const al of snapshot.assignment.split("\n")) lines.push(` ${theme.fg("toolOutput", replaceTabs(al))}`);
|
|
74
162
|
}
|
|
75
163
|
|
|
76
|
-
// Defense in depth: the producer only attaches `progress` when a live
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
164
|
+
// Defense in depth: the producer only attaches `progress` when a live producer
|
|
165
|
+
// exists (subagent.ts #liveProgressFields), but the renderer also honors an
|
|
166
|
+
// explicit `liveProgressAvailable: false` so stale retained progress can never
|
|
167
|
+
// resurrect a live panel (AC5). `staticTime` keeps wall-clock displays out of
|
|
168
|
+
// these cached lines.
|
|
80
169
|
if (snapshot.progress && snapshot.liveProgressAvailable !== false) {
|
|
81
|
-
|
|
82
|
-
for (const pl of renderSubagentLiveProgress(snapshot.progress, expanded, theme, spinnerFrame)) {
|
|
170
|
+
for (const pl of renderSubagentLiveProgress(snapshot.progress, expanded, theme, undefined, true)) {
|
|
83
171
|
lines.push(` ${pl}`);
|
|
84
172
|
}
|
|
85
173
|
} else if (snapshot.liveProgressAvailable && (snapshot.status === "running" || snapshot.status === "queued")) {
|
|
@@ -124,14 +212,17 @@ export const subagentToolRenderer = {
|
|
|
124
212
|
|
|
125
213
|
const runningCount = subagents.filter(s => s.status === "running").length;
|
|
126
214
|
|
|
127
|
-
|
|
215
|
+
// Each snapshot's rendered-state signature is constant for this component
|
|
216
|
+
// instance, so compute them at most once; the heavy per-subagent bodies are
|
|
217
|
+
// cached module-side and keyed by that signature.
|
|
218
|
+
let snapshotSignatures: string[] | undefined;
|
|
128
219
|
return {
|
|
129
220
|
render(width: number): string[] {
|
|
130
221
|
const expanded = options.expanded;
|
|
131
|
-
const spinnerFrame = options.spinnerFrame ?? 0;
|
|
132
|
-
const key = new Hasher().bool(expanded).u32(width).u32(spinnerFrame).digest();
|
|
133
|
-
if (cached?.key === key) return cached.lines;
|
|
134
222
|
|
|
223
|
+
// Cheap dynamic header: may animate with `spinnerFrame` and is rebuilt
|
|
224
|
+
// every frame, but it is a single status line plus an optional hint, so
|
|
225
|
+
// it is never gated by the heavy body cache.
|
|
135
226
|
const header = renderStatusLine(
|
|
136
227
|
{
|
|
137
228
|
icon: runningCount > 0 ? "info" : "success",
|
|
@@ -144,23 +235,31 @@ export const subagentToolRenderer = {
|
|
|
144
235
|
},
|
|
145
236
|
theme,
|
|
146
237
|
);
|
|
147
|
-
|
|
148
|
-
const lines: string[] = [header];
|
|
238
|
+
const out: string[] = [truncateToWidth(header, width, Ellipsis.Omit)];
|
|
149
239
|
// Discoverability: the inline panel is a bounded preview; the session
|
|
150
240
|
// observer (ctrl+s) streams the full per-subagent message history.
|
|
151
241
|
if (runningCount > 0) {
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
for (const snapshot of subagents) {
|
|
155
|
-
lines.push(...renderSubagentSnapshot(snapshot, expanded, theme, options.spinnerFrame));
|
|
242
|
+
out.push(truncateToWidth(` ${theme.fg("dim", "(ctrl+s to observe sessions)")}`, width, Ellipsis.Omit));
|
|
156
243
|
}
|
|
157
244
|
|
|
158
|
-
|
|
159
|
-
|
|
245
|
+
snapshotSignatures ??= subagents.map(snapshot => subagentAwaitRenderedStateSignature([snapshot]));
|
|
246
|
+
subagents.forEach((snapshot, index) => {
|
|
247
|
+
// Fresh per-subagent status line (cheap), then the cached heavy body.
|
|
248
|
+
out.push(
|
|
249
|
+
truncateToWidth(
|
|
250
|
+
renderSubagentStatusLine(snapshot, theme, options.spinnerFrame),
|
|
251
|
+
width,
|
|
252
|
+
Ellipsis.Omit,
|
|
253
|
+
),
|
|
254
|
+
);
|
|
255
|
+
out.push(...renderCachedSubagentBody(snapshot, snapshotSignatures![index]!, expanded, width, theme));
|
|
256
|
+
});
|
|
160
257
|
return out;
|
|
161
258
|
},
|
|
162
259
|
invalidate() {
|
|
163
|
-
|
|
260
|
+
// The heavy body cache is content-addressed (keyed by the rendered-state
|
|
261
|
+
// signature, width, expanded, and theme), so there is no instance-local
|
|
262
|
+
// state to clear here.
|
|
164
263
|
},
|
|
165
264
|
};
|
|
166
265
|
},
|
package/src/tools/subagent.ts
CHANGED
|
@@ -2,9 +2,9 @@ import * as path from "node:path";
|
|
|
2
2
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
|
|
3
3
|
import { prompt } from "@gajae-code/utils";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
-
import { type AsyncJob, AsyncJobManager, type SubagentRecord } from "../async";
|
|
5
|
+
import { type AsyncJob, AsyncJobManager, jobElapsedMs, type SubagentRecord } from "../async";
|
|
6
6
|
import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
|
|
7
|
-
import type { AgentProgress, AgentSource } from "../task/types";
|
|
7
|
+
import type { AgentProgress, AgentSource, TaskToolDetails } from "../task/types";
|
|
8
8
|
import { Ellipsis, truncateToWidth } from "../tui";
|
|
9
9
|
import type { ToolSession } from "./index";
|
|
10
10
|
import { replaceTabs } from "./render-utils";
|
|
@@ -67,6 +67,12 @@ export interface SubagentSnapshot {
|
|
|
67
67
|
progress?: AgentProgress;
|
|
68
68
|
/** True when a live in-session progress producer exists for this subagent. */
|
|
69
69
|
liveProgressAvailable?: boolean;
|
|
70
|
+
/** Model the subagent actually runs on (after any auth fallback). */
|
|
71
|
+
effectiveModel?: string;
|
|
72
|
+
/** Model originally requested via role/preset mapping; differs from effective on fallback. */
|
|
73
|
+
requestedModel?: string;
|
|
74
|
+
/** True when the requested model lacked credentials and fell back to the parent model. */
|
|
75
|
+
modelFellBack?: boolean;
|
|
70
76
|
}
|
|
71
77
|
|
|
72
78
|
export interface SubagentToolDetails {
|
|
@@ -324,12 +330,21 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
324
330
|
);
|
|
325
331
|
const watchedJobIds = runningJobs.map(job => job.id);
|
|
326
332
|
manager.watchJobs(watchedJobIds);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
+
let lastEmittedSignature: string | undefined;
|
|
334
|
+
const emitIfChanged = (force: boolean): void => {
|
|
335
|
+
if (!onUpdate) return;
|
|
336
|
+
const result = this.#progressResult(manager, records, true);
|
|
337
|
+
const signature = subagentAwaitRenderedStateSignature(result.details?.subagents ?? []);
|
|
338
|
+
if (!force && signature === lastEmittedSignature) return;
|
|
339
|
+
lastEmittedSignature = signature;
|
|
340
|
+
onUpdate(result);
|
|
341
|
+
};
|
|
342
|
+
const progressTimer = onUpdate ? setInterval(() => emitIfChanged(false), 500) : undefined;
|
|
343
|
+
// Initial emission so the panel appears immediately; later idle ticks are
|
|
344
|
+
// gated on a value-based rendered-state signature so unchanged progress no
|
|
345
|
+
// longer rebuilds the renderer component or mutates transcript lines above
|
|
346
|
+
// the viewport (the source of the await-panel repaint storms).
|
|
347
|
+
emitIfChanged(true);
|
|
333
348
|
|
|
334
349
|
let timedOut = false;
|
|
335
350
|
try {
|
|
@@ -508,6 +523,13 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
508
523
|
lines.push(`### ${snapshot.id} — ${snapshot.status}`);
|
|
509
524
|
if (snapshot.jobId !== snapshot.id) lines.push(`Job: ${snapshot.jobId}`);
|
|
510
525
|
if (snapshot.agent) lines.push(`Agent: ${snapshot.agent} (${snapshot.agentSource})`);
|
|
526
|
+
if (snapshot.effectiveModel) {
|
|
527
|
+
lines.push(
|
|
528
|
+
snapshot.modelFellBack && snapshot.requestedModel
|
|
529
|
+
? `Model: ${snapshot.effectiveModel} (requested ${snapshot.requestedModel}, fell back — no credentials)`
|
|
530
|
+
: `Model: ${snapshot.effectiveModel}`,
|
|
531
|
+
);
|
|
532
|
+
}
|
|
511
533
|
if (snapshot.description) lines.push(`Description: ${snapshot.description}`);
|
|
512
534
|
if (snapshot.outputRef) lines.push(`Output: ${snapshot.outputRef}`);
|
|
513
535
|
if (snapshot.assignment) lines.push("Assignment:", "```", snapshot.assignment, "```");
|
|
@@ -584,9 +606,19 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
584
606
|
durationMs: 0,
|
|
585
607
|
...(verifiedOutputIds.has(record.subagentId) ? { outputRef: `agent://${record.subagentId}` } : {}),
|
|
586
608
|
...liveFields,
|
|
609
|
+
...this.#modelFields(record),
|
|
587
610
|
};
|
|
588
611
|
}
|
|
589
612
|
|
|
613
|
+
#modelFields(record?: SubagentRecord): Partial<SubagentSnapshot> {
|
|
614
|
+
if (!record) return {};
|
|
615
|
+
const fields: Partial<SubagentSnapshot> = {};
|
|
616
|
+
if (record.effectiveModel) fields.effectiveModel = record.effectiveModel;
|
|
617
|
+
if (record.requestedModel) fields.requestedModel = record.requestedModel;
|
|
618
|
+
if (record.modelFellBack) fields.modelFellBack = true;
|
|
619
|
+
return fields;
|
|
620
|
+
}
|
|
621
|
+
|
|
590
622
|
#snapshot(
|
|
591
623
|
job: AsyncJob,
|
|
592
624
|
timedOut = false,
|
|
@@ -608,7 +640,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
608
640
|
label: sanitizeText(job.label, RECEIPT_PREVIEW_WIDTH),
|
|
609
641
|
agent: subagent?.agent ?? "unknown",
|
|
610
642
|
agentSource: subagent?.agentSource ?? "bundled",
|
|
611
|
-
durationMs:
|
|
643
|
+
durationMs: jobElapsedMs(job),
|
|
612
644
|
...(subagent?.description ? { description: sanitizeText(subagent.description, RECEIPT_PREVIEW_WIDTH) } : {}),
|
|
613
645
|
...(verbosity === "full" && subagent?.assignment
|
|
614
646
|
? { assignment: sanitizeText(subagent.assignment, FULL_PREVIEW_WIDTH) }
|
|
@@ -622,6 +654,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
|
|
|
622
654
|
: {}),
|
|
623
655
|
...(outputRef ? { outputRef } : {}),
|
|
624
656
|
...(runningTimeoutGuidance ? { guidance: runningTimeoutGuidance } : {}),
|
|
657
|
+
...this.#modelFields(record),
|
|
625
658
|
};
|
|
626
659
|
}
|
|
627
660
|
|
|
@@ -696,3 +729,134 @@ function previewJobOutput(
|
|
|
696
729
|
const preview = truncateToWidth(normalized, width, Ellipsis.Unicode);
|
|
697
730
|
return { type: source.type, preview, truncated: preview !== normalized };
|
|
698
731
|
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Canonical, value-based rendered-state signature for the `subagent` await panel.
|
|
735
|
+
*
|
|
736
|
+
* Producer-side await gating compares this signature against the last emitted one
|
|
737
|
+
* and only fires `onUpdate` when the *rendered* state actually changed. Unchanged
|
|
738
|
+
* idle ticks therefore stop rebuilding the renderer component and stop mutating
|
|
739
|
+
* transcript lines above the viewport, which is what triggers TUI full-redraw
|
|
740
|
+
* storms (`tui.ts` `firstChanged < viewportTop`).
|
|
741
|
+
*
|
|
742
|
+
* It is deliberately value-based, never object identity: `AsyncJobManager.record-
|
|
743
|
+
* SubagentProgress` stores a `structuredClone` but `getSubagentProgress` returns
|
|
744
|
+
* the retained object by reference, so identity comparison would be both noisy and
|
|
745
|
+
* unsafe.
|
|
746
|
+
*
|
|
747
|
+
* Time-derived fields are intentionally excluded so the panel does not churn while
|
|
748
|
+
* idle: raw durations (`durationMs`), current-tool elapsed (`currentToolStartMs`),
|
|
749
|
+
* and retry countdowns (`retryState.startedAtMs`) are omitted. Idle duration and
|
|
750
|
+
* countdown ticking is sacrificed by design; every real transition still changes
|
|
751
|
+
* the signature.
|
|
752
|
+
*/
|
|
753
|
+
export function subagentAwaitRenderedStateSignature(subagents: readonly SubagentSnapshot[]): string {
|
|
754
|
+
return JSON.stringify(subagents.map(canonicalizeSnapshotForSignature));
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function canonicalizeSnapshotForSignature(snapshot: SubagentSnapshot): unknown {
|
|
758
|
+
return {
|
|
759
|
+
id: snapshot.id,
|
|
760
|
+
jobId: snapshot.jobId,
|
|
761
|
+
status: snapshot.status,
|
|
762
|
+
label: snapshot.label,
|
|
763
|
+
agent: snapshot.agent,
|
|
764
|
+
agentSource: snapshot.agentSource,
|
|
765
|
+
description: snapshot.description ?? null,
|
|
766
|
+
assignment: snapshot.assignment ?? null,
|
|
767
|
+
resultText: snapshot.resultText ?? null,
|
|
768
|
+
errorText: snapshot.errorText ?? null,
|
|
769
|
+
resultPreview: snapshot.resultPreview ?? null,
|
|
770
|
+
outputRef: snapshot.outputRef ?? null,
|
|
771
|
+
truncated: snapshot.truncated ?? false,
|
|
772
|
+
guidance: snapshot.guidance ?? null,
|
|
773
|
+
liveProgressAvailable: snapshot.liveProgressAvailable ?? null,
|
|
774
|
+
effectiveModel: snapshot.effectiveModel ?? null,
|
|
775
|
+
requestedModel: snapshot.requestedModel ?? null,
|
|
776
|
+
modelFellBack: snapshot.modelFellBack ?? false,
|
|
777
|
+
// durationMs intentionally excluded (time-derived; would defeat idle gating).
|
|
778
|
+
progress: snapshot.progress ? canonicalizeProgressForSignature(snapshot.progress) : null,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function canonicalizeProgressForSignature(progress: AgentProgress): unknown {
|
|
783
|
+
return {
|
|
784
|
+
id: progress.id,
|
|
785
|
+
agent: progress.agent,
|
|
786
|
+
agentSource: progress.agentSource,
|
|
787
|
+
status: progress.status,
|
|
788
|
+
task: progress.task,
|
|
789
|
+
assignment: progress.assignment ?? null,
|
|
790
|
+
description: progress.description ?? null,
|
|
791
|
+
lastIntent: progress.lastIntent ?? null,
|
|
792
|
+
currentTool: progress.currentTool ?? null,
|
|
793
|
+
currentToolArgs: progress.currentToolArgs ?? null,
|
|
794
|
+
// currentToolStartMs intentionally excluded (only drives elapsed rendering).
|
|
795
|
+
recentTools: progress.recentTools.map(tool => ({ tool: tool.tool, args: tool.args })),
|
|
796
|
+
recentOutput: progress.recentOutput,
|
|
797
|
+
toolCount: progress.toolCount,
|
|
798
|
+
tokens: progress.tokens,
|
|
799
|
+
contextTokens: progress.contextTokens ?? null,
|
|
800
|
+
contextWindow: progress.contextWindow ?? null,
|
|
801
|
+
cost: progress.cost,
|
|
802
|
+
modelOverride: progress.modelOverride ?? null,
|
|
803
|
+
modelSubstitutionWarning: progress.modelSubstitutionWarning ?? null,
|
|
804
|
+
// durationMs intentionally excluded (time-derived).
|
|
805
|
+
extractedToolData: progress.extractedToolData
|
|
806
|
+
? canonicalizeExtractedToolDataForSignature(progress.extractedToolData)
|
|
807
|
+
: null,
|
|
808
|
+
retryState: progress.retryState
|
|
809
|
+
? {
|
|
810
|
+
attempt: progress.retryState.attempt,
|
|
811
|
+
maxAttempts: progress.retryState.maxAttempts,
|
|
812
|
+
unbounded: progress.retryState.unbounded ?? false,
|
|
813
|
+
delayMs: progress.retryState.delayMs,
|
|
814
|
+
errorMessage: progress.retryState.errorMessage,
|
|
815
|
+
// startedAtMs intentionally excluded (drives countdown only).
|
|
816
|
+
}
|
|
817
|
+
: null,
|
|
818
|
+
retryFailure: progress.retryFailure ?? null,
|
|
819
|
+
inflightTaskDetails: progress.inflightTaskDetails
|
|
820
|
+
? canonicalizeTaskDetailsForSignature(progress.inflightTaskDetails)
|
|
821
|
+
: null,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Nested `task` data (`extractedToolData.task` and `inflightTaskDetails`) is the
|
|
827
|
+
* one place the await signature reaches into a live, ticking structure: nested
|
|
828
|
+
* `AgentProgress` carries the same time-derived fields excluded above, and
|
|
829
|
+
* `TaskToolDetails` adds `totalDurationMs` / per-result `durationMs`. Signing it
|
|
830
|
+
* wholesale would defeat idle gating whenever an awaited subagent is itself inside
|
|
831
|
+
* a live `task` call, so these helpers canonicalize the rendered, non-time subset
|
|
832
|
+
* recursively (mutually recursive with `canonicalizeProgressForSignature`).
|
|
833
|
+
*/
|
|
834
|
+
function canonicalizeExtractedToolDataForSignature(data: Record<string, unknown[]>): Record<string, unknown> {
|
|
835
|
+
const out: Record<string, unknown> = {};
|
|
836
|
+
for (const key of Object.keys(data)) {
|
|
837
|
+
// Only the `task` key holds time-ticking `TaskToolDetails`; other handler
|
|
838
|
+
// data (yield/report_finding/generic) is stable and passes through as-is.
|
|
839
|
+
out[key] = key === "task" ? (data[key] as TaskToolDetails[]).map(canonicalizeTaskDetailsForSignature) : data[key];
|
|
840
|
+
}
|
|
841
|
+
return out;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function canonicalizeTaskDetailsForSignature(details: TaskToolDetails): unknown {
|
|
845
|
+
// `extractedToolData` is an untyped boundary (`Record<string, unknown[]>`), so
|
|
846
|
+
// guard each field instead of trusting the `TaskToolDetails` cast.
|
|
847
|
+
return {
|
|
848
|
+
// totalDurationMs intentionally excluded (time-derived).
|
|
849
|
+
results: Array.isArray(details.results) ? details.results.map(canonicalizeTaskResultForSignature) : null,
|
|
850
|
+
progress: Array.isArray(details.progress) ? details.progress.map(canonicalizeProgressForSignature) : null,
|
|
851
|
+
async: details.async
|
|
852
|
+
? { state: details.async.state, jobId: details.async.jobId, type: details.async.type }
|
|
853
|
+
: null,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
function canonicalizeTaskResultForSignature(result: TaskToolDetails["results"][number]): unknown {
|
|
858
|
+
// Completed results do not tick, but drop `durationMs` so the only time-derived
|
|
859
|
+
// field in the receipt can never reintroduce idle churn.
|
|
860
|
+
const { durationMs: _durationMs, ...rest } = result;
|
|
861
|
+
return rest;
|
|
862
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentTool } from "@gajae-code/agent-core";
|
|
2
|
+
import { isUltragoalAskBlocked, type UltragoalAskBlockDiagnostic } from "../gjc-runtime/ultragoal-guard";
|
|
3
|
+
import { ToolError } from "./tool-errors";
|
|
4
|
+
|
|
5
|
+
const ULTRAGOAL_ASK_GUARD = Symbol.for("gajae-code.ultragoalAskGuard");
|
|
6
|
+
|
|
7
|
+
type GuardedTool = AgentTool & { [ULTRAGOAL_ASK_GUARD]?: true };
|
|
8
|
+
|
|
9
|
+
export function formatUltragoalAskBlockMessage(diagnostic: UltragoalAskBlockDiagnostic): string {
|
|
10
|
+
return [
|
|
11
|
+
diagnostic.message,
|
|
12
|
+
`Ultragoal ask guard blocked ask (source: ${diagnostic.source}; reason: ${diagnostic.reason}).`,
|
|
13
|
+
"Use `gjc ultragoal record-review-blockers` to record the blocker instead of asking the user.",
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function assertUltragoalAskAllowed(cwd: string): Promise<void> {
|
|
18
|
+
const diagnostic = await isUltragoalAskBlocked(cwd);
|
|
19
|
+
if (!diagnostic.active) return;
|
|
20
|
+
throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function guardToolForUltragoalAsk<T extends AgentTool>(tool: T, getCwd: () => string): T {
|
|
24
|
+
if (tool.name !== "ask") return tool;
|
|
25
|
+
const candidate = tool as GuardedTool;
|
|
26
|
+
if (candidate[ULTRAGOAL_ASK_GUARD]) return tool;
|
|
27
|
+
const wrapped = new Proxy(tool, {
|
|
28
|
+
get(target, prop, receiver) {
|
|
29
|
+
if (prop === ULTRAGOAL_ASK_GUARD) return true;
|
|
30
|
+
if (prop !== "execute") return Reflect.get(target, prop, receiver);
|
|
31
|
+
return async (...args: unknown[]): Promise<unknown> => {
|
|
32
|
+
await assertUltragoalAskAllowed(getCwd());
|
|
33
|
+
return Reflect.apply(target.execute, target, args);
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
}) as T & GuardedTool;
|
|
37
|
+
wrapped[ULTRAGOAL_ASK_GUARD] = true;
|
|
38
|
+
return wrapped as T;
|
|
39
|
+
}
|
package/src/web/search/index.ts
CHANGED
|
@@ -8,7 +8,6 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
8
8
|
import type { AuthStorage } from "@gajae-code/ai";
|
|
9
9
|
import { prompt } from "@gajae-code/utils";
|
|
10
10
|
import * as z from "zod/v4";
|
|
11
|
-
import { parseModelString } from "../../config/model-resolver";
|
|
12
11
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
13
12
|
import type { Theme } from "../../modes/theme/theme";
|
|
14
13
|
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
|
@@ -19,7 +18,7 @@ import { formatAge } from "../../tools/render-utils";
|
|
|
19
18
|
import { throwIfAborted } from "../../tools/tool-errors";
|
|
20
19
|
import { getSearchProviderLabel, resolveProviderChain, type SearchProvider } from "./provider";
|
|
21
20
|
import { renderSearchCall, renderSearchResult, type SearchRenderDetails } from "./render";
|
|
22
|
-
import type { SearchProviderId, SearchResponse } from "./types";
|
|
21
|
+
import type { ActiveSearchModelContext, SearchProviderId, SearchResponse } from "./types";
|
|
23
22
|
import { SearchProviderError } from "./types";
|
|
24
23
|
|
|
25
24
|
/** Web search tool parameters schema */
|
|
@@ -116,21 +115,11 @@ function formatForLLM(response: SearchResponse): string {
|
|
|
116
115
|
return parts.join("\n");
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
/** Best-effort active model provider: prefer the resolved Model, fall back to parsing the model string. */
|
|
120
|
-
function resolveActiveModelProvider(
|
|
121
|
-
modelProvider: string | undefined,
|
|
122
|
-
modelString: string | undefined,
|
|
123
|
-
): string | undefined {
|
|
124
|
-
if (modelProvider) return modelProvider;
|
|
125
|
-
if (modelString) return parseModelString(modelString)?.provider;
|
|
126
|
-
return undefined;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
118
|
interface ExecuteSearchOptions {
|
|
130
119
|
authStorage: AuthStorage;
|
|
131
120
|
sessionId?: string;
|
|
132
121
|
signal?: AbortSignal;
|
|
133
|
-
|
|
122
|
+
activeModelContext?: ActiveSearchModelContext;
|
|
134
123
|
}
|
|
135
124
|
|
|
136
125
|
/** Execute web search */
|
|
@@ -139,11 +128,17 @@ async function executeSearch(
|
|
|
139
128
|
params: SearchQueryParams,
|
|
140
129
|
options: ExecuteSearchOptions,
|
|
141
130
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
142
|
-
const { authStorage, sessionId, signal,
|
|
131
|
+
const { authStorage, sessionId, signal, activeModelContext } = options;
|
|
143
132
|
// Pass `params.provider` straight through: when omitted (the normal model-facing
|
|
144
133
|
// path) it is `undefined`, so `resolveProviderChain` applies the settings-configured
|
|
145
134
|
// preferred provider. Coalescing to "auto" here would silently bypass that preference.
|
|
146
|
-
const providers = await resolveProviderChain(
|
|
135
|
+
const providers = await resolveProviderChain({
|
|
136
|
+
authStorage,
|
|
137
|
+
sessionId,
|
|
138
|
+
signal,
|
|
139
|
+
preferredProvider: params.provider,
|
|
140
|
+
activeModelContext,
|
|
141
|
+
});
|
|
147
142
|
|
|
148
143
|
const failures: Array<{ provider: SearchProvider; error: unknown }> = [];
|
|
149
144
|
let lastProvider = providers[0];
|
|
@@ -161,6 +156,7 @@ async function executeSearch(
|
|
|
161
156
|
signal,
|
|
162
157
|
authStorage,
|
|
163
158
|
sessionId,
|
|
159
|
+
activeModelContext,
|
|
164
160
|
});
|
|
165
161
|
|
|
166
162
|
const text = formatForLLM(response);
|
|
@@ -210,14 +206,19 @@ async function executeSearch(
|
|
|
210
206
|
*/
|
|
211
207
|
export async function runSearchQuery(
|
|
212
208
|
params: SearchQueryParams,
|
|
213
|
-
options: {
|
|
209
|
+
options: {
|
|
210
|
+
authStorage?: AuthStorage;
|
|
211
|
+
sessionId?: string;
|
|
212
|
+
signal?: AbortSignal;
|
|
213
|
+
activeModelContext?: ActiveSearchModelContext;
|
|
214
|
+
} = {},
|
|
214
215
|
): Promise<{ content: Array<{ type: "text"; text: string }>; details: SearchRenderDetails }> {
|
|
215
216
|
const authStorage = options.authStorage ?? (await discoverAuthStorage());
|
|
216
217
|
return executeSearch("cli-web-search", params, {
|
|
217
218
|
authStorage,
|
|
218
219
|
sessionId: options.sessionId,
|
|
219
220
|
signal: options.signal,
|
|
220
|
-
|
|
221
|
+
activeModelContext: options.activeModelContext,
|
|
221
222
|
});
|
|
222
223
|
}
|
|
223
224
|
|
|
@@ -251,11 +252,10 @@ export class WebSearchTool implements AgentTool<typeof webSearchSchema, SearchRe
|
|
|
251
252
|
): Promise<AgentToolResult<SearchRenderDetails>> {
|
|
252
253
|
const authStorage = this.#session.authStorage ?? (await discoverAuthStorage());
|
|
253
254
|
const sessionId = this.#session.getSessionId?.() ?? undefined;
|
|
254
|
-
const
|
|
255
|
-
this.#session.model
|
|
256
|
-
|
|
257
|
-
);
|
|
258
|
-
return executeSearch(_toolCallId, params, { authStorage, sessionId, signal, activeModelProvider });
|
|
255
|
+
const activeModelContext = this.#session.model
|
|
256
|
+
? this.#session.modelRegistry?.getActiveSearchModelContext(this.#session.model)
|
|
257
|
+
: undefined;
|
|
258
|
+
return executeSearch(_toolCallId, params, { authStorage, sessionId, signal, activeModelContext });
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -279,7 +279,7 @@ export const webSearchCustomTool: CustomTool<typeof webSearchSchema, SearchRende
|
|
|
279
279
|
authStorage,
|
|
280
280
|
sessionId,
|
|
281
281
|
signal,
|
|
282
|
-
|
|
282
|
+
activeModelContext: ctx.model ? ctx.modelRegistry?.getActiveSearchModelContext(ctx.model) : undefined,
|
|
283
283
|
});
|
|
284
284
|
},
|
|
285
285
|
|
|
@@ -296,6 +296,6 @@ export function getSearchTools(): CustomTool<any, any>[] {
|
|
|
296
296
|
return [webSearchCustomTool];
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
export { getSearchProvider, setPreferredSearchProvider } from "./provider";
|
|
299
|
+
export { getSearchProvider, setPreferredSearchProvider, setSearchFallbackProviders } from "./provider";
|
|
300
300
|
export type { SearchProviderId as SearchProvider, SearchResponse } from "./types";
|
|
301
|
-
export { isSearchProviderPreference } from "./types";
|
|
301
|
+
export { isConfigurableSearchProviderId, isSearchProviderPreference } from "./types";
|