@gajae-code/coding-agent 0.4.5 → 0.5.0
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 +43 -0
- package/dist/types/commands/harness.d.ts +3 -0
- package/dist/types/config/model-profile-activation.d.ts +11 -2
- package/dist/types/config/model-profiles.d.ts +7 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/model-resolver.d.ts +2 -0
- package/dist/types/config/models-config-schema.d.ts +30 -0
- package/dist/types/config/settings-schema.d.ts +4 -3
- package/dist/types/gjc-runtime/team-runtime.d.ts +0 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +3 -0
- package/dist/types/harness-control-plane/owner.d.ts +1 -1
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +6 -1
- package/dist/types/harness-control-plane/types.d.ts +4 -0
- package/dist/types/hindsight/mental-models.d.ts +5 -5
- package/dist/types/modes/components/model-selector.d.ts +1 -12
- package/dist/types/modes/rpc/rpc-client.d.ts +2 -2
- package/dist/types/modes/rpc/rpc-types.d.ts +4 -1
- package/dist/types/sdk.d.ts +5 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/blob-store.d.ts +20 -1
- package/dist/types/session/session-manager.d.ts +24 -6
- package/dist/types/session/streaming-output.d.ts +3 -2
- package/dist/types/session/tool-choice-queue.d.ts +6 -0
- package/dist/types/task/receipt.d.ts +1 -0
- package/dist/types/task/types.d.ts +7 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +3 -12
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/resolve.d.ts +0 -10
- package/dist/types/utils/tool-choice.d.ts +14 -1
- package/package.json +7 -7
- package/src/cli.ts +8 -4
- package/src/commands/harness.ts +36 -2
- package/src/commands/launch.ts +2 -2
- package/src/commands/session.ts +3 -1
- package/src/config/model-profile-activation.ts +15 -3
- package/src/config/model-profiles.ts +255 -56
- package/src/config/model-resolver.ts +9 -6
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/coordinator-mcp/server.ts +54 -23
- package/src/cursor.ts +16 -2
- package/src/defaults/gjc/skills/team/SKILL.md +3 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +8 -2
- package/src/export/html/index.ts +13 -9
- package/src/gjc-runtime/team-runtime.ts +33 -7
- package/src/gjc-runtime/tmux-common.ts +15 -0
- package/src/gjc-runtime/tmux-sessions.ts +19 -11
- package/src/gjc-runtime/ultragoal-runtime.ts +505 -41
- package/src/gjc-runtime/workflow-manifest.generated.json +27 -1
- package/src/gjc-runtime/workflow-manifest.ts +16 -1
- package/src/harness-control-plane/owner.ts +78 -27
- package/src/harness-control-plane/receipt-spool.ts +128 -0
- package/src/harness-control-plane/state-machine.ts +27 -6
- package/src/harness-control-plane/storage.ts +23 -0
- package/src/harness-control-plane/types.ts +4 -0
- package/src/hindsight/mental-models.ts +17 -16
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/modes/components/assistant-message.ts +26 -14
- package/src/modes/components/diff.ts +97 -0
- package/src/modes/components/model-selector.ts +353 -181
- package/src/modes/components/tool-execution.ts +30 -13
- package/src/modes/controllers/selector-controller.ts +33 -42
- package/src/modes/rpc/rpc-client.ts +3 -2
- package/src/modes/rpc/rpc-mode.ts +44 -14
- package/src/modes/rpc/rpc-types.ts +5 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +10 -5
- package/src/modes/shared/agent-wire/command-validation.ts +11 -0
- package/src/sdk.ts +29 -2
- package/src/secrets/obfuscator.ts +102 -27
- package/src/session/agent-session.ts +105 -20
- package/src/session/blob-store.ts +89 -3
- package/src/session/session-manager.ts +309 -58
- package/src/session/streaming-output.ts +185 -122
- package/src/session/tool-choice-queue.ts +23 -0
- package/src/task/executor.ts +69 -6
- package/src/task/receipt.ts +5 -0
- package/src/task/render.ts +21 -1
- package/src/task/types.ts +8 -0
- package/src/thinking-metadata.ts +51 -0
- package/src/thinking.ts +26 -46
- package/src/tools/bash.ts +1 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/resolve.ts +93 -18
- package/src/utils/edit-mode.ts +1 -1
- package/src/utils/tool-choice.ts +45 -16
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ThinkingLevelValue = "inherit" | "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
2
|
+
/**
|
|
3
|
+
* Metadata used to render thinking selector values in the coding-agent UI.
|
|
4
|
+
*
|
|
5
|
+
* This module is intentionally provider/native-free so schema generation can
|
|
6
|
+
* import settings metadata before native addons have been built in CI.
|
|
7
|
+
*/
|
|
8
|
+
export interface ThinkingLevelMetadata {
|
|
9
|
+
value: ThinkingLevelValue;
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Returns display metadata for a thinking selector.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getThinkingLevelMetadata(level: ThinkingLevelValue): ThinkingLevelMetadata;
|
package/dist/types/thinking.d.ts
CHANGED
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
import { type ResolvedThinkingLevel, ThinkingLevel } from "@gajae-code/agent-core/thinking";
|
|
2
2
|
import { type Effort } from "@gajae-code/ai/model-thinking";
|
|
3
3
|
import type { Model } from "@gajae-code/ai/types";
|
|
4
|
-
|
|
5
|
-
* Metadata used to render thinking selector values in the coding-agent UI.
|
|
6
|
-
*/
|
|
7
|
-
export interface ThinkingLevelMetadata {
|
|
8
|
-
value: ThinkingLevel;
|
|
9
|
-
label: string;
|
|
10
|
-
description: string;
|
|
11
|
-
}
|
|
4
|
+
export { getThinkingLevelMetadata, type ThinkingLevelMetadata } from "./thinking-metadata";
|
|
12
5
|
/**
|
|
13
6
|
* Parses a provider-facing effort value.
|
|
14
7
|
*/
|
|
@@ -17,10 +10,6 @@ export declare function parseEffort(value: string | null | undefined): Effort |
|
|
|
17
10
|
* Parses an agent-local thinking selector.
|
|
18
11
|
*/
|
|
19
12
|
export declare function parseThinkingLevel(value: string | null | undefined): ThinkingLevel | undefined;
|
|
20
|
-
/**
|
|
21
|
-
* Returns display metadata for a thinking selector.
|
|
22
|
-
*/
|
|
23
|
-
export declare function getThinkingLevelMetadata(level: ThinkingLevel): ThinkingLevelMetadata;
|
|
24
13
|
/**
|
|
25
14
|
* Converts an agent-local selector into the effort sent to providers.
|
|
26
15
|
*/
|
|
@@ -29,3 +18,5 @@ export declare function toReasoningEffort(level: ThinkingLevel | undefined): Eff
|
|
|
29
18
|
* Resolves a selector against the current model while preserving explicit "off".
|
|
30
19
|
*/
|
|
31
20
|
export declare function resolveThinkingLevelForModel(model: Model | undefined, level: ThinkingLevel | undefined): ResolvedThinkingLevel | undefined;
|
|
21
|
+
export declare function clampExplicitThinkingLevelForModel(model: Model | undefined, level: ThinkingLevel | undefined): ThinkingLevel | undefined;
|
|
22
|
+
export declare function formatClampedModelSelector(selector: string, model: Model | undefined): string;
|
|
@@ -193,6 +193,8 @@ export interface ToolSession {
|
|
|
193
193
|
getToolChoiceQueue?(): ToolChoiceQueue;
|
|
194
194
|
/** Build a model-provider-specific ToolChoice that targets the named tool, or undefined if unsupported. */
|
|
195
195
|
buildToolChoice?(toolName: string): ToolChoice | undefined;
|
|
196
|
+
/** Build a named tool-choice decision, preserving whether exact named forcing survived capability degradation. */
|
|
197
|
+
buildToolChoiceResult?(toolName: string): import("../utils/tool-choice").NamedToolChoiceResult;
|
|
196
198
|
/** Steer a hidden custom message into the conversation (e.g. a preview reminder). */
|
|
197
199
|
steer?(message: {
|
|
198
200
|
customType: string;
|
|
@@ -21,16 +21,6 @@ export interface ResolveToolDetails {
|
|
|
21
21
|
label?: string;
|
|
22
22
|
sourceResultDetails?: unknown;
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Queue a resolve-protocol handler on the tool-choice queue. Forces the next
|
|
26
|
-
* LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
|
|
27
|
-
* callbacks into an onInvoked closure that matches the resolve schema, and
|
|
28
|
-
* steers a preview reminder so the model understands why.
|
|
29
|
-
*
|
|
30
|
-
* This is the canonical entry point for any tool that wants preview/apply
|
|
31
|
-
* semantics. No session-level abstraction is needed: callers pass their
|
|
32
|
-
* apply/reject functions directly.
|
|
33
|
-
*/
|
|
34
24
|
export declare function queueResolveHandler(session: ToolSession, options: {
|
|
35
25
|
label: string;
|
|
36
26
|
sourceToolName: string;
|
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import type { Api, Model, ToolChoice } from "@gajae-code/ai";
|
|
1
|
+
import type { Api, Model, ResolveToolChoiceResult, ToolChoice } from "@gajae-code/ai";
|
|
2
2
|
/**
|
|
3
3
|
* Build a provider-aware tool choice that targets one specific tool when supported.
|
|
4
4
|
* Providers that only expose required/any forcing may still honor named choices by
|
|
5
5
|
* narrowing their request tool list before transport.
|
|
6
6
|
*/
|
|
7
|
+
export interface NamedToolChoiceResult {
|
|
8
|
+
choice: ToolChoice | undefined;
|
|
9
|
+
exactNamed: boolean;
|
|
10
|
+
resolved?: ResolveToolChoiceResult;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildNamedToolChoiceResult(toolName: string, model?: Model<Api>): NamedToolChoiceResult;
|
|
13
|
+
/**
|
|
14
|
+
* Legacy capability-aware wrapper. May return a lossy `"required"` when named
|
|
15
|
+
* forcing degrades (e.g. Google APIs, or compat `toolChoiceSupport: "required"`),
|
|
16
|
+
* which forces *some* tool rather than `toolName` specifically. Queue directives
|
|
17
|
+
* that need exact tool identity (resolve / todo_write / yield) MUST use
|
|
18
|
+
* `buildNamedToolChoiceResult` and gate on `exactNamed` instead.
|
|
19
|
+
*/
|
|
7
20
|
export declare function buildNamedToolChoice(toolName: string, model?: Model<Api>): ToolChoice | undefined;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@gajae-code/coding-agent",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"description": "Gajae Code CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://gaebal-gajae.dev",
|
|
7
7
|
"author": "Yeachan-Heo",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"@agentclientprotocol/sdk": "0.21.0",
|
|
52
52
|
"@babel/parser": "^7.29.3",
|
|
53
53
|
"@mozilla/readability": "^0.6.0",
|
|
54
|
-
"@gajae-code/stats": "0.
|
|
55
|
-
"@gajae-code/agent-core": "0.
|
|
56
|
-
"@gajae-code/ai": "0.
|
|
57
|
-
"@gajae-code/natives": "0.
|
|
58
|
-
"@gajae-code/tui": "0.
|
|
59
|
-
"@gajae-code/utils": "0.
|
|
54
|
+
"@gajae-code/stats": "0.5.0",
|
|
55
|
+
"@gajae-code/agent-core": "0.5.0",
|
|
56
|
+
"@gajae-code/ai": "0.5.0",
|
|
57
|
+
"@gajae-code/natives": "0.5.0",
|
|
58
|
+
"@gajae-code/tui": "0.5.0",
|
|
59
|
+
"@gajae-code/utils": "0.5.0",
|
|
60
60
|
"@puppeteer/browsers": "^2.13.0",
|
|
61
61
|
"@types/turndown": "5.0.6",
|
|
62
62
|
"@xterm/headless": "^6.0.0",
|
package/src/cli.ts
CHANGED
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
* lightweight CLI runner from pi-utils.
|
|
6
6
|
*/
|
|
7
7
|
import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@gajae-code/utils/cli";
|
|
8
|
-
import { APP_NAME, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
|
|
8
|
+
import { APP_NAME, formatBunRuntimeError, MIN_BUN_VERSION, VERSION } from "@gajae-code/utils/dirs";
|
|
9
9
|
|
|
10
10
|
if (Bun.semver.order(Bun.version, MIN_BUN_VERSION) < 0) {
|
|
11
11
|
process.stderr.write(
|
|
12
|
-
|
|
12
|
+
formatBunRuntimeError({
|
|
13
|
+
currentVersion: Bun.version,
|
|
14
|
+
minVersion: MIN_BUN_VERSION,
|
|
15
|
+
execPath: process.execPath,
|
|
16
|
+
}),
|
|
13
17
|
);
|
|
14
18
|
process.exit(1);
|
|
15
19
|
}
|
|
@@ -130,8 +134,8 @@ class RootHelpCommand extends Command {
|
|
|
130
134
|
`# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
|
|
131
135
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
132
136
|
`# Limit model cycling to specific models\n ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o`,
|
|
133
|
-
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-
|
|
134
|
-
`# Persist a model profile as the default\n ${APP_NAME} --mpreset
|
|
137
|
+
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-medium`,
|
|
138
|
+
`# Persist a model profile as the default\n ${APP_NAME} --mpreset opencodego --default`,
|
|
135
139
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/--path--/session.jsonl`,
|
|
136
140
|
];
|
|
137
141
|
static strict = false;
|
package/src/commands/harness.ts
CHANGED
|
@@ -11,16 +11,18 @@
|
|
|
11
11
|
import { execFileSync } from "node:child_process";
|
|
12
12
|
import { randomBytes } from "node:crypto";
|
|
13
13
|
import { existsSync, readFileSync } from "node:fs";
|
|
14
|
+
import * as path from "node:path";
|
|
14
15
|
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
15
16
|
import { resolveGjcTmuxCommand, sanitizeTmuxToken } from "../gjc-runtime/tmux-common";
|
|
16
17
|
import { classifyRecovery } from "../harness-control-plane/classifier";
|
|
17
18
|
import { callEndpoint, EndpointUnreachableError } from "../harness-control-plane/control-endpoint";
|
|
18
19
|
import { type ResolvedOwner, RuntimeOwner, resolveOwner } from "../harness-control-plane/owner";
|
|
19
20
|
import { preserveDirtyWorktree } from "../harness-control-plane/preserve";
|
|
21
|
+
import { RECEIPT_SPOOL_DIR_ENV } from "../harness-control-plane/receipt-spool";
|
|
20
22
|
import { buildReceipt, requiresVanishBeforeAction, type VanishEvidence } from "../harness-control-plane/receipts";
|
|
21
23
|
import { GajaeCodeRpc } from "../harness-control-plane/rpc-adapter";
|
|
22
24
|
import { classifyLeaseStatus, readLease } from "../harness-control-plane/session-lease";
|
|
23
|
-
import { buildResponse, buildStateView } from "../harness-control-plane/state-machine";
|
|
25
|
+
import { buildResponse, buildStateView, submitUnavailableReason } from "../harness-control-plane/state-machine";
|
|
24
26
|
import {
|
|
25
27
|
canonicalWorkspacePath,
|
|
26
28
|
generateSessionId,
|
|
@@ -513,6 +515,9 @@ export default class Harness extends Command {
|
|
|
513
515
|
cursor: Flags.string({ description: "Event cursor for events --follow (exclusive)", default: "0" }),
|
|
514
516
|
follow: Flags.boolean({ description: "Tail the owner-written event log", default: false }),
|
|
515
517
|
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: true }),
|
|
518
|
+
"receipt-spool-dir": Flags.string({
|
|
519
|
+
description: "Append persisted ReceiptEnvelope records to spool.jsonl under this directory",
|
|
520
|
+
}),
|
|
516
521
|
};
|
|
517
522
|
|
|
518
523
|
static examples = [
|
|
@@ -527,6 +532,11 @@ export default class Harness extends Command {
|
|
|
527
532
|
const verb = String(args.verb);
|
|
528
533
|
let root = resolveHarnessRoot();
|
|
529
534
|
try {
|
|
535
|
+
const receiptSpoolDir = flags["receipt-spool-dir"];
|
|
536
|
+
if (receiptSpoolDir !== undefined) {
|
|
537
|
+
if (!receiptSpoolDir.trim()) throw new Error("receipt_spool_dir_empty");
|
|
538
|
+
process.env[RECEIPT_SPOOL_DIR_ENV] = path.resolve(receiptSpoolDir.trim());
|
|
539
|
+
}
|
|
530
540
|
const input = parseInput(flags.input);
|
|
531
541
|
const promptFile = flags["prompt-file"];
|
|
532
542
|
if (promptFile !== undefined) {
|
|
@@ -676,6 +686,9 @@ export default class Harness extends Command {
|
|
|
676
686
|
}
|
|
677
687
|
const sessionName = deterministicHarnessTmuxSessionName(sessionId);
|
|
678
688
|
const envAssignments = [`GJC_HARNESS_STATE_ROOT=${shellQuote(root)}`];
|
|
689
|
+
if (process.env[RECEIPT_SPOOL_DIR_ENV]) {
|
|
690
|
+
envAssignments.push(`${RECEIPT_SPOOL_DIR_ENV}=${shellQuote(process.env[RECEIPT_SPOOL_DIR_ENV])}`);
|
|
691
|
+
}
|
|
679
692
|
if (process.env.GJC_HARNESS_RPC_COMMAND) {
|
|
680
693
|
envAssignments.push(`GJC_HARNESS_RPC_COMMAND=${shellQuote(process.env.GJC_HARNESS_RPC_COMMAND)}`);
|
|
681
694
|
}
|
|
@@ -715,6 +728,9 @@ export default class Harness extends Command {
|
|
|
715
728
|
env: {
|
|
716
729
|
...process.env,
|
|
717
730
|
GJC_HARNESS_STATE_ROOT: root,
|
|
731
|
+
...(process.env[RECEIPT_SPOOL_DIR_ENV]
|
|
732
|
+
? { [RECEIPT_SPOOL_DIR_ENV]: process.env[RECEIPT_SPOOL_DIR_ENV] }
|
|
733
|
+
: {}),
|
|
718
734
|
...(process.env.GJC_HARNESS_TEST_NODE_MODULES
|
|
719
735
|
? { GJC_HARNESS_TEST_NODE_MODULES: process.env.GJC_HARNESS_TEST_NODE_MODULES }
|
|
720
736
|
: {}),
|
|
@@ -878,7 +894,9 @@ export default class Harness extends Command {
|
|
|
878
894
|
): Promise<boolean> {
|
|
879
895
|
const owner = await resolveOwner(root, sessionId);
|
|
880
896
|
if (!owner.live || !owner.socketPath) return false;
|
|
897
|
+
const priorSpoolDir = input[RECEIPT_SPOOL_DIR_ENV];
|
|
881
898
|
try {
|
|
899
|
+
if (process.env[RECEIPT_SPOOL_DIR_ENV]) input[RECEIPT_SPOOL_DIR_ENV] = process.env[RECEIPT_SPOOL_DIR_ENV];
|
|
882
900
|
const res = (await callEndpoint(owner.socketPath, { verb, input })) as { ok?: boolean };
|
|
883
901
|
writeJson(res);
|
|
884
902
|
if (res?.ok === false) process.exitCode = 1;
|
|
@@ -886,6 +904,9 @@ export default class Harness extends Command {
|
|
|
886
904
|
} catch (error) {
|
|
887
905
|
if (error instanceof EndpointUnreachableError) return false;
|
|
888
906
|
throw error;
|
|
907
|
+
} finally {
|
|
908
|
+
if (priorSpoolDir === undefined) delete input[RECEIPT_SPOOL_DIR_ENV];
|
|
909
|
+
else input[RECEIPT_SPOOL_DIR_ENV] = priorSpoolDir;
|
|
889
910
|
}
|
|
890
911
|
}
|
|
891
912
|
|
|
@@ -978,8 +999,21 @@ export default class Harness extends Command {
|
|
|
978
999
|
|
|
979
1000
|
async #submit(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
980
1001
|
const sessionId = requireSessionId(input, flagSession);
|
|
981
|
-
if (await this.#tryOwnerRoute(root, sessionId, "submit", { ...input, sessionId })) return;
|
|
982
1002
|
let state = await loadState(root, sessionId);
|
|
1003
|
+
const noOwnerGate = submitUnavailableReason(state.lifecycle, false);
|
|
1004
|
+
if (!noOwnerGate || noOwnerGate === "owner-not-live") {
|
|
1005
|
+
if (await this.#tryOwnerRoute(root, sessionId, "submit", { ...input, sessionId })) return;
|
|
1006
|
+
state = await loadState(root, sessionId);
|
|
1007
|
+
}
|
|
1008
|
+
const blockedByOwnerLiveness = state.blockers.some(
|
|
1009
|
+
blocker => isOwnerLivenessBlocker(blocker) || blocker === OWNER_STARTUP_BLOCKER,
|
|
1010
|
+
);
|
|
1011
|
+
const lifecycleGate = submitUnavailableReason(state.lifecycle, false);
|
|
1012
|
+
if (lifecycleGate && lifecycleGate !== "owner-not-live" && !blockedByOwnerLiveness) {
|
|
1013
|
+
writeJson(buildResponse(state, false, { accepted: false, submitted: false, reason: lifecycleGate }, false));
|
|
1014
|
+
process.exitCode = 1;
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
983
1017
|
// No live owner: submission is blocked (never echoed-as-accepted). Surface owner exit
|
|
984
1018
|
// evidence + explicit recovery guidance so the caller is not left with a bare gate.
|
|
985
1019
|
const ownerExit = await buildOwnerExitEvidence(root, state);
|
package/src/commands/launch.ts
CHANGED
|
@@ -142,8 +142,8 @@ export default class Index extends Command {
|
|
|
142
142
|
`# Launch in a sibling git worktree\n ${APP_NAME} --worktree`,
|
|
143
143
|
`# Use different model (fuzzy matching)\n ${APP_NAME} --model opus "Help me refactor this code"`,
|
|
144
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-
|
|
146
|
-
`# Persist a model profile as the default\n ${APP_NAME} --mpreset
|
|
145
|
+
`# Activate a model profile for this session\n ${APP_NAME} --mpreset codex-medium`,
|
|
146
|
+
`# Persist a model profile as the default\n ${APP_NAME} --mpreset opencodego --default`,
|
|
147
147
|
`# Export a session file to HTML\n ${APP_NAME} --export ~/.gjc/agent/sessions/--path--/session.jsonl`,
|
|
148
148
|
];
|
|
149
149
|
|
package/src/commands/session.ts
CHANGED
|
@@ -18,7 +18,9 @@ function writeText(lines: string[]): void {
|
|
|
18
18
|
function writeJsonFailure(error: unknown): void {
|
|
19
19
|
const message = error instanceof Error ? error.message : String(error);
|
|
20
20
|
const [reason = "session_error"] = message.split(":");
|
|
21
|
-
|
|
21
|
+
const hintIndex = message.indexOf(" — ");
|
|
22
|
+
const detail = hintIndex >= 0 ? message.slice(hintIndex + " — ".length).trim() : "";
|
|
23
|
+
writeJson(detail ? { ok: false, reason, detail } : { ok: false, reason });
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
interface SessionJsonDto {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@gajae-code/agent-core";
|
|
2
2
|
import type { Api, Model } from "@gajae-code/ai";
|
|
3
3
|
import type { AgentSession } from "../session/agent-session";
|
|
4
|
+
import { formatClampedModelSelector } from "../thinking";
|
|
4
5
|
import {
|
|
5
6
|
aggregateModelProfileRequiredProviders,
|
|
6
7
|
formatAvailableProfileNames,
|
|
@@ -10,8 +11,14 @@ import { type GjcModelAssignmentTargetId, isAuthenticated, type ModelRegistry }
|
|
|
10
11
|
import { resolveModelRoleValue } from "./model-resolver";
|
|
11
12
|
import type { Settings } from "./settings";
|
|
12
13
|
|
|
14
|
+
type ModelProfileActivationSession = Pick<AgentSession, "model" | "thinkingLevel" | "sessionId"> & {
|
|
15
|
+
setModelTemporary?: AgentSession["setModelTemporary"];
|
|
16
|
+
setActiveModelProfile?: (name: string | undefined) => void;
|
|
17
|
+
getActiveModelProfile?: () => string | undefined;
|
|
18
|
+
};
|
|
19
|
+
|
|
13
20
|
export interface PrepareModelProfileActivationOptions {
|
|
14
|
-
session:
|
|
21
|
+
session: ModelProfileActivationSession;
|
|
15
22
|
modelRegistry: Pick<
|
|
16
23
|
ModelRegistry,
|
|
17
24
|
| "getModelProfile"
|
|
@@ -29,7 +36,7 @@ export interface PrepareModelProfileActivationOptions {
|
|
|
29
36
|
|
|
30
37
|
export interface PreparedModelProfileActivation {
|
|
31
38
|
profileName: string;
|
|
32
|
-
session:
|
|
39
|
+
session: ModelProfileActivationSession & { setModelTemporary: AgentSession["setModelTemporary"] };
|
|
33
40
|
settings: Pick<Settings, "get" | "override" | "set" | "flush">;
|
|
34
41
|
previousModel: Model<Api> | undefined;
|
|
35
42
|
previousThinkingLevel: ThinkingLevel | undefined;
|
|
@@ -37,6 +44,7 @@ export interface PreparedModelProfileActivation {
|
|
|
37
44
|
defaultModel: Model<Api> | undefined;
|
|
38
45
|
defaultThinkingLevel: ThinkingLevel | undefined;
|
|
39
46
|
agentModelOverrides: Record<string, string>;
|
|
47
|
+
previousActiveModelProfile: string | undefined;
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
export function formatModelProfileCredentialError(profileName: string, providers: readonly string[]): string {
|
|
@@ -89,7 +97,7 @@ export async function prepareModelProfileActivation(
|
|
|
89
97
|
if (!resolved.model) {
|
|
90
98
|
throw new Error(`Model profile "${options.profileName}" ${role} selector did not resolve: ${selector}`);
|
|
91
99
|
}
|
|
92
|
-
agentModelOverrides[role] = selector;
|
|
100
|
+
agentModelOverrides[role] = formatClampedModelSelector(selector, resolved.model);
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
return {
|
|
@@ -102,6 +110,7 @@ export async function prepareModelProfileActivation(
|
|
|
102
110
|
defaultModel: resolvedDefault?.model,
|
|
103
111
|
defaultThinkingLevel: resolvedDefault?.thinkingLevel,
|
|
104
112
|
agentModelOverrides,
|
|
113
|
+
previousActiveModelProfile: options.session.getActiveModelProfile?.(),
|
|
105
114
|
};
|
|
106
115
|
}
|
|
107
116
|
|
|
@@ -113,6 +122,7 @@ export async function applyPreparedModelProfileActivation(
|
|
|
113
122
|
const previousThinkingLevel = prepared.previousThinkingLevel;
|
|
114
123
|
const previousAgentModelOverrides = prepared.previousAgentModelOverrides;
|
|
115
124
|
const previousPersistedDefault = prepared.settings.get("modelProfile.default");
|
|
125
|
+
const previousActiveModelProfile = prepared.previousActiveModelProfile;
|
|
116
126
|
let modelChanged = false;
|
|
117
127
|
let overridesChanged = false;
|
|
118
128
|
let defaultChanged = false;
|
|
@@ -134,6 +144,7 @@ export async function applyPreparedModelProfileActivation(
|
|
|
134
144
|
defaultChanged = true;
|
|
135
145
|
await prepared.settings.flush();
|
|
136
146
|
}
|
|
147
|
+
prepared.session.setActiveModelProfile?.(prepared.profileName);
|
|
137
148
|
} catch (error) {
|
|
138
149
|
if (defaultChanged) {
|
|
139
150
|
prepared.settings.set("modelProfile.default", previousPersistedDefault);
|
|
@@ -141,6 +152,7 @@ export async function applyPreparedModelProfileActivation(
|
|
|
141
152
|
if (overridesChanged) {
|
|
142
153
|
prepared.settings.override("task.agentModelOverrides", previousAgentModelOverrides);
|
|
143
154
|
}
|
|
155
|
+
prepared.session.setActiveModelProfile?.(previousActiveModelProfile);
|
|
144
156
|
if (modelChanged && previousModel) {
|
|
145
157
|
await prepared.session.setModelTemporary(previousModel, previousThinkingLevel);
|
|
146
158
|
}
|