@gajae-code/coding-agent 0.12.11 → 0.12.13
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 +68 -0
- package/dist/types/cli/marketplace-hint.d.ts +28 -0
- package/dist/types/cli/notify-cli.d.ts +42 -1
- package/dist/types/cli/update-cli.d.ts +9 -3
- package/dist/types/cli.d.ts +10 -0
- package/dist/types/commands/notify.d.ts +6 -0
- package/dist/types/config/file-lock-gc.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/models-config-schema.d.ts +99 -0
- package/dist/types/coordinator/contract.d.ts +1 -1
- package/dist/types/coordinator-mcp/question-state.d.ts +3 -1
- package/dist/types/extensibility/extensions/index.d.ts +1 -0
- package/dist/types/extensibility/extensions/ouroboros-ooo-bridge.d.ts +13 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +0 -1
- package/dist/types/extensibility/extensions/types.d.ts +2 -0
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +14 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +11 -0
- package/dist/types/main.d.ts +6 -2
- package/dist/types/modes/acp/acp-agent.d.ts +24 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/utils/host-status.d.ts +23 -0
- package/dist/types/runtime-mcp/types.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +12 -0
- package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -0
- package/dist/types/sdk/bus/chat-daemon-command-channel.d.ts +244 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +11 -1
- package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +7 -0
- package/dist/types/sdk/bus/control-plane-frames.d.ts +43 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +18 -0
- package/dist/types/sdk/bus/existing-thread-readiness.d.ts +71 -0
- package/dist/types/sdk/bus/html-format.d.ts +10 -0
- package/dist/types/sdk/bus/notification-service.d.ts +10 -0
- package/dist/types/sdk/bus/slack-daemon.d.ts +27 -0
- package/dist/types/sdk/bus/slack-live-provider.d.ts +11 -0
- package/dist/types/sdk/bus/slack-provider.d.ts +14 -0
- package/dist/types/sdk/bus/slack-thread-binding.d.ts +140 -0
- package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +2 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +28 -7
- package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -17
- package/dist/types/sdk/bus/telegram-reference.d.ts +20 -1
- package/dist/types/sdk/bus/topic-registry.d.ts +130 -85
- package/dist/types/sdk/host/host.d.ts +52 -0
- package/dist/types/sdk/lifecycle-session.d.ts +6 -0
- package/dist/types/sdk/session-activation.d.ts +41 -0
- package/dist/types/sdk/session.d.ts +18 -0
- package/dist/types/sdk/startup-capability.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +7 -2
- package/dist/types/session/artifacts.d.ts +2 -2
- package/dist/types/session/session-manager.d.ts +40 -6
- package/dist/types/tools/index.d.ts +15 -0
- package/dist/types/utils/npm-registry.d.ts +71 -0
- package/examples/extensions/README.md +38 -4
- package/examples/extensions/ooo-bridge.ts +15 -0
- package/package.json +8 -8
- package/scripts/generate-sdk-operation-inventory.ts +4 -0
- package/src/async/job-manager.ts +9 -2
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/marketplace-hint.ts +39 -0
- package/src/cli/notify-cli.ts +226 -2
- package/src/cli/plugin-cli.ts +10 -0
- package/src/cli/update-cli.ts +90 -23
- package/src/cli.ts +23 -0
- package/src/commands/notify.ts +27 -3
- package/src/commands/sdk.ts +1 -0
- package/src/config/file-lock-gc.ts +65 -26
- package/src/config/file-lock.ts +61 -16
- package/src/config/model-profiles.ts +46 -30
- package/src/config/model-registry.ts +17 -5
- package/src/config/models-config-schema.ts +9 -4
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/question-state.ts +2 -0
- package/src/coordinator-mcp/server.ts +358 -7
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
- package/src/extensibility/extensions/index.ts +1 -0
- package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +235 -0
- package/src/extensibility/extensions/prefix-command-bridge.ts +4 -9
- package/src/extensibility/extensions/runner.ts +15 -2
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/gjc-runtime/deep-interview-recorder.ts +9 -5
- package/src/gjc-runtime/deep-interview-state.ts +27 -3
- package/src/gjc-runtime/gc-render.ts +8 -1
- package/src/gjc-runtime/gc-runtime.ts +15 -1
- package/src/gjc-runtime/launch-tmux.ts +53 -1
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/main.ts +214 -70
- package/src/modes/DESIGN.md +8 -5
- package/src/modes/acp/acp-agent.ts +123 -7
- package/src/modes/components/footer.ts +10 -18
- package/src/modes/components/model-selector.ts +52 -5
- package/src/modes/components/session-selector.ts +26 -3
- package/src/modes/components/status-line/gh.ts +23 -2
- package/src/modes/controllers/event-controller.ts +3 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +11 -0
- package/src/modes/controllers/selector-controller.ts +8 -2
- package/src/modes/interactive-mode.ts +7 -1
- package/src/modes/print-mode.ts +10 -0
- package/src/modes/utils/host-status.ts +38 -0
- package/src/prompts/system/project-prompt.md +19 -0
- package/src/runtime-mcp/types.ts +2 -0
- package/src/sdk/broker/lifecycle.ts +60 -3
- package/src/sdk/bus/chat-daemon-cli.ts +42 -0
- package/src/sdk/bus/chat-daemon-command-channel.ts +921 -0
- package/src/sdk/bus/chat-daemon-control.ts +16 -3
- package/src/sdk/bus/chat-daemon-runtime.ts +257 -21
- package/src/sdk/bus/control-plane-frames.ts +51 -0
- package/src/sdk/bus/conversation-store.ts +29 -1
- package/src/sdk/bus/existing-thread-readiness.ts +155 -0
- package/src/sdk/bus/html-format.ts +19 -3
- package/src/sdk/bus/index.ts +91 -12
- package/src/sdk/bus/notification-service.ts +28 -1
- package/src/sdk/bus/rich-render.ts +3 -2
- package/src/sdk/bus/slack-daemon.ts +99 -4
- package/src/sdk/bus/slack-live-provider.ts +32 -2
- package/src/sdk/bus/slack-provider.ts +11 -0
- package/src/sdk/bus/slack-thread-binding.ts +641 -0
- package/src/sdk/bus/telegram-daemon-cli.ts +11 -0
- package/src/sdk/bus/telegram-daemon-contract.ts +28 -7
- package/src/sdk/bus/telegram-daemon.ts +2007 -509
- package/src/sdk/bus/telegram-reference.ts +114 -15
- package/src/sdk/bus/topic-registry.ts +674 -181
- package/src/sdk/host/host.ts +180 -1
- package/src/sdk/lifecycle-session.ts +8 -2
- package/src/sdk/protocol/operation-inventory.generated.json +22 -0
- package/src/sdk/session-activation.ts +127 -0
- package/src/sdk/session.ts +144 -29
- package/src/sdk/startup-capability.ts +10 -1
- package/src/session/agent-session.ts +220 -13
- package/src/session/artifacts.ts +56 -13
- package/src/session/internal/managed-session-storage.ts +228 -7
- package/src/session/session-manager.ts +225 -60
- package/src/setup/provider-presets.json +3 -3
- package/src/task/index.ts +92 -44
- package/src/thinking-metadata.ts +2 -1
- package/src/tools/ask.ts +10 -0
- package/src/tools/index.ts +15 -0
- package/src/tools/todo-write.ts +4 -4
- package/src/utils/npm-registry.ts +660 -0
|
@@ -13,13 +13,15 @@ import type {
|
|
|
13
13
|
GcPruneOutcome,
|
|
14
14
|
GcRecord,
|
|
15
15
|
GcStoreAdapter,
|
|
16
|
+
GcWarning,
|
|
16
17
|
} from "../gjc-runtime/gc-runtime";
|
|
17
18
|
import { gcPidStatusLabel } from "../gjc-runtime/gc-runtime";
|
|
18
19
|
import { resolveReceiptSpoolDir } from "../harness-control-plane/receipt-spool";
|
|
19
20
|
import { readFileLockInfoForGc, removeFileLockDirForGc } from "./file-lock";
|
|
20
21
|
|
|
21
22
|
const MAX_WALK_DEPTH = 6;
|
|
22
|
-
|
|
23
|
+
/** Default per-root walk budget. Truncation is a warning, not a hard error. */
|
|
24
|
+
export const FILE_LOCK_GC_MAX_WALK_ENTRIES = 20_000;
|
|
23
25
|
|
|
24
26
|
// High-cardinality, lock-free subtrees we never descend into. `.lock` dirs are
|
|
25
27
|
// created next to config files, never inside these.
|
|
@@ -30,6 +32,11 @@ interface WalkState {
|
|
|
30
32
|
truncated: boolean;
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
export interface FileLocksGcCollectOptions {
|
|
36
|
+
/** Override per-root entry budget (tests). Defaults to {@link FILE_LOCK_GC_MAX_WALK_ENTRIES}. */
|
|
37
|
+
maxWalkEntries?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
33
40
|
// Global, env-aware GJC lock roots. Per the approved scope this covers the
|
|
34
41
|
// user config root, the agent dir (honors GJC_CODING_AGENT_DIR), and the
|
|
35
42
|
// configured receipt-spool dir — NOT the invocation cwd's project `.gjc`.
|
|
@@ -61,6 +68,21 @@ function keptMalformedRecord(lockDir: string): GcRecord {
|
|
|
61
68
|
async function collectLockRecord(lockDir: string, ctx: GcContext): Promise<GcRecord> {
|
|
62
69
|
const info = await readFileLockInfoForGc(lockDir);
|
|
63
70
|
if (!info) return keptMalformedRecord(lockDir);
|
|
71
|
+
if (info.owner_host_id !== undefined) {
|
|
72
|
+
return {
|
|
73
|
+
store: "file_locks",
|
|
74
|
+
id: lockDir,
|
|
75
|
+
path: lockDir,
|
|
76
|
+
pid: info.pid,
|
|
77
|
+
pid_status: "unknown",
|
|
78
|
+
status: "host_qualified",
|
|
79
|
+
stale: false,
|
|
80
|
+
removable: false,
|
|
81
|
+
action: "none",
|
|
82
|
+
reason: "host_qualified_lock_requires_owner_reclamation",
|
|
83
|
+
detail: `timestamp=${info.timestamp}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
64
86
|
|
|
65
87
|
const probeResult = ctx.probe(info.pid);
|
|
66
88
|
const pidStatus = gcPidStatusLabel(probeResult);
|
|
@@ -87,8 +109,9 @@ async function walkForLockDirs(
|
|
|
87
109
|
state: WalkState,
|
|
88
110
|
lockDirs: Set<string>,
|
|
89
111
|
errors: GcError[],
|
|
112
|
+
maxWalkEntries: number,
|
|
90
113
|
): Promise<void> {
|
|
91
|
-
if (state.entries >=
|
|
114
|
+
if (state.entries >= maxWalkEntries) {
|
|
92
115
|
state.truncated = true;
|
|
93
116
|
return;
|
|
94
117
|
}
|
|
@@ -122,50 +145,66 @@ async function walkForLockDirs(
|
|
|
122
145
|
}
|
|
123
146
|
|
|
124
147
|
for (const entry of entries) {
|
|
125
|
-
if (state.entries >=
|
|
148
|
+
if (state.entries >= maxWalkEntries) {
|
|
126
149
|
state.truncated = true;
|
|
127
150
|
return;
|
|
128
151
|
}
|
|
129
152
|
if (PRUNED_DIR_NAMES.has(entry)) continue;
|
|
130
|
-
await walkForLockDirs(path.join(dir, entry), depth + 1, state, lockDirs, errors);
|
|
153
|
+
await walkForLockDirs(path.join(dir, entry), depth + 1, state, lockDirs, errors, maxWalkEntries);
|
|
131
154
|
}
|
|
132
155
|
}
|
|
133
156
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Discover + classify file-lock records. Each known lock root gets its own walk
|
|
159
|
+
* budget so truncating one root never skips the others. Caps surface as
|
|
160
|
+
* warnings (partial results), not hard discovery errors.
|
|
161
|
+
*/
|
|
162
|
+
export async function collectFileLocksForGc(
|
|
163
|
+
ctx: GcContext,
|
|
164
|
+
options: FileLocksGcCollectOptions = {},
|
|
165
|
+
): Promise<GcCollectResult> {
|
|
166
|
+
const maxWalkEntries = options.maxWalkEntries ?? FILE_LOCK_GC_MAX_WALK_ENTRIES;
|
|
167
|
+
const records: GcRecord[] = [];
|
|
168
|
+
const errors: GcError[] = [];
|
|
169
|
+
const warnings: GcWarning[] = [];
|
|
170
|
+
const lockDirs = new Set<string>();
|
|
171
|
+
|
|
172
|
+
for (const root of knownFileLockRoots(ctx)) {
|
|
173
|
+
// Fresh budget per root so a huge agent dir cannot starve config/spool.
|
|
140
174
|
const state: WalkState = { entries: 0, truncated: false };
|
|
141
|
-
|
|
142
|
-
for (const root of knownFileLockRoots(ctx)) {
|
|
143
|
-
await walkForLockDirs(root, 0, state, lockDirs, errors);
|
|
144
|
-
if (state.truncated) break;
|
|
145
|
-
}
|
|
146
|
-
|
|
175
|
+
await walkForLockDirs(root, 0, state, lockDirs, errors, maxWalkEntries);
|
|
147
176
|
if (state.truncated) {
|
|
148
|
-
|
|
177
|
+
warnings.push({
|
|
149
178
|
store: "file_locks",
|
|
150
|
-
scope:
|
|
151
|
-
message: `file lock discovery capped at ${
|
|
179
|
+
scope: root,
|
|
180
|
+
message: `file lock discovery capped at ${maxWalkEntries} entries for root ${root} (scanned ${state.entries})`,
|
|
152
181
|
});
|
|
153
182
|
}
|
|
183
|
+
}
|
|
154
184
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
185
|
+
for (const lockDir of lockDirs) {
|
|
186
|
+
try {
|
|
187
|
+
records.push(await collectLockRecord(lockDir, ctx));
|
|
188
|
+
} catch (error) {
|
|
189
|
+
errors.push({ store: "file_locks", scope: lockDir, message: errorMessage(error) });
|
|
161
190
|
}
|
|
191
|
+
}
|
|
162
192
|
|
|
163
|
-
|
|
193
|
+
return { records, errors, warnings };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export const fileLocksGcAdapter: GcStoreAdapter = {
|
|
197
|
+
store: "file_locks",
|
|
198
|
+
async collect(ctx: GcContext): Promise<GcCollectResult> {
|
|
199
|
+
return collectFileLocksForGc(ctx);
|
|
164
200
|
},
|
|
165
201
|
async prune(record: GcRecord, ctx: GcContext): Promise<GcPruneOutcome> {
|
|
166
202
|
const lockDir = record.path ?? record.id;
|
|
167
203
|
const info = await readFileLockInfoForGc(lockDir);
|
|
168
204
|
if (!info) return { removed: false, skipped: "lock_no_longer_dead_or_missing" };
|
|
205
|
+
if (info.owner_host_id !== undefined) {
|
|
206
|
+
return { removed: false, skipped: "host_qualified_lock_requires_owner_reclamation" };
|
|
207
|
+
}
|
|
169
208
|
|
|
170
209
|
const probeResult = ctx.probe(info.pid);
|
|
171
210
|
if (probeResult.status !== "dead") {
|
package/src/config/file-lock.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
1
2
|
import type { Stats } from "node:fs";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import * as path from "node:path";
|
|
@@ -7,9 +8,11 @@ export interface FileLockOptions {
|
|
|
7
8
|
staleMs?: number;
|
|
8
9
|
retries?: number;
|
|
9
10
|
retryDelayMs?: number;
|
|
11
|
+
/** Stable host identity required to safely reclaim locks on a shared volume. */
|
|
12
|
+
ownerHostId?: string;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
const DEFAULT_OPTIONS: Required<FileLockOptions
|
|
15
|
+
const DEFAULT_OPTIONS: Required<Omit<FileLockOptions, "ownerHostId">> = {
|
|
13
16
|
staleMs: 10_000,
|
|
14
17
|
retries: 50,
|
|
15
18
|
retryDelayMs: 100,
|
|
@@ -61,8 +64,16 @@ function ownerIsAlive(owner: FileLockOwnerToken, startTimeCache?: Map<string, st
|
|
|
61
64
|
return currentStartTime === null || currentStartTime === owner.start_time;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
|
-
function
|
|
65
|
-
|
|
67
|
+
function lockInfo(ownerHostId?: string): LockInfo {
|
|
68
|
+
return {
|
|
69
|
+
pid: process.pid,
|
|
70
|
+
start_time: currentProcessStartTime(),
|
|
71
|
+
timestamp: Date.now(),
|
|
72
|
+
...(ownerHostId === undefined ? {} : { owner_host_id: ownerHostId }),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function writeLockInfo(lockPath: string, info: LockInfo): Promise<LockInfo> {
|
|
66
77
|
return Bun.write(`${lockPath}/info`, JSON.stringify(info)).then(() => info);
|
|
67
78
|
}
|
|
68
79
|
|
|
@@ -76,17 +87,18 @@ async function readLockInfo(lockPath: string): Promise<LockInfo | null> {
|
|
|
76
87
|
}
|
|
77
88
|
|
|
78
89
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
79
|
-
const { pid, start_time, timestamp } = parsed as Partial<LockInfo>;
|
|
90
|
+
const { pid, start_time, timestamp, owner_host_id } = parsed as Partial<LockInfo>;
|
|
80
91
|
if (
|
|
81
92
|
typeof pid !== "number" ||
|
|
82
93
|
!Number.isInteger(pid) ||
|
|
83
94
|
pid <= 0 ||
|
|
84
95
|
typeof timestamp !== "number" ||
|
|
85
96
|
!Number.isFinite(timestamp) ||
|
|
86
|
-
(start_time !== undefined && (typeof start_time !== "string" || !start_time))
|
|
97
|
+
(start_time !== undefined && (typeof start_time !== "string" || !start_time)) ||
|
|
98
|
+
(owner_host_id !== undefined && (typeof owner_host_id !== "string" || !owner_host_id))
|
|
87
99
|
)
|
|
88
100
|
return null;
|
|
89
|
-
return { pid, start_time, timestamp };
|
|
101
|
+
return { pid, start_time, timestamp, owner_host_id };
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
/** @internal */
|
|
@@ -98,7 +110,7 @@ export async function readFileLockInfoForGc(lockDir: string): Promise<FileLockOw
|
|
|
98
110
|
export interface FileLockOwnerToken {
|
|
99
111
|
pid: number;
|
|
100
112
|
start_time?: string;
|
|
101
|
-
|
|
113
|
+
owner_host_id?: string;
|
|
102
114
|
timestamp: number;
|
|
103
115
|
}
|
|
104
116
|
|
|
@@ -147,6 +159,7 @@ export async function removeFileLockDirForGc(
|
|
|
147
159
|
if (
|
|
148
160
|
current.pid !== expected.pid ||
|
|
149
161
|
(expected.start_time !== undefined && current.start_time !== expected.start_time) ||
|
|
162
|
+
current.owner_host_id !== expected.owner_host_id ||
|
|
150
163
|
current.timestamp !== expected.timestamp
|
|
151
164
|
) {
|
|
152
165
|
return "owner_changed";
|
|
@@ -187,6 +200,7 @@ function sameStatToken(a: LockDirStatToken, b: LockDirStatToken): boolean {
|
|
|
187
200
|
async function staleLockSnapshot(
|
|
188
201
|
lockPath: string,
|
|
189
202
|
staleMs: number,
|
|
203
|
+
ownerHostId?: string,
|
|
190
204
|
startTimeCache?: Map<string, string | null>,
|
|
191
205
|
): Promise<LockStaleSnapshot> {
|
|
192
206
|
let info: LockInfo | null;
|
|
@@ -199,6 +213,7 @@ async function staleLockSnapshot(
|
|
|
199
213
|
if (hasFsCode(error, "EPERM")) return { stale: false };
|
|
200
214
|
throw error;
|
|
201
215
|
}
|
|
216
|
+
if (!info && ownerHostId !== undefined) return { stale: false };
|
|
202
217
|
if (!info) {
|
|
203
218
|
try {
|
|
204
219
|
const stats = await fs.stat(lockPath);
|
|
@@ -210,6 +225,10 @@ async function staleLockSnapshot(
|
|
|
210
225
|
}
|
|
211
226
|
}
|
|
212
227
|
|
|
228
|
+
// A host-qualified lock may only be reclaimed after proving that its owner is
|
|
229
|
+
// local. Foreign and malformed host-qualified records fail closed: PID values
|
|
230
|
+
// and clocks are not meaningful across hosts.
|
|
231
|
+
if (ownerHostId !== undefined && info.owner_host_id !== ownerHostId) return { stale: false };
|
|
213
232
|
// Never reap a live owner by elapsed time: a long legitimate critical section must
|
|
214
233
|
// not have its lock stolen (#652). Reclaim a dead owner immediately. Only when owner
|
|
215
234
|
// liveness is indeterminate do we fall back to the staleMs elapsed-time heuristic.
|
|
@@ -239,18 +258,43 @@ async function removeStaleLockForAcquire(lockPath: string, snapshot: LockStaleSn
|
|
|
239
258
|
}
|
|
240
259
|
}
|
|
241
260
|
|
|
242
|
-
async function tryAcquireLock(lockPath: string): Promise<LockInfo | null> {
|
|
261
|
+
async function tryAcquireLock(lockPath: string, ownerHostId?: string): Promise<LockInfo | null> {
|
|
243
262
|
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
244
263
|
const afterParentMkdir = FileLockTestHooks.afterParentMkdir;
|
|
245
264
|
if (afterParentMkdir) await afterParentMkdir(lockPath);
|
|
265
|
+
if (ownerHostId === undefined) {
|
|
266
|
+
try {
|
|
267
|
+
await fs.mkdir(lockPath);
|
|
268
|
+
return await writeLockInfo(lockPath, lockInfo());
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if ((error as NodeJS.ErrnoException).code === "EEXIST") return null;
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const pendingPath = `${lockPath}.pending.${process.pid}.${crypto.randomUUID()}`;
|
|
276
|
+
const owner = lockInfo(ownerHostId);
|
|
246
277
|
try {
|
|
247
|
-
await fs.mkdir(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return
|
|
278
|
+
await fs.mkdir(pendingPath);
|
|
279
|
+
await writeLockInfo(pendingPath, owner);
|
|
280
|
+
try {
|
|
281
|
+
await fs.rename(pendingPath, lockPath);
|
|
282
|
+
return owner;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
285
|
+
if (code === "EEXIST" || code === "ENOTEMPTY") return null;
|
|
286
|
+
if (code === "EPERM") {
|
|
287
|
+
try {
|
|
288
|
+
await fs.stat(lockPath);
|
|
289
|
+
return null;
|
|
290
|
+
} catch (statError) {
|
|
291
|
+
if (!isEnoent(statError)) throw statError;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
throw error;
|
|
252
295
|
}
|
|
253
|
-
|
|
296
|
+
} finally {
|
|
297
|
+
await fs.rm(pendingPath, { recursive: true, force: true }).catch(() => undefined);
|
|
254
298
|
}
|
|
255
299
|
}
|
|
256
300
|
|
|
@@ -259,14 +303,15 @@ async function releaseLock(lockPath: string, owner: FileLockOwnerToken): Promise
|
|
|
259
303
|
if (outcome !== "removed") throw new Error(`Failed to release file lock: ${outcome}.`);
|
|
260
304
|
}
|
|
261
305
|
async function acquireLock(filePath: string, options: FileLockOptions = {}): Promise<() => Promise<void>> {
|
|
306
|
+
if (options.ownerHostId !== undefined && !options.ownerHostId) throw new Error("ownerHostId must be non-empty");
|
|
262
307
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
263
308
|
const lockPath = getLockPath(filePath);
|
|
264
309
|
const contentionStartTimes = new Map<string, string | null>();
|
|
265
310
|
for (let attempt = 0; attempt < opts.retries; attempt++) {
|
|
266
|
-
const owner = await tryAcquireLock(lockPath);
|
|
311
|
+
const owner = await tryAcquireLock(lockPath, opts.ownerHostId);
|
|
267
312
|
if (owner) return () => releaseLock(lockPath, owner);
|
|
268
313
|
|
|
269
|
-
const stale = await staleLockSnapshot(lockPath, opts.staleMs, contentionStartTimes);
|
|
314
|
+
const stale = await staleLockSnapshot(lockPath, opts.staleMs, opts.ownerHostId, contentionStartTimes);
|
|
270
315
|
if (await removeStaleLockForAcquire(lockPath, stale)) continue;
|
|
271
316
|
await Bun.sleep(opts.retryDelayMs);
|
|
272
317
|
}
|
|
@@ -243,46 +243,46 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
243
243
|
architect: "grok-build/grok-build",
|
|
244
244
|
}),
|
|
245
245
|
profile("cursor-eco", ["cursor"], {
|
|
246
|
-
default: "cursor/composer-
|
|
247
|
-
executor: "cursor/composer-
|
|
248
|
-
planner: "cursor/composer-
|
|
249
|
-
critic: "cursor/composer-
|
|
250
|
-
architect: "cursor/composer-
|
|
246
|
+
default: "cursor/composer-2.5",
|
|
247
|
+
executor: "cursor/composer-2.5",
|
|
248
|
+
planner: "cursor/composer-2.5",
|
|
249
|
+
critic: "cursor/composer-2.5",
|
|
250
|
+
architect: "cursor/composer-2.5",
|
|
251
251
|
}),
|
|
252
252
|
profile("cursor-medium", ["cursor"], {
|
|
253
|
-
default: "cursor/composer-
|
|
254
|
-
executor: "cursor/composer-
|
|
255
|
-
planner: "cursor/composer-
|
|
256
|
-
critic: "cursor/composer-
|
|
257
|
-
architect: "cursor/composer-
|
|
253
|
+
default: "cursor/composer-2.5",
|
|
254
|
+
executor: "cursor/composer-2.5-fast",
|
|
255
|
+
planner: "cursor/composer-2.5",
|
|
256
|
+
critic: "cursor/composer-2.5-fast",
|
|
257
|
+
architect: "cursor/composer-2.5-fast",
|
|
258
258
|
}),
|
|
259
259
|
profile("cursor-pro", ["cursor"], {
|
|
260
|
-
default: "cursor/composer-
|
|
261
|
-
executor: "cursor/composer-
|
|
262
|
-
planner: "cursor/composer-
|
|
263
|
-
critic: "cursor/composer-
|
|
264
|
-
architect: "cursor/composer-
|
|
260
|
+
default: "cursor/composer-2.5-fast",
|
|
261
|
+
executor: "cursor/composer-2.5-fast",
|
|
262
|
+
planner: "cursor/composer-2.5-fast",
|
|
263
|
+
critic: "cursor/composer-2.5-fast",
|
|
264
|
+
architect: "cursor/composer-2.5-fast",
|
|
265
265
|
}),
|
|
266
266
|
profile("minimax-eco", ["minimax-code"], {
|
|
267
|
-
default: "minimax-code/
|
|
268
|
-
executor: "minimax-code/
|
|
269
|
-
planner: "minimax-code/
|
|
270
|
-
critic: "minimax-code/
|
|
271
|
-
architect: "minimax-code/
|
|
267
|
+
default: "minimax-code/MiniMax-M3:low",
|
|
268
|
+
executor: "minimax-code/MiniMax-M3:minimal",
|
|
269
|
+
planner: "minimax-code/MiniMax-M3:low",
|
|
270
|
+
critic: "minimax-code/MiniMax-M3:medium",
|
|
271
|
+
architect: "minimax-code/MiniMax-M3:high",
|
|
272
272
|
}),
|
|
273
273
|
profile("minimax-medium", ["minimax-code"], {
|
|
274
|
-
default: "minimax-code/
|
|
275
|
-
executor: "minimax-code/
|
|
276
|
-
planner: "minimax-code/
|
|
277
|
-
critic: "minimax-code/
|
|
278
|
-
architect: "minimax-code/
|
|
274
|
+
default: "minimax-code/MiniMax-M3:medium",
|
|
275
|
+
executor: "minimax-code/MiniMax-M3:low",
|
|
276
|
+
planner: "minimax-code/MiniMax-M3:medium",
|
|
277
|
+
critic: "minimax-code/MiniMax-M3:high",
|
|
278
|
+
architect: "minimax-code/MiniMax-M3:xhigh",
|
|
279
279
|
}),
|
|
280
280
|
profile("minimax-pro", ["minimax-code"], {
|
|
281
|
-
default: "minimax-code/
|
|
282
|
-
executor: "minimax-code/
|
|
283
|
-
planner: "minimax-code/
|
|
284
|
-
critic: "minimax-code/
|
|
285
|
-
architect: "minimax-code/
|
|
281
|
+
default: "minimax-code/MiniMax-M3:xhigh",
|
|
282
|
+
executor: "minimax-code/MiniMax-M3:medium",
|
|
283
|
+
planner: "minimax-code/MiniMax-M3:high",
|
|
284
|
+
critic: "minimax-code/MiniMax-M3:xhigh",
|
|
285
|
+
architect: "minimax-code/MiniMax-M3:xhigh",
|
|
286
286
|
}),
|
|
287
287
|
profile("alibaba-token-plan-balanced", ["alibaba-token-plan"], {
|
|
288
288
|
default: "alibaba-token-plan/qwen3.8-max-preview:medium",
|
|
@@ -305,6 +305,20 @@ export const BUILTIN_MODEL_PROFILES: readonly ModelProfileDefinition[] = [
|
|
|
305
305
|
architect: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
306
306
|
critic: "alibaba-token-plan/qwen3.8-max-preview:xhigh",
|
|
307
307
|
}),
|
|
308
|
+
profile("alibaba-token-plan-qwen-deepseek", ["alibaba-token-plan"], {
|
|
309
|
+
default: "alibaba-token-plan/qwen3.8-max:high",
|
|
310
|
+
executor: "alibaba-token-plan/deepseek-v4-flash-0731:high",
|
|
311
|
+
planner: "alibaba-token-plan/deepseek-v4-flash-0731:max",
|
|
312
|
+
architect: "alibaba-token-plan/qwen3.8-max:xhigh",
|
|
313
|
+
critic: "alibaba-token-plan/qwen3.8-max:xhigh",
|
|
314
|
+
}),
|
|
315
|
+
profile("alibaba-token-plan-glm-deepseek", ["alibaba-token-plan"], {
|
|
316
|
+
default: "alibaba-token-plan/glm-5.2:high",
|
|
317
|
+
executor: "alibaba-token-plan/deepseek-v4-flash-0731:high",
|
|
318
|
+
planner: "alibaba-token-plan/deepseek-v4-flash-0731:max",
|
|
319
|
+
architect: "alibaba-token-plan/glm-5.2:xhigh",
|
|
320
|
+
critic: "alibaba-token-plan/glm-5.2:xhigh",
|
|
321
|
+
}),
|
|
308
322
|
profile("opus-codex", ["anthropic", "openai-codex"], {
|
|
309
323
|
default: "anthropic/claude-opus-5:xhigh",
|
|
310
324
|
executor: "openai-codex/gpt-5.6-terra:low",
|
|
@@ -370,6 +384,8 @@ const PROFILE_PRESENTATION: Record<string, ModelProfilePresentation> = {
|
|
|
370
384
|
"alibaba-token-plan-balanced": { displayName: "Balanced", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
371
385
|
"alibaba-token-plan-pro": { displayName: "Pro", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
372
386
|
"alibaba-token-plan-qwenmaxxing": { displayName: "QwenMaxxing", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
387
|
+
"alibaba-token-plan-qwen-deepseek": { displayName: "Qwen + DeepSeek", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
388
|
+
"alibaba-token-plan-glm-deepseek": { displayName: "GLM + DeepSeek", providerGroup: "ALIBABA TOKEN PLAN" },
|
|
373
389
|
"opus-codex": { displayName: "Opus + Codex", providerGroup: "COMBOS" },
|
|
374
390
|
"codex-opencodego": { displayName: "Codex + OpenCodeGo", providerGroup: "COMBOS" },
|
|
375
391
|
"fable-opus-codex": { displayName: "Fable + Opus + Codex", providerGroup: "COMBOS" },
|
|
@@ -926,12 +926,24 @@ const customReferenceMap = buildCustomReferenceMap();
|
|
|
926
926
|
|
|
927
927
|
function getCustomReferenceCandidateIds(modelId: string): string[] {
|
|
928
928
|
const candidates = new Set<string>();
|
|
929
|
-
const
|
|
930
|
-
const
|
|
929
|
+
const trimmedId = modelId.trim();
|
|
930
|
+
const minimaxM = /^minimax-m(\d+(?:\.\d+)*)$/i.exec(trimmedId);
|
|
931
|
+
const queue = minimaxM ? [`MiniMax-M${minimaxM[1]}`, trimmedId] : [trimmedId];
|
|
931
932
|
if (minimaxM) {
|
|
932
|
-
// MiniMax catalogs
|
|
933
|
-
//
|
|
934
|
-
// canonical display casing
|
|
933
|
+
// First-class MiniMax catalogs expose canonical `MiniMax-M*` ids only,
|
|
934
|
+
// but custom providers may still use lowercase wire ids. Normalize to
|
|
935
|
+
// the canonical display casing so metadata inheritance keeps working.
|
|
936
|
+
}
|
|
937
|
+
// Namespaced wire IDs (e.g. `cline-pass/deepseek-v4-flash`) keep the full id for
|
|
938
|
+
// the API request, but should still try the leaf segment against bundled
|
|
939
|
+
// references so capability metadata is not silently replaced by 128K/16K defaults.
|
|
940
|
+
// Only an exact leaf match in the reference map inherits; unknown leaves stay defaulted.
|
|
941
|
+
const slashIndex = trimmedId.lastIndexOf("/");
|
|
942
|
+
if (slashIndex >= 0 && slashIndex < trimmedId.length - 1) {
|
|
943
|
+
const leafId = trimmedId.slice(slashIndex + 1).trim();
|
|
944
|
+
if (leafId && leafId !== trimmedId) {
|
|
945
|
+
queue.push(leafId);
|
|
946
|
+
}
|
|
935
947
|
}
|
|
936
948
|
for (let index = 0; index < queue.length; index += 1) {
|
|
937
949
|
const candidate = queue[index]?.trim();
|
|
@@ -20,7 +20,7 @@ const ReasoningEffortMapSchema = z.object({
|
|
|
20
20
|
max: z.string().optional(),
|
|
21
21
|
});
|
|
22
22
|
|
|
23
|
-
export const
|
|
23
|
+
export const ModelCompatSchema = z.object({
|
|
24
24
|
supportsStore: z.boolean().optional(),
|
|
25
25
|
supportsDeveloperRole: z.boolean().optional(),
|
|
26
26
|
sendSessionHeaders: z.boolean().optional(),
|
|
@@ -48,8 +48,13 @@ export const OpenAICompatSchema = z.object({
|
|
|
48
48
|
extraBody: z.record(z.string(), z.unknown()).optional(),
|
|
49
49
|
supportsStrictMode: z.boolean().optional(),
|
|
50
50
|
toolStrictMode: z.enum(["all_strict", "none"]).optional(),
|
|
51
|
+
supportsLongCacheRetention: z.boolean().optional(),
|
|
52
|
+
promptCacheMode: z.enum(["none", "explicit", "automatic"]).optional(),
|
|
51
53
|
});
|
|
52
54
|
|
|
55
|
+
// Backward-compatible export for callers that imported the original schema name.
|
|
56
|
+
export const OpenAICompatSchema = ModelCompatSchema;
|
|
57
|
+
|
|
53
58
|
export const GJC_MODEL_EFFORT_IDS = ["minimal", "low", "medium", "high", "xhigh", "max"] as const;
|
|
54
59
|
export const GJC_MODEL_ASSIGNMENT_TARGET_IDS = ["default", "executor", "architect", "planner", "critic"] as const;
|
|
55
60
|
export const EffortSchema = z.enum(GJC_MODEL_EFFORT_IDS);
|
|
@@ -146,7 +151,7 @@ const ModelDefinitionSchema = z
|
|
|
146
151
|
contextWindow: z.number().optional(),
|
|
147
152
|
maxTokens: z.number().optional(),
|
|
148
153
|
headers: z.record(z.string(), z.string()).optional(),
|
|
149
|
-
compat:
|
|
154
|
+
compat: ModelCompatSchema.optional(),
|
|
150
155
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
151
156
|
wireModelId: z.string().min(1).optional(),
|
|
152
157
|
requestTransform: RequestTransformSchema.optional(),
|
|
@@ -173,7 +178,7 @@ export const ModelOverrideSchema = z
|
|
|
173
178
|
contextWindow: z.number().optional(),
|
|
174
179
|
maxTokens: z.number().optional(),
|
|
175
180
|
headers: z.record(z.string(), z.string()).optional(),
|
|
176
|
-
compat:
|
|
181
|
+
compat: ModelCompatSchema.optional(),
|
|
177
182
|
contextPromotionTarget: z.string().min(1).optional(),
|
|
178
183
|
wireModelId: z.string().min(1).optional(),
|
|
179
184
|
requestTransform: RequestTransformSchema.optional(),
|
|
@@ -221,7 +226,7 @@ const ProviderConfigSchema = z
|
|
|
221
226
|
])
|
|
222
227
|
.optional(),
|
|
223
228
|
headers: z.record(z.string(), z.string()).optional(),
|
|
224
|
-
compat:
|
|
229
|
+
compat: ModelCompatSchema.optional(),
|
|
225
230
|
webSearch: z.enum(["on", "off", "auto"]).optional(),
|
|
226
231
|
authHeader: z.boolean().optional(),
|
|
227
232
|
auth: ProviderAuthSchema.optional(),
|
|
@@ -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_activate_session",
|
|
15
16
|
"gjc_coordinator_stop_session",
|
|
16
17
|
"gjc_coordinator_send_prompt",
|
|
17
18
|
"gjc_coordinator_submit_question_answer",
|
|
@@ -6,6 +6,8 @@ import type { PrivateAskGateCodecV1, PublicReason } from "./question-gate-codec"
|
|
|
6
6
|
|
|
7
7
|
export type CoordinatorSessionState =
|
|
8
8
|
| "booting"
|
|
9
|
+
/** Live and endpoint-addressable, but withholding readiness until activation. */
|
|
10
|
+
| "prepared"
|
|
9
11
|
| "ready_for_input"
|
|
10
12
|
| "running"
|
|
11
13
|
| "needs_user_input"
|