@gajae-code/coding-agent 0.3.0 → 0.3.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 +32 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +3 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +8 -0
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +47 -0
- package/dist/types/config/settings-schema.d.ts +14 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -1
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/main.d.ts +10 -1
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +9 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +6 -4
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +19 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +10 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +19 -0
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +36 -0
- package/src/config/settings-schema.ts +16 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -3
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +231 -38
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +8 -10
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +88 -6
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/custom-provider-wizard.ts +318 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +86 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +2 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +5 -1
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +9 -1
- package/src/task/executor.ts +31 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +259 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +6 -18
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +4 -3
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
package/src/commands/launch.ts
CHANGED
|
@@ -36,6 +36,12 @@ export default class Index extends Command {
|
|
|
36
36
|
plan: Flags.string({
|
|
37
37
|
description: "Plan model for architectural planning (or GJC_PLAN_MODEL env)",
|
|
38
38
|
}),
|
|
39
|
+
mpreset: Flags.string({
|
|
40
|
+
description: "Model profile preset to activate for this session",
|
|
41
|
+
}),
|
|
42
|
+
default: Flags.boolean({
|
|
43
|
+
description: "Persist --mpreset as the default model profile",
|
|
44
|
+
}),
|
|
39
45
|
provider: Flags.string({
|
|
40
46
|
description: "Provider to use (legacy; prefer --model)",
|
|
41
47
|
}),
|
|
@@ -52,8 +58,8 @@ export default class Index extends Command {
|
|
|
52
58
|
description: "Allow starting in ~ without auto-switching to a temp dir",
|
|
53
59
|
}),
|
|
54
60
|
mode: Flags.string({
|
|
55
|
-
description: "Output mode: text (default), json, rpc,
|
|
56
|
-
options: ["text", "json", "rpc", "acp", "rpc-ui"],
|
|
61
|
+
description: "Output mode: text (default), json, rpc, acp, rpc-ui, or bridge",
|
|
62
|
+
options: ["text", "json", "rpc", "acp", "rpc-ui", "bridge"],
|
|
57
63
|
}),
|
|
58
64
|
print: Flags.boolean({
|
|
59
65
|
char: "p",
|
|
@@ -136,6 +142,8 @@ export default class Index extends Command {
|
|
|
136
142
|
`# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
|
|
137
143
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
138
144
|
`# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
|
|
145
|
+
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-standard`,
|
|
146
|
+
`# Persist a model profile as the default\n ${APP_NAME} --mpreset opencode-go-pro --default`,
|
|
139
147
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/--path--/session.jsonl`,
|
|
140
148
|
];
|
|
141
149
|
|
package/src/commands/state.ts
CHANGED
|
@@ -9,9 +9,10 @@ export default class State extends Command {
|
|
|
9
9
|
'$ gjc state write --input \'{"state":{"interview_id":"abc"}}\' --mode deep-interview --json',
|
|
10
10
|
"$ gjc state clear --mode deep-interview",
|
|
11
11
|
"$ gjc state deep-interview read --json",
|
|
12
|
-
'$ gjc state ralplan write --input \'{"phase":"
|
|
12
|
+
'$ gjc state ralplan write --input \'{"phase":"planner","active":true}\' --json',
|
|
13
13
|
"$ gjc state team contract",
|
|
14
14
|
"$ gjc state deep-interview handoff --to ralplan --json",
|
|
15
|
+
"$ gjc state doctor --skill ralplan --json",
|
|
15
16
|
];
|
|
16
17
|
|
|
17
18
|
async run(): Promise<void> {
|
package/src/commands/team.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
2
|
+
import { renderCliWriteReceipt } from "../gjc-runtime/cli-write-receipt";
|
|
2
3
|
import { renderTeamStatusMarkdown } from "../gjc-runtime/state-renderer";
|
|
3
4
|
import {
|
|
4
5
|
buildTeamHudSummary,
|
|
@@ -45,6 +46,23 @@ function formatTaskCounts(counts: Record<string, number>): string {
|
|
|
45
46
|
.join(" ");
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function snapshotWriteReceipt(snapshot: GjcTeamSnapshot): Record<string, unknown> {
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
team_name: snapshot.team_name,
|
|
53
|
+
phase: snapshot.phase,
|
|
54
|
+
state_dir: snapshot.state_dir,
|
|
55
|
+
tmux_session: snapshot.tmux_session,
|
|
56
|
+
tmux_target: snapshot.tmux_target,
|
|
57
|
+
worker_count: snapshot.workers.length,
|
|
58
|
+
task_counts: snapshot.task_counts,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeReceipt(value: Record<string, unknown>): void {
|
|
63
|
+
process.stdout.write(renderCliWriteReceipt(value));
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
function parseInputFlag(argv: string[]): Record<string, unknown> {
|
|
49
67
|
const index = argv.indexOf("--input");
|
|
50
68
|
if (index < 0) return {};
|
|
@@ -124,7 +142,7 @@ export default class Team extends Command {
|
|
|
124
142
|
const snapshot = await monitorGjcTeamSnapshot(teamName);
|
|
125
143
|
await syncTeamHud(snapshot);
|
|
126
144
|
if (json) {
|
|
127
|
-
|
|
145
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
128
146
|
return;
|
|
129
147
|
}
|
|
130
148
|
writeText([
|
|
@@ -141,7 +159,7 @@ export default class Team extends Command {
|
|
|
141
159
|
const snapshot = await shutdownGjcTeam(teamName);
|
|
142
160
|
await syncTeamHud(snapshot);
|
|
143
161
|
if (json) {
|
|
144
|
-
|
|
162
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
145
163
|
return;
|
|
146
164
|
}
|
|
147
165
|
writeText([`team: ${snapshot.team_name}`, `phase: ${snapshot.phase}`, `state: ${snapshot.state_dir}`]);
|
|
@@ -174,7 +192,7 @@ export default class Team extends Command {
|
|
|
174
192
|
// API operations without a resolvable snapshot leave HUD state unchanged.
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
|
-
|
|
195
|
+
writeReceipt(result as Record<string, unknown>);
|
|
178
196
|
return;
|
|
179
197
|
}
|
|
180
198
|
|
|
@@ -183,7 +201,7 @@ export default class Team extends Command {
|
|
|
183
201
|
const snapshot = await startGjcTeam({ ...options, dryRun });
|
|
184
202
|
await syncTeamHud(snapshot);
|
|
185
203
|
if (json) {
|
|
186
|
-
|
|
204
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
187
205
|
return;
|
|
188
206
|
}
|
|
189
207
|
writeText([
|
|
@@ -38,6 +38,7 @@ interface AppKeybindings {
|
|
|
38
38
|
"app.session.fork": true;
|
|
39
39
|
"app.session.resume": true;
|
|
40
40
|
"app.session.observe": true;
|
|
41
|
+
"app.jobs.open": true;
|
|
41
42
|
"app.session.togglePath": true;
|
|
42
43
|
"app.session.toggleSort": true;
|
|
43
44
|
"app.session.rename": true;
|
|
@@ -149,6 +150,11 @@ export const KEYBINDINGS = {
|
|
|
149
150
|
defaultKeys: "ctrl+s",
|
|
150
151
|
description: "Observe subagent sessions",
|
|
151
152
|
},
|
|
153
|
+
|
|
154
|
+
"app.jobs.open": {
|
|
155
|
+
defaultKeys: "alt+j",
|
|
156
|
+
description: "Open monitor/cron jobs overlay",
|
|
157
|
+
},
|
|
152
158
|
"app.session.togglePath": {
|
|
153
159
|
defaultKeys: "ctrl+p",
|
|
154
160
|
description: "Toggle session path display",
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
2
|
+
import type { Api, Model } from "@gajae-code/ai";
|
|
3
|
+
import type { AgentSession } from "../session/agent-session";
|
|
4
|
+
import {
|
|
5
|
+
aggregateModelProfileRequiredProviders,
|
|
6
|
+
formatAvailableProfileNames,
|
|
7
|
+
resolveProfileBindings,
|
|
8
|
+
} from "./model-profiles";
|
|
9
|
+
import { type GjcModelAssignmentTargetId, isAuthenticated, type ModelRegistry } from "./model-registry";
|
|
10
|
+
import { resolveModelRoleValue } from "./model-resolver";
|
|
11
|
+
import type { Settings } from "./settings";
|
|
12
|
+
|
|
13
|
+
export interface PrepareModelProfileActivationOptions {
|
|
14
|
+
session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId">;
|
|
15
|
+
modelRegistry: Pick<
|
|
16
|
+
ModelRegistry,
|
|
17
|
+
| "getModelProfile"
|
|
18
|
+
| "getModelProfiles"
|
|
19
|
+
| "getAvailableModelProfileNames"
|
|
20
|
+
| "getApiKeyForProvider"
|
|
21
|
+
| "getAll"
|
|
22
|
+
| "resolveCanonicalModel"
|
|
23
|
+
| "getCanonicalVariants"
|
|
24
|
+
| "getCanonicalId"
|
|
25
|
+
>;
|
|
26
|
+
settings: Pick<Settings, "get">;
|
|
27
|
+
profileName: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface PreparedModelProfileActivation {
|
|
31
|
+
profileName: string;
|
|
32
|
+
session: Pick<AgentSession, "model" | "thinkingLevel" | "sessionId" | "setModelTemporary">;
|
|
33
|
+
settings: Pick<Settings, "get" | "override" | "set" | "flush">;
|
|
34
|
+
previousModel: Model<Api> | undefined;
|
|
35
|
+
previousThinkingLevel: ThinkingLevel | undefined;
|
|
36
|
+
previousAgentModelOverrides: Record<string, string>;
|
|
37
|
+
defaultModel: Model<Api> | undefined;
|
|
38
|
+
defaultThinkingLevel: ThinkingLevel | undefined;
|
|
39
|
+
agentModelOverrides: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
|
|
43
|
+
return `Model profile "${profileName}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function prepareModelProfileActivation(
|
|
47
|
+
options: PrepareModelProfileActivationOptions,
|
|
48
|
+
): Promise<PreparedModelProfileActivation> {
|
|
49
|
+
const profile = options.modelRegistry.getModelProfile(options.profileName);
|
|
50
|
+
if (!profile) {
|
|
51
|
+
const available = formatAvailableProfileNames(options.modelRegistry.getModelProfiles());
|
|
52
|
+
throw new Error(`Unknown model profile "${options.profileName}". Available profiles: ${available}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const missingProviders: string[] = [];
|
|
56
|
+
for (const provider of aggregateModelProfileRequiredProviders(profile.requiredProviders, profile)) {
|
|
57
|
+
const apiKey = await options.modelRegistry.getApiKeyForProvider(provider, options.session.sessionId);
|
|
58
|
+
if (!isAuthenticated(apiKey)) {
|
|
59
|
+
missingProviders.push(provider);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (missingProviders.length > 0) {
|
|
63
|
+
throw new Error(formatModelProfileCredentialError(options.profileName, missingProviders));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const availableModels = options.modelRegistry.getAll();
|
|
67
|
+
const bindings = resolveProfileBindings(profile);
|
|
68
|
+
const resolvedDefault = bindings.defaultSelector
|
|
69
|
+
? resolveModelRoleValue(bindings.defaultSelector, availableModels, {
|
|
70
|
+
settings: options.settings as Settings,
|
|
71
|
+
modelRegistry: options.modelRegistry,
|
|
72
|
+
})
|
|
73
|
+
: undefined;
|
|
74
|
+
if (bindings.defaultSelector && !resolvedDefault?.model) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Model profile "${options.profileName}" default selector did not resolve: ${bindings.defaultSelector}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const agentModelOverrides: Record<string, string> = {};
|
|
81
|
+
for (const [role, selector] of Object.entries(bindings.agentModelOverrides) as [
|
|
82
|
+
GjcModelAssignmentTargetId,
|
|
83
|
+
string,
|
|
84
|
+
][]) {
|
|
85
|
+
const resolved = resolveModelRoleValue(selector, availableModels, {
|
|
86
|
+
settings: options.settings as Settings,
|
|
87
|
+
modelRegistry: options.modelRegistry,
|
|
88
|
+
});
|
|
89
|
+
if (!resolved.model) {
|
|
90
|
+
throw new Error(`Model profile "${options.profileName}" ${role} selector did not resolve: ${selector}`);
|
|
91
|
+
}
|
|
92
|
+
agentModelOverrides[role] = selector;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
profileName: options.profileName,
|
|
97
|
+
session: options.session as PreparedModelProfileActivation["session"],
|
|
98
|
+
settings: options.settings as PreparedModelProfileActivation["settings"],
|
|
99
|
+
previousModel: options.session.model,
|
|
100
|
+
previousThinkingLevel: options.session.thinkingLevel,
|
|
101
|
+
previousAgentModelOverrides: { ...options.settings.get("task.agentModelOverrides") },
|
|
102
|
+
defaultModel: resolvedDefault?.model,
|
|
103
|
+
defaultThinkingLevel: resolvedDefault?.thinkingLevel,
|
|
104
|
+
agentModelOverrides,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function applyPreparedModelProfileActivation(
|
|
109
|
+
prepared: PreparedModelProfileActivation,
|
|
110
|
+
options: { persistDefault?: boolean } = {},
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const previousModel = prepared.previousModel;
|
|
113
|
+
const previousThinkingLevel = prepared.previousThinkingLevel;
|
|
114
|
+
const previousAgentModelOverrides = prepared.previousAgentModelOverrides;
|
|
115
|
+
const previousPersistedDefault = prepared.settings.get("modelProfile.default");
|
|
116
|
+
let modelChanged = false;
|
|
117
|
+
let overridesChanged = false;
|
|
118
|
+
let defaultChanged = false;
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
if (prepared.defaultModel) {
|
|
122
|
+
await prepared.session.setModelTemporary(prepared.defaultModel, prepared.defaultThinkingLevel);
|
|
123
|
+
modelChanged = true;
|
|
124
|
+
}
|
|
125
|
+
if (Object.keys(prepared.agentModelOverrides).length > 0) {
|
|
126
|
+
prepared.settings.override("task.agentModelOverrides", {
|
|
127
|
+
...prepared.settings.get("task.agentModelOverrides"),
|
|
128
|
+
...prepared.agentModelOverrides,
|
|
129
|
+
});
|
|
130
|
+
overridesChanged = true;
|
|
131
|
+
}
|
|
132
|
+
if (options.persistDefault) {
|
|
133
|
+
prepared.settings.set("modelProfile.default", prepared.profileName);
|
|
134
|
+
defaultChanged = true;
|
|
135
|
+
await prepared.settings.flush();
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (defaultChanged) {
|
|
139
|
+
prepared.settings.set("modelProfile.default", previousPersistedDefault);
|
|
140
|
+
}
|
|
141
|
+
if (overridesChanged) {
|
|
142
|
+
prepared.settings.override("task.agentModelOverrides", previousAgentModelOverrides);
|
|
143
|
+
}
|
|
144
|
+
if (modelChanged && previousModel) {
|
|
145
|
+
await prepared.session.setModelTemporary(previousModel, previousThinkingLevel);
|
|
146
|
+
}
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function activateModelProfile(
|
|
152
|
+
options: PrepareModelProfileActivationOptions,
|
|
153
|
+
applyOptions: { persistDefault?: boolean } = {},
|
|
154
|
+
): Promise<void> {
|
|
155
|
+
const prepared = await prepareModelProfileActivation(options);
|
|
156
|
+
await applyPreparedModelProfileActivation(prepared, applyOptions);
|
|
157
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type { GjcModelAssignmentTargetId } from "./model-registry";
|
|
2
|
+
import type { ModelsConfig } from "./models-config-schema";
|
|
3
|
+
|
|
4
|
+
export type ModelProfileRole = GjcModelAssignmentTargetId;
|
|
5
|
+
|
|
6
|
+
export interface ModelProfileDefinition {
|
|
7
|
+
name: string;
|
|
8
|
+
requiredProviders: string[];
|
|
9
|
+
modelMapping: Partial<Record<ModelProfileRole, string>>;
|
|
10
|
+
source: "builtin" | "user";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ResolvedProfileBinding {
|
|
14
|
+
defaultSelector?: string;
|
|
15
|
+
agentModelOverrides: Partial<Record<Exclude<ModelProfileRole, "default">, string>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseModelSelectorProvider(selector: string): string | undefined {
|
|
19
|
+
const slashIdx = selector.indexOf("/");
|
|
20
|
+
if (slashIdx <= 0) return undefined;
|
|
21
|
+
return selector.slice(0, slashIdx);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function deriveModelProfileMappedProviders(definition: Pick<ModelProfileDefinition, "modelMapping">): string[] {
|
|
25
|
+
const providers = new Set<string>();
|
|
26
|
+
for (const selector of Object.values(definition.modelMapping)) {
|
|
27
|
+
if (!selector) continue;
|
|
28
|
+
const provider = parseModelSelectorProvider(selector);
|
|
29
|
+
if (provider) providers.add(provider);
|
|
30
|
+
}
|
|
31
|
+
return [...providers].sort((a, b) => a.localeCompare(b));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function aggregateModelProfileRequiredProviders(
|
|
35
|
+
requiredProviders: readonly string[],
|
|
36
|
+
definition: Pick<ModelProfileDefinition, "modelMapping">,
|
|
37
|
+
): string[] {
|
|
38
|
+
const providers = new Set(requiredProviders);
|
|
39
|
+
for (const provider of deriveModelProfileMappedProviders(definition)) {
|
|
40
|
+
providers.add(provider);
|
|
41
|
+
}
|
|
42
|
+
return [...providers];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const profile = (
|
|
46
|
+
name: string,
|
|
47
|
+
requiredProviders: string[],
|
|
48
|
+
modelMapping: Record<ModelProfileRole, string>,
|
|
49
|
+
): ModelProfileDefinition => ({
|
|
50
|
+
name,
|
|
51
|
+
requiredProviders: aggregateModelProfileRequiredProviders(requiredProviders, { modelMapping }),
|
|
52
|
+
modelMapping,
|
|
53
|
+
source: "builtin",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
57
|
+
profile("opencode-go-eco", ["opencode-go"], {
|
|
58
|
+
default: "opencode-go/deepseek-v4-flash",
|
|
59
|
+
executor: "opencode-go/qwen3.5-plus",
|
|
60
|
+
architect: "opencode-go/glm-5",
|
|
61
|
+
planner: "opencode-go/minimax-m2.5",
|
|
62
|
+
critic: "opencode-go/kimi-k2.5",
|
|
63
|
+
}),
|
|
64
|
+
profile("opencode-go-standard", ["opencode-go"], {
|
|
65
|
+
default: "opencode-go/kimi-k2.6",
|
|
66
|
+
executor: "opencode-go/qwen3.6-plus",
|
|
67
|
+
architect: "opencode-go/glm-5.1",
|
|
68
|
+
planner: "opencode-go/minimax-m2.7",
|
|
69
|
+
critic: "opencode-go/deepseek-v4-pro",
|
|
70
|
+
}),
|
|
71
|
+
profile("opencode-go-pro", ["opencode-go"], {
|
|
72
|
+
default: "opencode-go/qwen3.7-max",
|
|
73
|
+
executor: "opencode-go/kimi-k2.6",
|
|
74
|
+
architect: "opencode-go/deepseek-v4-pro:high",
|
|
75
|
+
planner: "opencode-go/glm-5.1:high",
|
|
76
|
+
critic: "opencode-go/minimax-m2.7:high",
|
|
77
|
+
}),
|
|
78
|
+
profile("codex-eco", ["openai-codex"], {
|
|
79
|
+
default: "openai-codex/gpt-5.4-mini",
|
|
80
|
+
executor: "openai-codex/gpt-5.4-nano",
|
|
81
|
+
architect: "openai-codex/gpt-5.4-mini",
|
|
82
|
+
planner: "openai-codex/gpt-5.4-mini",
|
|
83
|
+
critic: "openai-codex/gpt-5.4-mini",
|
|
84
|
+
}),
|
|
85
|
+
profile("codex-standard", ["openai-codex"], {
|
|
86
|
+
default: "openai-codex/gpt-5.4:medium",
|
|
87
|
+
executor: "openai-codex/gpt-5.4:low",
|
|
88
|
+
architect: "openai-codex/gpt-5.4:xhigh",
|
|
89
|
+
planner: "openai-codex/gpt-5.4:medium",
|
|
90
|
+
critic: "openai-codex/gpt-5.4:high",
|
|
91
|
+
}),
|
|
92
|
+
profile("codex-pro", ["openai-codex"], {
|
|
93
|
+
default: "openai-codex/gpt-5.5",
|
|
94
|
+
executor: "openai-codex/gpt-5.2-codex",
|
|
95
|
+
architect: "openai-codex/gpt-5.1-codex-max:high",
|
|
96
|
+
planner: "openai-codex/gpt-5.5:high",
|
|
97
|
+
critic: "openai-codex/gpt-5.3-codex-spark:high",
|
|
98
|
+
}),
|
|
99
|
+
profile("opencode-go-codex-eco", ["opencode-go", "openai-codex"], {
|
|
100
|
+
default: "opencode-go/deepseek-v4-flash",
|
|
101
|
+
executor: "opencode-go/qwen3.5-plus",
|
|
102
|
+
architect: "openai-codex/gpt-5.4-mini",
|
|
103
|
+
planner: "openai-codex/gpt-5.4-mini",
|
|
104
|
+
critic: "openai-codex/gpt-5.4-mini",
|
|
105
|
+
}),
|
|
106
|
+
profile("opencode-go-codex-standard", ["opencode-go", "openai-codex"], {
|
|
107
|
+
default: "opencode-go/kimi-k2.6",
|
|
108
|
+
executor: "opencode-go/qwen3.6-plus",
|
|
109
|
+
architect: "openai-codex/gpt-5.4",
|
|
110
|
+
planner: "openai-codex/gpt-5.4",
|
|
111
|
+
critic: "openai-codex/gpt-5.4",
|
|
112
|
+
}),
|
|
113
|
+
profile("opencode-go-codex-pro", ["opencode-go", "openai-codex"], {
|
|
114
|
+
default: "opencode-go/qwen3.7-max",
|
|
115
|
+
executor: "opencode-go/kimi-k2.6",
|
|
116
|
+
architect: "openai-codex/gpt-5.1-codex-max:high",
|
|
117
|
+
planner: "openai-codex/gpt-5.5:high",
|
|
118
|
+
critic: "openai-codex/gpt-5.3-codex-spark:high",
|
|
119
|
+
}),
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
export function mergeModelProfiles(userProfiles?: ModelsConfig["profiles"]): Map<string, ModelProfileDefinition> {
|
|
123
|
+
const profiles = new Map<string, ModelProfileDefinition>();
|
|
124
|
+
for (const definition of BUILTIN_MODEL_PROFILES) {
|
|
125
|
+
profiles.set(definition.name, {
|
|
126
|
+
...definition,
|
|
127
|
+
requiredProviders: [...definition.requiredProviders],
|
|
128
|
+
modelMapping: { ...definition.modelMapping },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
for (const [name, definition] of Object.entries(userProfiles ?? {})) {
|
|
132
|
+
const modelMapping = { ...definition.model_mapping };
|
|
133
|
+
profiles.set(name, {
|
|
134
|
+
name,
|
|
135
|
+
requiredProviders: aggregateModelProfileRequiredProviders(definition.required_providers, { modelMapping }),
|
|
136
|
+
modelMapping,
|
|
137
|
+
source: "user",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return profiles;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function resolveProfileBindings(definition: ModelProfileDefinition): ResolvedProfileBinding {
|
|
144
|
+
const { default: defaultSelector, executor, architect, planner, critic } = definition.modelMapping;
|
|
145
|
+
const agentModelOverrides: ResolvedProfileBinding["agentModelOverrides"] = {};
|
|
146
|
+
if (executor !== undefined) agentModelOverrides.executor = executor;
|
|
147
|
+
if (architect !== undefined) agentModelOverrides.architect = architect;
|
|
148
|
+
if (planner !== undefined) agentModelOverrides.planner = planner;
|
|
149
|
+
if (critic !== undefined) agentModelOverrides.critic = critic;
|
|
150
|
+
return { defaultSelector, agentModelOverrides };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function formatAvailableProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string {
|
|
154
|
+
return [...profiles.keys()].sort((a, b) => a.localeCompare(b)).join(", ");
|
|
155
|
+
}
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
formatCanonicalVariantSelector,
|
|
44
44
|
type ModelEquivalenceConfig,
|
|
45
45
|
} from "./model-equivalence";
|
|
46
|
+
import { type ModelProfileDefinition, mergeModelProfiles } from "./model-profiles";
|
|
46
47
|
import {
|
|
47
48
|
type ModelOverride,
|
|
48
49
|
type ModelsConfig,
|
|
@@ -511,6 +512,7 @@ interface CustomModelsResult {
|
|
|
511
512
|
configuredProviders?: Set<string>;
|
|
512
513
|
equivalence?: ModelEquivalenceConfig;
|
|
513
514
|
modelBindings?: NonNullable<ModelsConfig["modelBindings"]>;
|
|
515
|
+
profiles?: ModelsConfig["profiles"];
|
|
514
516
|
error?: ConfigError;
|
|
515
517
|
found: boolean;
|
|
516
518
|
}
|
|
@@ -954,6 +956,7 @@ export class ModelRegistry {
|
|
|
954
956
|
#modelOverrides: Map<string, Map<string, ModelOverride>> = new Map();
|
|
955
957
|
#equivalenceConfig: ModelEquivalenceConfig | undefined;
|
|
956
958
|
#configuredModelBindings: NonNullable<ModelsConfig["modelBindings"]> | undefined;
|
|
959
|
+
#modelProfiles: Map<string, ModelProfileDefinition> = mergeModelProfiles();
|
|
957
960
|
#modelBindingsTargetSettings: Settings | undefined;
|
|
958
961
|
#appliedModelBindingRoles = new Set<string>();
|
|
959
962
|
#appliedAgentModelBindingOverrides = new Set<string>();
|
|
@@ -1093,6 +1096,7 @@ export class ModelRegistry {
|
|
|
1093
1096
|
configuredProviders = new Set(),
|
|
1094
1097
|
equivalence,
|
|
1095
1098
|
modelBindings,
|
|
1099
|
+
profiles,
|
|
1096
1100
|
error: configError,
|
|
1097
1101
|
} = this.#loadCustomModels();
|
|
1098
1102
|
this.#configError = configError;
|
|
@@ -1103,6 +1107,7 @@ export class ModelRegistry {
|
|
|
1103
1107
|
this.#modelOverrides = modelOverrides;
|
|
1104
1108
|
this.#equivalenceConfig = equivalence;
|
|
1105
1109
|
this.#configuredModelBindings = modelBindings;
|
|
1110
|
+
this.#modelProfiles = mergeModelProfiles(profiles);
|
|
1106
1111
|
|
|
1107
1112
|
this.#addImplicitDiscoverableProviders(configuredProviders);
|
|
1108
1113
|
const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
|
|
@@ -1343,6 +1348,7 @@ export class ModelRegistry {
|
|
|
1343
1348
|
discoverableProviders: [],
|
|
1344
1349
|
configuredProviders: new Set(),
|
|
1345
1350
|
error,
|
|
1351
|
+
profiles: undefined,
|
|
1346
1352
|
found: true,
|
|
1347
1353
|
};
|
|
1348
1354
|
} else if (status === "not-found") {
|
|
@@ -1353,6 +1359,7 @@ export class ModelRegistry {
|
|
|
1353
1359
|
keylessProviders: new Set(),
|
|
1354
1360
|
discoverableProviders: [],
|
|
1355
1361
|
configuredProviders: new Set(),
|
|
1362
|
+
profiles: undefined,
|
|
1356
1363
|
found: false,
|
|
1357
1364
|
};
|
|
1358
1365
|
}
|
|
@@ -1441,10 +1448,22 @@ export class ModelRegistry {
|
|
|
1441
1448
|
configuredProviders,
|
|
1442
1449
|
equivalence: value.equivalence,
|
|
1443
1450
|
modelBindings: value.modelBindings,
|
|
1451
|
+
profiles: value.profiles,
|
|
1444
1452
|
found: true,
|
|
1445
1453
|
};
|
|
1446
1454
|
}
|
|
1447
1455
|
|
|
1456
|
+
getModelProfiles(): Map<string, ModelProfileDefinition> {
|
|
1457
|
+
return new Map(this.#modelProfiles);
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
getModelProfile(name: string): ModelProfileDefinition | undefined {
|
|
1461
|
+
return this.#modelProfiles.get(name);
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
getAvailableModelProfileNames(): string[] {
|
|
1465
|
+
return [...this.#modelProfiles.keys()].sort((a, b) => a.localeCompare(b));
|
|
1466
|
+
}
|
|
1448
1467
|
applyConfiguredModelBindings(targetSettings: Settings): void {
|
|
1449
1468
|
this.#modelBindingsTargetSettings = targetSettings;
|
|
1450
1469
|
this.#applyConfiguredModelBindingsToTarget();
|
|
@@ -764,6 +764,7 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
764
764
|
parentActiveModelPattern: string | undefined,
|
|
765
765
|
modelRegistry: ModelLookupRegistry & Pick<ModelRegistry, "getApiKey">,
|
|
766
766
|
settings?: Settings,
|
|
767
|
+
sessionId?: string,
|
|
767
768
|
): Promise<{
|
|
768
769
|
model?: Model<Api>;
|
|
769
770
|
thinkingLevel?: ThinkingLevel;
|
|
@@ -775,7 +776,7 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
775
776
|
return { ...primary, authFallbackUsed: false };
|
|
776
777
|
}
|
|
777
778
|
|
|
778
|
-
const primaryKey = await modelRegistry.getApiKey(primary.model);
|
|
779
|
+
const primaryKey = await modelRegistry.getApiKey(primary.model, sessionId);
|
|
779
780
|
if (primaryKey === kNoAuth || isAuthenticated(primaryKey)) {
|
|
780
781
|
return { ...primary, authFallbackUsed: false };
|
|
781
782
|
}
|
|
@@ -787,7 +788,7 @@ export async function resolveModelOverrideWithAuthFallback(
|
|
|
787
788
|
if (modelsAreEqual(fallback.model, primary.model)) {
|
|
788
789
|
return { ...primary, authFallbackUsed: false };
|
|
789
790
|
}
|
|
790
|
-
const fallbackKey = await modelRegistry.getApiKey(fallback.model);
|
|
791
|
+
const fallbackKey = await modelRegistry.getApiKey(fallback.model, sessionId);
|
|
791
792
|
if (!isAuthenticated(fallbackKey)) {
|
|
792
793
|
return { ...primary, authFallbackUsed: false };
|
|
793
794
|
}
|
|
@@ -76,6 +76,39 @@ const ModelBindingsSchema = z.object({
|
|
|
76
76
|
modelRoles: z.record(z.string(), z.string().min(1)).optional(),
|
|
77
77
|
agentModelOverrides: z.record(z.string(), z.string().min(1)).optional(),
|
|
78
78
|
});
|
|
79
|
+
export const ProfileRoleSchema = z.enum(["default", "executor", "architect", "planner", "critic"]);
|
|
80
|
+
|
|
81
|
+
function isValidProfileModelSelector(value: string): boolean {
|
|
82
|
+
if (value.includes(",")) return false;
|
|
83
|
+
const slashIdx = value.indexOf("/");
|
|
84
|
+
if (slashIdx <= 0 || slashIdx === value.length - 1) return false;
|
|
85
|
+
const provider = value.slice(0, slashIdx);
|
|
86
|
+
const modelId = value.slice(slashIdx + 1);
|
|
87
|
+
if (!provider || !modelId) return false;
|
|
88
|
+
const parts = modelId.split(":");
|
|
89
|
+
if (parts.length > 2) return false;
|
|
90
|
+
const [base, suffix] = parts;
|
|
91
|
+
if (!base) return false;
|
|
92
|
+
return suffix === undefined || ["minimal", "low", "medium", "high", "xhigh"].includes(suffix);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const ProfileModelSelectorSchema = z
|
|
96
|
+
.string()
|
|
97
|
+
.min(1)
|
|
98
|
+
.refine(value => isValidProfileModelSelector(value), {
|
|
99
|
+
message: "Expected provider/modelId with optional :effort suffix",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
export const ProfileModelMappingSchema = z.partialRecord(ProfileRoleSchema, ProfileModelSelectorSchema);
|
|
103
|
+
|
|
104
|
+
export const ProfileDefinitionSchema = z
|
|
105
|
+
.object({
|
|
106
|
+
required_providers: z.array(z.string().min(1)).min(1),
|
|
107
|
+
model_mapping: ProfileModelMappingSchema,
|
|
108
|
+
})
|
|
109
|
+
.strict();
|
|
110
|
+
|
|
111
|
+
export const ProfilesSchema = z.record(z.string().min(1), ProfileDefinitionSchema);
|
|
79
112
|
|
|
80
113
|
const ModelDefinitionSchema = z
|
|
81
114
|
.object({
|
|
@@ -205,7 +238,10 @@ export const ModelsConfigSchema = z
|
|
|
205
238
|
providers: z.record(z.string(), ProviderConfigSchema).optional(),
|
|
206
239
|
modelBindings: ModelBindingsSchema.optional(),
|
|
207
240
|
equivalence: EquivalenceConfigSchema.optional(),
|
|
241
|
+
profiles: ProfilesSchema.optional(),
|
|
208
242
|
})
|
|
209
243
|
.strict();
|
|
210
244
|
|
|
211
245
|
export type ModelsConfig = z.infer<typeof ModelsConfigSchema>;
|
|
246
|
+
export type ModelProfileConfig = z.infer<typeof ProfileDefinitionSchema>;
|
|
247
|
+
export type ModelProfilesConfig = z.infer<typeof ProfilesSchema>;
|
|
@@ -74,6 +74,7 @@ export type StatusLineSegmentId =
|
|
|
74
74
|
| "git"
|
|
75
75
|
| "pr"
|
|
76
76
|
| "subagents"
|
|
77
|
+
| "jobs"
|
|
77
78
|
| "token_in"
|
|
78
79
|
| "token_out"
|
|
79
80
|
| "token_total"
|
|
@@ -313,6 +314,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
313
314
|
disabledExtensions: { type: "array", default: DEFAULT_DISABLED_EXTENSIONS },
|
|
314
315
|
|
|
315
316
|
modelRoles: { type: "record", default: EMPTY_STRING_RECORD },
|
|
317
|
+
"modelProfile.default": {
|
|
318
|
+
type: "string",
|
|
319
|
+
default: undefined,
|
|
320
|
+
ui: {
|
|
321
|
+
tab: "model",
|
|
322
|
+
label: "Default Model Profile",
|
|
323
|
+
description: "Model profile applied automatically at startup",
|
|
324
|
+
options: "runtime",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
316
327
|
|
|
317
328
|
modelTags: { type: "record", default: EMPTY_MODEL_TAGS_RECORD },
|
|
318
329
|
|
|
@@ -2352,11 +2363,12 @@ export const SETTINGS_SCHEMA = {
|
|
|
2352
2363
|
|
|
2353
2364
|
"task.maxConcurrency": {
|
|
2354
2365
|
type: "number",
|
|
2355
|
-
default:
|
|
2366
|
+
default: 8,
|
|
2356
2367
|
ui: {
|
|
2357
2368
|
tab: "tasks",
|
|
2358
2369
|
label: "Max Concurrent Tasks",
|
|
2359
|
-
description:
|
|
2370
|
+
description:
|
|
2371
|
+
"Safer concurrent limit for subagents; higher fan-out still requires an explicit plan above 4 tasks.",
|
|
2360
2372
|
options: [
|
|
2361
2373
|
{ value: "0", label: "Unlimited" },
|
|
2362
2374
|
{ value: "1", label: "1 task" },
|
|
@@ -2408,7 +2420,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
2408
2420
|
ui: {
|
|
2409
2421
|
tab: "tasks",
|
|
2410
2422
|
label: "Fork Context Max Tokens",
|
|
2411
|
-
description:
|
|
2423
|
+
description:
|
|
2424
|
+
"Approximate token cap for explicit full fork-context seeds. 0 uses 15% of the target model context window, with a 15k fallback when the window is unknown.",
|
|
2412
2425
|
},
|
|
2413
2426
|
},
|
|
2414
2427
|
|
package/src/dap/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger, ptree } from "@gajae-code/utils";
|
|
2
|
+
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
2
3
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
3
4
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
5
|
import type {
|
|
@@ -532,15 +533,28 @@ export class DapClient {
|
|
|
532
533
|
}
|
|
533
534
|
}
|
|
534
535
|
|
|
535
|
-
#handleProcessExit(): void {
|
|
536
|
+
async #handleProcessExit(): Promise<void> {
|
|
536
537
|
if (this.#disposed) return;
|
|
537
538
|
this.#disposed = true;
|
|
538
539
|
const stderr = this.proc.peekStderr().trim();
|
|
539
540
|
const exitCode = this.proc.exitCode;
|
|
541
|
+
const crashNotice = formatCrashDiagnosticNotice(
|
|
542
|
+
await writeCrashReport(
|
|
543
|
+
{
|
|
544
|
+
kind: "dap",
|
|
545
|
+
command: [this.adapter.resolvedCommand, ...this.adapter.args],
|
|
546
|
+
exitCode,
|
|
547
|
+
stderr,
|
|
548
|
+
protocol: this.adapter.connectMode ?? "stdio",
|
|
549
|
+
},
|
|
550
|
+
{ cwd: this.cwd },
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
const diagnosticSuffix = crashNotice ? `\n${crashNotice}` : "";
|
|
540
554
|
const error = new Error(
|
|
541
555
|
stderr
|
|
542
|
-
? `DAP adapter exited (code ${exitCode}): ${stderr}`
|
|
543
|
-
: `DAP adapter exited unexpectedly (code ${exitCode})`,
|
|
556
|
+
? `DAP adapter exited (code ${exitCode}): ${stderr}${diagnosticSuffix}`
|
|
557
|
+
: `DAP adapter exited unexpectedly (code ${exitCode})${diagnosticSuffix}`,
|
|
544
558
|
);
|
|
545
559
|
this.#rejectPendingRequests(error);
|
|
546
560
|
}
|