@linzumi/cli 0.0.41-beta → 0.0.43-beta
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/README.md +1 -1
- package/dist/index.js +645 -191
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { randomUUID as
|
|
3
|
-
import { existsSync as
|
|
2
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
3
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, realpathSync as realpathSync6 } from "node:fs";
|
|
4
4
|
import { homedir as homedir9 } from "node:os";
|
|
5
5
|
import { resolve as resolve9 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/runner.ts
|
|
9
9
|
import { spawn as spawn6 } from "node:child_process";
|
|
10
|
-
import { randomUUID as
|
|
11
|
-
import { realpathSync as
|
|
10
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
12
12
|
import { hostname as hostname2 } from "node:os";
|
|
13
|
-
import { join as
|
|
13
|
+
import { join as join8, resolve as resolve6 } from "node:path";
|
|
14
14
|
|
|
15
15
|
// src/channelSessionSupport.ts
|
|
16
16
|
import { spawnSync } from "node:child_process";
|
|
@@ -248,7 +248,7 @@ function codexThreadRuntimeOverrides(options) {
|
|
|
248
248
|
...session.model === undefined ? {} : { model: session.model },
|
|
249
249
|
...session.reasoningEffort === undefined ? {} : { reasoningEffort: session.reasoningEffort },
|
|
250
250
|
...options.fast === true ? { serviceTier: "fast" } : {},
|
|
251
|
-
|
|
251
|
+
approvalPolicy: codexApprovalPolicyForRequest(session.approvalPolicy, session.sandbox),
|
|
252
252
|
...session.sandbox === undefined ? {} : { sandbox: session.sandbox }
|
|
253
253
|
};
|
|
254
254
|
}
|
|
@@ -259,10 +259,30 @@ function codexTurnRuntimeOverrides(options) {
|
|
|
259
259
|
...session.model === undefined ? {} : { model: session.model },
|
|
260
260
|
...session.reasoningEffort === undefined ? {} : { effort: session.reasoningEffort },
|
|
261
261
|
...options.fast === true ? { serviceTier: "fast" } : {},
|
|
262
|
-
|
|
262
|
+
approvalPolicy: codexApprovalPolicyForRequest(session.approvalPolicy, session.sandbox),
|
|
263
263
|
...session.sandbox === undefined ? {} : { sandboxPolicy: codexSandboxPolicy(session.sandbox, options.cwd) }
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
+
function codexApprovalPolicySetting(approvalPolicy, sandbox) {
|
|
267
|
+
if (approvalPolicy === undefined || approvalPolicy === "default") {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
return codexApprovalPolicyForRequest(approvalPolicy, sandbox);
|
|
271
|
+
}
|
|
272
|
+
function codexApprovalPolicyForRequest(approvalPolicy, sandbox) {
|
|
273
|
+
switch (approvalPolicy) {
|
|
274
|
+
case undefined:
|
|
275
|
+
case "default":
|
|
276
|
+
return "on-request";
|
|
277
|
+
case "never":
|
|
278
|
+
return sandbox === "danger-full-access" ? "never" : "on-request";
|
|
279
|
+
case "on-request":
|
|
280
|
+
case "on-failure":
|
|
281
|
+
return approvalPolicy;
|
|
282
|
+
default:
|
|
283
|
+
throw new Error(`unsupported Codex approval policy: ${approvalPolicy}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
266
286
|
function codexSandboxPolicy(sandbox, cwd) {
|
|
267
287
|
switch (sandbox) {
|
|
268
288
|
case "danger-full-access":
|
|
@@ -2441,6 +2461,8 @@ function shellCommandTokens(command) {
|
|
|
2441
2461
|
var codexTypingHeartbeatMs = 5000;
|
|
2442
2462
|
var defaultStreamFlushIntervalMs = 150;
|
|
2443
2463
|
var maxForwardedTurnIds = 64;
|
|
2464
|
+
var maxClaimedKandanMessageKeys = 1024;
|
|
2465
|
+
var claimedKandanMessageKeys = new Set;
|
|
2444
2466
|
async function attachChannelSession(args) {
|
|
2445
2467
|
const session = args.options.channelSession;
|
|
2446
2468
|
const chatTopic = `chat:${session.workspaceSlug}:${session.channelSlug}`;
|
|
@@ -2589,6 +2611,7 @@ async function attachChannelSession(args) {
|
|
|
2589
2611
|
clearPendingStreamFlushTimers(state);
|
|
2590
2612
|
rejectPendingApprovalRequests(state, new Error("runner closed"));
|
|
2591
2613
|
await stopCodexTyping(args, state);
|
|
2614
|
+
releaseKandanMessageClaims(state);
|
|
2592
2615
|
}
|
|
2593
2616
|
};
|
|
2594
2617
|
}
|
|
@@ -2630,6 +2653,7 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
|
|
|
2630
2653
|
forwardedTurnIds: new Set,
|
|
2631
2654
|
forwardingTurnIds: new Set,
|
|
2632
2655
|
retryableTurnIds: new Set,
|
|
2656
|
+
claimedKandanMessageKeys: new Set,
|
|
2633
2657
|
localTuiTurnIds: new Set,
|
|
2634
2658
|
mirroredTuiInputProjections: createBoundedCache(maxForwardedTurnIds),
|
|
2635
2659
|
pendingTuiInputMirrors: new Map,
|
|
@@ -3356,6 +3380,15 @@ async function handleKandanChatEvent(args, state, runnerIdentity, payloadContext
|
|
|
3356
3380
|
});
|
|
3357
3381
|
return;
|
|
3358
3382
|
}
|
|
3383
|
+
if (!claimKandanMessage(args, state, event)) {
|
|
3384
|
+
args.log("kandan.message_ignored", {
|
|
3385
|
+
seq: event.seq,
|
|
3386
|
+
actor_slug: event.actorSlug ?? null,
|
|
3387
|
+
actor_user_id: event.actorUserId ?? null,
|
|
3388
|
+
reason: "duplicate_message_claim"
|
|
3389
|
+
});
|
|
3390
|
+
return;
|
|
3391
|
+
}
|
|
3359
3392
|
enqueuePendingKandanMessage(state.queue, {
|
|
3360
3393
|
seq: event.seq,
|
|
3361
3394
|
actorSlug: event.actorSlug,
|
|
@@ -3372,6 +3405,43 @@ async function handleKandanChatEvent(args, state, runnerIdentity, payloadContext
|
|
|
3372
3405
|
await publishKandanMessageState(args, event, { status: "queued" });
|
|
3373
3406
|
await drainKandanMessageQueue(args, state, payloadContext);
|
|
3374
3407
|
}
|
|
3408
|
+
function kandanMessageClaimKey(args, threadId, seq) {
|
|
3409
|
+
const session = args.options.channelSession;
|
|
3410
|
+
return [
|
|
3411
|
+
args.options.runnerId,
|
|
3412
|
+
args.instanceId,
|
|
3413
|
+
session.workspaceSlug,
|
|
3414
|
+
session.channelSlug,
|
|
3415
|
+
threadId,
|
|
3416
|
+
String(seq)
|
|
3417
|
+
].join(":");
|
|
3418
|
+
}
|
|
3419
|
+
function claimKandanMessage(args, state, event) {
|
|
3420
|
+
const threadId = event.threadId;
|
|
3421
|
+
if (threadId === undefined) {
|
|
3422
|
+
return false;
|
|
3423
|
+
}
|
|
3424
|
+
const key = kandanMessageClaimKey(args, threadId, event.seq);
|
|
3425
|
+
if (claimedKandanMessageKeys.has(key)) {
|
|
3426
|
+
return false;
|
|
3427
|
+
}
|
|
3428
|
+
claimedKandanMessageKeys.add(key);
|
|
3429
|
+
state.claimedKandanMessageKeys.add(key);
|
|
3430
|
+
if (state.claimedKandanMessageKeys.size > maxClaimedKandanMessageKeys) {
|
|
3431
|
+
const [expiredKey] = state.claimedKandanMessageKeys;
|
|
3432
|
+
if (expiredKey !== undefined) {
|
|
3433
|
+
state.claimedKandanMessageKeys.delete(expiredKey);
|
|
3434
|
+
claimedKandanMessageKeys.delete(expiredKey);
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
return true;
|
|
3438
|
+
}
|
|
3439
|
+
function releaseKandanMessageClaims(state) {
|
|
3440
|
+
for (const key of state.claimedKandanMessageKeys) {
|
|
3441
|
+
claimedKandanMessageKeys.delete(key);
|
|
3442
|
+
}
|
|
3443
|
+
state.claimedKandanMessageKeys.clear();
|
|
3444
|
+
}
|
|
3375
3445
|
async function startThreadMessageTurn(args, state, payloadContext, message) {
|
|
3376
3446
|
if (state.kandanThreadId === undefined || state.codexThreadId === undefined) {
|
|
3377
3447
|
throw new Error("cannot start a local Codex turn before thread binding");
|
|
@@ -3611,7 +3681,7 @@ async function handleCodexServerRequest(args, state, payloadContext, request) {
|
|
|
3611
3681
|
throw new Error(message);
|
|
3612
3682
|
}
|
|
3613
3683
|
function codexApprovalRequestCanAutoAccept(settings, method) {
|
|
3614
|
-
return settings.approvalPolicy === "never" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3684
|
+
return settings.approvalPolicy === "never" && settings.sandbox === "danger-full-access" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3615
3685
|
}
|
|
3616
3686
|
function codexApprovalRequestCanSurface(method) {
|
|
3617
3687
|
return method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval";
|
|
@@ -5132,20 +5202,27 @@ function runtimeSettingsFromOptions(options) {
|
|
|
5132
5202
|
return {
|
|
5133
5203
|
model: options.channelSession.model,
|
|
5134
5204
|
reasoningEffort: options.channelSession.reasoningEffort,
|
|
5135
|
-
approvalPolicy: options.channelSession.approvalPolicy,
|
|
5205
|
+
approvalPolicy: codexApprovalPolicySetting(options.channelSession.approvalPolicy, options.channelSession.sandbox),
|
|
5136
5206
|
sandbox: options.channelSession.sandbox,
|
|
5137
5207
|
fast: options.fast
|
|
5138
5208
|
};
|
|
5139
5209
|
}
|
|
5140
5210
|
function mergeRuntimeSettings(current, update) {
|
|
5211
|
+
const sandbox = mergeOptionalStringRuntimeSetting(current.sandbox, update, "sandbox");
|
|
5141
5212
|
return {
|
|
5142
5213
|
model: mergeOptionalStringRuntimeSetting(current.model, update, "model"),
|
|
5143
5214
|
reasoningEffort: mergeOptionalStringRuntimeSetting(current.reasoningEffort, update, "reasoningEffort"),
|
|
5144
|
-
approvalPolicy:
|
|
5145
|
-
sandbox
|
|
5215
|
+
approvalPolicy: mergeOptionalApprovalPolicyRuntimeSetting(current.approvalPolicy, update, sandbox),
|
|
5216
|
+
sandbox,
|
|
5146
5217
|
fast: update.fast ?? current.fast
|
|
5147
5218
|
};
|
|
5148
5219
|
}
|
|
5220
|
+
function mergeOptionalApprovalPolicyRuntimeSetting(current, update, sandbox) {
|
|
5221
|
+
if (Object.prototype.hasOwnProperty.call(update, "approvalPolicy")) {
|
|
5222
|
+
return codexApprovalPolicySetting(update.approvalPolicy ?? undefined, sandbox);
|
|
5223
|
+
}
|
|
5224
|
+
return codexApprovalPolicySetting(current, sandbox);
|
|
5225
|
+
}
|
|
5149
5226
|
function mergeOptionalStringRuntimeSetting(current, update, key) {
|
|
5150
5227
|
if (Object.prototype.hasOwnProperty.call(update, key)) {
|
|
5151
5228
|
return update[key] ?? undefined;
|
|
@@ -7947,6 +8024,356 @@ function waitForOpen2(websocket) {
|
|
|
7947
8024
|
});
|
|
7948
8025
|
}
|
|
7949
8026
|
|
|
8027
|
+
// src/runnerLock.ts
|
|
8028
|
+
import {
|
|
8029
|
+
closeSync,
|
|
8030
|
+
existsSync as existsSync5,
|
|
8031
|
+
mkdirSync as mkdirSync6,
|
|
8032
|
+
openSync as openSync2,
|
|
8033
|
+
readFileSync as readFileSync4,
|
|
8034
|
+
unlinkSync as unlinkSync2,
|
|
8035
|
+
writeSync
|
|
8036
|
+
} from "node:fs";
|
|
8037
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
8038
|
+
|
|
8039
|
+
// src/localConfig.ts
|
|
8040
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
8041
|
+
import {
|
|
8042
|
+
existsSync as existsSync4,
|
|
8043
|
+
linkSync,
|
|
8044
|
+
mkdirSync as mkdirSync5,
|
|
8045
|
+
readFileSync as readFileSync3,
|
|
8046
|
+
realpathSync as realpathSync4,
|
|
8047
|
+
unlinkSync,
|
|
8048
|
+
writeFileSync as writeFileSync4
|
|
8049
|
+
} from "node:fs";
|
|
8050
|
+
import { homedir as homedir5 } from "node:os";
|
|
8051
|
+
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
|
|
8052
|
+
function localConfigPath(env = process.env) {
|
|
8053
|
+
const override = env.LINZUMI_CONFIG_FILE;
|
|
8054
|
+
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
|
|
8055
|
+
}
|
|
8056
|
+
function readLocalConfig(path = localConfigPath()) {
|
|
8057
|
+
if (!existsSync4(path)) {
|
|
8058
|
+
return { version: 1, allowedCwds: [] };
|
|
8059
|
+
}
|
|
8060
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
8061
|
+
if (!isConfigPayload(parsed)) {
|
|
8062
|
+
throw new Error(`invalid Linzumi config: ${path}`);
|
|
8063
|
+
}
|
|
8064
|
+
const allowedCwds = uniqueStrings(parsed.allowedCwds);
|
|
8065
|
+
return parsed.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: parsed.machineId, allowedCwds };
|
|
8066
|
+
}
|
|
8067
|
+
function ensureLocalMachineId(path = localConfigPath(), createMachineId = randomUUID2) {
|
|
8068
|
+
const config = readLocalConfig(path);
|
|
8069
|
+
if (config.machineId !== undefined) {
|
|
8070
|
+
return config.machineId;
|
|
8071
|
+
}
|
|
8072
|
+
const machineId = ensureLocalMachineIdSeed(path, createMachineId);
|
|
8073
|
+
const latestConfig = readLocalConfig(path);
|
|
8074
|
+
const latestMachineId = latestConfig.machineId;
|
|
8075
|
+
if (latestMachineId !== undefined) {
|
|
8076
|
+
return latestMachineId;
|
|
8077
|
+
}
|
|
8078
|
+
writeLocalConfig({ ...latestConfig, machineId }, path);
|
|
8079
|
+
return machineId;
|
|
8080
|
+
}
|
|
8081
|
+
function localMachineIdSeedPath(configPath = localConfigPath()) {
|
|
8082
|
+
return join6(dirname4(configPath), `${basename4(configPath)}.machine-id`);
|
|
8083
|
+
}
|
|
8084
|
+
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
8085
|
+
const allowedCwds = [];
|
|
8086
|
+
const missingAllowedCwds = [];
|
|
8087
|
+
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
8088
|
+
const absolutePath = resolve5(expandUserPath(cwd));
|
|
8089
|
+
try {
|
|
8090
|
+
const realPath = realpathSync4(absolutePath);
|
|
8091
|
+
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
8092
|
+
} catch (error) {
|
|
8093
|
+
if (isMissingPathError(error)) {
|
|
8094
|
+
missingAllowedCwds.push(absolutePath);
|
|
8095
|
+
continue;
|
|
8096
|
+
}
|
|
8097
|
+
throw error;
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
return {
|
|
8101
|
+
allowedCwds: uniqueStrings(allowedCwds),
|
|
8102
|
+
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
8103
|
+
};
|
|
8104
|
+
}
|
|
8105
|
+
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8106
|
+
const normalizedPath = realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8107
|
+
const config = readLocalConfig(path);
|
|
8108
|
+
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
8109
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8110
|
+
return allowedCwds;
|
|
8111
|
+
}
|
|
8112
|
+
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8113
|
+
const requestedPath = resolve5(expandUserPath(pathValue));
|
|
8114
|
+
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
8115
|
+
const config = readLocalConfig(path);
|
|
8116
|
+
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
8117
|
+
const normalizedExisting = realpathOrResolved(cwd);
|
|
8118
|
+
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
8119
|
+
});
|
|
8120
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8121
|
+
return allowedCwds;
|
|
8122
|
+
}
|
|
8123
|
+
function writeLocalConfig(config, path = localConfigPath()) {
|
|
8124
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
8125
|
+
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
8126
|
+
`, "utf8");
|
|
8127
|
+
}
|
|
8128
|
+
function isConfigPayload(value) {
|
|
8129
|
+
return typeof value === "object" && value !== null && value.version === 1 && Array.isArray(value.allowedCwds) && machineIdValid(value.machineId) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
|
|
8130
|
+
}
|
|
8131
|
+
function machineIdValid(value) {
|
|
8132
|
+
return value === undefined || typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
8133
|
+
}
|
|
8134
|
+
function ensureLocalMachineIdSeed(configPath, createMachineId) {
|
|
8135
|
+
const seedPath = localMachineIdSeedPath(configPath);
|
|
8136
|
+
if (existsSync4(seedPath)) {
|
|
8137
|
+
return readMachineIdSeed(seedPath);
|
|
8138
|
+
}
|
|
8139
|
+
const machineId = createMachineId();
|
|
8140
|
+
if (!machineIdValid(machineId)) {
|
|
8141
|
+
throw new Error(`invalid generated Linzumi machine id: ${machineId}`);
|
|
8142
|
+
}
|
|
8143
|
+
mkdirSync5(dirname4(seedPath), { recursive: true });
|
|
8144
|
+
const tempPath = join6(dirname4(seedPath), `.${basename4(seedPath)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
8145
|
+
writeFileSync4(tempPath, `${machineId}
|
|
8146
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
8147
|
+
try {
|
|
8148
|
+
linkSync(tempPath, seedPath);
|
|
8149
|
+
return machineId;
|
|
8150
|
+
} catch (error) {
|
|
8151
|
+
if (isNodeErrorCode(error, "EEXIST")) {
|
|
8152
|
+
return readMachineIdSeed(seedPath);
|
|
8153
|
+
}
|
|
8154
|
+
throw error;
|
|
8155
|
+
} finally {
|
|
8156
|
+
unlinkSync(tempPath);
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
8159
|
+
function readMachineIdSeed(seedPath) {
|
|
8160
|
+
const machineId = readFileSync3(seedPath, "utf8").trim();
|
|
8161
|
+
if (!machineIdValid(machineId)) {
|
|
8162
|
+
throw new Error(`invalid Linzumi machine id seed: ${seedPath}`);
|
|
8163
|
+
}
|
|
8164
|
+
return machineId;
|
|
8165
|
+
}
|
|
8166
|
+
function uniqueStrings(values) {
|
|
8167
|
+
return [
|
|
8168
|
+
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
8169
|
+
];
|
|
8170
|
+
}
|
|
8171
|
+
function isMissingPathError(error) {
|
|
8172
|
+
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
8173
|
+
}
|
|
8174
|
+
function isNodeErrorCode(error, code) {
|
|
8175
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8176
|
+
}
|
|
8177
|
+
function realpathOrResolved(pathValue) {
|
|
8178
|
+
try {
|
|
8179
|
+
return realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8180
|
+
} catch (_error) {
|
|
8181
|
+
return resolve5(expandUserPath(pathValue));
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
|
|
8185
|
+
// src/version.ts
|
|
8186
|
+
var linzumiCliVersion = "0.0.43-beta";
|
|
8187
|
+
var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
8188
|
+
|
|
8189
|
+
// src/runnerLock.ts
|
|
8190
|
+
function runnerLockPath(machineId, configPath = localConfigPath()) {
|
|
8191
|
+
return join7(dirname5(configPath), "runners", `${encodeURIComponent(machineId)}.lock`);
|
|
8192
|
+
}
|
|
8193
|
+
function acquireRunnerLock(options) {
|
|
8194
|
+
const path = runnerLockPath(options.machineId, options.configPath);
|
|
8195
|
+
const isPidAlive = options.isPidAlive ?? processIsAlive;
|
|
8196
|
+
const record = {
|
|
8197
|
+
version: 1,
|
|
8198
|
+
machineId: options.machineId,
|
|
8199
|
+
runnerId: options.runnerId,
|
|
8200
|
+
pid: options.pid ?? process.pid,
|
|
8201
|
+
cwd: options.cwd,
|
|
8202
|
+
workspace: options.workspace,
|
|
8203
|
+
startedAt: (options.now ?? (() => new Date))().toISOString(),
|
|
8204
|
+
cliVersion: options.cliVersion ?? linzumiCliVersion
|
|
8205
|
+
};
|
|
8206
|
+
writeLockOrHandleExisting(path, record, isPidAlive, options.beforeReadExistingLock, options.beforeReplaceStaleLock);
|
|
8207
|
+
return {
|
|
8208
|
+
path,
|
|
8209
|
+
record,
|
|
8210
|
+
release: () => releaseRunnerLock(path, record)
|
|
8211
|
+
};
|
|
8212
|
+
}
|
|
8213
|
+
function writeLockOrHandleExisting(path, record, isPidAlive, beforeReadExistingLock, beforeReplaceStaleLock) {
|
|
8214
|
+
if (tryCreateLock(path, record)) {
|
|
8215
|
+
return;
|
|
8216
|
+
}
|
|
8217
|
+
beforeReadExistingLock?.();
|
|
8218
|
+
const existing = readRunnerLockIfPresent(path);
|
|
8219
|
+
if (existing === undefined) {
|
|
8220
|
+
if (tryCreateLock(path, record)) {
|
|
8221
|
+
return;
|
|
8222
|
+
}
|
|
8223
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8224
|
+
}
|
|
8225
|
+
if (isPidAlive(existing.pid)) {
|
|
8226
|
+
throw new Error(activeRunnerLockMessage(path, existing));
|
|
8227
|
+
}
|
|
8228
|
+
beforeReplaceStaleLock?.();
|
|
8229
|
+
withStaleReplacementLock(path, isPidAlive, () => {
|
|
8230
|
+
const latest = existsSync5(path) ? readRunnerLock(path) : undefined;
|
|
8231
|
+
if (latest !== undefined && isPidAlive(latest.pid)) {
|
|
8232
|
+
throw new Error(activeRunnerLockMessage(path, latest));
|
|
8233
|
+
}
|
|
8234
|
+
if (latest !== undefined) {
|
|
8235
|
+
unlinkSync2(path);
|
|
8236
|
+
}
|
|
8237
|
+
if (!tryCreateLock(path, record)) {
|
|
8238
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8239
|
+
}
|
|
8240
|
+
});
|
|
8241
|
+
}
|
|
8242
|
+
function tryCreateLock(path, record) {
|
|
8243
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
8244
|
+
try {
|
|
8245
|
+
const fd = openSync2(path, "wx");
|
|
8246
|
+
try {
|
|
8247
|
+
writeSync(fd, `${JSON.stringify(record, null, 2)}
|
|
8248
|
+
`);
|
|
8249
|
+
} finally {
|
|
8250
|
+
closeSync(fd);
|
|
8251
|
+
}
|
|
8252
|
+
return true;
|
|
8253
|
+
} catch (error) {
|
|
8254
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8255
|
+
return false;
|
|
8256
|
+
}
|
|
8257
|
+
throw error;
|
|
8258
|
+
}
|
|
8259
|
+
}
|
|
8260
|
+
function withStaleReplacementLock(path, isPidAlive, callback) {
|
|
8261
|
+
const replacementPath = `${path}.replace`;
|
|
8262
|
+
while (true) {
|
|
8263
|
+
try {
|
|
8264
|
+
const fd = openSync2(replacementPath, "wx");
|
|
8265
|
+
try {
|
|
8266
|
+
writeSync(fd, `${process.pid}
|
|
8267
|
+
`);
|
|
8268
|
+
callback();
|
|
8269
|
+
} finally {
|
|
8270
|
+
closeSync(fd);
|
|
8271
|
+
unlinkSync2(replacementPath);
|
|
8272
|
+
}
|
|
8273
|
+
return;
|
|
8274
|
+
} catch (error) {
|
|
8275
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8276
|
+
const replacementPid = readReplacementLockPidIfPresent(replacementPath);
|
|
8277
|
+
if (replacementPid === undefined) {
|
|
8278
|
+
continue;
|
|
8279
|
+
}
|
|
8280
|
+
if (isPidAlive(replacementPid)) {
|
|
8281
|
+
throw new Error([
|
|
8282
|
+
"another Linzumi runner is already replacing a stale runner lock",
|
|
8283
|
+
`lock: ${path}`,
|
|
8284
|
+
"Wait for that startup to finish, then retry."
|
|
8285
|
+
].join(`
|
|
8286
|
+
`));
|
|
8287
|
+
}
|
|
8288
|
+
unlinkSync2(replacementPath);
|
|
8289
|
+
continue;
|
|
8290
|
+
}
|
|
8291
|
+
throw error;
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
}
|
|
8295
|
+
function readReplacementLockPidIfPresent(path) {
|
|
8296
|
+
let value;
|
|
8297
|
+
try {
|
|
8298
|
+
value = readFileSync4(path, "utf8").trim();
|
|
8299
|
+
} catch (error) {
|
|
8300
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8301
|
+
return;
|
|
8302
|
+
}
|
|
8303
|
+
throw error;
|
|
8304
|
+
}
|
|
8305
|
+
const pid = Number.parseInt(value, 10);
|
|
8306
|
+
if (pid.toString() !== value || pid <= 0) {
|
|
8307
|
+
throw new Error(`invalid Linzumi runner replacement lock: ${path}`);
|
|
8308
|
+
}
|
|
8309
|
+
return pid;
|
|
8310
|
+
}
|
|
8311
|
+
function releaseRunnerLock(path, record) {
|
|
8312
|
+
const current = readRunnerLockForRelease(path);
|
|
8313
|
+
if (current !== undefined && current.machineId === record.machineId && current.runnerId === record.runnerId && current.pid === record.pid) {
|
|
8314
|
+
unlinkSync2(path);
|
|
8315
|
+
}
|
|
8316
|
+
}
|
|
8317
|
+
function readRunnerLockForRelease(path) {
|
|
8318
|
+
try {
|
|
8319
|
+
return readRunnerLockIfPresent(path);
|
|
8320
|
+
} catch (_error) {
|
|
8321
|
+
return;
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8324
|
+
function readRunnerLockIfPresent(path) {
|
|
8325
|
+
if (!existsSync5(path)) {
|
|
8326
|
+
return;
|
|
8327
|
+
}
|
|
8328
|
+
try {
|
|
8329
|
+
return readRunnerLock(path);
|
|
8330
|
+
} catch (error) {
|
|
8331
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8332
|
+
return;
|
|
8333
|
+
}
|
|
8334
|
+
throw error;
|
|
8335
|
+
}
|
|
8336
|
+
}
|
|
8337
|
+
function readRunnerLock(path) {
|
|
8338
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
8339
|
+
if (!isRunnerLockRecord(parsed)) {
|
|
8340
|
+
throw new Error(`invalid Linzumi runner lock: ${path}`);
|
|
8341
|
+
}
|
|
8342
|
+
return parsed;
|
|
8343
|
+
}
|
|
8344
|
+
function isRunnerLockRecord(value) {
|
|
8345
|
+
return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
|
|
8346
|
+
}
|
|
8347
|
+
function workspaceValid(value) {
|
|
8348
|
+
return value === null || typeof value === "string" && value.trim() !== "";
|
|
8349
|
+
}
|
|
8350
|
+
function processIsAlive(pid) {
|
|
8351
|
+
try {
|
|
8352
|
+
process.kill(pid, 0);
|
|
8353
|
+
return true;
|
|
8354
|
+
} catch (error) {
|
|
8355
|
+
return isNodeErrorCode2(error, "ESRCH") ? false : true;
|
|
8356
|
+
}
|
|
8357
|
+
}
|
|
8358
|
+
function activeRunnerLockMessage(path, record) {
|
|
8359
|
+
const workspace = record.workspace === null ? "workspace: unknown" : `workspace: ${record.workspace}`;
|
|
8360
|
+
return [
|
|
8361
|
+
"another Linzumi runner is already running for this machine",
|
|
8362
|
+
`runner id: ${record.runnerId}`,
|
|
8363
|
+
`pid: ${record.pid}`,
|
|
8364
|
+
`cwd: ${record.cwd}`,
|
|
8365
|
+
workspace,
|
|
8366
|
+
`CLI version: ${record.cliVersion}`,
|
|
8367
|
+
`started at: ${record.startedAt}`,
|
|
8368
|
+
`lock: ${path}`,
|
|
8369
|
+
"Stop that process first, then retry. If the process has already exited, remove the stale lock file and retry."
|
|
8370
|
+
].join(`
|
|
8371
|
+
`);
|
|
8372
|
+
}
|
|
8373
|
+
function isNodeErrorCode2(error, code) {
|
|
8374
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8375
|
+
}
|
|
8376
|
+
|
|
7950
8377
|
// src/runnerConsoleReporter.ts
|
|
7951
8378
|
function reportRunnerConsoleEvent(event, payload) {
|
|
7952
8379
|
const line = formatRunnerConsoleEvent(event, payload);
|
|
@@ -7958,7 +8385,15 @@ function reportRunnerConsoleEvent(event, payload) {
|
|
|
7958
8385
|
function formatRunnerConsoleEvent(event, payload) {
|
|
7959
8386
|
switch (event) {
|
|
7960
8387
|
case "runner.instance_started":
|
|
7961
|
-
return
|
|
8388
|
+
return connectedRunnerMessage(payload);
|
|
8389
|
+
case "runner.replaced":
|
|
8390
|
+
return [
|
|
8391
|
+
"Runner replaced: another Linzumi CLI connected from this machine.",
|
|
8392
|
+
`New runner: ${text(payload.replacementRunnerId)}`,
|
|
8393
|
+
`Version: ${text(payload.replacementVersion)}`,
|
|
8394
|
+
"This process is exiting."
|
|
8395
|
+
].join(`
|
|
8396
|
+
`);
|
|
7962
8397
|
case "kandan.message_ignored":
|
|
7963
8398
|
return `Incoming message from ${sender(payload)}: ignored for reason ${text(payload.reason)}`;
|
|
7964
8399
|
case "kandan.message_queued":
|
|
@@ -7993,6 +8428,40 @@ function formatRunnerConsoleEvent(event, payload) {
|
|
|
7993
8428
|
return;
|
|
7994
8429
|
}
|
|
7995
8430
|
}
|
|
8431
|
+
function connectedRunnerMessage(payload) {
|
|
8432
|
+
return [
|
|
8433
|
+
"Connected to Linzumi",
|
|
8434
|
+
optionalLine("Computer", payload.hostname),
|
|
8435
|
+
optionalLine("Workspace", payload.workspace),
|
|
8436
|
+
`Runner: ${text(payload.runnerId)}`,
|
|
8437
|
+
`CLI: ${text(payload.version)}`,
|
|
8438
|
+
optionalLine("Codex", payload.codexUrl),
|
|
8439
|
+
...replacementLines(payload.replacedRunners)
|
|
8440
|
+
].filter((line) => line !== undefined).join(`
|
|
8441
|
+
`);
|
|
8442
|
+
}
|
|
8443
|
+
function optionalLine(label, value) {
|
|
8444
|
+
const normalized = stringValue2(value) ?? numberValue(value)?.toString();
|
|
8445
|
+
return normalized === undefined ? undefined : `${label}: ${normalized}`;
|
|
8446
|
+
}
|
|
8447
|
+
function replacementLines(value) {
|
|
8448
|
+
if (!Array.isArray(value)) {
|
|
8449
|
+
return [];
|
|
8450
|
+
}
|
|
8451
|
+
return value.flatMap((entry) => {
|
|
8452
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
8453
|
+
return [];
|
|
8454
|
+
}
|
|
8455
|
+
const record = entry;
|
|
8456
|
+
const runnerId = stringValue2(record.runnerId);
|
|
8457
|
+
if (runnerId === undefined) {
|
|
8458
|
+
return [];
|
|
8459
|
+
}
|
|
8460
|
+
const version = stringValue2(record.version);
|
|
8461
|
+
const suffix = version === undefined ? "" : ` (CLI ${version})`;
|
|
8462
|
+
return [`Replaced older runner from this machine: ${runnerId}${suffix}`];
|
|
8463
|
+
});
|
|
8464
|
+
}
|
|
7996
8465
|
function sender(payload) {
|
|
7997
8466
|
const slug = stringValue2(payload.actor_slug);
|
|
7998
8467
|
const userId = numberValue(payload.actor_user_id);
|
|
@@ -8056,6 +8525,21 @@ async function runLocalCodexRunner(options) {
|
|
|
8056
8525
|
kandanUrl: options.kandanUrl
|
|
8057
8526
|
});
|
|
8058
8527
|
try {
|
|
8528
|
+
if (options.machineId !== undefined) {
|
|
8529
|
+
const runnerLock = acquireRunnerLock({
|
|
8530
|
+
machineId: options.machineId,
|
|
8531
|
+
runnerId: options.runnerId,
|
|
8532
|
+
cwd: options.cwd,
|
|
8533
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8534
|
+
configPath: options.runnerLockConfigPath
|
|
8535
|
+
});
|
|
8536
|
+
cleanup.actions.push(() => runnerLock.release());
|
|
8537
|
+
log("runner.lock_acquired", {
|
|
8538
|
+
path: runnerLock.path,
|
|
8539
|
+
machineId: options.machineId,
|
|
8540
|
+
runnerId: options.runnerId
|
|
8541
|
+
});
|
|
8542
|
+
}
|
|
8059
8543
|
return await openLocalCodexRunner(options, log, cleanup, close);
|
|
8060
8544
|
} catch (error) {
|
|
8061
8545
|
await close().catch(() => {
|
|
@@ -8123,9 +8607,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8123
8607
|
const kandan = await connectPhoenixClient(options.kandanUrl, options.token, options.socketFactory);
|
|
8124
8608
|
cleanup.actions.push(() => kandan.close());
|
|
8125
8609
|
const topic = `local_runner:${options.runnerId}`;
|
|
8610
|
+
const clientId = options.machineId ?? options.runnerId;
|
|
8126
8611
|
const joinPayload = () => ({
|
|
8127
8612
|
clientName: "kandan-local-codex-runner",
|
|
8128
|
-
|
|
8613
|
+
clientId,
|
|
8614
|
+
version: linzumiCliVersion,
|
|
8129
8615
|
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8130
8616
|
channel: options.channelSession?.channelSlug ?? null,
|
|
8131
8617
|
capabilities: capabilitiesPayload()
|
|
@@ -8140,7 +8626,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8140
8626
|
}
|
|
8141
8627
|
dispatcher(control);
|
|
8142
8628
|
});
|
|
8143
|
-
await kandan.join(topic, joinPayload(), {
|
|
8629
|
+
const joinResponse = await kandan.join(topic, joinPayload(), {
|
|
8630
|
+
rejoinPayload: joinPayload
|
|
8631
|
+
});
|
|
8632
|
+
const replacedRunners = replacementRunnerSummaries(objectValue(joinResponse)?.replaced_runners);
|
|
8144
8633
|
const started = options.codexUrl === undefined ? await startOwnedCodexAppServer(options) : undefined;
|
|
8145
8634
|
if (started !== undefined) {
|
|
8146
8635
|
cleanup.actions.push(() => {
|
|
@@ -8151,7 +8640,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8151
8640
|
if (codexUrl === undefined) {
|
|
8152
8641
|
throw new Error("missing codex app-server websocket URL");
|
|
8153
8642
|
}
|
|
8154
|
-
const instanceId = `codex-${
|
|
8643
|
+
const instanceId = `codex-${randomUUID3()}`;
|
|
8155
8644
|
const publishLocalEditorStatus = (payload) => {
|
|
8156
8645
|
kandan.push(topic, "local_editor_status", payload).catch((error) => {
|
|
8157
8646
|
log("kandan.local_editor_status_push_failed", {
|
|
@@ -8199,6 +8688,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8199
8688
|
const runtimeDefaults = runnerRuntimeDefaults(options);
|
|
8200
8689
|
const instancePayload = {
|
|
8201
8690
|
instanceId,
|
|
8691
|
+
clientId,
|
|
8202
8692
|
codexUrl,
|
|
8203
8693
|
tuiLaunched: options.launchTui,
|
|
8204
8694
|
cwd: options.cwd,
|
|
@@ -8211,7 +8701,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8211
8701
|
fast: options.fast ?? false
|
|
8212
8702
|
};
|
|
8213
8703
|
await kandan.push(topic, "instance_started", instancePayload);
|
|
8214
|
-
log("runner.instance_started", {
|
|
8704
|
+
log("runner.instance_started", {
|
|
8705
|
+
runnerId: options.runnerId,
|
|
8706
|
+
hostname: runnerHost,
|
|
8707
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8708
|
+
version: linzumiCliVersion,
|
|
8709
|
+
instanceId,
|
|
8710
|
+
codexUrl,
|
|
8711
|
+
replacedRunners
|
|
8712
|
+
});
|
|
8215
8713
|
const channelSession = options.channelSession === undefined ? undefined : await attachChannelSession({
|
|
8216
8714
|
kandan,
|
|
8217
8715
|
codex,
|
|
@@ -8312,6 +8810,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8312
8810
|
};
|
|
8313
8811
|
const heartbeatPayload = () => ({
|
|
8314
8812
|
instanceId,
|
|
8813
|
+
clientId,
|
|
8315
8814
|
codexUrl,
|
|
8316
8815
|
cwd: options.cwd,
|
|
8317
8816
|
hostname: runnerHost,
|
|
@@ -8390,6 +8889,20 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8390
8889
|
});
|
|
8391
8890
|
const handleControl = (control) => {
|
|
8392
8891
|
log("kandan.control", { control });
|
|
8892
|
+
if (control.type === "replace_runner") {
|
|
8893
|
+
log("runner.replaced", {
|
|
8894
|
+
runnerId: options.runnerId,
|
|
8895
|
+
reason: control.reason,
|
|
8896
|
+
replacementRunnerId: control.replacementRunnerId,
|
|
8897
|
+
replacementVersion: control.replacementVersion
|
|
8898
|
+
});
|
|
8899
|
+
close().catch((error) => {
|
|
8900
|
+
log("runner.replace_close_failed", {
|
|
8901
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8902
|
+
});
|
|
8903
|
+
});
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8393
8906
|
if (!controlTargetsInstance(control, instanceId)) {
|
|
8394
8907
|
log("kandan.control_ignored", {
|
|
8395
8908
|
reason: "instance_id_mismatch",
|
|
@@ -8604,8 +9117,24 @@ function normalizedWorkDescription(value) {
|
|
|
8604
9117
|
const normalized = value?.trim();
|
|
8605
9118
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
8606
9119
|
}
|
|
9120
|
+
function replacementRunnerSummaries(value) {
|
|
9121
|
+
const entries = arrayValue(value) ?? [];
|
|
9122
|
+
return entries.flatMap((entry) => {
|
|
9123
|
+
const record = objectValue(entry);
|
|
9124
|
+
const runnerId = stringValue(record?.runnerId);
|
|
9125
|
+
if (runnerId === undefined) {
|
|
9126
|
+
return [];
|
|
9127
|
+
}
|
|
9128
|
+
return [
|
|
9129
|
+
{
|
|
9130
|
+
runnerId,
|
|
9131
|
+
version: stringValue(record?.version) ?? null
|
|
9132
|
+
}
|
|
9133
|
+
];
|
|
9134
|
+
});
|
|
9135
|
+
}
|
|
8607
9136
|
function makeRunnerLogger(options) {
|
|
8608
|
-
return createRunnerLogger(options.logFile ??
|
|
9137
|
+
return createRunnerLogger(options.logFile ?? join8(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
|
|
8609
9138
|
}
|
|
8610
9139
|
function installCleanupHandlers(close) {
|
|
8611
9140
|
const closeAndExit = () => {
|
|
@@ -9054,11 +9583,12 @@ function optionalThreadControlField(control, field) {
|
|
|
9054
9583
|
}
|
|
9055
9584
|
function startInstanceRuntimeSettings(options, control) {
|
|
9056
9585
|
const defaults = runnerRuntimeDefaults(options);
|
|
9586
|
+
const sandbox = control.sandbox ?? defaults.sandbox;
|
|
9057
9587
|
return {
|
|
9058
9588
|
model: control.model ?? defaults.model,
|
|
9059
9589
|
reasoningEffort: control.reasoningEffort ?? defaults.reasoningEffort,
|
|
9060
|
-
approvalPolicy: control.approvalPolicy ?? defaults.approvalPolicy,
|
|
9061
|
-
sandbox
|
|
9590
|
+
approvalPolicy: codexApprovalPolicyForRequest(control.approvalPolicy ?? defaults.approvalPolicy, sandbox),
|
|
9591
|
+
sandbox,
|
|
9062
9592
|
fast: control.fast ?? options.fast
|
|
9063
9593
|
};
|
|
9064
9594
|
}
|
|
@@ -9077,11 +9607,12 @@ function runnerWorkspaceSlug(options) {
|
|
|
9077
9607
|
function runnerRuntimeDefaults(options) {
|
|
9078
9608
|
const session = options.channelSession;
|
|
9079
9609
|
const defaults = options.runtimeDefaults;
|
|
9610
|
+
const sandbox = defaults?.sandbox ?? session?.sandbox;
|
|
9080
9611
|
return {
|
|
9081
9612
|
model: defaults?.model ?? session?.model,
|
|
9082
9613
|
reasoningEffort: defaults?.reasoningEffort ?? session?.reasoningEffort,
|
|
9083
|
-
approvalPolicy: defaults?.approvalPolicy ?? session?.approvalPolicy,
|
|
9084
|
-
sandbox
|
|
9614
|
+
approvalPolicy: codexApprovalPolicySetting(defaults?.approvalPolicy ?? session?.approvalPolicy, sandbox),
|
|
9615
|
+
sandbox
|
|
9085
9616
|
};
|
|
9086
9617
|
}
|
|
9087
9618
|
function isUpdateRunnerConfigControl(control) {
|
|
@@ -9100,9 +9631,9 @@ function configuredAllowedCwds(values) {
|
|
|
9100
9631
|
const allowedCwds = [];
|
|
9101
9632
|
const missingAllowedCwds = [];
|
|
9102
9633
|
for (const value of normalizeAllowedCwds(values)) {
|
|
9103
|
-
const absolutePath =
|
|
9634
|
+
const absolutePath = resolve6(expandUserPath(value));
|
|
9104
9635
|
try {
|
|
9105
|
-
const realPath =
|
|
9636
|
+
const realPath = realpathSync5(absolutePath);
|
|
9106
9637
|
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9107
9638
|
} catch (error) {
|
|
9108
9639
|
if (isMissingAllowedCwdError(error)) {
|
|
@@ -9125,18 +9656,18 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
9125
9656
|
}
|
|
9126
9657
|
|
|
9127
9658
|
// src/authCache.ts
|
|
9128
|
-
import { existsSync as
|
|
9129
|
-
import { homedir as
|
|
9130
|
-
import { dirname as
|
|
9659
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9660
|
+
import { homedir as homedir6 } from "node:os";
|
|
9661
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
9131
9662
|
function defaultAuthFilePath() {
|
|
9132
|
-
const base = process.env.KANDAN_HOME ??
|
|
9133
|
-
return
|
|
9663
|
+
const base = process.env.KANDAN_HOME ?? join9(homedir6(), ".kandan");
|
|
9664
|
+
return join9(base, "auth.json");
|
|
9134
9665
|
}
|
|
9135
9666
|
function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
|
|
9136
|
-
if (!
|
|
9667
|
+
if (!existsSync6(authFilePath)) {
|
|
9137
9668
|
return;
|
|
9138
9669
|
}
|
|
9139
|
-
const authFile = parseAuthFile(
|
|
9670
|
+
const authFile = parseAuthFile(readFileSync5(authFilePath, "utf8"));
|
|
9140
9671
|
const kandanBaseUrl = kandanHttpBaseUrl(kandanUrl);
|
|
9141
9672
|
const entry = authFile.local_codex_runner?.[kandanBaseUrl];
|
|
9142
9673
|
if (entry === undefined || entry.access_token.trim() === "") {
|
|
@@ -9154,7 +9685,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
|
|
|
9154
9685
|
}
|
|
9155
9686
|
function writeCachedLocalRunnerToken(args) {
|
|
9156
9687
|
const authFilePath = args.authFilePath ?? defaultAuthFilePath();
|
|
9157
|
-
const existing =
|
|
9688
|
+
const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync5(authFilePath, "utf8")) : { version: 1 };
|
|
9158
9689
|
const kandanBaseUrl = kandanHttpBaseUrl(args.kandanUrl);
|
|
9159
9690
|
const issuedAt = new Date;
|
|
9160
9691
|
const expiresAt = args.expiresInSeconds === undefined ? undefined : new Date(issuedAt.getTime() + args.expiresInSeconds * 1000).toISOString();
|
|
@@ -9170,8 +9701,8 @@ function writeCachedLocalRunnerToken(args) {
|
|
|
9170
9701
|
}
|
|
9171
9702
|
}
|
|
9172
9703
|
};
|
|
9173
|
-
|
|
9174
|
-
|
|
9704
|
+
mkdirSync7(dirname6(authFilePath), { recursive: true });
|
|
9705
|
+
writeFileSync5(authFilePath, `${JSON.stringify(next, null, 2)}
|
|
9175
9706
|
`, "utf8");
|
|
9176
9707
|
return {
|
|
9177
9708
|
accessToken: args.accessToken,
|
|
@@ -9263,102 +9794,12 @@ async function acquireAndCacheToken(args) {
|
|
|
9263
9794
|
return token.accessToken;
|
|
9264
9795
|
}
|
|
9265
9796
|
|
|
9266
|
-
// src/localConfig.ts
|
|
9267
|
-
import {
|
|
9268
|
-
existsSync as existsSync5,
|
|
9269
|
-
mkdirSync as mkdirSync6,
|
|
9270
|
-
readFileSync as readFileSync4,
|
|
9271
|
-
realpathSync as realpathSync5,
|
|
9272
|
-
writeFileSync as writeFileSync5
|
|
9273
|
-
} from "node:fs";
|
|
9274
|
-
import { homedir as homedir6 } from "node:os";
|
|
9275
|
-
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
9276
|
-
function localConfigPath(env = process.env) {
|
|
9277
|
-
const override = env.LINZUMI_CONFIG_FILE;
|
|
9278
|
-
return override !== undefined && override.trim() !== "" ? resolve6(expandUserPath(override)) : resolve6(homedir6(), ".linzumi", "config.json");
|
|
9279
|
-
}
|
|
9280
|
-
function readLocalConfig(path = localConfigPath()) {
|
|
9281
|
-
if (!existsSync5(path)) {
|
|
9282
|
-
return { version: 1, allowedCwds: [] };
|
|
9283
|
-
}
|
|
9284
|
-
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
9285
|
-
if (!isConfigPayload(parsed)) {
|
|
9286
|
-
throw new Error(`invalid Linzumi config: ${path}`);
|
|
9287
|
-
}
|
|
9288
|
-
return {
|
|
9289
|
-
version: 1,
|
|
9290
|
-
allowedCwds: uniqueStrings(parsed.allowedCwds)
|
|
9291
|
-
};
|
|
9292
|
-
}
|
|
9293
|
-
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
9294
|
-
const allowedCwds = [];
|
|
9295
|
-
const missingAllowedCwds = [];
|
|
9296
|
-
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
9297
|
-
const absolutePath = resolve6(expandUserPath(cwd));
|
|
9298
|
-
try {
|
|
9299
|
-
const realPath = realpathSync5(absolutePath);
|
|
9300
|
-
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9301
|
-
} catch (error) {
|
|
9302
|
-
if (isMissingPathError(error)) {
|
|
9303
|
-
missingAllowedCwds.push(absolutePath);
|
|
9304
|
-
continue;
|
|
9305
|
-
}
|
|
9306
|
-
throw error;
|
|
9307
|
-
}
|
|
9308
|
-
}
|
|
9309
|
-
return {
|
|
9310
|
-
allowedCwds: uniqueStrings(allowedCwds),
|
|
9311
|
-
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
9312
|
-
};
|
|
9313
|
-
}
|
|
9314
|
-
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9315
|
-
const normalizedPath = realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9316
|
-
const config = readLocalConfig(path);
|
|
9317
|
-
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
9318
|
-
writeLocalConfig({ version: 1, allowedCwds }, path);
|
|
9319
|
-
return allowedCwds;
|
|
9320
|
-
}
|
|
9321
|
-
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9322
|
-
const requestedPath = resolve6(expandUserPath(pathValue));
|
|
9323
|
-
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
9324
|
-
const config = readLocalConfig(path);
|
|
9325
|
-
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
9326
|
-
const normalizedExisting = realpathOrResolved(cwd);
|
|
9327
|
-
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
9328
|
-
});
|
|
9329
|
-
writeLocalConfig({ version: 1, allowedCwds }, path);
|
|
9330
|
-
return allowedCwds;
|
|
9331
|
-
}
|
|
9332
|
-
function writeLocalConfig(config, path = localConfigPath()) {
|
|
9333
|
-
mkdirSync6(dirname5(path), { recursive: true });
|
|
9334
|
-
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
9335
|
-
`, "utf8");
|
|
9336
|
-
}
|
|
9337
|
-
function isConfigPayload(value) {
|
|
9338
|
-
return typeof value === "object" && value !== null && value.version === 1 && Array.isArray(value.allowedCwds) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
|
|
9339
|
-
}
|
|
9340
|
-
function uniqueStrings(values) {
|
|
9341
|
-
return [
|
|
9342
|
-
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
9343
|
-
];
|
|
9344
|
-
}
|
|
9345
|
-
function isMissingPathError(error) {
|
|
9346
|
-
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
9347
|
-
}
|
|
9348
|
-
function realpathOrResolved(pathValue) {
|
|
9349
|
-
try {
|
|
9350
|
-
return realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9351
|
-
} catch (_error) {
|
|
9352
|
-
return resolve6(expandUserPath(pathValue));
|
|
9353
|
-
}
|
|
9354
|
-
}
|
|
9355
|
-
|
|
9356
9797
|
// src/defaultUrls.ts
|
|
9357
9798
|
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
9358
9799
|
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
9359
9800
|
|
|
9360
9801
|
// src/kandanTls.ts
|
|
9361
|
-
import { existsSync as
|
|
9802
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
9362
9803
|
import { Agent } from "undici";
|
|
9363
9804
|
import { WebSocket as WsWebSocket } from "ws";
|
|
9364
9805
|
function kandanTlsTrustFromEnv() {
|
|
@@ -9369,10 +9810,10 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
9369
9810
|
return;
|
|
9370
9811
|
}
|
|
9371
9812
|
const trimmed = caFile.trim();
|
|
9372
|
-
if (!
|
|
9813
|
+
if (!existsSync7(trimmed)) {
|
|
9373
9814
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
9374
9815
|
}
|
|
9375
|
-
const ca =
|
|
9816
|
+
const ca = readFileSync6(trimmed, "utf8");
|
|
9376
9817
|
return {
|
|
9377
9818
|
caFile: trimmed,
|
|
9378
9819
|
ca,
|
|
@@ -9401,8 +9842,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
9401
9842
|
}
|
|
9402
9843
|
|
|
9403
9844
|
// src/agentBootstrap.ts
|
|
9404
|
-
import { existsSync as
|
|
9405
|
-
import { dirname as
|
|
9845
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9846
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
9406
9847
|
import { homedir as homedir7 } from "node:os";
|
|
9407
9848
|
async function runAgentCliCommand(args, deps = {
|
|
9408
9849
|
fetchImpl: fetch,
|
|
@@ -10002,7 +10443,7 @@ function agentTokenFile(flags) {
|
|
|
10002
10443
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
10003
10444
|
}
|
|
10004
10445
|
function defaultAgentTokenFilePath() {
|
|
10005
|
-
return
|
|
10446
|
+
return join10(homedir7(), ".linzumi", "agent-token.json");
|
|
10006
10447
|
}
|
|
10007
10448
|
function normalizedApiUrl(apiUrl) {
|
|
10008
10449
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -10011,10 +10452,10 @@ function authorizationHeaders(token) {
|
|
|
10011
10452
|
return { authorization: `Bearer ${token}` };
|
|
10012
10453
|
}
|
|
10013
10454
|
function readOptionalTextFile(path) {
|
|
10014
|
-
return
|
|
10455
|
+
return existsSync8(path) ? readFileSync7(path, "utf8") : undefined;
|
|
10015
10456
|
}
|
|
10016
10457
|
function writeTextFile(path, content) {
|
|
10017
|
-
|
|
10458
|
+
mkdirSync8(dirname7(path), { recursive: true });
|
|
10018
10459
|
writeFileSync6(path, content);
|
|
10019
10460
|
}
|
|
10020
10461
|
function readStoredAgentTokenFile(path, readTextFile = readOptionalTextFile) {
|
|
@@ -10091,8 +10532,8 @@ Launch target:
|
|
|
10091
10532
|
}
|
|
10092
10533
|
|
|
10093
10534
|
// src/helloLinzumiProject.ts
|
|
10094
|
-
import { existsSync as
|
|
10095
|
-
import { dirname as
|
|
10535
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10536
|
+
import { dirname as dirname8, join as join11, resolve as resolve7 } from "node:path";
|
|
10096
10537
|
import { fileURLToPath } from "node:url";
|
|
10097
10538
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
10098
10539
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -10100,8 +10541,8 @@ var defaultHelloLinzumiParentDir = "/tmp";
|
|
|
10100
10541
|
var defaultHelloLinzumiPort = 8787;
|
|
10101
10542
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
10102
10543
|
var markerFile = ".linzumi-demo-project";
|
|
10103
|
-
var moduleDir =
|
|
10104
|
-
var linzumiLogoSvg =
|
|
10544
|
+
var moduleDir = dirname8(fileURLToPath(import.meta.url));
|
|
10545
|
+
var linzumiLogoSvg = readFileSync8(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
10105
10546
|
function createHelloLinzumiProject(input = {}) {
|
|
10106
10547
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
10107
10548
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -10109,9 +10550,9 @@ function createHelloLinzumiProject(input = {}) {
|
|
|
10109
10550
|
const host = normalizeHost(options.host);
|
|
10110
10551
|
assertTcpPort(port);
|
|
10111
10552
|
assertWritableDemoRoot(root, options.reset === true);
|
|
10112
|
-
|
|
10553
|
+
mkdirSync9(join11(root, "src"), { recursive: true });
|
|
10113
10554
|
for (const file of demoFiles({ root, port, host })) {
|
|
10114
|
-
writeFileSync7(
|
|
10555
|
+
writeFileSync7(join11(root, file.path), file.content, "utf8");
|
|
10115
10556
|
}
|
|
10116
10557
|
return {
|
|
10117
10558
|
root,
|
|
@@ -10152,11 +10593,11 @@ function assertTcpPort(port) {
|
|
|
10152
10593
|
throw new Error("--port must be a TCP port from 1 to 65535");
|
|
10153
10594
|
}
|
|
10154
10595
|
function assertWritableDemoRoot(root, reset) {
|
|
10155
|
-
if (!
|
|
10596
|
+
if (!existsSync9(root)) {
|
|
10156
10597
|
return;
|
|
10157
10598
|
}
|
|
10158
|
-
const markerPath =
|
|
10159
|
-
const isDemoRoot =
|
|
10599
|
+
const markerPath = join11(root, markerFile);
|
|
10600
|
+
const isDemoRoot = existsSync9(markerPath) && readFileSync8(markerPath, "utf8").trim() === "hello-linzumi";
|
|
10160
10601
|
if (isDemoRoot && reset) {
|
|
10161
10602
|
rmSync2(root, { recursive: true, force: true });
|
|
10162
10603
|
return;
|
|
@@ -10655,27 +11096,30 @@ To kick the agent off:
|
|
|
10655
11096
|
|
|
10656
11097
|
// src/commanderDaemon.ts
|
|
10657
11098
|
import {
|
|
10658
|
-
existsSync as
|
|
10659
|
-
closeSync,
|
|
10660
|
-
mkdirSync as
|
|
10661
|
-
openSync as
|
|
10662
|
-
readFileSync as
|
|
11099
|
+
existsSync as existsSync10,
|
|
11100
|
+
closeSync as closeSync2,
|
|
11101
|
+
mkdirSync as mkdirSync10,
|
|
11102
|
+
openSync as openSync3,
|
|
11103
|
+
readFileSync as readFileSync9,
|
|
10663
11104
|
watch,
|
|
10664
11105
|
writeFileSync as writeFileSync8
|
|
10665
11106
|
} from "node:fs";
|
|
10666
11107
|
import { homedir as homedir8 } from "node:os";
|
|
10667
|
-
import { dirname as
|
|
11108
|
+
import { dirname as dirname9, join as join12, resolve as resolve8 } from "node:path";
|
|
10668
11109
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
10669
11110
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10670
|
-
var
|
|
11111
|
+
var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
|
|
10671
11112
|
function commanderStatusDir() {
|
|
10672
|
-
return
|
|
11113
|
+
return join12(homedir8(), ".linzumi", "commanders");
|
|
10673
11114
|
}
|
|
10674
11115
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10675
|
-
return
|
|
11116
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
10676
11117
|
}
|
|
10677
11118
|
function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10678
|
-
return
|
|
11119
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.log`);
|
|
11120
|
+
}
|
|
11121
|
+
function commanderLogIsConnected(log) {
|
|
11122
|
+
return connectedMarkers.some((marker) => log.includes(marker));
|
|
10679
11123
|
}
|
|
10680
11124
|
function startCommanderDaemon(options) {
|
|
10681
11125
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
@@ -10693,10 +11137,10 @@ function startCommanderDaemon(options) {
|
|
|
10693
11137
|
"--log-file",
|
|
10694
11138
|
logFile
|
|
10695
11139
|
];
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
const out =
|
|
10699
|
-
const err =
|
|
11140
|
+
mkdirSync10(statusDir, { recursive: true });
|
|
11141
|
+
mkdirSync10(dirname9(logFile), { recursive: true });
|
|
11142
|
+
const out = openSync3(logFile, "a");
|
|
11143
|
+
const err = openSync3(logFile, "a");
|
|
10700
11144
|
writeCliAuditEvent("process.spawn", {
|
|
10701
11145
|
command: nodeBin,
|
|
10702
11146
|
args: command,
|
|
@@ -10715,8 +11159,8 @@ function startCommanderDaemon(options) {
|
|
|
10715
11159
|
pid: child.pid,
|
|
10716
11160
|
purpose: "commander_daemon.start"
|
|
10717
11161
|
}, { sessionId: options.runnerId });
|
|
10718
|
-
|
|
10719
|
-
|
|
11162
|
+
closeSync2(out);
|
|
11163
|
+
closeSync2(err);
|
|
10720
11164
|
child.unref();
|
|
10721
11165
|
if (child.pid === undefined) {
|
|
10722
11166
|
throw new Error("commander daemon did not report a pid");
|
|
@@ -10738,15 +11182,15 @@ function startCommanderDaemon(options) {
|
|
|
10738
11182
|
}
|
|
10739
11183
|
function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
|
|
10740
11184
|
const statusFile = commanderStatusFile(runnerId, statusDir);
|
|
10741
|
-
if (!
|
|
11185
|
+
if (!existsSync10(statusFile)) {
|
|
10742
11186
|
return { status: "missing", runnerId, statusFile };
|
|
10743
11187
|
}
|
|
10744
|
-
const record = parseRecord(
|
|
11188
|
+
const record = parseRecord(readFileSync9(statusFile, "utf8"));
|
|
10745
11189
|
return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
|
|
10746
11190
|
}
|
|
10747
11191
|
async function waitForCommanderDaemon(options) {
|
|
10748
11192
|
const now = options.now ?? (() => Date.now());
|
|
10749
|
-
const readTextFile = options.readTextFile ?? ((path) =>
|
|
11193
|
+
const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync9(path, "utf8") : undefined);
|
|
10750
11194
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
10751
11195
|
const deadline = now() + options.timeoutMs;
|
|
10752
11196
|
while (now() <= deadline) {
|
|
@@ -10761,12 +11205,12 @@ async function waitForCommanderDaemon(options) {
|
|
|
10761
11205
|
if (log === undefined) {
|
|
10762
11206
|
return { ok: false, reason: "timeout" };
|
|
10763
11207
|
}
|
|
10764
|
-
if (log
|
|
11208
|
+
if (commanderLogIsConnected(log)) {
|
|
10765
11209
|
return { ok: true, record: status.record };
|
|
10766
11210
|
}
|
|
10767
11211
|
await waitForFileChangeOrTimeout(status.record.logFile, deadline, now, () => {
|
|
10768
11212
|
const updatedLog = readTextFile(status.record.logFile);
|
|
10769
|
-
return updatedLog !== undefined && updatedLog
|
|
11213
|
+
return updatedLog !== undefined && commanderLogIsConnected(updatedLog);
|
|
10770
11214
|
});
|
|
10771
11215
|
}
|
|
10772
11216
|
}
|
|
@@ -10976,7 +11420,7 @@ async function main(args) {
|
|
|
10976
11420
|
process.stdout.write(connectGuideText());
|
|
10977
11421
|
return;
|
|
10978
11422
|
case "version":
|
|
10979
|
-
process.stdout.write(
|
|
11423
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
10980
11424
|
`);
|
|
10981
11425
|
return;
|
|
10982
11426
|
case "auth":
|
|
@@ -10996,18 +11440,18 @@ async function main(args) {
|
|
|
10996
11440
|
return;
|
|
10997
11441
|
case "agentRunner": {
|
|
10998
11442
|
const options = await parseAgentRunnerArgs(parsed.args);
|
|
10999
|
-
await runLocalCodexRunner(options);
|
|
11443
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11000
11444
|
return;
|
|
11001
11445
|
}
|
|
11002
11446
|
case "start": {
|
|
11003
11447
|
const options = await parseStartRunnerArgs(parsed.args);
|
|
11004
11448
|
addAllowedCwd(options.cwd);
|
|
11005
|
-
await runLocalCodexRunner(options);
|
|
11449
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11006
11450
|
return;
|
|
11007
11451
|
}
|
|
11008
11452
|
case "run": {
|
|
11009
11453
|
const options = await parseRunnerArgs(parsed.args);
|
|
11010
|
-
await runLocalCodexRunner(options);
|
|
11454
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11011
11455
|
return;
|
|
11012
11456
|
}
|
|
11013
11457
|
}
|
|
@@ -11318,7 +11762,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
11318
11762
|
return {
|
|
11319
11763
|
kandanUrl,
|
|
11320
11764
|
token: targetToken,
|
|
11321
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11765
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11322
11766
|
workspaceSlug: target.workspaceSlug,
|
|
11323
11767
|
cwd,
|
|
11324
11768
|
codexBin,
|
|
@@ -11406,7 +11850,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11406
11850
|
return {
|
|
11407
11851
|
kandanUrl,
|
|
11408
11852
|
token: tokenFile.commanderToken,
|
|
11409
|
-
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${
|
|
11853
|
+
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${randomUUID4()}`,
|
|
11410
11854
|
workspaceSlug: tokenFile.workspaceId,
|
|
11411
11855
|
cwd,
|
|
11412
11856
|
codexBin,
|
|
@@ -11426,7 +11870,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11426
11870
|
};
|
|
11427
11871
|
}
|
|
11428
11872
|
function readAgentTokenTextFile(path) {
|
|
11429
|
-
return
|
|
11873
|
+
return existsSync11(path) ? readFileSync10(path, "utf8") : undefined;
|
|
11430
11874
|
}
|
|
11431
11875
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
11432
11876
|
const unsupportedFlags = [
|
|
@@ -11502,12 +11946,12 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11502
11946
|
process.exit(0);
|
|
11503
11947
|
}
|
|
11504
11948
|
if (values.get("version") === true) {
|
|
11505
|
-
process.stdout.write(
|
|
11949
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
11506
11950
|
`);
|
|
11507
11951
|
process.exit(0);
|
|
11508
11952
|
}
|
|
11509
|
-
const
|
|
11510
|
-
const kandanUrl =
|
|
11953
|
+
const connectTarget = parseConnectTarget(values);
|
|
11954
|
+
const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
11511
11955
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
11512
11956
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
11513
11957
|
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetails();
|
|
@@ -11518,8 +11962,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11518
11962
|
const token = await deps.resolveToken({
|
|
11519
11963
|
kandanUrl,
|
|
11520
11964
|
explicitToken,
|
|
11521
|
-
workspaceSlug:
|
|
11522
|
-
channelSlug:
|
|
11965
|
+
workspaceSlug: connectTarget?.workspaceSlug,
|
|
11966
|
+
channelSlug: connectTarget?.channelSlug,
|
|
11523
11967
|
authFilePath: stringValue3(values, "auth-file"),
|
|
11524
11968
|
callbackHost: stringValue3(values, "oauth-callback-host"),
|
|
11525
11969
|
reportRejectedCachedToken: () => {
|
|
@@ -11527,7 +11971,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11527
11971
|
`);
|
|
11528
11972
|
}
|
|
11529
11973
|
});
|
|
11530
|
-
const channelSession = parseChannelSession(values, token,
|
|
11974
|
+
const channelSession = parseChannelSession(values, token, connectTarget);
|
|
11531
11975
|
const editorRuntime = await deps.resolveEditorRuntime({
|
|
11532
11976
|
kandanUrl,
|
|
11533
11977
|
token,
|
|
@@ -11544,7 +11988,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11544
11988
|
return {
|
|
11545
11989
|
kandanUrl,
|
|
11546
11990
|
token,
|
|
11547
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11991
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11548
11992
|
cwd,
|
|
11549
11993
|
codexBin,
|
|
11550
11994
|
codexUrl: stringValue3(values, "codex-url"),
|
|
@@ -11558,7 +12002,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11558
12002
|
editorRuntime: editorRuntime.runtime,
|
|
11559
12003
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11560
12004
|
dependencyStatus,
|
|
11561
|
-
workspaceSlug:
|
|
12005
|
+
workspaceSlug: connectTarget?.workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
11562
12006
|
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11563
12007
|
channelSession
|
|
11564
12008
|
};
|
|
@@ -11670,7 +12114,7 @@ function resolveUserPath(pathValue) {
|
|
|
11670
12114
|
return resolve9(pathValue);
|
|
11671
12115
|
}
|
|
11672
12116
|
function parseChannelSession(values, token, target) {
|
|
11673
|
-
if (target === undefined) {
|
|
12117
|
+
if (target === undefined || target.channelSlug === undefined) {
|
|
11674
12118
|
return;
|
|
11675
12119
|
}
|
|
11676
12120
|
const listenUser = stringValue3(values, "listen-user") ?? defaultListenUserFromToken(token);
|
|
@@ -11686,7 +12130,7 @@ function parseChannelSession(values, token, target) {
|
|
|
11686
12130
|
streamFlushMs: positiveIntegerValue(values, "stream-flush-ms")
|
|
11687
12131
|
};
|
|
11688
12132
|
}
|
|
11689
|
-
function
|
|
12133
|
+
function parseConnectTarget(values) {
|
|
11690
12134
|
return parseOptionalChannelTarget(values);
|
|
11691
12135
|
}
|
|
11692
12136
|
function defaultListenUserFromToken(token) {
|
|
@@ -11699,12 +12143,15 @@ function defaultListenUserFromToken(token) {
|
|
|
11699
12143
|
function parseOptionalChannelTarget(values) {
|
|
11700
12144
|
const channel = stringValue3(values, "channel");
|
|
11701
12145
|
const workspace = stringValue3(values, "workspace");
|
|
11702
|
-
if (channel === undefined
|
|
11703
|
-
|
|
12146
|
+
if (channel === undefined) {
|
|
12147
|
+
if (workspace === undefined) {
|
|
12148
|
+
return;
|
|
12149
|
+
}
|
|
12150
|
+
return { workspaceSlug: workspace };
|
|
11704
12151
|
}
|
|
11705
|
-
return channel
|
|
12152
|
+
return channel.includes("/") ? parseChannelPath(channel) : {
|
|
11706
12153
|
workspaceSlug: workspace ?? required(values, "workspace"),
|
|
11707
|
-
channelSlug: channel
|
|
12154
|
+
channelSlug: channel
|
|
11708
12155
|
};
|
|
11709
12156
|
}
|
|
11710
12157
|
function parseChannelPath(channel) {
|
|
@@ -11717,6 +12164,12 @@ function parseChannelPath(channel) {
|
|
|
11717
12164
|
channelSlug: channelSlug.trim()
|
|
11718
12165
|
};
|
|
11719
12166
|
}
|
|
12167
|
+
function withLocalMachineId(options) {
|
|
12168
|
+
return {
|
|
12169
|
+
...options,
|
|
12170
|
+
machineId: ensureLocalMachineId()
|
|
12171
|
+
};
|
|
12172
|
+
}
|
|
11720
12173
|
function required(values, key) {
|
|
11721
12174
|
const value = stringValue3(values, key);
|
|
11722
12175
|
if (value === undefined) {
|
|
@@ -11773,19 +12226,19 @@ Usage:
|
|
|
11773
12226
|
linzumi commander <folder> [options]
|
|
11774
12227
|
linzumi start <folder> [options]
|
|
11775
12228
|
linzumi paths list|add|remove [path]
|
|
11776
|
-
linzumi connect --
|
|
12229
|
+
linzumi connect --workspace <slug> [--channel <slug>] [options]
|
|
11777
12230
|
linzumi auth --linzumi-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
11778
12231
|
|
|
11779
|
-
|
|
12232
|
+
Connection:
|
|
11780
12233
|
--linzumi-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
11781
12234
|
(deprecated alias: --kandan-url)
|
|
11782
12235
|
--token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
|
|
11783
12236
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
11784
12237
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
11785
12238
|
|
|
11786
|
-
|
|
12239
|
+
Workspace and optional channel binding:
|
|
11787
12240
|
--workspace <slug> Workspace slug
|
|
11788
|
-
--channel <slug|w/c>
|
|
12241
|
+
--channel <slug|w/c> Optional channel slug, or workspace/channel
|
|
11789
12242
|
--linzumi-thread-id <uuid> Resume an existing Linzumi thread instead of announcing a new root
|
|
11790
12243
|
(deprecated alias: --kandan-thread-id)
|
|
11791
12244
|
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
@@ -11818,8 +12271,9 @@ Examples:
|
|
|
11818
12271
|
linzumi commander daemon --runner-id launch-commander
|
|
11819
12272
|
linzumi start ~/
|
|
11820
12273
|
linzumi start ~/code/my-app
|
|
11821
|
-
linzumi connect --workspace <your-workspace> --
|
|
11822
|
-
linzumi connect --workspace <your-workspace> --
|
|
12274
|
+
linzumi connect --workspace <your-workspace> --launch-tui
|
|
12275
|
+
linzumi connect --workspace <your-workspace> --model gpt-5 --reasoning-effort low --fast --launch-tui
|
|
12276
|
+
linzumi connect --workspace <your-workspace> --channel <your-channel>
|
|
11823
12277
|
linzumi auth --workspace <your-workspace> --channel <your-channel>
|
|
11824
12278
|
linzumi paths add ~/code/my-app
|
|
11825
12279
|
linzumi paths list
|
|
@@ -11829,9 +12283,9 @@ Examples:
|
|
|
11829
12283
|
Missing --listen-user and authenticated user is unavailable.
|
|
11830
12284
|
linzumi connect --token "$TOKEN" --listen-users sean
|
|
11831
12285
|
Invalid flag: use --listen-user.
|
|
11832
|
-
linzumi connect --workspace <your-workspace> --
|
|
12286
|
+
linzumi connect --workspace <your-workspace> --allowed-cwd /does/not/exist
|
|
11833
12287
|
Invalid --allowed-cwd: allowed cwd roots must exist locally.
|
|
11834
|
-
linzumi connect --workspace <your-workspace> --
|
|
12288
|
+
linzumi connect --workspace <your-workspace> --forward-port vite
|
|
11835
12289
|
Invalid --forward-port: value must be a TCP port from 1 to 65535.
|
|
11836
12290
|
`;
|
|
11837
12291
|
}
|
|
@@ -11989,7 +12443,7 @@ space, persists this folder to your trusted-paths list, and starts this
|
|
|
11989
12443
|
computer as a local Codex runner.
|
|
11990
12444
|
|
|
11991
12445
|
Advanced (when you already know your workspace and channel):
|
|
11992
|
-
linzumi connect --workspace <your-workspace>
|
|
12446
|
+
linzumi connect --workspace <your-workspace>
|
|
11993
12447
|
|
|
11994
12448
|
For help:
|
|
11995
12449
|
linzumi connect --help
|
package/package.json
CHANGED