@gajae-code/coding-agent 0.15.5 → 0.16.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 +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/state-version.d.ts +4 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +548 -0
- package/src/sdk/broker/state-version.ts +26 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { getAgentDir } from "@gajae-code/utils";
|
|
2
|
+
import {
|
|
3
|
+
getModelPresetRegistryStatus,
|
|
4
|
+
type ModelPresetRegistryStatus,
|
|
5
|
+
refreshModelPresetRegistry,
|
|
6
|
+
rollbackModelPresetRegistry,
|
|
7
|
+
setModelPresetRegistryDisabled,
|
|
8
|
+
setModelPresetRegistryPin,
|
|
9
|
+
} from "../config/model-preset-registry";
|
|
10
|
+
|
|
11
|
+
export const MODEL_PRESETS_ACTIONS = ["status", "refresh", "rollback", "pin", "unpin", "disable", "enable"] as const;
|
|
12
|
+
export type ModelPresetsAction = (typeof MODEL_PRESETS_ACTIONS)[number];
|
|
13
|
+
|
|
14
|
+
export interface ModelPresetsCommandArgs {
|
|
15
|
+
action: ModelPresetsAction;
|
|
16
|
+
revision?: string;
|
|
17
|
+
json?: boolean;
|
|
18
|
+
agentDir?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseRevision(value: string | undefined, action: "rollback" | "pin"): number {
|
|
22
|
+
if (!value || !/^\d+$/.test(value)) throw new Error(`${action} requires a positive accepted revision.`);
|
|
23
|
+
const revision = Number(value);
|
|
24
|
+
if (!Number.isSafeInteger(revision) || revision <= 0)
|
|
25
|
+
throw new Error(`${action} requires a positive accepted revision.`);
|
|
26
|
+
return revision;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function humanStatus(status: ModelPresetRegistryStatus): string {
|
|
30
|
+
const lines = [
|
|
31
|
+
`source: ${status.source}`,
|
|
32
|
+
`cache: ${status.cacheHealth}`,
|
|
33
|
+
`contract: ${status.contractVersion}`,
|
|
34
|
+
`active revision: ${status.activeRevisionId ?? status.activeRevision ?? "none"}`,
|
|
35
|
+
`highest seen revision: ${status.highestSeenRevision ?? "none"}`,
|
|
36
|
+
`pinned revision: ${status.pinnedRevision ?? "none"}`,
|
|
37
|
+
`disabled: ${status.disabled ? "yes" : "no"}`,
|
|
38
|
+
`profiles: ${status.profileCount}`,
|
|
39
|
+
`model presets: ${status.presetCount}`,
|
|
40
|
+
`history: ${status.historyRevisions.length > 0 ? status.historyRevisions.join(", ") : "none"}`,
|
|
41
|
+
];
|
|
42
|
+
if (status.manifestSha256) lines.push(`manifest sha256: ${status.manifestSha256}`);
|
|
43
|
+
if (status.snapshotSha256) lines.push(`snapshot sha256: ${status.snapshotSha256}`);
|
|
44
|
+
if (status.profilesSha256) lines.push(`profiles sha256: ${status.profilesSha256}`);
|
|
45
|
+
if (status.presetsSha256) lines.push(`presets sha256: ${status.presetsSha256}`);
|
|
46
|
+
if (status.keyId) lines.push(`signature key: ${status.keyId}`);
|
|
47
|
+
if (status.sourceRevision) lines.push(`source revision: ${status.sourceRevision}`);
|
|
48
|
+
if (status.acceptedAt) lines.push(`accepted: ${status.acceptedAt}`);
|
|
49
|
+
if (status.lastCheckedAt) lines.push(`last checked: ${status.lastCheckedAt}`);
|
|
50
|
+
if (status.retainedProfiles.length > 0)
|
|
51
|
+
lines.push(`retained removed profiles: ${status.retainedProfiles.join(", ")}`);
|
|
52
|
+
if (status.retainedPresets.length > 0) lines.push(`retained removed presets: ${status.retainedPresets.join(", ")}`);
|
|
53
|
+
if (status.lastError) lines.push(`last error: ${status.lastError}`);
|
|
54
|
+
return `${lines.join("\n")}\n`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeStatus(status: ModelPresetRegistryStatus, json: boolean): void {
|
|
58
|
+
process.stdout.write(json ? `${JSON.stringify(status)}\n` : humanStatus(status));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function runModelPresetsCommand(args: ModelPresetsCommandArgs): Promise<void> {
|
|
62
|
+
const agentDir = args.agentDir ?? getAgentDir();
|
|
63
|
+
const dependencies = { agentDir };
|
|
64
|
+
switch (args.action) {
|
|
65
|
+
case "status":
|
|
66
|
+
writeStatus(getModelPresetRegistryStatus(dependencies), args.json === true);
|
|
67
|
+
return;
|
|
68
|
+
case "refresh": {
|
|
69
|
+
const result = await refreshModelPresetRegistry(dependencies);
|
|
70
|
+
if (args.json) {
|
|
71
|
+
process.stdout.write(`${JSON.stringify({ result, status: getModelPresetRegistryStatus(dependencies) })}\n`);
|
|
72
|
+
} else {
|
|
73
|
+
process.stdout.write(
|
|
74
|
+
result.status === "updated"
|
|
75
|
+
? `Accepted preset registry revision ${result.revision}.\n`
|
|
76
|
+
: result.status === "not_modified"
|
|
77
|
+
? "Preset registry is already current.\n"
|
|
78
|
+
: "Preset registry refresh is disabled.\n",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
case "rollback":
|
|
84
|
+
await rollbackModelPresetRegistry({ ...dependencies, revision: parseRevision(args.revision, "rollback") });
|
|
85
|
+
break;
|
|
86
|
+
case "pin":
|
|
87
|
+
await setModelPresetRegistryPin({ ...dependencies, revision: parseRevision(args.revision, "pin") });
|
|
88
|
+
break;
|
|
89
|
+
case "unpin":
|
|
90
|
+
await setModelPresetRegistryPin(dependencies);
|
|
91
|
+
break;
|
|
92
|
+
case "disable":
|
|
93
|
+
await setModelPresetRegistryDisabled({ ...dependencies, disabled: true });
|
|
94
|
+
break;
|
|
95
|
+
case "enable":
|
|
96
|
+
await setModelPresetRegistryDisabled({ ...dependencies, disabled: false });
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
writeStatus(getModelPresetRegistryStatus(dependencies), args.json === true);
|
|
100
|
+
}
|
package/src/cli/root-flags.ts
CHANGED
|
@@ -73,6 +73,11 @@ export const ROOT_LAUNCH_FLAGS = {
|
|
|
73
73
|
optionalValue: true,
|
|
74
74
|
}),
|
|
75
75
|
"no-title": Flags.boolean({ description: "Disable title auto-generation" }),
|
|
76
|
+
master: Flags.boolean({ description: "Launch an interactive master session" }),
|
|
77
|
+
scope: Flags.string({
|
|
78
|
+
description: "Master scope: repo (default), pwd, or global",
|
|
79
|
+
options: ["repo", "pwd", "global"],
|
|
80
|
+
}),
|
|
76
81
|
};
|
|
77
82
|
|
|
78
83
|
export type ConsumerLaunchFlagName = "hook" | "extension" | "no-extensions" | "no-skills" | "skills";
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as fs from "node:fs";
|
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { pipeline } from "node:stream/promises";
|
|
12
|
-
import { $which, APP_NAME, isCompiledBinary, isEnoent, VERSION } from "@gajae-code/utils";
|
|
12
|
+
import { $which, APP_NAME, isCompiledBinary, isEnoent, redactCrashSecrets, VERSION } from "@gajae-code/utils";
|
|
13
13
|
import { $ } from "bun";
|
|
14
14
|
import chalk from "chalk";
|
|
15
15
|
import { Settings } from "../config/settings";
|
|
@@ -17,6 +17,8 @@ import { isUpdateChannel, UPDATE_CHANNELS, type UpdateChannel } from "../config/
|
|
|
17
17
|
import { installDefaultGjcDefinitions } from "../defaults/gjc-defaults";
|
|
18
18
|
import { theme } from "../modes/theme/theme";
|
|
19
19
|
import { getNotificationConfig, type NotificationProvider, resolveNotificationProvider } from "../sdk/bus/config";
|
|
20
|
+
import type { TelemetryDetails, TelemetryEventName } from "../telemetry";
|
|
21
|
+
import { recordTelemetryEvent } from "../telemetry";
|
|
20
22
|
import { runDaemonCommand } from "./daemon-cli";
|
|
21
23
|
import {
|
|
22
24
|
fetchGithubChannelRelease,
|
|
@@ -53,6 +55,7 @@ export interface InstalledVersionVerification {
|
|
|
53
55
|
ok: boolean;
|
|
54
56
|
actual?: string;
|
|
55
57
|
path?: string;
|
|
58
|
+
versionOutput?: string;
|
|
56
59
|
smokeTestFailed?: boolean;
|
|
57
60
|
smokeTestOutput?: string;
|
|
58
61
|
cleanupWarning?: string;
|
|
@@ -460,6 +463,71 @@ export function parseReportedVersionForTest(output: string): string | undefined
|
|
|
460
463
|
return parseReportedVersion(output);
|
|
461
464
|
}
|
|
462
465
|
|
|
466
|
+
const VERIFICATION_OUTPUT_MAX_LENGTH = 512;
|
|
467
|
+
const ANSI_ESCAPE = /\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/gu;
|
|
468
|
+
const UNSAFE_CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu;
|
|
469
|
+
|
|
470
|
+
function sanitizeVerificationOutput(...streams: Array<string | undefined>): string | undefined {
|
|
471
|
+
const output = streams
|
|
472
|
+
.map(stream => stream?.replace(ANSI_ESCAPE, "").replace(UNSAFE_CONTROL, " ").replace(/\s+/g, " ").trim() ?? "")
|
|
473
|
+
.filter(Boolean)
|
|
474
|
+
.filter((stream, index, all) => all.indexOf(stream) === index)
|
|
475
|
+
.join(" ");
|
|
476
|
+
if (!output) return undefined;
|
|
477
|
+
const redacted = redactCrashSecrets(output);
|
|
478
|
+
if (redacted.length <= VERIFICATION_OUTPUT_MAX_LENGTH) return redacted;
|
|
479
|
+
return `${redacted.slice(0, VERIFICATION_OUTPUT_MAX_LENGTH - 3)}...`;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function sanitizeVerificationOutputForTest(
|
|
483
|
+
stderr: string | undefined,
|
|
484
|
+
stdout: string | undefined,
|
|
485
|
+
): string | undefined {
|
|
486
|
+
return sanitizeVerificationOutput(stderr, stdout);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
interface InstalledVersionCommandResult {
|
|
490
|
+
exitCode: number | null;
|
|
491
|
+
stdout: string;
|
|
492
|
+
stderr: string;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
type InstalledVersionCommandRunner = (runtimePath: string) => Promise<InstalledVersionCommandResult>;
|
|
496
|
+
|
|
497
|
+
async function verifyInstalledVersionWith(
|
|
498
|
+
expectedVersion: string,
|
|
499
|
+
runtimePath: string | undefined,
|
|
500
|
+
runVersion: InstalledVersionCommandRunner,
|
|
501
|
+
): Promise<InstalledVersionVerification> {
|
|
502
|
+
if (!runtimePath) return { ok: false };
|
|
503
|
+
try {
|
|
504
|
+
const result = await runVersion(runtimePath);
|
|
505
|
+
if (result.exitCode !== 0) {
|
|
506
|
+
return {
|
|
507
|
+
ok: false,
|
|
508
|
+
path: runtimePath,
|
|
509
|
+
versionOutput: sanitizeVerificationOutput(result.stderr, result.stdout),
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
const actual = parseReportedVersion(result.stdout);
|
|
513
|
+
return { ok: actual === expectedVersion, actual, path: runtimePath };
|
|
514
|
+
} catch (error) {
|
|
515
|
+
return {
|
|
516
|
+
ok: false,
|
|
517
|
+
path: runtimePath,
|
|
518
|
+
versionOutput: sanitizeVerificationOutput(error instanceof Error ? error.message : String(error)),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export async function verifyInstalledVersionForTest(options: {
|
|
524
|
+
expectedVersion: string;
|
|
525
|
+
runtimePath: string | undefined;
|
|
526
|
+
runVersion: InstalledVersionCommandRunner;
|
|
527
|
+
}): Promise<InstalledVersionVerification> {
|
|
528
|
+
return await verifyInstalledVersionWith(options.expectedVersion, options.runtimePath, options.runVersion);
|
|
529
|
+
}
|
|
530
|
+
|
|
463
531
|
/**
|
|
464
532
|
* Run the resolved gjc binary and check if it reports the expected version.
|
|
465
533
|
*/
|
|
@@ -467,15 +535,14 @@ async function verifyInstalledVersion(
|
|
|
467
535
|
expectedVersion: string,
|
|
468
536
|
runtimePath: string | undefined = resolveGjcPath(),
|
|
469
537
|
): Promise<InstalledVersionVerification> {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
538
|
+
return await verifyInstalledVersionWith(expectedVersion, runtimePath, async resolvedPath => {
|
|
539
|
+
const result = await $`${resolvedPath} --version`.quiet().nothrow();
|
|
540
|
+
return {
|
|
541
|
+
exitCode: result.exitCode,
|
|
542
|
+
stdout: result.stdout.toString(),
|
|
543
|
+
stderr: result.stderr.toString(),
|
|
544
|
+
};
|
|
545
|
+
});
|
|
479
546
|
}
|
|
480
547
|
|
|
481
548
|
async function verifyInstalledRuntime(
|
|
@@ -663,7 +730,8 @@ function formatVerificationFailure(result: InstalledVersionVerification, expecte
|
|
|
663
730
|
if (result.actual) {
|
|
664
731
|
return `${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`;
|
|
665
732
|
}
|
|
666
|
-
|
|
733
|
+
const outputSuffix = result.versionOutput ? `: ${result.versionOutput}` : "";
|
|
734
|
+
return `could not verify updated version${result.path ? ` at ${result.path}` : ""}${outputSuffix}`;
|
|
667
735
|
}
|
|
668
736
|
|
|
669
737
|
export function formatVerificationFailureForTest(
|
|
@@ -1142,6 +1210,7 @@ export interface UpdateCommandDependencies {
|
|
|
1142
1210
|
restartDaemon?: (settings: Settings) => Promise<void>;
|
|
1143
1211
|
recoverNotifications?: (settings: Settings) => Promise<void>;
|
|
1144
1212
|
runPostUpdateRecovery?: (runtimePath: string) => Promise<void>;
|
|
1213
|
+
recordTelemetryEvent?: (event: TelemetryEventName, details: TelemetryDetails) => unknown;
|
|
1145
1214
|
exit?: (code: number) => never;
|
|
1146
1215
|
}
|
|
1147
1216
|
|
|
@@ -1363,6 +1432,24 @@ export async function runUpdateCommand(
|
|
|
1363
1432
|
const update = deps.performUpdate ?? performUpdate;
|
|
1364
1433
|
const refreshDefaults = deps.refreshInstalledDefaultSkills ?? refreshInstalledDefaultSkills;
|
|
1365
1434
|
const exit = deps.exit ?? process.exit;
|
|
1435
|
+
const recordEvent = deps.recordTelemetryEvent ?? ((event, details) => recordTelemetryEvent(event, details));
|
|
1436
|
+
const pendingTelemetry = new Set<Promise<void>>();
|
|
1437
|
+
const record = (event: TelemetryEventName, details: TelemetryDetails): void => {
|
|
1438
|
+
const result = recordEvent(event, details);
|
|
1439
|
+
if (result !== null && (typeof result === "object" || typeof result === "function") && "then" in result) {
|
|
1440
|
+
const pending = Promise.resolve(result as PromiseLike<unknown>).then(
|
|
1441
|
+
() => undefined,
|
|
1442
|
+
() => undefined,
|
|
1443
|
+
);
|
|
1444
|
+
pendingTelemetry.add(pending);
|
|
1445
|
+
void pending.finally(() => pendingTelemetry.delete(pending));
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const flushTelemetryBeforeExit = async (): Promise<never> => {
|
|
1449
|
+
await Promise.race([Promise.allSettled([...pendingTelemetry]), Bun.sleep(2000)]);
|
|
1450
|
+
return exit(1);
|
|
1451
|
+
};
|
|
1452
|
+
record("update_check_started", { channel });
|
|
1366
1453
|
|
|
1367
1454
|
console.log(chalk.dim(`Current version: ${VERSION}`));
|
|
1368
1455
|
if (channel !== "stable") {
|
|
@@ -1373,16 +1460,18 @@ export async function runUpdateCommand(
|
|
|
1373
1460
|
try {
|
|
1374
1461
|
target = await resolveTarget();
|
|
1375
1462
|
} catch (err) {
|
|
1463
|
+
record("update_check_completed", { channel, result: "failed" });
|
|
1376
1464
|
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
1377
|
-
return
|
|
1465
|
+
return flushTelemetryBeforeExit();
|
|
1378
1466
|
}
|
|
1379
1467
|
|
|
1380
1468
|
let release: ReleaseInfo;
|
|
1381
1469
|
try {
|
|
1382
1470
|
release = await lookupRelease({ channel });
|
|
1383
1471
|
} catch (err) {
|
|
1472
|
+
record("update_check_completed", { channel, result: "failed" });
|
|
1384
1473
|
console.error(chalk.red(`Failed to check for updates: ${err}`));
|
|
1385
|
-
return
|
|
1474
|
+
return flushTelemetryBeforeExit();
|
|
1386
1475
|
}
|
|
1387
1476
|
|
|
1388
1477
|
// A config file that exists but could not be read changes which registry
|
|
@@ -1395,12 +1484,13 @@ export async function runUpdateCommand(
|
|
|
1395
1484
|
try {
|
|
1396
1485
|
comparison = compareVersions(release.version, VERSION);
|
|
1397
1486
|
} catch (err) {
|
|
1487
|
+
record("update_check_completed", { channel, result: "failed" });
|
|
1398
1488
|
console.error(
|
|
1399
1489
|
chalk.red(
|
|
1400
1490
|
`Failed to check for updates: the ${channel} channel reported an unparseable version "${release.version}": ${err instanceof Error ? err.message : String(err)}`,
|
|
1401
1491
|
),
|
|
1402
1492
|
);
|
|
1403
|
-
return
|
|
1493
|
+
return flushTelemetryBeforeExit();
|
|
1404
1494
|
}
|
|
1405
1495
|
|
|
1406
1496
|
const decision = resolveUpdateDecision({
|
|
@@ -1416,7 +1506,10 @@ export async function runUpdateCommand(
|
|
|
1416
1506
|
try {
|
|
1417
1507
|
const verification = await verifyTarget(release, target.path);
|
|
1418
1508
|
if (verification.ok) {
|
|
1509
|
+
record("update_check_completed", { channel, result: "available" });
|
|
1510
|
+
record("update_install_started", { channel, installMethod: target.method });
|
|
1419
1511
|
printVerifiedMigrationTarget(target, release.version);
|
|
1512
|
+
record("update_install_completed", { channel, result: "installed", installMethod: target.method });
|
|
1420
1513
|
return;
|
|
1421
1514
|
}
|
|
1422
1515
|
} finally {
|
|
@@ -1425,6 +1518,7 @@ export async function runUpdateCommand(
|
|
|
1425
1518
|
}
|
|
1426
1519
|
|
|
1427
1520
|
if (!decision.install) {
|
|
1521
|
+
record("update_check_completed", { channel, result: "up_to_date" });
|
|
1428
1522
|
console.log(chalk.green(`${theme.status.success} Already up to date`));
|
|
1429
1523
|
return;
|
|
1430
1524
|
}
|
|
@@ -1439,9 +1533,14 @@ export async function runUpdateCommand(
|
|
|
1439
1533
|
console.log(chalk.yellow(`Forcing reinstall of ${release.version}`));
|
|
1440
1534
|
}
|
|
1441
1535
|
|
|
1442
|
-
|
|
1536
|
+
record("update_check_completed", { channel, result: "available" });
|
|
1537
|
+
if (opts.check) {
|
|
1538
|
+
record("update_install_completed", { channel, result: "skipped" });
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1443
1541
|
|
|
1444
1542
|
let installedVersion: string | undefined;
|
|
1543
|
+
record("update_install_started", { channel, installMethod: target.method });
|
|
1445
1544
|
try {
|
|
1446
1545
|
const resolved = target ?? (await resolveTarget());
|
|
1447
1546
|
const verification = await update(resolved, release.version, release.registry);
|
|
@@ -1450,16 +1549,18 @@ export async function runUpdateCommand(
|
|
|
1450
1549
|
await (deps.runPostUpdateRecovery ?? runPostUpdateRecovery)(verification.path);
|
|
1451
1550
|
} else if (!deps.performUpdate) throw new Error("verified installed runtime path is unavailable");
|
|
1452
1551
|
} catch (err) {
|
|
1552
|
+
record("update_install_failed", { channel, result: "failed", installMethod: target.method });
|
|
1453
1553
|
const prefix = installedVersion
|
|
1454
1554
|
? `Updated to ${installedVersion}, but post-update recovery failed`
|
|
1455
1555
|
: "Update failed";
|
|
1456
1556
|
console.error(chalk.red(`${prefix}: ${err}`));
|
|
1457
|
-
return
|
|
1557
|
+
return flushTelemetryBeforeExit();
|
|
1458
1558
|
}
|
|
1459
1559
|
|
|
1460
1560
|
// The installed runtime completes recovery before this old updater process
|
|
1461
1561
|
// refreshes opt-in local definitions, avoiding stale-module daemon control.
|
|
1462
1562
|
await refreshDefaults();
|
|
1563
|
+
record("update_install_completed", { channel, result: "installed", installMethod: target.method });
|
|
1463
1564
|
}
|
|
1464
1565
|
|
|
1465
1566
|
/**
|
package/src/cli.ts
CHANGED
|
@@ -54,6 +54,7 @@ export const commands: CommandEntry[] = [
|
|
|
54
54
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
55
55
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
56
56
|
{ name: "local-provider", load: () => import("./commands/local-provider").then(m => m.default) },
|
|
57
|
+
{ name: "model-presets", load: () => import("./commands/model-presets").then(m => m.default) },
|
|
57
58
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
58
59
|
{ name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
|
|
59
60
|
{
|
|
@@ -364,6 +365,7 @@ function routeLegacyRootArgv(argv: readonly string[]): string[] | undefined {
|
|
|
364
365
|
export function routeModelsAlias(argv: readonly string[]): string[] | undefined {
|
|
365
366
|
if (argv[0] !== "models") return undefined;
|
|
366
367
|
const rest = argv.slice(1);
|
|
368
|
+
if (rest[0] === "presets") return ["model-presets", ...rest.slice(1)];
|
|
367
369
|
if (rest.length === 0) return ["launch", "--list-models"];
|
|
368
370
|
// Pure search tokens become a single fuzzy pattern (matches --list-models).
|
|
369
371
|
if (rest.every(token => !token.startsWith("-") && !token.startsWith("@"))) {
|
|
@@ -461,7 +463,9 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
461
463
|
}
|
|
462
464
|
const normalizedArgv = normalizeResumeAlias(argv);
|
|
463
465
|
const legacyArgv = routeLegacyRootArgv(normalizedArgv);
|
|
464
|
-
|
|
466
|
+
const modelPresetsArgv =
|
|
467
|
+
normalizedArgv[0] === "models" && normalizedArgv[1] === "presets" ? routeModelsAlias(normalizedArgv) : undefined;
|
|
468
|
+
if (!legacyArgv && !modelPresetsArgv && hasRootHelpFlag(normalizedArgv)) {
|
|
465
469
|
const { renderRootHelp } = await import("@gajae-code/utils/cli");
|
|
466
470
|
const { getExtraHelpText } = await import("./cli/fast-help");
|
|
467
471
|
renderRootHelp({ bin: APP_NAME, version: VERSION, commands: new Map([["launch", RootHelpCommand]]) });
|
|
@@ -475,7 +479,7 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
475
479
|
process.stdout.write(`${APP_NAME}/${VERSION}\n`);
|
|
476
480
|
return;
|
|
477
481
|
}
|
|
478
|
-
const runArgv = legacyArgv ?? routeRootArgv(normalizedArgv);
|
|
482
|
+
const runArgv = legacyArgv ?? modelPresetsArgv ?? routeRootArgv(normalizedArgv);
|
|
479
483
|
if (isStatsHelpFastPath(runArgv)) {
|
|
480
484
|
showStatsFastHelp();
|
|
481
485
|
return;
|
|
@@ -22,24 +22,27 @@ export default class AuthGateway extends Command {
|
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
static flags = {
|
|
25
|
-
json: Flags.boolean({ description: "Output JSON (token/status)" }),
|
|
25
|
+
json: Flags.boolean({ description: "Output JSON (token/status/check)" }),
|
|
26
26
|
bind: Flags.string({ description: "Bind address for `serve` (host:port)", char: "b" }),
|
|
27
|
+
provider: Flags.string({
|
|
28
|
+
description: "Required provider scope for `serve`; also filters `status` and `check`",
|
|
29
|
+
}),
|
|
27
30
|
regenerate: Flags.boolean({ description: "Regenerate the gateway bearer token (token)" }),
|
|
28
31
|
"no-auth": Flags.boolean({
|
|
29
32
|
description:
|
|
30
|
-
"Disable inbound bearer-token auth (serve).
|
|
33
|
+
"Disable inbound bearer-token auth (serve). Loopback non-browser clients without an Origin header are allowed.",
|
|
31
34
|
}),
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
static examples = [
|
|
35
|
-
"# Boot
|
|
36
|
-
"# Boot on a non-default port\n gjc auth-gateway serve --bind=127.0.0.1:4000",
|
|
38
|
+
"# Boot a provider-scoped gateway against the configured broker\n gjc auth-gateway serve --provider=openai-codex",
|
|
39
|
+
"# Boot on a non-default port\n gjc auth-gateway serve --provider=openai-codex --bind=127.0.0.1:4000",
|
|
37
40
|
"# Print the gateway bearer token (creates one on first run)\n gjc auth-gateway token",
|
|
38
41
|
"# Rotate the gateway bearer token\n gjc auth-gateway token --regenerate",
|
|
39
|
-
"# Run on loopback
|
|
40
|
-
"# Show local gateway + broker config status\n gjc auth-gateway status",
|
|
41
|
-
"# Probe
|
|
42
|
-
"# Same, machine-readable for scripts\n gjc auth-gateway check --json",
|
|
42
|
+
"# Run the scoped gateway on loopback for non-browser local clients without a bearer\n gjc auth-gateway serve --provider=openai-codex --no-auth",
|
|
43
|
+
"# Show scoped local gateway + broker config status\n gjc auth-gateway status --provider=openai-codex",
|
|
44
|
+
"# Probe credentials for one gateway scope\n gjc auth-gateway check --provider=openai-codex",
|
|
45
|
+
"# Same, machine-readable for scripts\n gjc auth-gateway check --provider=openai-codex --json",
|
|
43
46
|
];
|
|
44
47
|
|
|
45
48
|
async run(): Promise<void> {
|
|
@@ -53,6 +56,7 @@ export default class AuthGateway extends Command {
|
|
|
53
56
|
flags: {
|
|
54
57
|
json: flags.json,
|
|
55
58
|
bind: flags.bind,
|
|
59
|
+
provider: flags.provider,
|
|
56
60
|
regenerate: flags.regenerate,
|
|
57
61
|
noAuth: flags["no-auth"],
|
|
58
62
|
},
|
package/src/commands/gc.ts
CHANGED
|
@@ -20,6 +20,11 @@ function resolveDiskPolicyFromSettings(): Partial<GcDiskPolicy> | undefined {
|
|
|
20
20
|
export default class Gc extends Command {
|
|
21
21
|
static description = "Garbage-collect stale GJC session/PID records (dry-run by default)";
|
|
22
22
|
static strict = false;
|
|
23
|
+
// The hand parser in gc-runtime owns the real syntax (repeatable space-form
|
|
24
|
+
// operands, dash-prefix rejection, orphan-operand rules); delegate help so the
|
|
25
|
+
// public output is the authoritative gcHelpText() instead of generic flag
|
|
26
|
+
// metadata that would advertise unsupported `--flag=<value>` forms.
|
|
27
|
+
static delegateHelp = true;
|
|
23
28
|
static flags = {
|
|
24
29
|
json: Flags.boolean({ char: "j", description: "Emit machine-readable JSON", default: false }),
|
|
25
30
|
prune: Flags.boolean({ description: "Remove stale records (default: report only)", default: false }),
|
|
@@ -33,6 +38,18 @@ export default class Gc extends Command {
|
|
|
33
38
|
description: "Quarantine a corrupt session-index suffix and retain its valid prefix",
|
|
34
39
|
default: false,
|
|
35
40
|
}),
|
|
41
|
+
"empty-delete-receipts": Flags.boolean({
|
|
42
|
+
description: "Report (and with --prune, prune) empty .gjc-delete-* receipts under --root/--manifest",
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
root: Flags.string({
|
|
46
|
+
description: "Operand root for --empty-delete-receipts (repeatable)",
|
|
47
|
+
multiple: true,
|
|
48
|
+
}),
|
|
49
|
+
manifest: Flags.string({
|
|
50
|
+
description: 'JSON {"roots":[...]} file for --empty-delete-receipts (repeatable)',
|
|
51
|
+
multiple: true,
|
|
52
|
+
}),
|
|
36
53
|
};
|
|
37
54
|
|
|
38
55
|
static examples = [
|
|
@@ -44,6 +61,8 @@ export default class Gc extends Command {
|
|
|
44
61
|
"gjc gc --disk --json",
|
|
45
62
|
"gjc gc --disk --prune",
|
|
46
63
|
"gjc gc --repair-session-index --json",
|
|
64
|
+
"gjc gc --empty-delete-receipts --root ~/.gjc/agent/session-states",
|
|
65
|
+
"gjc gc --empty-delete-receipts --manifest receipts-manifest.json --prune --json",
|
|
47
66
|
];
|
|
48
67
|
|
|
49
68
|
async run(): Promise<void> {
|
package/src/commands/launch.ts
CHANGED
|
@@ -2,20 +2,30 @@
|
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { APP_NAME, setProjectDir } from "@gajae-code/utils";
|
|
5
|
+
import { APP_NAME, getAgentDir, setProjectDir } from "@gajae-code/utils";
|
|
6
6
|
import { Args, Command } from "@gajae-code/utils/cli";
|
|
7
7
|
import { assertLocalLaunchArgs, parseArgs } from "../cli/args";
|
|
8
8
|
import { ROOT_LAUNCH_FLAGS } from "../cli/root-flags";
|
|
9
9
|
import { writeCoordinatorAtomic } from "../coordinator-mcp/durability";
|
|
10
10
|
import { launchDefaultTmuxIfNeeded } from "../gjc-runtime/launch-tmux";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type GjcLaunchWorktreePlan,
|
|
13
|
+
type PreparedLaunchWorktree,
|
|
14
|
+
parseLaunchWorktreeMode,
|
|
15
|
+
planLaunchWorktree,
|
|
16
|
+
prepareLaunchWorktree,
|
|
17
|
+
} from "../gjc-runtime/launch-worktree";
|
|
18
|
+
import { type LaunchWorktreeReservation, reserveLaunchWorktree } from "../gjc-runtime/launch-worktree-reservation";
|
|
12
19
|
import {
|
|
13
20
|
GJC_COORDINATOR_SESSION_ID_ENV,
|
|
14
21
|
GJC_COORDINATOR_SESSION_STATE_FILE_ENV,
|
|
15
22
|
GJC_TMUX_OWNER_GENERATION_ENV,
|
|
16
23
|
} from "../gjc-runtime/session-state-sidecar";
|
|
17
24
|
import { runRootCommand } from "../main";
|
|
25
|
+
import { assertMasterLaunchArgs } from "../master-mode/context";
|
|
18
26
|
import { prepareAcpTerminalAuthArgs } from "../modes/acp/terminal-auth";
|
|
27
|
+
import { type IndexedSession, SessionIndex } from "../sdk/broker/session-index";
|
|
28
|
+
import { worktreeOccupant } from "../sdk/broker/worktree-occupancy";
|
|
19
29
|
|
|
20
30
|
export async function persistCoordinatorLaunchFailure(
|
|
21
31
|
error: unknown,
|
|
@@ -56,6 +66,54 @@ export async function persistCoordinatorLaunchFailure(
|
|
|
56
66
|
await writeCoordinatorAtomic(stateFile, `${JSON.stringify(payload, null, 2)}\n`);
|
|
57
67
|
}
|
|
58
68
|
|
|
69
|
+
function worktreeInUseError(worktreePath: string, occupant?: string): Error {
|
|
70
|
+
const holder = occupant ? `session ${occupant}` : "another launch";
|
|
71
|
+
return new Error(
|
|
72
|
+
`worktree_in_use:${worktreePath} is already held by ${holder}. ` +
|
|
73
|
+
"Use gjc --worktree <name> for a separate checkout, or stop that session.",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function reserveLaunchWorktreeOrFail(agentDir: string, worktreePath: string): Promise<LaunchWorktreeReservation> {
|
|
78
|
+
const reservation = await reserveLaunchWorktree(agentDir, worktreePath);
|
|
79
|
+
if (!reservation) throw worktreeInUseError(worktreePath);
|
|
80
|
+
return reservation;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Test seam for the path-keyed atomic reservation. */
|
|
84
|
+
export async function reserveLaunchWorktreeForTest(
|
|
85
|
+
agentDir: string,
|
|
86
|
+
worktreePath: string,
|
|
87
|
+
): Promise<LaunchWorktreeReservation> {
|
|
88
|
+
return await reserveLaunchWorktreeOrFail(agentDir, worktreePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function assertLaunchWorktreeUnoccupied(worktree: GjcLaunchWorktreePlan | { enabled: false }): Promise<void> {
|
|
92
|
+
if (!worktree.enabled) return;
|
|
93
|
+
let sessions: readonly IndexedSession[];
|
|
94
|
+
try {
|
|
95
|
+
sessions = (await new SessionIndex(getAgentDir()).open()).listSessionIdentities();
|
|
96
|
+
} catch {
|
|
97
|
+
throw new Error(`worktree_occupancy_unavailable:${worktree.worktreePath}`);
|
|
98
|
+
}
|
|
99
|
+
const occupant = worktreeOccupant(sessions, worktree.worktreePath);
|
|
100
|
+
if (occupant) throw worktreeInUseError(worktree.worktreePath, occupant);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function reserveLaunchWorktreePreflight(
|
|
104
|
+
worktree: GjcLaunchWorktreePlan | { enabled: false },
|
|
105
|
+
): Promise<LaunchWorktreeReservation | undefined> {
|
|
106
|
+
if (!worktree.enabled) return undefined;
|
|
107
|
+
const reservation = await reserveLaunchWorktreeOrFail(getAgentDir(), worktree.worktreePath);
|
|
108
|
+
try {
|
|
109
|
+
await assertLaunchWorktreeUnoccupied(worktree);
|
|
110
|
+
return reservation;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
await reservation.release();
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
59
117
|
export default class Index extends Command {
|
|
60
118
|
static description = "Red-claw AI coding assistant";
|
|
61
119
|
static hidden = true;
|
|
@@ -93,34 +151,44 @@ export default class Index extends Command {
|
|
|
93
151
|
const { args } = prepareAcpTerminalAuthArgs(this.argv);
|
|
94
152
|
const parsed = parseArgs([...args], "deferred");
|
|
95
153
|
if (parsed.mode !== "acp") assertLocalLaunchArgs(parsed);
|
|
154
|
+
assertMasterLaunchArgs(parsed);
|
|
96
155
|
if (parsed.help || parsed.version) {
|
|
97
156
|
await runRootCommand(parsed, args);
|
|
98
157
|
return;
|
|
99
158
|
}
|
|
100
159
|
|
|
101
160
|
let launch: PreparedLaunchWorktree;
|
|
161
|
+
let reservation: LaunchWorktreeReservation | undefined;
|
|
102
162
|
try {
|
|
163
|
+
const plannedWorktree = planLaunchWorktree(process.cwd(), parseLaunchWorktreeMode(args).mode);
|
|
164
|
+
reservation = await reserveLaunchWorktreePreflight(plannedWorktree);
|
|
103
165
|
launch = prepareLaunchWorktree(process.cwd(), args);
|
|
104
166
|
} catch (error) {
|
|
167
|
+
await reservation?.release();
|
|
105
168
|
await persistCoordinatorLaunchFailure(error, process.cwd());
|
|
106
169
|
throw error;
|
|
107
170
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
171
|
+
try {
|
|
172
|
+
if (launch.worktree.enabled) {
|
|
173
|
+
process.chdir(launch.cwd);
|
|
174
|
+
setProjectDir(launch.cwd);
|
|
175
|
+
}
|
|
176
|
+
const launchParsed = parseArgs(launch.args, "deferred");
|
|
177
|
+
if (launchParsed.mode !== "acp") assertLocalLaunchArgs(launchParsed);
|
|
178
|
+
assertMasterLaunchArgs(launchParsed);
|
|
179
|
+
if (
|
|
180
|
+
launchDefaultTmuxIfNeeded({
|
|
181
|
+
parsed: launchParsed,
|
|
182
|
+
rawArgs: launch.args,
|
|
183
|
+
cwd: launch.cwd,
|
|
184
|
+
worktreeBranch: launch.worktree.enabled && !launch.worktree.detached ? launch.worktree.branchName : null,
|
|
185
|
+
project: launch.cwd,
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
return;
|
|
189
|
+
await runRootCommand(launchParsed, launch.args);
|
|
190
|
+
} finally {
|
|
191
|
+
await reservation?.release();
|
|
111
192
|
}
|
|
112
|
-
const launchParsed = parseArgs(launch.args, "deferred");
|
|
113
|
-
if (launchParsed.mode !== "acp") assertLocalLaunchArgs(launchParsed);
|
|
114
|
-
if (
|
|
115
|
-
launchDefaultTmuxIfNeeded({
|
|
116
|
-
parsed: launchParsed,
|
|
117
|
-
rawArgs: launch.args,
|
|
118
|
-
cwd: launch.cwd,
|
|
119
|
-
worktreeBranch: launch.worktree.enabled && !launch.worktree.detached ? launch.worktree.branchName : null,
|
|
120
|
-
project: launch.cwd,
|
|
121
|
-
})
|
|
122
|
-
)
|
|
123
|
-
return;
|
|
124
|
-
await runRootCommand(launchParsed, launch.args);
|
|
125
193
|
}
|
|
126
194
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
2
|
+
import { MODEL_PRESETS_ACTIONS, type ModelPresetsAction, runModelPresetsCommand } from "../cli/model-presets-cli";
|
|
3
|
+
|
|
4
|
+
export default class ModelPresets extends Command {
|
|
5
|
+
static description = "Manage the signed model preset registry";
|
|
6
|
+
|
|
7
|
+
static args = {
|
|
8
|
+
action: Args.string({ description: "Action", required: false, options: MODEL_PRESETS_ACTIONS }),
|
|
9
|
+
revision: Args.string({ description: "Accepted revision for pin or rollback", required: false }),
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
static flags = {
|
|
13
|
+
json: Flags.boolean({ description: "Output JSON" }),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
async run(): Promise<void> {
|
|
17
|
+
const { args, flags } = await this.parse(ModelPresets);
|
|
18
|
+
await runModelPresetsCommand({
|
|
19
|
+
action: (args.action ?? "status") as ModelPresetsAction,
|
|
20
|
+
revision: args.revision,
|
|
21
|
+
json: flags.json,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|