@gajae-code/coding-agent 0.9.6 → 0.10.1
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 +58 -0
- package/dist/types/cli/classify-install-target.d.ts +0 -10
- package/dist/types/cli/daemon-cli.d.ts +2 -0
- package/dist/types/cli/initial-message.d.ts +1 -1
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/notify-cli.d.ts +3 -1
- package/dist/types/cli/session-picker.d.ts +4 -3
- package/dist/types/cli/update-cli.d.ts +22 -3
- package/dist/types/cli.d.ts +5 -0
- package/dist/types/commands/daemon.d.ts +6 -2
- package/dist/types/commands/launch.d.ts +1 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/commit/analysis/conventional.d.ts +1 -1
- package/dist/types/commit/analysis/summary.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/map-phase.d.ts +1 -1
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +1 -0
- package/dist/types/config/settings-schema.d.ts +47 -7
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +3 -0
- package/dist/types/coordinator-mcp/server.d.ts +79 -6
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/daemon/control-types.d.ts +10 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.d.ts +4 -0
- package/dist/types/discovery/helpers.d.ts +1 -1
- package/dist/types/discovery/substitute-plugin-root.d.ts +0 -4
- package/dist/types/extensibility/extensions/types.d.ts +3 -1
- package/dist/types/gjc-runtime/launch-tmux.d.ts +26 -7
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +34 -3
- package/dist/types/gjc-runtime/tmux-common.d.ts +6 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +49 -17
- package/dist/types/internal-urls/local-protocol.d.ts +13 -9
- package/dist/types/main.d.ts +66 -2
- package/dist/types/modes/acp/acp-agent.d.ts +19 -2
- package/dist/types/modes/acp/acp-mode.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/gajae-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/session-selector.d.ts +22 -9
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/status-line/types.d.ts +1 -1
- package/dist/types/modes/components/status-line.d.ts +0 -10
- package/dist/types/modes/components/tool-execution.d.ts +1 -0
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/controllers/event-controller.d.ts +4 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +24 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/print-mode.d.ts +3 -4
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +17 -1
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +8 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +5 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +45 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +42 -2
- package/dist/types/modes/theme/defaults/index.d.ts +568 -568
- package/dist/types/modes/types.d.ts +28 -2
- package/dist/types/modes/utils/abort-message.d.ts +1 -1
- package/dist/types/modes/utils/context-usage.d.ts +4 -19
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +13 -4
- package/dist/types/notifications/config.d.ts +34 -0
- package/dist/types/notifications/daemon-paths.d.ts +7 -0
- package/dist/types/notifications/index.d.ts +27 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +12 -4
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +27 -9
- package/dist/types/notifications/telegram-daemon-control.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +130 -3
- package/dist/types/notifications/telegram-reference.d.ts +19 -2
- package/dist/types/notifications/topic-registry.d.ts +24 -4
- package/dist/types/registry/agent-registry.d.ts +2 -0
- package/dist/types/rlm/index.d.ts +1 -1
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +40 -3
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-manager.d.ts +124 -4
- package/dist/types/session/session-storage.d.ts +155 -13
- package/dist/types/setup/credential-auto-import.d.ts +2 -2
- package/dist/types/task/executor.d.ts +3 -0
- package/dist/types/task/receipt.d.ts +3 -1
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/tools/ask-answer-registry.d.ts +5 -0
- package/dist/types/tools/ask.d.ts +25 -1
- package/dist/types/tools/bash.d.ts +1 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +6 -0
- package/dist/types/tools/index.d.ts +58 -6
- package/dist/types/tools/telegram-send.d.ts +7 -0
- package/dist/types/utils/external-editor.d.ts +2 -0
- package/package.json +9 -9
- package/scripts/g004-tmux-smoke.ts +196 -36
- package/src/cli/daemon-cli.ts +19 -29
- package/src/cli/malloc-env-guard.test.ts +104 -0
- package/src/cli/malloc-env-guard.ts +149 -0
- package/src/cli/notify-cli.ts +78 -14
- package/src/cli/session-picker.ts +23 -40
- package/src/cli/update-cli.ts +56 -37
- package/src/cli.ts +51 -10
- package/src/commands/daemon.ts +19 -5
- package/src/commands/harness.ts +515 -47
- package/src/commands/launch.ts +11 -3
- package/src/commands/notify.ts +5 -1
- package/src/commands/session.ts +6 -1
- package/src/config/keybindings.ts +5 -0
- package/src/config/model-profile-activation.ts +1 -1
- package/src/config/model-profiles.ts +14 -14
- package/src/config/settings-schema.ts +41 -1
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/model-preset.ts +100 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +2881 -425
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/daemon/control-types.ts +11 -0
- package/src/daemon/operator-contract.ts +121 -0
- package/src/deep-interview/render-middleware.ts +67 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +57 -17
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/payload/sanitize.ts +15 -2
- package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/register.ts +5 -1
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/gjc-runtime/launch-tmux.ts +702 -135
- package/src/gjc-runtime/session-state-sidecar.ts +756 -135
- package/src/gjc-runtime/team-runtime.ts +6 -0
- package/src/gjc-runtime/tmux-common.ts +26 -7
- package/src/gjc-runtime/tmux-gc.ts +52 -9
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +185 -0
- package/src/gjc-runtime/tmux-owner-isolation.ts +2060 -0
- package/src/gjc-runtime/tmux-sessions.ts +871 -68
- package/src/internal-urls/docs-index.generated.ts +12 -11
- package/src/internal-urls/local-protocol.ts +29 -10
- package/src/main.ts +301 -80
- package/src/modes/acp/acp-agent.ts +730 -100
- package/src/modes/acp/acp-mode.ts +29 -2
- package/src/modes/components/assistant-message.ts +32 -3
- package/src/modes/components/bash-execution.ts +43 -13
- package/src/modes/components/custom-editor.ts +5 -0
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/gajae-pet-widget.ts +360 -0
- package/src/modes/components/hook-selector.ts +12 -0
- package/src/modes/components/irc-sidebar.ts +206 -0
- package/src/modes/components/pet-selector.ts +55 -0
- package/src/modes/components/session-selector.ts +222 -221
- package/src/modes/components/settings-selector.ts +10 -0
- package/src/modes/components/status-line/segments.ts +13 -6
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/components/user-message.ts +26 -9
- package/src/modes/controllers/command-controller.ts +28 -2
- package/src/modes/controllers/event-controller.ts +159 -36
- package/src/modes/controllers/extension-ui-controller.ts +37 -18
- package/src/modes/controllers/input-controller.ts +8 -2
- package/src/modes/controllers/selector-controller.ts +62 -15
- package/src/modes/interactive-mode.ts +199 -7
- package/src/modes/irc-observation-ledger.ts +127 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +45 -2
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/shared/agent-wire/unattended-session.ts +288 -1
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +130 -20
- package/src/modes/types.ts +34 -2
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/irc-message.ts +138 -0
- package/src/modes/utils/ui-helpers.ts +216 -50
- package/src/notifications/config.ts +44 -0
- package/src/notifications/daemon-paths.ts +8 -0
- package/src/notifications/index.ts +535 -75
- package/src/notifications/lifecycle-control-runtime.ts +930 -77
- package/src/notifications/notification-service.ts +710 -0
- package/src/notifications/rate-limit-pool.ts +46 -12
- package/src/notifications/telegram-daemon-cli.ts +2 -0
- package/src/notifications/telegram-daemon-control.ts +27 -2
- package/src/notifications/telegram-daemon.ts +635 -94
- package/src/notifications/telegram-reference.ts +73 -13
- package/src/notifications/topic-registry.ts +86 -9
- package/src/prompts/system/irc-peer-roster.md +1 -0
- package/src/prompts/system/subagent-system-prompt.md +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/irc.md +1 -1
- package/src/registry/agent-registry.ts +3 -0
- package/src/sdk.ts +52 -1
- package/src/secrets/obfuscator.ts +4 -2
- package/src/session/agent-session.ts +670 -217
- package/src/session/cache-economics.ts +275 -0
- package/src/session/context-estimation.ts +91 -0
- package/src/session/provider-safety-stop.ts +9 -0
- package/src/session/session-dump-format.ts +6 -0
- package/src/session/session-manager.ts +809 -39
- package/src/session/session-storage.ts +582 -38
- package/src/slash-commands/builtin-registry.ts +153 -9
- package/src/slash-commands/helpers/context-report.ts +26 -4
- package/src/task/executor.ts +12 -3
- package/src/task/index.ts +8 -0
- package/src/task/receipt.ts +7 -2
- package/src/task/types.ts +26 -0
- package/src/tools/ask-answer-registry.ts +25 -0
- package/src/tools/ask.ts +355 -33
- package/src/tools/browser/tab-supervisor.ts +63 -6
- package/src/tools/browser.ts +8 -3
- package/src/tools/index.ts +81 -6
- package/src/tools/sqlite-reader.ts +8 -4
- package/src/tui/output-block.ts +18 -6
- package/src/utils/external-editor.ts +6 -1
package/src/commands/launch.ts
CHANGED
|
@@ -13,19 +13,24 @@ import { prepareLaunchWorktree } from "../gjc-runtime/launch-worktree";
|
|
|
13
13
|
import {
|
|
14
14
|
GJC_COORDINATOR_SESSION_ID_ENV,
|
|
15
15
|
GJC_COORDINATOR_SESSION_STATE_FILE_ENV,
|
|
16
|
+
GJC_TMUX_OWNER_GENERATION_ENV,
|
|
16
17
|
} from "../gjc-runtime/session-state-sidecar";
|
|
17
18
|
import { runRootCommand } from "../main";
|
|
18
19
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
19
20
|
|
|
20
|
-
async function persistCoordinatorLaunchFailure(
|
|
21
|
-
|
|
21
|
+
export async function persistCoordinatorLaunchFailure(
|
|
22
|
+
error: unknown,
|
|
23
|
+
cwd: string,
|
|
24
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
const stateFile = env[GJC_COORDINATOR_SESSION_STATE_FILE_ENV]?.trim();
|
|
22
27
|
if (!stateFile) return;
|
|
23
28
|
const message = error instanceof Error ? error.message : String(error);
|
|
24
29
|
const code = message.split(":", 1)[0] || "launch_failed";
|
|
25
30
|
const now = new Date().toISOString();
|
|
26
31
|
const payload = {
|
|
27
32
|
schema_version: 1,
|
|
28
|
-
session_id:
|
|
33
|
+
session_id: env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || null,
|
|
29
34
|
state: "errored",
|
|
30
35
|
ready_for_input: false,
|
|
31
36
|
updated_at: now,
|
|
@@ -45,6 +50,9 @@ async function persistCoordinatorLaunchFailure(error: unknown, cwd: string): Pro
|
|
|
45
50
|
truncated: false,
|
|
46
51
|
},
|
|
47
52
|
error: { code, message, recoverable: true },
|
|
53
|
+
...(env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim()
|
|
54
|
+
? { owner_generation: env[GJC_TMUX_OWNER_GENERATION_ENV] ?? null }
|
|
55
|
+
: {}),
|
|
48
56
|
};
|
|
49
57
|
await fs.mkdir(path.dirname(stateFile), { recursive: true });
|
|
50
58
|
await Bun.write(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
package/src/commands/notify.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
|
5
5
|
import { type NotifyAction, type NotifyCommandArgs, runNotifyCliCommand } from "../cli/notify-cli";
|
|
6
6
|
import { initTheme } from "../modes/theme/theme";
|
|
7
7
|
|
|
8
|
-
const ACTIONS: NotifyAction[] = ["setup", "status", "daemon-internal"];
|
|
8
|
+
const ACTIONS: NotifyAction[] = ["setup", "status", "health", "test", "recovery", "daemon-internal"];
|
|
9
9
|
|
|
10
10
|
export default class Notify extends Command {
|
|
11
11
|
static description = "Configure Telegram notifications";
|
|
@@ -28,6 +28,8 @@ export default class Notify extends Command {
|
|
|
28
28
|
token: Flags.string({ description: "Telegram bot token (non-interactive setup)" }),
|
|
29
29
|
"chat-id": Flags.string({ description: "Telegram chat id to pair (non-interactive setup)" }),
|
|
30
30
|
redact: Flags.boolean({ description: "Enable redaction of remote notification content" }),
|
|
31
|
+
probe: Flags.boolean({ description: "notify health: probe Telegram reachability (getMe)" }),
|
|
32
|
+
message: Flags.string({ description: "notify test: custom message body" }),
|
|
31
33
|
"owner-id": Flags.string({ description: "Internal: daemon owner id" }),
|
|
32
34
|
"agent-dir": Flags.string({ description: "Internal: agent dir for the daemon" }),
|
|
33
35
|
};
|
|
@@ -53,6 +55,8 @@ export default class Notify extends Command {
|
|
|
53
55
|
token: flags.token as string | undefined,
|
|
54
56
|
chatId: (flags as Record<string, unknown>)["chat-id"] as string | undefined,
|
|
55
57
|
redact: Boolean(flags.redact),
|
|
58
|
+
probe: Boolean(flags.probe),
|
|
59
|
+
message: flags.message as string | undefined,
|
|
56
60
|
};
|
|
57
61
|
|
|
58
62
|
if (action !== "daemon-internal") await initTheme();
|
package/src/commands/session.ts
CHANGED
|
@@ -145,7 +145,12 @@ export default class Session extends Command {
|
|
|
145
145
|
}
|
|
146
146
|
|
|
147
147
|
if (action === "force-close" || action === "force-remove") {
|
|
148
|
-
const closed = forceCloseGjcTmuxSession(
|
|
148
|
+
const closed = await forceCloseGjcTmuxSession(
|
|
149
|
+
sessionName,
|
|
150
|
+
process.env,
|
|
151
|
+
flags["session-id"],
|
|
152
|
+
flags["state-file"],
|
|
153
|
+
);
|
|
149
154
|
if (json) {
|
|
150
155
|
writeJson({ ok: true, session: sessionJson(closed) });
|
|
151
156
|
return;
|
|
@@ -52,6 +52,7 @@ interface AppKeybindings {
|
|
|
52
52
|
"app.plan.toggle": true;
|
|
53
53
|
"app.history.search": true;
|
|
54
54
|
"app.stt.toggle": true;
|
|
55
|
+
"app.irc.sidebar.toggle": true;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export type AppKeybinding = keyof AppKeybindings;
|
|
@@ -214,6 +215,10 @@ export const KEYBINDINGS = {
|
|
|
214
215
|
defaultKeys: "alt+h",
|
|
215
216
|
description: "Toggle speech-to-text",
|
|
216
217
|
},
|
|
218
|
+
"app.irc.sidebar.toggle": {
|
|
219
|
+
defaultKeys: "alt+i",
|
|
220
|
+
description: "Toggle IRC sidebar",
|
|
221
|
+
},
|
|
217
222
|
} as const satisfies KeybindingDefinitions;
|
|
218
223
|
|
|
219
224
|
/**
|
|
@@ -181,7 +181,7 @@ export function formatModelProfileCredentialError(profileLabel: string, provider
|
|
|
181
181
|
return `Model profile "${profileLabel}" requires credentials for: ${providers.join(", ")}. Run /login and configure the missing provider(s), then retry.`;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
function resolveModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
184
|
+
export function resolveModelProfileName(profileName: string, profiles: ReadonlyMap<string, unknown>): string {
|
|
185
185
|
// A retired-name alias is fallback-only: never shadow a profile that actually
|
|
186
186
|
// exists under the requested name (e.g. a user-defined `codex-standard`).
|
|
187
187
|
if (profiles.has(profileName)) return profileName;
|
|
@@ -70,22 +70,22 @@ const profile = (
|
|
|
70
70
|
|
|
71
71
|
export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
72
72
|
profile("codex-eco", ["openai-codex"], {
|
|
73
|
-
default: "openai-codex/gpt-5.6-
|
|
74
|
-
executor: "openai-codex/gpt-5.6-luna:
|
|
75
|
-
planner: "openai-codex/gpt-5.6-
|
|
76
|
-
critic: "openai-codex/gpt-5.6-
|
|
73
|
+
default: "openai-codex/gpt-5.6-terra:high",
|
|
74
|
+
executor: "openai-codex/gpt-5.6-luna:high",
|
|
75
|
+
planner: "openai-codex/gpt-5.6-terra:medium",
|
|
76
|
+
critic: "openai-codex/gpt-5.6-terra:high",
|
|
77
77
|
architect: "openai-codex/gpt-5.6-sol:medium",
|
|
78
78
|
}),
|
|
79
79
|
profile("codex-medium", ["openai-codex"], {
|
|
80
|
-
default: "openai-codex/gpt-5.6-sol:
|
|
81
|
-
executor: "openai-codex/gpt-5.6-
|
|
82
|
-
planner: "openai-codex/gpt-5.6-terra:
|
|
83
|
-
critic: "openai-codex/gpt-5.6-terra:
|
|
80
|
+
default: "openai-codex/gpt-5.6-sol:medium",
|
|
81
|
+
executor: "openai-codex/gpt-5.6-terra:xhigh",
|
|
82
|
+
planner: "openai-codex/gpt-5.6-terra:high",
|
|
83
|
+
critic: "openai-codex/gpt-5.6-terra:xhigh",
|
|
84
84
|
architect: "openai-codex/gpt-5.6-sol:high",
|
|
85
85
|
}),
|
|
86
86
|
profile("codex-pro", ["openai-codex"], {
|
|
87
87
|
default: "openai-codex/gpt-5.6-sol:xhigh",
|
|
88
|
-
executor: "openai-codex/gpt-5.6-terra:
|
|
88
|
+
executor: "openai-codex/gpt-5.6-terra:xhigh",
|
|
89
89
|
planner: "openai-codex/gpt-5.6-sol:high",
|
|
90
90
|
critic: "openai-codex/gpt-5.6-sol:xhigh",
|
|
91
91
|
architect: "openai-codex/gpt-5.6-sol:max",
|
|
@@ -256,13 +256,13 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
256
256
|
}),
|
|
257
257
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
258
258
|
default: "anthropic/claude-opus-4-8:xhigh",
|
|
259
|
-
executor: "openai-codex/gpt-5.6-terra:
|
|
260
|
-
planner: "openai-codex/gpt-5.6-terra:
|
|
261
|
-
critic: "openai-codex/gpt-5.6-terra:
|
|
259
|
+
executor: "openai-codex/gpt-5.6-terra:xhigh",
|
|
260
|
+
planner: "openai-codex/gpt-5.6-terra:high",
|
|
261
|
+
critic: "openai-codex/gpt-5.6-terra:xhigh",
|
|
262
262
|
architect: "openai-codex/gpt-5.6-sol:xhigh",
|
|
263
263
|
}),
|
|
264
264
|
profile("codex-opencodego", ["openai-codex", "opencode-go"], {
|
|
265
|
-
default: "openai-codex/gpt-5.6-
|
|
265
|
+
default: "openai-codex/gpt-5.6-terra:xhigh",
|
|
266
266
|
executor: "opencode-go/deepseek-v4-pro",
|
|
267
267
|
planner: "opencode-go/kimi-k2.6",
|
|
268
268
|
critic: "opencode-go/mimo-v2.5-pro",
|
|
@@ -270,7 +270,7 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
270
270
|
}),
|
|
271
271
|
profile("fable-opus-codex", ["anthropic", "openai-codex"], {
|
|
272
272
|
default: "anthropic/claude-fable-5:high",
|
|
273
|
-
executor: "openai-codex/gpt-5.6-terra:
|
|
273
|
+
executor: "openai-codex/gpt-5.6-terra:xhigh",
|
|
274
274
|
planner: "anthropic/claude-opus-4-8:medium",
|
|
275
275
|
critic: "anthropic/claude-opus-4-8:high",
|
|
276
276
|
architect: "openai-codex/gpt-5.6-sol:xhigh",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Effort } from "@gajae-code/ai/model-thinking";
|
|
2
|
+
import { PET_MODE_IDS, PET_SKIN_IDS, PET_SKINS } from "@gajae-code/tui/components/gajae-pet";
|
|
2
3
|
import { TASK_SIMPLE_MODES } from "../task/simple-mode";
|
|
3
4
|
import { getThinkingLevelMetadata } from "../thinking-metadata";
|
|
4
5
|
import { EDIT_MODES } from "../utils/edit-mode";
|
|
@@ -261,6 +262,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
261
262
|
"notifications.telegram.chatId": { type: "string", default: undefined },
|
|
262
263
|
"notifications.telegram.rich.enabled": { type: "boolean", default: true },
|
|
263
264
|
"notifications.telegram.richDraft.enabled": { type: "boolean", default: false },
|
|
265
|
+
"notifications.telegram.topics.nameTemplate": { type: "string", default: undefined },
|
|
264
266
|
"notifications.discord.botToken": { type: "string", default: undefined },
|
|
265
267
|
"notifications.discord.channelId": { type: "string", default: undefined },
|
|
266
268
|
"notifications.slack.botToken": { type: "string", default: undefined },
|
|
@@ -271,6 +273,11 @@ export const SETTINGS_SCHEMA = {
|
|
|
271
273
|
default: "lean",
|
|
272
274
|
validate: (value: string) => value === "lean" || value === "verbose",
|
|
273
275
|
},
|
|
276
|
+
"notifications.sessionScope": {
|
|
277
|
+
type: "string",
|
|
278
|
+
default: "all",
|
|
279
|
+
validate: (value: string) => value === "all" || value === "primary",
|
|
280
|
+
},
|
|
274
281
|
"notifications.daemon.idleTimeoutMs": {
|
|
275
282
|
type: "number",
|
|
276
283
|
default: 60000,
|
|
@@ -510,6 +517,25 @@ export const SETTINGS_SCHEMA = {
|
|
|
510
517
|
description: "Use the session name color for the editor border and status line gap",
|
|
511
518
|
},
|
|
512
519
|
},
|
|
520
|
+
|
|
521
|
+
"pet.mode": {
|
|
522
|
+
type: "enum",
|
|
523
|
+
values: PET_MODE_IDS,
|
|
524
|
+
default: "off",
|
|
525
|
+
ui: {
|
|
526
|
+
tab: "appearance",
|
|
527
|
+
label: "Gajae Pet",
|
|
528
|
+
description: "16x16 real-pixel gajae living beside the composer (sixel/kitty terminals)",
|
|
529
|
+
options: [
|
|
530
|
+
{ value: "off", label: "Off", description: "No pet" },
|
|
531
|
+
...PET_SKIN_IDS.map(id => ({
|
|
532
|
+
value: id,
|
|
533
|
+
label: PET_SKINS[id].label,
|
|
534
|
+
description: PET_SKINS[id].description,
|
|
535
|
+
})),
|
|
536
|
+
],
|
|
537
|
+
},
|
|
538
|
+
},
|
|
513
539
|
"statusLine.maxRows": {
|
|
514
540
|
type: "number",
|
|
515
541
|
default: 1,
|
|
@@ -1224,7 +1250,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1224
1250
|
ui: {
|
|
1225
1251
|
tab: "interaction",
|
|
1226
1252
|
label: "Check for Updates",
|
|
1227
|
-
description:
|
|
1253
|
+
description:
|
|
1254
|
+
"At interactive startup, notify of newer versions; never install. Use `gjc update` only for recognized Bun global, Windows npm, or bundled-installer binaries; source, linked, and unrecognized installs use their original method.",
|
|
1228
1255
|
},
|
|
1229
1256
|
},
|
|
1230
1257
|
|
|
@@ -2164,6 +2191,16 @@ export const SETTINGS_SCHEMA = {
|
|
|
2164
2191
|
},
|
|
2165
2192
|
},
|
|
2166
2193
|
|
|
2194
|
+
"irc.sidebar.enabled": {
|
|
2195
|
+
type: "boolean",
|
|
2196
|
+
default: true,
|
|
2197
|
+
ui: {
|
|
2198
|
+
tab: "tools",
|
|
2199
|
+
label: "IRC Sidebar",
|
|
2200
|
+
description: "Enable the IRC message sidebar (opens with the toggle key; starts closed)",
|
|
2201
|
+
},
|
|
2202
|
+
},
|
|
2203
|
+
|
|
2167
2204
|
// Optional tools
|
|
2168
2205
|
|
|
2169
2206
|
"renderMermaid.enabled": {
|
|
@@ -3417,6 +3454,9 @@ export interface NotificationsSettings {
|
|
|
3417
3454
|
richDraft: {
|
|
3418
3455
|
enabled: boolean;
|
|
3419
3456
|
};
|
|
3457
|
+
topics: {
|
|
3458
|
+
nameTemplate: string | undefined;
|
|
3459
|
+
};
|
|
3420
3460
|
};
|
|
3421
3461
|
discord: {
|
|
3422
3462
|
botToken: string | undefined;
|
package/src/config/settings.ts
CHANGED
|
@@ -55,6 +55,12 @@ export interface RawSettings {
|
|
|
55
55
|
[key: string]: unknown;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
type SettingsPatch = {
|
|
59
|
+
readonly path: string;
|
|
60
|
+
readonly value: unknown;
|
|
61
|
+
readonly generation: number;
|
|
62
|
+
};
|
|
63
|
+
|
|
58
64
|
export interface SettingsOptions {
|
|
59
65
|
/** Current working directory for project settings discovery */
|
|
60
66
|
cwd?: string;
|
|
@@ -207,12 +213,14 @@ export class Settings {
|
|
|
207
213
|
/** Merged view (global + project + overrides) */
|
|
208
214
|
#merged: RawSettings = {};
|
|
209
215
|
|
|
210
|
-
/**
|
|
211
|
-
#modified = new
|
|
216
|
+
/** Latest dirty patch for each path, owned by its generation. */
|
|
217
|
+
#modified = new Map<string, SettingsPatch>();
|
|
218
|
+
#nextGeneration = 0;
|
|
212
219
|
|
|
213
220
|
/** Pending save (debounced) */
|
|
214
221
|
#saveTimer?: NodeJS.Timeout;
|
|
215
|
-
#
|
|
222
|
+
#saveTail: Promise<void> = Promise.resolve();
|
|
223
|
+
#globalModelRoleTail: Promise<void> = Promise.resolve();
|
|
216
224
|
|
|
217
225
|
/** Whether to persist changes */
|
|
218
226
|
#persist: boolean;
|
|
@@ -320,9 +328,13 @@ export class Settings {
|
|
|
320
328
|
*/
|
|
321
329
|
set<P extends SettingPath>(path: P, value: SettingValue<P>): void {
|
|
322
330
|
const prev = this.get(path);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
331
|
+
const patch: SettingsPatch = {
|
|
332
|
+
path,
|
|
333
|
+
value: structuredClone(value),
|
|
334
|
+
generation: ++this.#nextGeneration,
|
|
335
|
+
};
|
|
336
|
+
setByPath(this.#global, path.split("."), structuredClone(patch.value));
|
|
337
|
+
this.#modified.set(path, patch);
|
|
326
338
|
this.#rebuildMerged();
|
|
327
339
|
this.#queueSave();
|
|
328
340
|
|
|
@@ -366,12 +378,8 @@ export class Settings {
|
|
|
366
378
|
clearTimeout(this.#saveTimer);
|
|
367
379
|
this.#saveTimer = undefined;
|
|
368
380
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
if (this.#modified.size > 0) {
|
|
373
|
-
await this.#saveNow();
|
|
374
|
-
}
|
|
381
|
+
const save = this.#modified.size > 0 ? this.#saveNow() : this.#saveTail;
|
|
382
|
+
await save;
|
|
375
383
|
}
|
|
376
384
|
|
|
377
385
|
/**
|
|
@@ -385,12 +393,8 @@ export class Settings {
|
|
|
385
393
|
clearTimeout(this.#saveTimer);
|
|
386
394
|
this.#saveTimer = undefined;
|
|
387
395
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
if (this.#modified.size > 0) {
|
|
392
|
-
await this.#saveNow({ throwOnError: true });
|
|
393
|
-
}
|
|
396
|
+
const save = this.#modified.size > 0 ? this.#saveNow({ throwOnError: true }) : this.#saveTail;
|
|
397
|
+
await save;
|
|
394
398
|
}
|
|
395
399
|
|
|
396
400
|
async cloneForCwd(cwd: string): Promise<Settings> {
|
|
@@ -480,7 +484,6 @@ export class Settings {
|
|
|
480
484
|
* Set a model role (helper for modelRoles record).
|
|
481
485
|
*/
|
|
482
486
|
setModelRole(role: ModelRole | string, modelId: string): void {
|
|
483
|
-
const current = shallowStringRecord(getByPath(this.#global, ["modelRoles"]));
|
|
484
487
|
const runtimeOverrides = getByPath(this.#overrides, ["modelRoles"]);
|
|
485
488
|
const updateRuntimeOverride =
|
|
486
489
|
!!runtimeOverrides &&
|
|
@@ -488,12 +491,55 @@ export class Settings {
|
|
|
488
491
|
!Array.isArray(runtimeOverrides) &&
|
|
489
492
|
Object.hasOwn(runtimeOverrides, role);
|
|
490
493
|
|
|
491
|
-
this.
|
|
494
|
+
this.setGlobalModelRole(role, modelId);
|
|
492
495
|
|
|
493
496
|
if (updateRuntimeOverride) {
|
|
494
497
|
this.override("modelRoles", { ...shallowStringRecord(runtimeOverrides), [role]: modelId });
|
|
495
498
|
}
|
|
496
499
|
}
|
|
500
|
+
|
|
501
|
+
setGlobalModelRole(role: ModelRole | string, modelId: string | undefined): void {
|
|
502
|
+
const current = shallowStringRecord(getByPath(this.#global, ["modelRoles"]));
|
|
503
|
+
if (modelId === undefined) {
|
|
504
|
+
const { [role]: _removed, ...remaining } = current;
|
|
505
|
+
this.set("modelRoles", remaining);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
this.set("modelRoles", { ...current, [role]: modelId });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
setGlobalModelRoleAndFlush(role: ModelRole | string, modelId: string | undefined): Promise<void> {
|
|
512
|
+
const transaction = this.#globalModelRoleTail.then(async () => {
|
|
513
|
+
const hadModelRoles = Object.hasOwn(this.#global, "modelRoles");
|
|
514
|
+
const previousModelRoles = structuredClone(this.#global.modelRoles);
|
|
515
|
+
const previousPatch = this.#modified.get("modelRoles");
|
|
516
|
+
this.setGlobalModelRole(role, modelId);
|
|
517
|
+
const generation = this.#modified.get("modelRoles")?.generation;
|
|
518
|
+
if (this.#saveTimer) {
|
|
519
|
+
clearTimeout(this.#saveTimer);
|
|
520
|
+
this.#saveTimer = undefined;
|
|
521
|
+
}
|
|
522
|
+
const save = this.#saveNow({ throwOnError: true });
|
|
523
|
+
try {
|
|
524
|
+
await save;
|
|
525
|
+
} catch (error) {
|
|
526
|
+
const currentPatch = this.#modified.get("modelRoles");
|
|
527
|
+
if (currentPatch?.generation === generation) {
|
|
528
|
+
if (hadModelRoles) this.#global.modelRoles = previousModelRoles;
|
|
529
|
+
else delete this.#global.modelRoles;
|
|
530
|
+
if (previousPatch) this.#modified.set("modelRoles", previousPatch);
|
|
531
|
+
else this.#modified.delete("modelRoles");
|
|
532
|
+
this.#rebuildMerged();
|
|
533
|
+
}
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
});
|
|
537
|
+
this.#globalModelRoleTail = transaction.then(
|
|
538
|
+
() => undefined,
|
|
539
|
+
() => undefined,
|
|
540
|
+
);
|
|
541
|
+
return transaction;
|
|
542
|
+
}
|
|
497
543
|
/**
|
|
498
544
|
* Set an agent model override while keeping any live runtime override aligned.
|
|
499
545
|
*
|
|
@@ -824,15 +870,10 @@ export class Settings {
|
|
|
824
870
|
#queueSave(): void {
|
|
825
871
|
if (!this.#persist || !this.#configPath) return;
|
|
826
872
|
|
|
827
|
-
|
|
828
|
-
if (this.#saveTimer) {
|
|
829
|
-
clearTimeout(this.#saveTimer);
|
|
830
|
-
}
|
|
873
|
+
if (this.#saveTimer) clearTimeout(this.#saveTimer);
|
|
831
874
|
this.#saveTimer = setTimeout(() => {
|
|
832
875
|
this.#saveTimer = undefined;
|
|
833
|
-
this.#saveNow()
|
|
834
|
-
logger.warn("Settings: background save failed", { error: String(err) });
|
|
835
|
-
});
|
|
876
|
+
void this.#saveNow();
|
|
836
877
|
}, 100);
|
|
837
878
|
}
|
|
838
879
|
|
|
@@ -840,30 +881,40 @@ export class Settings {
|
|
|
840
881
|
if (!this.#persist || !this.#configPath || this.#modified.size === 0) return;
|
|
841
882
|
|
|
842
883
|
const configPath = this.#configPath;
|
|
843
|
-
const
|
|
844
|
-
this.#modified.clear();
|
|
884
|
+
const patches = [...this.#modified.values()];
|
|
845
885
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
// Re-read to preserve external changes
|
|
886
|
+
const save = this.#saveTail.then(() =>
|
|
887
|
+
withFileLock(configPath, async () => {
|
|
849
888
|
const current = await this.#loadYaml(configPath);
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
889
|
+
for (const patch of patches) {
|
|
890
|
+
setByPath(current, patch.path.split("."), patch.value);
|
|
891
|
+
}
|
|
892
|
+
await Bun.write(configPath, YAML.stringify(current, null, 2));
|
|
893
|
+
for (const patch of patches) {
|
|
894
|
+
if (this.#modified.get(patch.path)?.generation === patch.generation) {
|
|
895
|
+
this.#modified.delete(patch.path);
|
|
896
|
+
}
|
|
856
897
|
}
|
|
857
|
-
|
|
858
|
-
// Update our global with any external changes we preserved
|
|
859
898
|
this.#global = current;
|
|
860
|
-
|
|
861
|
-
|
|
899
|
+
for (const patch of this.#modified.values()) {
|
|
900
|
+
setByPath(this.#global, patch.path.split("."), structuredClone(patch.value));
|
|
901
|
+
}
|
|
902
|
+
}),
|
|
903
|
+
);
|
|
904
|
+
this.#saveTail = save.then(
|
|
905
|
+
() => undefined,
|
|
906
|
+
() => undefined,
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
try {
|
|
910
|
+
await save;
|
|
862
911
|
} catch (error) {
|
|
863
912
|
logger.warn("Settings: save failed", { error: String(error) });
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
913
|
+
for (const patch of patches) {
|
|
914
|
+
const currentPatch = this.#modified.get(patch.path);
|
|
915
|
+
if (currentPatch?.generation === patch.generation) {
|
|
916
|
+
this.#modified.set(patch.path, patch);
|
|
917
|
+
}
|
|
867
918
|
}
|
|
868
919
|
if (options.throwOnError) {
|
|
869
920
|
this.#rebuildMerged();
|
|
@@ -12,6 +12,7 @@ export const COORDINATOR_MCP_TOOL_NAMES = [
|
|
|
12
12
|
"gjc_coordinator_watch_events",
|
|
13
13
|
"gjc_coordinator_register_session",
|
|
14
14
|
"gjc_coordinator_start_session",
|
|
15
|
+
"gjc_coordinator_stop_session",
|
|
15
16
|
"gjc_coordinator_send_prompt",
|
|
16
17
|
"gjc_coordinator_submit_question_answer",
|
|
17
18
|
"gjc_coordinator_read_turn",
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import * as path from "node:path";
|
|
2
|
+
import { getAgentDir } from "@gajae-code/utils";
|
|
3
|
+
import { resolveModelProfileName } from "../config/model-profile-activation";
|
|
4
|
+
import { type ModelProfileDefinition, mergeModelProfiles } from "../config/model-profiles";
|
|
5
|
+
import { ModelsConfigFile } from "../config/model-registry";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Loads the merged built-in + custom model-profile registry the way the `gjc`
|
|
9
|
+
* CLI resolves `--mpreset`, so coordinator MCP launches select the same
|
|
10
|
+
* authoritative profile the spawned child will activate. Custom profiles live
|
|
11
|
+
* in the shared models config, which the child inherits, so both sides agree.
|
|
12
|
+
*/
|
|
13
|
+
export type CoordinatorModelProfileLoader = () =>
|
|
14
|
+
| Map<string, ModelProfileDefinition>
|
|
15
|
+
| Promise<Map<string, ModelProfileDefinition>>;
|
|
16
|
+
|
|
17
|
+
const MAX_ECHOED_MPRESET_LENGTH = 128;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Thrown by the default loader when `models.yml` exists but is invalid or
|
|
21
|
+
* unreadable. This lets the resolver fail closed with a distinct, stable reason
|
|
22
|
+
* instead of silently collapsing a broken registry to the built-ins-only set
|
|
23
|
+
* (which would misreport a caller's valid custom profile as unknown).
|
|
24
|
+
*/
|
|
25
|
+
export class CoordinatorModelProfileRegistryError extends Error {
|
|
26
|
+
constructor(cause?: unknown) {
|
|
27
|
+
super("coordinator_model_profile_registry_error");
|
|
28
|
+
this.name = "CoordinatorModelProfileRegistryError";
|
|
29
|
+
if (cause !== undefined) (this as { cause?: unknown }).cause = cause;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const loadCoordinatorModelProfiles: CoordinatorModelProfileLoader = () => {
|
|
34
|
+
const configFile = ModelsConfigFile.relocate(path.join(getAgentDir(), "models.yml"));
|
|
35
|
+
configFile.invalidate();
|
|
36
|
+
const loaded = configFile.tryLoad();
|
|
37
|
+
// A present-but-invalid config must fail closed; absence (`value` undefined
|
|
38
|
+
// with a non-error status) is fine and yields the built-in profiles only.
|
|
39
|
+
if (loaded.status === "error") throw new CoordinatorModelProfileRegistryError(loaded.error);
|
|
40
|
+
return mergeModelProfiles(loaded.value?.profiles);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function sortedProfileNames(profiles: ReadonlyMap<string, ModelProfileDefinition>): string[] {
|
|
44
|
+
return [...profiles.keys()].sort((left, right) => left.localeCompare(right));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type CoordinatorMpresetResolution =
|
|
48
|
+
| { ok: true; mpreset: string | null }
|
|
49
|
+
| { ok: false; reason: "unknown_model_profile"; mpreset: string; available_profiles: string[] }
|
|
50
|
+
| { ok: false; reason: "model_profile_registry_error"; mpreset: string; available_profiles: string[] };
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Resolve a coordinator `mpreset` argument against the merged profile registry.
|
|
54
|
+
*
|
|
55
|
+
* Only an absent (`undefined`/`null`) value is a no-op (`mpreset: null`); an
|
|
56
|
+
* explicit empty/whitespace string is a caller error and is rejected rather
|
|
57
|
+
* than silently launching at the default tier. Legacy aliases are canonicalized
|
|
58
|
+
* exactly like the CLI (e.g. `codex-standard` -> `codex-medium`) so coordinator
|
|
59
|
+
* selection stays in parity with `gjc --mpreset <profile>`; the resolved value
|
|
60
|
+
* is the canonical profile name. Unknown names are rejected with the
|
|
61
|
+
* available-profile listing and never reach a spawned child command, and a
|
|
62
|
+
* broken registry fails closed with `model_profile_registry_error`.
|
|
63
|
+
*/
|
|
64
|
+
export async function resolveCoordinatorMpreset(
|
|
65
|
+
raw: unknown,
|
|
66
|
+
loadProfiles: CoordinatorModelProfileLoader,
|
|
67
|
+
): Promise<CoordinatorMpresetResolution> {
|
|
68
|
+
if (raw === undefined || raw === null) return { ok: true, mpreset: null };
|
|
69
|
+
const requested = typeof raw === "string" ? raw.trim() : "";
|
|
70
|
+
const echoed = requested.slice(0, MAX_ECHOED_MPRESET_LENGTH);
|
|
71
|
+
let profiles: Map<string, ModelProfileDefinition>;
|
|
72
|
+
try {
|
|
73
|
+
profiles = await loadProfiles();
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof CoordinatorModelProfileRegistryError) {
|
|
76
|
+
return { ok: false, reason: "model_profile_registry_error", mpreset: echoed, available_profiles: [] };
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
// Non-string input and explicit blank/whitespace strings can never name a
|
|
81
|
+
// profile; only absent/null (handled above) means "no selection".
|
|
82
|
+
if (typeof raw !== "string" || requested.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
reason: "unknown_model_profile",
|
|
86
|
+
mpreset: echoed,
|
|
87
|
+
available_profiles: sortedProfileNames(profiles),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const canonical = resolveModelProfileName(requested, profiles);
|
|
91
|
+
if (!profiles.has(canonical)) {
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
reason: "unknown_model_profile",
|
|
95
|
+
mpreset: echoed,
|
|
96
|
+
available_profiles: sortedProfileNames(profiles),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { ok: true, mpreset: canonical };
|
|
100
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { coordinatorMcpStateRoot, gjcRoot } from "../gjc-runtime/session-layout";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_SESSION_IDLE_TTL_MS,
|
|
6
|
+
DEFAULT_SESSION_SWEEP_INTERVAL_MS,
|
|
7
|
+
MIN_SESSION_IDLE_TTL_MS,
|
|
8
|
+
MIN_SESSION_SWEEP_INTERVAL_MS,
|
|
9
|
+
} from "./session-reaper";
|
|
4
10
|
|
|
5
11
|
export type CoordinatorMutationClass = "sessions" | "questions" | "reports";
|
|
6
12
|
|
|
@@ -16,6 +22,9 @@ export interface CoordinatorMcpConfig {
|
|
|
16
22
|
namespace: CoordinatorNamespace;
|
|
17
23
|
stateRoot: string;
|
|
18
24
|
sessionCommand: string | null;
|
|
25
|
+
sessionIdleTtlMs: number;
|
|
26
|
+
sessionSweepIntervalMs: number;
|
|
27
|
+
forceStopEnabled: boolean;
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
export interface CoordinatorMutationRequest {
|
|
@@ -32,6 +41,16 @@ const LEGACY_MUTATION_CLASS_ALIASES = new Map<string, CoordinatorMutationClass>(
|
|
|
32
41
|
["report", "reports"],
|
|
33
42
|
]);
|
|
34
43
|
|
|
44
|
+
function parsePositiveIntMs(value: string | undefined, fallback: number, floor: number): number {
|
|
45
|
+
const parsed = Number.parseInt((value ?? "").trim(), 10);
|
|
46
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
47
|
+
return Math.max(floor, parsed);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseBool(value: string | undefined): boolean {
|
|
51
|
+
return /^(1|true|yes|on)$/i.test((value ?? "").trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
35
54
|
function parseList(value: string | undefined): string[] {
|
|
36
55
|
return (value ?? "")
|
|
37
56
|
.split(/[\n,;:]+/)
|
|
@@ -98,6 +117,17 @@ export function buildCoordinatorMcpConfig(env: NodeJS.ProcessEnv = process.env):
|
|
|
98
117
|
},
|
|
99
118
|
stateRoot: path.resolve(stateRoot),
|
|
100
119
|
sessionCommand: env.GJC_COORDINATOR_MCP_SESSION_COMMAND?.trim() || null,
|
|
120
|
+
sessionIdleTtlMs: parsePositiveIntMs(
|
|
121
|
+
env.GJC_COORDINATOR_MCP_SESSION_IDLE_TTL_MS,
|
|
122
|
+
DEFAULT_SESSION_IDLE_TTL_MS,
|
|
123
|
+
MIN_SESSION_IDLE_TTL_MS,
|
|
124
|
+
),
|
|
125
|
+
sessionSweepIntervalMs: parsePositiveIntMs(
|
|
126
|
+
env.GJC_COORDINATOR_MCP_SESSION_SWEEP_INTERVAL_MS,
|
|
127
|
+
DEFAULT_SESSION_SWEEP_INTERVAL_MS,
|
|
128
|
+
MIN_SESSION_SWEEP_INTERVAL_MS,
|
|
129
|
+
),
|
|
130
|
+
forceStopEnabled: parseBool(env.GJC_COORDINATOR_MCP_FORCE_STOP),
|
|
101
131
|
};
|
|
102
132
|
}
|
|
103
133
|
|