@linzumi/cli 0.0.41-beta → 0.0.42-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 +595 -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":
|
|
@@ -3611,7 +3631,7 @@ async function handleCodexServerRequest(args, state, payloadContext, request) {
|
|
|
3611
3631
|
throw new Error(message);
|
|
3612
3632
|
}
|
|
3613
3633
|
function codexApprovalRequestCanAutoAccept(settings, method) {
|
|
3614
|
-
return settings.approvalPolicy === "never" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3634
|
+
return settings.approvalPolicy === "never" && settings.sandbox === "danger-full-access" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3615
3635
|
}
|
|
3616
3636
|
function codexApprovalRequestCanSurface(method) {
|
|
3617
3637
|
return method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval";
|
|
@@ -5132,20 +5152,27 @@ function runtimeSettingsFromOptions(options) {
|
|
|
5132
5152
|
return {
|
|
5133
5153
|
model: options.channelSession.model,
|
|
5134
5154
|
reasoningEffort: options.channelSession.reasoningEffort,
|
|
5135
|
-
approvalPolicy: options.channelSession.approvalPolicy,
|
|
5155
|
+
approvalPolicy: codexApprovalPolicySetting(options.channelSession.approvalPolicy, options.channelSession.sandbox),
|
|
5136
5156
|
sandbox: options.channelSession.sandbox,
|
|
5137
5157
|
fast: options.fast
|
|
5138
5158
|
};
|
|
5139
5159
|
}
|
|
5140
5160
|
function mergeRuntimeSettings(current, update) {
|
|
5161
|
+
const sandbox = mergeOptionalStringRuntimeSetting(current.sandbox, update, "sandbox");
|
|
5141
5162
|
return {
|
|
5142
5163
|
model: mergeOptionalStringRuntimeSetting(current.model, update, "model"),
|
|
5143
5164
|
reasoningEffort: mergeOptionalStringRuntimeSetting(current.reasoningEffort, update, "reasoningEffort"),
|
|
5144
|
-
approvalPolicy:
|
|
5145
|
-
sandbox
|
|
5165
|
+
approvalPolicy: mergeOptionalApprovalPolicyRuntimeSetting(current.approvalPolicy, update, sandbox),
|
|
5166
|
+
sandbox,
|
|
5146
5167
|
fast: update.fast ?? current.fast
|
|
5147
5168
|
};
|
|
5148
5169
|
}
|
|
5170
|
+
function mergeOptionalApprovalPolicyRuntimeSetting(current, update, sandbox) {
|
|
5171
|
+
if (Object.prototype.hasOwnProperty.call(update, "approvalPolicy")) {
|
|
5172
|
+
return codexApprovalPolicySetting(update.approvalPolicy ?? undefined, sandbox);
|
|
5173
|
+
}
|
|
5174
|
+
return codexApprovalPolicySetting(current, sandbox);
|
|
5175
|
+
}
|
|
5149
5176
|
function mergeOptionalStringRuntimeSetting(current, update, key) {
|
|
5150
5177
|
if (Object.prototype.hasOwnProperty.call(update, key)) {
|
|
5151
5178
|
return update[key] ?? undefined;
|
|
@@ -7947,6 +7974,356 @@ function waitForOpen2(websocket) {
|
|
|
7947
7974
|
});
|
|
7948
7975
|
}
|
|
7949
7976
|
|
|
7977
|
+
// src/runnerLock.ts
|
|
7978
|
+
import {
|
|
7979
|
+
closeSync,
|
|
7980
|
+
existsSync as existsSync5,
|
|
7981
|
+
mkdirSync as mkdirSync6,
|
|
7982
|
+
openSync as openSync2,
|
|
7983
|
+
readFileSync as readFileSync4,
|
|
7984
|
+
unlinkSync as unlinkSync2,
|
|
7985
|
+
writeSync
|
|
7986
|
+
} from "node:fs";
|
|
7987
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
7988
|
+
|
|
7989
|
+
// src/localConfig.ts
|
|
7990
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7991
|
+
import {
|
|
7992
|
+
existsSync as existsSync4,
|
|
7993
|
+
linkSync,
|
|
7994
|
+
mkdirSync as mkdirSync5,
|
|
7995
|
+
readFileSync as readFileSync3,
|
|
7996
|
+
realpathSync as realpathSync4,
|
|
7997
|
+
unlinkSync,
|
|
7998
|
+
writeFileSync as writeFileSync4
|
|
7999
|
+
} from "node:fs";
|
|
8000
|
+
import { homedir as homedir5 } from "node:os";
|
|
8001
|
+
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
|
|
8002
|
+
function localConfigPath(env = process.env) {
|
|
8003
|
+
const override = env.LINZUMI_CONFIG_FILE;
|
|
8004
|
+
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
|
|
8005
|
+
}
|
|
8006
|
+
function readLocalConfig(path = localConfigPath()) {
|
|
8007
|
+
if (!existsSync4(path)) {
|
|
8008
|
+
return { version: 1, allowedCwds: [] };
|
|
8009
|
+
}
|
|
8010
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
8011
|
+
if (!isConfigPayload(parsed)) {
|
|
8012
|
+
throw new Error(`invalid Linzumi config: ${path}`);
|
|
8013
|
+
}
|
|
8014
|
+
const allowedCwds = uniqueStrings(parsed.allowedCwds);
|
|
8015
|
+
return parsed.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: parsed.machineId, allowedCwds };
|
|
8016
|
+
}
|
|
8017
|
+
function ensureLocalMachineId(path = localConfigPath(), createMachineId = randomUUID2) {
|
|
8018
|
+
const config = readLocalConfig(path);
|
|
8019
|
+
if (config.machineId !== undefined) {
|
|
8020
|
+
return config.machineId;
|
|
8021
|
+
}
|
|
8022
|
+
const machineId = ensureLocalMachineIdSeed(path, createMachineId);
|
|
8023
|
+
const latestConfig = readLocalConfig(path);
|
|
8024
|
+
const latestMachineId = latestConfig.machineId;
|
|
8025
|
+
if (latestMachineId !== undefined) {
|
|
8026
|
+
return latestMachineId;
|
|
8027
|
+
}
|
|
8028
|
+
writeLocalConfig({ ...latestConfig, machineId }, path);
|
|
8029
|
+
return machineId;
|
|
8030
|
+
}
|
|
8031
|
+
function localMachineIdSeedPath(configPath = localConfigPath()) {
|
|
8032
|
+
return join6(dirname4(configPath), `${basename4(configPath)}.machine-id`);
|
|
8033
|
+
}
|
|
8034
|
+
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
8035
|
+
const allowedCwds = [];
|
|
8036
|
+
const missingAllowedCwds = [];
|
|
8037
|
+
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
8038
|
+
const absolutePath = resolve5(expandUserPath(cwd));
|
|
8039
|
+
try {
|
|
8040
|
+
const realPath = realpathSync4(absolutePath);
|
|
8041
|
+
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
8042
|
+
} catch (error) {
|
|
8043
|
+
if (isMissingPathError(error)) {
|
|
8044
|
+
missingAllowedCwds.push(absolutePath);
|
|
8045
|
+
continue;
|
|
8046
|
+
}
|
|
8047
|
+
throw error;
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
return {
|
|
8051
|
+
allowedCwds: uniqueStrings(allowedCwds),
|
|
8052
|
+
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
8053
|
+
};
|
|
8054
|
+
}
|
|
8055
|
+
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8056
|
+
const normalizedPath = realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8057
|
+
const config = readLocalConfig(path);
|
|
8058
|
+
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
8059
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8060
|
+
return allowedCwds;
|
|
8061
|
+
}
|
|
8062
|
+
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8063
|
+
const requestedPath = resolve5(expandUserPath(pathValue));
|
|
8064
|
+
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
8065
|
+
const config = readLocalConfig(path);
|
|
8066
|
+
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
8067
|
+
const normalizedExisting = realpathOrResolved(cwd);
|
|
8068
|
+
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
8069
|
+
});
|
|
8070
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8071
|
+
return allowedCwds;
|
|
8072
|
+
}
|
|
8073
|
+
function writeLocalConfig(config, path = localConfigPath()) {
|
|
8074
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
8075
|
+
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
8076
|
+
`, "utf8");
|
|
8077
|
+
}
|
|
8078
|
+
function isConfigPayload(value) {
|
|
8079
|
+
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() !== "");
|
|
8080
|
+
}
|
|
8081
|
+
function machineIdValid(value) {
|
|
8082
|
+
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);
|
|
8083
|
+
}
|
|
8084
|
+
function ensureLocalMachineIdSeed(configPath, createMachineId) {
|
|
8085
|
+
const seedPath = localMachineIdSeedPath(configPath);
|
|
8086
|
+
if (existsSync4(seedPath)) {
|
|
8087
|
+
return readMachineIdSeed(seedPath);
|
|
8088
|
+
}
|
|
8089
|
+
const machineId = createMachineId();
|
|
8090
|
+
if (!machineIdValid(machineId)) {
|
|
8091
|
+
throw new Error(`invalid generated Linzumi machine id: ${machineId}`);
|
|
8092
|
+
}
|
|
8093
|
+
mkdirSync5(dirname4(seedPath), { recursive: true });
|
|
8094
|
+
const tempPath = join6(dirname4(seedPath), `.${basename4(seedPath)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
8095
|
+
writeFileSync4(tempPath, `${machineId}
|
|
8096
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
8097
|
+
try {
|
|
8098
|
+
linkSync(tempPath, seedPath);
|
|
8099
|
+
return machineId;
|
|
8100
|
+
} catch (error) {
|
|
8101
|
+
if (isNodeErrorCode(error, "EEXIST")) {
|
|
8102
|
+
return readMachineIdSeed(seedPath);
|
|
8103
|
+
}
|
|
8104
|
+
throw error;
|
|
8105
|
+
} finally {
|
|
8106
|
+
unlinkSync(tempPath);
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
function readMachineIdSeed(seedPath) {
|
|
8110
|
+
const machineId = readFileSync3(seedPath, "utf8").trim();
|
|
8111
|
+
if (!machineIdValid(machineId)) {
|
|
8112
|
+
throw new Error(`invalid Linzumi machine id seed: ${seedPath}`);
|
|
8113
|
+
}
|
|
8114
|
+
return machineId;
|
|
8115
|
+
}
|
|
8116
|
+
function uniqueStrings(values) {
|
|
8117
|
+
return [
|
|
8118
|
+
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
8119
|
+
];
|
|
8120
|
+
}
|
|
8121
|
+
function isMissingPathError(error) {
|
|
8122
|
+
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
8123
|
+
}
|
|
8124
|
+
function isNodeErrorCode(error, code) {
|
|
8125
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8126
|
+
}
|
|
8127
|
+
function realpathOrResolved(pathValue) {
|
|
8128
|
+
try {
|
|
8129
|
+
return realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8130
|
+
} catch (_error) {
|
|
8131
|
+
return resolve5(expandUserPath(pathValue));
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
|
|
8135
|
+
// src/version.ts
|
|
8136
|
+
var linzumiCliVersion = "0.0.42-beta";
|
|
8137
|
+
var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
8138
|
+
|
|
8139
|
+
// src/runnerLock.ts
|
|
8140
|
+
function runnerLockPath(machineId, configPath = localConfigPath()) {
|
|
8141
|
+
return join7(dirname5(configPath), "runners", `${encodeURIComponent(machineId)}.lock`);
|
|
8142
|
+
}
|
|
8143
|
+
function acquireRunnerLock(options) {
|
|
8144
|
+
const path = runnerLockPath(options.machineId, options.configPath);
|
|
8145
|
+
const isPidAlive = options.isPidAlive ?? processIsAlive;
|
|
8146
|
+
const record = {
|
|
8147
|
+
version: 1,
|
|
8148
|
+
machineId: options.machineId,
|
|
8149
|
+
runnerId: options.runnerId,
|
|
8150
|
+
pid: options.pid ?? process.pid,
|
|
8151
|
+
cwd: options.cwd,
|
|
8152
|
+
workspace: options.workspace,
|
|
8153
|
+
startedAt: (options.now ?? (() => new Date))().toISOString(),
|
|
8154
|
+
cliVersion: options.cliVersion ?? linzumiCliVersion
|
|
8155
|
+
};
|
|
8156
|
+
writeLockOrHandleExisting(path, record, isPidAlive, options.beforeReadExistingLock, options.beforeReplaceStaleLock);
|
|
8157
|
+
return {
|
|
8158
|
+
path,
|
|
8159
|
+
record,
|
|
8160
|
+
release: () => releaseRunnerLock(path, record)
|
|
8161
|
+
};
|
|
8162
|
+
}
|
|
8163
|
+
function writeLockOrHandleExisting(path, record, isPidAlive, beforeReadExistingLock, beforeReplaceStaleLock) {
|
|
8164
|
+
if (tryCreateLock(path, record)) {
|
|
8165
|
+
return;
|
|
8166
|
+
}
|
|
8167
|
+
beforeReadExistingLock?.();
|
|
8168
|
+
const existing = readRunnerLockIfPresent(path);
|
|
8169
|
+
if (existing === undefined) {
|
|
8170
|
+
if (tryCreateLock(path, record)) {
|
|
8171
|
+
return;
|
|
8172
|
+
}
|
|
8173
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8174
|
+
}
|
|
8175
|
+
if (isPidAlive(existing.pid)) {
|
|
8176
|
+
throw new Error(activeRunnerLockMessage(path, existing));
|
|
8177
|
+
}
|
|
8178
|
+
beforeReplaceStaleLock?.();
|
|
8179
|
+
withStaleReplacementLock(path, isPidAlive, () => {
|
|
8180
|
+
const latest = existsSync5(path) ? readRunnerLock(path) : undefined;
|
|
8181
|
+
if (latest !== undefined && isPidAlive(latest.pid)) {
|
|
8182
|
+
throw new Error(activeRunnerLockMessage(path, latest));
|
|
8183
|
+
}
|
|
8184
|
+
if (latest !== undefined) {
|
|
8185
|
+
unlinkSync2(path);
|
|
8186
|
+
}
|
|
8187
|
+
if (!tryCreateLock(path, record)) {
|
|
8188
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8189
|
+
}
|
|
8190
|
+
});
|
|
8191
|
+
}
|
|
8192
|
+
function tryCreateLock(path, record) {
|
|
8193
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
8194
|
+
try {
|
|
8195
|
+
const fd = openSync2(path, "wx");
|
|
8196
|
+
try {
|
|
8197
|
+
writeSync(fd, `${JSON.stringify(record, null, 2)}
|
|
8198
|
+
`);
|
|
8199
|
+
} finally {
|
|
8200
|
+
closeSync(fd);
|
|
8201
|
+
}
|
|
8202
|
+
return true;
|
|
8203
|
+
} catch (error) {
|
|
8204
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8205
|
+
return false;
|
|
8206
|
+
}
|
|
8207
|
+
throw error;
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
function withStaleReplacementLock(path, isPidAlive, callback) {
|
|
8211
|
+
const replacementPath = `${path}.replace`;
|
|
8212
|
+
while (true) {
|
|
8213
|
+
try {
|
|
8214
|
+
const fd = openSync2(replacementPath, "wx");
|
|
8215
|
+
try {
|
|
8216
|
+
writeSync(fd, `${process.pid}
|
|
8217
|
+
`);
|
|
8218
|
+
callback();
|
|
8219
|
+
} finally {
|
|
8220
|
+
closeSync(fd);
|
|
8221
|
+
unlinkSync2(replacementPath);
|
|
8222
|
+
}
|
|
8223
|
+
return;
|
|
8224
|
+
} catch (error) {
|
|
8225
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8226
|
+
const replacementPid = readReplacementLockPidIfPresent(replacementPath);
|
|
8227
|
+
if (replacementPid === undefined) {
|
|
8228
|
+
continue;
|
|
8229
|
+
}
|
|
8230
|
+
if (isPidAlive(replacementPid)) {
|
|
8231
|
+
throw new Error([
|
|
8232
|
+
"another Linzumi runner is already replacing a stale runner lock",
|
|
8233
|
+
`lock: ${path}`,
|
|
8234
|
+
"Wait for that startup to finish, then retry."
|
|
8235
|
+
].join(`
|
|
8236
|
+
`));
|
|
8237
|
+
}
|
|
8238
|
+
unlinkSync2(replacementPath);
|
|
8239
|
+
continue;
|
|
8240
|
+
}
|
|
8241
|
+
throw error;
|
|
8242
|
+
}
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
function readReplacementLockPidIfPresent(path) {
|
|
8246
|
+
let value;
|
|
8247
|
+
try {
|
|
8248
|
+
value = readFileSync4(path, "utf8").trim();
|
|
8249
|
+
} catch (error) {
|
|
8250
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8251
|
+
return;
|
|
8252
|
+
}
|
|
8253
|
+
throw error;
|
|
8254
|
+
}
|
|
8255
|
+
const pid = Number.parseInt(value, 10);
|
|
8256
|
+
if (pid.toString() !== value || pid <= 0) {
|
|
8257
|
+
throw new Error(`invalid Linzumi runner replacement lock: ${path}`);
|
|
8258
|
+
}
|
|
8259
|
+
return pid;
|
|
8260
|
+
}
|
|
8261
|
+
function releaseRunnerLock(path, record) {
|
|
8262
|
+
const current = readRunnerLockForRelease(path);
|
|
8263
|
+
if (current !== undefined && current.machineId === record.machineId && current.runnerId === record.runnerId && current.pid === record.pid) {
|
|
8264
|
+
unlinkSync2(path);
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
function readRunnerLockForRelease(path) {
|
|
8268
|
+
try {
|
|
8269
|
+
return readRunnerLockIfPresent(path);
|
|
8270
|
+
} catch (_error) {
|
|
8271
|
+
return;
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
function readRunnerLockIfPresent(path) {
|
|
8275
|
+
if (!existsSync5(path)) {
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8278
|
+
try {
|
|
8279
|
+
return readRunnerLock(path);
|
|
8280
|
+
} catch (error) {
|
|
8281
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8282
|
+
return;
|
|
8283
|
+
}
|
|
8284
|
+
throw error;
|
|
8285
|
+
}
|
|
8286
|
+
}
|
|
8287
|
+
function readRunnerLock(path) {
|
|
8288
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
8289
|
+
if (!isRunnerLockRecord(parsed)) {
|
|
8290
|
+
throw new Error(`invalid Linzumi runner lock: ${path}`);
|
|
8291
|
+
}
|
|
8292
|
+
return parsed;
|
|
8293
|
+
}
|
|
8294
|
+
function isRunnerLockRecord(value) {
|
|
8295
|
+
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() !== "";
|
|
8296
|
+
}
|
|
8297
|
+
function workspaceValid(value) {
|
|
8298
|
+
return value === null || typeof value === "string" && value.trim() !== "";
|
|
8299
|
+
}
|
|
8300
|
+
function processIsAlive(pid) {
|
|
8301
|
+
try {
|
|
8302
|
+
process.kill(pid, 0);
|
|
8303
|
+
return true;
|
|
8304
|
+
} catch (error) {
|
|
8305
|
+
return isNodeErrorCode2(error, "ESRCH") ? false : true;
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
function activeRunnerLockMessage(path, record) {
|
|
8309
|
+
const workspace = record.workspace === null ? "workspace: unknown" : `workspace: ${record.workspace}`;
|
|
8310
|
+
return [
|
|
8311
|
+
"another Linzumi runner is already running for this machine",
|
|
8312
|
+
`runner id: ${record.runnerId}`,
|
|
8313
|
+
`pid: ${record.pid}`,
|
|
8314
|
+
`cwd: ${record.cwd}`,
|
|
8315
|
+
workspace,
|
|
8316
|
+
`CLI version: ${record.cliVersion}`,
|
|
8317
|
+
`started at: ${record.startedAt}`,
|
|
8318
|
+
`lock: ${path}`,
|
|
8319
|
+
"Stop that process first, then retry. If the process has already exited, remove the stale lock file and retry."
|
|
8320
|
+
].join(`
|
|
8321
|
+
`);
|
|
8322
|
+
}
|
|
8323
|
+
function isNodeErrorCode2(error, code) {
|
|
8324
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8325
|
+
}
|
|
8326
|
+
|
|
7950
8327
|
// src/runnerConsoleReporter.ts
|
|
7951
8328
|
function reportRunnerConsoleEvent(event, payload) {
|
|
7952
8329
|
const line = formatRunnerConsoleEvent(event, payload);
|
|
@@ -7958,7 +8335,15 @@ function reportRunnerConsoleEvent(event, payload) {
|
|
|
7958
8335
|
function formatRunnerConsoleEvent(event, payload) {
|
|
7959
8336
|
switch (event) {
|
|
7960
8337
|
case "runner.instance_started":
|
|
7961
|
-
return
|
|
8338
|
+
return connectedRunnerMessage(payload);
|
|
8339
|
+
case "runner.replaced":
|
|
8340
|
+
return [
|
|
8341
|
+
"Runner replaced: another Linzumi CLI connected from this machine.",
|
|
8342
|
+
`New runner: ${text(payload.replacementRunnerId)}`,
|
|
8343
|
+
`Version: ${text(payload.replacementVersion)}`,
|
|
8344
|
+
"This process is exiting."
|
|
8345
|
+
].join(`
|
|
8346
|
+
`);
|
|
7962
8347
|
case "kandan.message_ignored":
|
|
7963
8348
|
return `Incoming message from ${sender(payload)}: ignored for reason ${text(payload.reason)}`;
|
|
7964
8349
|
case "kandan.message_queued":
|
|
@@ -7993,6 +8378,40 @@ function formatRunnerConsoleEvent(event, payload) {
|
|
|
7993
8378
|
return;
|
|
7994
8379
|
}
|
|
7995
8380
|
}
|
|
8381
|
+
function connectedRunnerMessage(payload) {
|
|
8382
|
+
return [
|
|
8383
|
+
"Connected to Linzumi",
|
|
8384
|
+
optionalLine("Computer", payload.hostname),
|
|
8385
|
+
optionalLine("Workspace", payload.workspace),
|
|
8386
|
+
`Runner: ${text(payload.runnerId)}`,
|
|
8387
|
+
`CLI: ${text(payload.version)}`,
|
|
8388
|
+
optionalLine("Codex", payload.codexUrl),
|
|
8389
|
+
...replacementLines(payload.replacedRunners)
|
|
8390
|
+
].filter((line) => line !== undefined).join(`
|
|
8391
|
+
`);
|
|
8392
|
+
}
|
|
8393
|
+
function optionalLine(label, value) {
|
|
8394
|
+
const normalized = stringValue2(value) ?? numberValue(value)?.toString();
|
|
8395
|
+
return normalized === undefined ? undefined : `${label}: ${normalized}`;
|
|
8396
|
+
}
|
|
8397
|
+
function replacementLines(value) {
|
|
8398
|
+
if (!Array.isArray(value)) {
|
|
8399
|
+
return [];
|
|
8400
|
+
}
|
|
8401
|
+
return value.flatMap((entry) => {
|
|
8402
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
8403
|
+
return [];
|
|
8404
|
+
}
|
|
8405
|
+
const record = entry;
|
|
8406
|
+
const runnerId = stringValue2(record.runnerId);
|
|
8407
|
+
if (runnerId === undefined) {
|
|
8408
|
+
return [];
|
|
8409
|
+
}
|
|
8410
|
+
const version = stringValue2(record.version);
|
|
8411
|
+
const suffix = version === undefined ? "" : ` (CLI ${version})`;
|
|
8412
|
+
return [`Replaced older runner from this machine: ${runnerId}${suffix}`];
|
|
8413
|
+
});
|
|
8414
|
+
}
|
|
7996
8415
|
function sender(payload) {
|
|
7997
8416
|
const slug = stringValue2(payload.actor_slug);
|
|
7998
8417
|
const userId = numberValue(payload.actor_user_id);
|
|
@@ -8056,6 +8475,21 @@ async function runLocalCodexRunner(options) {
|
|
|
8056
8475
|
kandanUrl: options.kandanUrl
|
|
8057
8476
|
});
|
|
8058
8477
|
try {
|
|
8478
|
+
if (options.machineId !== undefined) {
|
|
8479
|
+
const runnerLock = acquireRunnerLock({
|
|
8480
|
+
machineId: options.machineId,
|
|
8481
|
+
runnerId: options.runnerId,
|
|
8482
|
+
cwd: options.cwd,
|
|
8483
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8484
|
+
configPath: options.runnerLockConfigPath
|
|
8485
|
+
});
|
|
8486
|
+
cleanup.actions.push(() => runnerLock.release());
|
|
8487
|
+
log("runner.lock_acquired", {
|
|
8488
|
+
path: runnerLock.path,
|
|
8489
|
+
machineId: options.machineId,
|
|
8490
|
+
runnerId: options.runnerId
|
|
8491
|
+
});
|
|
8492
|
+
}
|
|
8059
8493
|
return await openLocalCodexRunner(options, log, cleanup, close);
|
|
8060
8494
|
} catch (error) {
|
|
8061
8495
|
await close().catch(() => {
|
|
@@ -8123,9 +8557,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8123
8557
|
const kandan = await connectPhoenixClient(options.kandanUrl, options.token, options.socketFactory);
|
|
8124
8558
|
cleanup.actions.push(() => kandan.close());
|
|
8125
8559
|
const topic = `local_runner:${options.runnerId}`;
|
|
8560
|
+
const clientId = options.machineId ?? options.runnerId;
|
|
8126
8561
|
const joinPayload = () => ({
|
|
8127
8562
|
clientName: "kandan-local-codex-runner",
|
|
8128
|
-
|
|
8563
|
+
clientId,
|
|
8564
|
+
version: linzumiCliVersion,
|
|
8129
8565
|
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8130
8566
|
channel: options.channelSession?.channelSlug ?? null,
|
|
8131
8567
|
capabilities: capabilitiesPayload()
|
|
@@ -8140,7 +8576,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8140
8576
|
}
|
|
8141
8577
|
dispatcher(control);
|
|
8142
8578
|
});
|
|
8143
|
-
await kandan.join(topic, joinPayload(), {
|
|
8579
|
+
const joinResponse = await kandan.join(topic, joinPayload(), {
|
|
8580
|
+
rejoinPayload: joinPayload
|
|
8581
|
+
});
|
|
8582
|
+
const replacedRunners = replacementRunnerSummaries(objectValue(joinResponse)?.replaced_runners);
|
|
8144
8583
|
const started = options.codexUrl === undefined ? await startOwnedCodexAppServer(options) : undefined;
|
|
8145
8584
|
if (started !== undefined) {
|
|
8146
8585
|
cleanup.actions.push(() => {
|
|
@@ -8151,7 +8590,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8151
8590
|
if (codexUrl === undefined) {
|
|
8152
8591
|
throw new Error("missing codex app-server websocket URL");
|
|
8153
8592
|
}
|
|
8154
|
-
const instanceId = `codex-${
|
|
8593
|
+
const instanceId = `codex-${randomUUID3()}`;
|
|
8155
8594
|
const publishLocalEditorStatus = (payload) => {
|
|
8156
8595
|
kandan.push(topic, "local_editor_status", payload).catch((error) => {
|
|
8157
8596
|
log("kandan.local_editor_status_push_failed", {
|
|
@@ -8199,6 +8638,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8199
8638
|
const runtimeDefaults = runnerRuntimeDefaults(options);
|
|
8200
8639
|
const instancePayload = {
|
|
8201
8640
|
instanceId,
|
|
8641
|
+
clientId,
|
|
8202
8642
|
codexUrl,
|
|
8203
8643
|
tuiLaunched: options.launchTui,
|
|
8204
8644
|
cwd: options.cwd,
|
|
@@ -8211,7 +8651,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8211
8651
|
fast: options.fast ?? false
|
|
8212
8652
|
};
|
|
8213
8653
|
await kandan.push(topic, "instance_started", instancePayload);
|
|
8214
|
-
log("runner.instance_started", {
|
|
8654
|
+
log("runner.instance_started", {
|
|
8655
|
+
runnerId: options.runnerId,
|
|
8656
|
+
hostname: runnerHost,
|
|
8657
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8658
|
+
version: linzumiCliVersion,
|
|
8659
|
+
instanceId,
|
|
8660
|
+
codexUrl,
|
|
8661
|
+
replacedRunners
|
|
8662
|
+
});
|
|
8215
8663
|
const channelSession = options.channelSession === undefined ? undefined : await attachChannelSession({
|
|
8216
8664
|
kandan,
|
|
8217
8665
|
codex,
|
|
@@ -8312,6 +8760,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8312
8760
|
};
|
|
8313
8761
|
const heartbeatPayload = () => ({
|
|
8314
8762
|
instanceId,
|
|
8763
|
+
clientId,
|
|
8315
8764
|
codexUrl,
|
|
8316
8765
|
cwd: options.cwd,
|
|
8317
8766
|
hostname: runnerHost,
|
|
@@ -8390,6 +8839,20 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8390
8839
|
});
|
|
8391
8840
|
const handleControl = (control) => {
|
|
8392
8841
|
log("kandan.control", { control });
|
|
8842
|
+
if (control.type === "replace_runner") {
|
|
8843
|
+
log("runner.replaced", {
|
|
8844
|
+
runnerId: options.runnerId,
|
|
8845
|
+
reason: control.reason,
|
|
8846
|
+
replacementRunnerId: control.replacementRunnerId,
|
|
8847
|
+
replacementVersion: control.replacementVersion
|
|
8848
|
+
});
|
|
8849
|
+
close().catch((error) => {
|
|
8850
|
+
log("runner.replace_close_failed", {
|
|
8851
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8852
|
+
});
|
|
8853
|
+
});
|
|
8854
|
+
return;
|
|
8855
|
+
}
|
|
8393
8856
|
if (!controlTargetsInstance(control, instanceId)) {
|
|
8394
8857
|
log("kandan.control_ignored", {
|
|
8395
8858
|
reason: "instance_id_mismatch",
|
|
@@ -8604,8 +9067,24 @@ function normalizedWorkDescription(value) {
|
|
|
8604
9067
|
const normalized = value?.trim();
|
|
8605
9068
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
8606
9069
|
}
|
|
9070
|
+
function replacementRunnerSummaries(value) {
|
|
9071
|
+
const entries = arrayValue(value) ?? [];
|
|
9072
|
+
return entries.flatMap((entry) => {
|
|
9073
|
+
const record = objectValue(entry);
|
|
9074
|
+
const runnerId = stringValue(record?.runnerId);
|
|
9075
|
+
if (runnerId === undefined) {
|
|
9076
|
+
return [];
|
|
9077
|
+
}
|
|
9078
|
+
return [
|
|
9079
|
+
{
|
|
9080
|
+
runnerId,
|
|
9081
|
+
version: stringValue(record?.version) ?? null
|
|
9082
|
+
}
|
|
9083
|
+
];
|
|
9084
|
+
});
|
|
9085
|
+
}
|
|
8607
9086
|
function makeRunnerLogger(options) {
|
|
8608
|
-
return createRunnerLogger(options.logFile ??
|
|
9087
|
+
return createRunnerLogger(options.logFile ?? join8(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
|
|
8609
9088
|
}
|
|
8610
9089
|
function installCleanupHandlers(close) {
|
|
8611
9090
|
const closeAndExit = () => {
|
|
@@ -9054,11 +9533,12 @@ function optionalThreadControlField(control, field) {
|
|
|
9054
9533
|
}
|
|
9055
9534
|
function startInstanceRuntimeSettings(options, control) {
|
|
9056
9535
|
const defaults = runnerRuntimeDefaults(options);
|
|
9536
|
+
const sandbox = control.sandbox ?? defaults.sandbox;
|
|
9057
9537
|
return {
|
|
9058
9538
|
model: control.model ?? defaults.model,
|
|
9059
9539
|
reasoningEffort: control.reasoningEffort ?? defaults.reasoningEffort,
|
|
9060
|
-
approvalPolicy: control.approvalPolicy ?? defaults.approvalPolicy,
|
|
9061
|
-
sandbox
|
|
9540
|
+
approvalPolicy: codexApprovalPolicyForRequest(control.approvalPolicy ?? defaults.approvalPolicy, sandbox),
|
|
9541
|
+
sandbox,
|
|
9062
9542
|
fast: control.fast ?? options.fast
|
|
9063
9543
|
};
|
|
9064
9544
|
}
|
|
@@ -9077,11 +9557,12 @@ function runnerWorkspaceSlug(options) {
|
|
|
9077
9557
|
function runnerRuntimeDefaults(options) {
|
|
9078
9558
|
const session = options.channelSession;
|
|
9079
9559
|
const defaults = options.runtimeDefaults;
|
|
9560
|
+
const sandbox = defaults?.sandbox ?? session?.sandbox;
|
|
9080
9561
|
return {
|
|
9081
9562
|
model: defaults?.model ?? session?.model,
|
|
9082
9563
|
reasoningEffort: defaults?.reasoningEffort ?? session?.reasoningEffort,
|
|
9083
|
-
approvalPolicy: defaults?.approvalPolicy ?? session?.approvalPolicy,
|
|
9084
|
-
sandbox
|
|
9564
|
+
approvalPolicy: codexApprovalPolicySetting(defaults?.approvalPolicy ?? session?.approvalPolicy, sandbox),
|
|
9565
|
+
sandbox
|
|
9085
9566
|
};
|
|
9086
9567
|
}
|
|
9087
9568
|
function isUpdateRunnerConfigControl(control) {
|
|
@@ -9100,9 +9581,9 @@ function configuredAllowedCwds(values) {
|
|
|
9100
9581
|
const allowedCwds = [];
|
|
9101
9582
|
const missingAllowedCwds = [];
|
|
9102
9583
|
for (const value of normalizeAllowedCwds(values)) {
|
|
9103
|
-
const absolutePath =
|
|
9584
|
+
const absolutePath = resolve6(expandUserPath(value));
|
|
9104
9585
|
try {
|
|
9105
|
-
const realPath =
|
|
9586
|
+
const realPath = realpathSync5(absolutePath);
|
|
9106
9587
|
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9107
9588
|
} catch (error) {
|
|
9108
9589
|
if (isMissingAllowedCwdError(error)) {
|
|
@@ -9125,18 +9606,18 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
9125
9606
|
}
|
|
9126
9607
|
|
|
9127
9608
|
// src/authCache.ts
|
|
9128
|
-
import { existsSync as
|
|
9129
|
-
import { homedir as
|
|
9130
|
-
import { dirname as
|
|
9609
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9610
|
+
import { homedir as homedir6 } from "node:os";
|
|
9611
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
9131
9612
|
function defaultAuthFilePath() {
|
|
9132
|
-
const base = process.env.KANDAN_HOME ??
|
|
9133
|
-
return
|
|
9613
|
+
const base = process.env.KANDAN_HOME ?? join9(homedir6(), ".kandan");
|
|
9614
|
+
return join9(base, "auth.json");
|
|
9134
9615
|
}
|
|
9135
9616
|
function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
|
|
9136
|
-
if (!
|
|
9617
|
+
if (!existsSync6(authFilePath)) {
|
|
9137
9618
|
return;
|
|
9138
9619
|
}
|
|
9139
|
-
const authFile = parseAuthFile(
|
|
9620
|
+
const authFile = parseAuthFile(readFileSync5(authFilePath, "utf8"));
|
|
9140
9621
|
const kandanBaseUrl = kandanHttpBaseUrl(kandanUrl);
|
|
9141
9622
|
const entry = authFile.local_codex_runner?.[kandanBaseUrl];
|
|
9142
9623
|
if (entry === undefined || entry.access_token.trim() === "") {
|
|
@@ -9154,7 +9635,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
|
|
|
9154
9635
|
}
|
|
9155
9636
|
function writeCachedLocalRunnerToken(args) {
|
|
9156
9637
|
const authFilePath = args.authFilePath ?? defaultAuthFilePath();
|
|
9157
|
-
const existing =
|
|
9638
|
+
const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync5(authFilePath, "utf8")) : { version: 1 };
|
|
9158
9639
|
const kandanBaseUrl = kandanHttpBaseUrl(args.kandanUrl);
|
|
9159
9640
|
const issuedAt = new Date;
|
|
9160
9641
|
const expiresAt = args.expiresInSeconds === undefined ? undefined : new Date(issuedAt.getTime() + args.expiresInSeconds * 1000).toISOString();
|
|
@@ -9170,8 +9651,8 @@ function writeCachedLocalRunnerToken(args) {
|
|
|
9170
9651
|
}
|
|
9171
9652
|
}
|
|
9172
9653
|
};
|
|
9173
|
-
|
|
9174
|
-
|
|
9654
|
+
mkdirSync7(dirname6(authFilePath), { recursive: true });
|
|
9655
|
+
writeFileSync5(authFilePath, `${JSON.stringify(next, null, 2)}
|
|
9175
9656
|
`, "utf8");
|
|
9176
9657
|
return {
|
|
9177
9658
|
accessToken: args.accessToken,
|
|
@@ -9263,102 +9744,12 @@ async function acquireAndCacheToken(args) {
|
|
|
9263
9744
|
return token.accessToken;
|
|
9264
9745
|
}
|
|
9265
9746
|
|
|
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
9747
|
// src/defaultUrls.ts
|
|
9357
9748
|
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
9358
9749
|
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
9359
9750
|
|
|
9360
9751
|
// src/kandanTls.ts
|
|
9361
|
-
import { existsSync as
|
|
9752
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
9362
9753
|
import { Agent } from "undici";
|
|
9363
9754
|
import { WebSocket as WsWebSocket } from "ws";
|
|
9364
9755
|
function kandanTlsTrustFromEnv() {
|
|
@@ -9369,10 +9760,10 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
9369
9760
|
return;
|
|
9370
9761
|
}
|
|
9371
9762
|
const trimmed = caFile.trim();
|
|
9372
|
-
if (!
|
|
9763
|
+
if (!existsSync7(trimmed)) {
|
|
9373
9764
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
9374
9765
|
}
|
|
9375
|
-
const ca =
|
|
9766
|
+
const ca = readFileSync6(trimmed, "utf8");
|
|
9376
9767
|
return {
|
|
9377
9768
|
caFile: trimmed,
|
|
9378
9769
|
ca,
|
|
@@ -9401,8 +9792,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
9401
9792
|
}
|
|
9402
9793
|
|
|
9403
9794
|
// src/agentBootstrap.ts
|
|
9404
|
-
import { existsSync as
|
|
9405
|
-
import { dirname as
|
|
9795
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9796
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
9406
9797
|
import { homedir as homedir7 } from "node:os";
|
|
9407
9798
|
async function runAgentCliCommand(args, deps = {
|
|
9408
9799
|
fetchImpl: fetch,
|
|
@@ -10002,7 +10393,7 @@ function agentTokenFile(flags) {
|
|
|
10002
10393
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
10003
10394
|
}
|
|
10004
10395
|
function defaultAgentTokenFilePath() {
|
|
10005
|
-
return
|
|
10396
|
+
return join10(homedir7(), ".linzumi", "agent-token.json");
|
|
10006
10397
|
}
|
|
10007
10398
|
function normalizedApiUrl(apiUrl) {
|
|
10008
10399
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -10011,10 +10402,10 @@ function authorizationHeaders(token) {
|
|
|
10011
10402
|
return { authorization: `Bearer ${token}` };
|
|
10012
10403
|
}
|
|
10013
10404
|
function readOptionalTextFile(path) {
|
|
10014
|
-
return
|
|
10405
|
+
return existsSync8(path) ? readFileSync7(path, "utf8") : undefined;
|
|
10015
10406
|
}
|
|
10016
10407
|
function writeTextFile(path, content) {
|
|
10017
|
-
|
|
10408
|
+
mkdirSync8(dirname7(path), { recursive: true });
|
|
10018
10409
|
writeFileSync6(path, content);
|
|
10019
10410
|
}
|
|
10020
10411
|
function readStoredAgentTokenFile(path, readTextFile = readOptionalTextFile) {
|
|
@@ -10091,8 +10482,8 @@ Launch target:
|
|
|
10091
10482
|
}
|
|
10092
10483
|
|
|
10093
10484
|
// src/helloLinzumiProject.ts
|
|
10094
|
-
import { existsSync as
|
|
10095
|
-
import { dirname as
|
|
10485
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10486
|
+
import { dirname as dirname8, join as join11, resolve as resolve7 } from "node:path";
|
|
10096
10487
|
import { fileURLToPath } from "node:url";
|
|
10097
10488
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
10098
10489
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -10100,8 +10491,8 @@ var defaultHelloLinzumiParentDir = "/tmp";
|
|
|
10100
10491
|
var defaultHelloLinzumiPort = 8787;
|
|
10101
10492
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
10102
10493
|
var markerFile = ".linzumi-demo-project";
|
|
10103
|
-
var moduleDir =
|
|
10104
|
-
var linzumiLogoSvg =
|
|
10494
|
+
var moduleDir = dirname8(fileURLToPath(import.meta.url));
|
|
10495
|
+
var linzumiLogoSvg = readFileSync8(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
10105
10496
|
function createHelloLinzumiProject(input = {}) {
|
|
10106
10497
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
10107
10498
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -10109,9 +10500,9 @@ function createHelloLinzumiProject(input = {}) {
|
|
|
10109
10500
|
const host = normalizeHost(options.host);
|
|
10110
10501
|
assertTcpPort(port);
|
|
10111
10502
|
assertWritableDemoRoot(root, options.reset === true);
|
|
10112
|
-
|
|
10503
|
+
mkdirSync9(join11(root, "src"), { recursive: true });
|
|
10113
10504
|
for (const file of demoFiles({ root, port, host })) {
|
|
10114
|
-
writeFileSync7(
|
|
10505
|
+
writeFileSync7(join11(root, file.path), file.content, "utf8");
|
|
10115
10506
|
}
|
|
10116
10507
|
return {
|
|
10117
10508
|
root,
|
|
@@ -10152,11 +10543,11 @@ function assertTcpPort(port) {
|
|
|
10152
10543
|
throw new Error("--port must be a TCP port from 1 to 65535");
|
|
10153
10544
|
}
|
|
10154
10545
|
function assertWritableDemoRoot(root, reset) {
|
|
10155
|
-
if (!
|
|
10546
|
+
if (!existsSync9(root)) {
|
|
10156
10547
|
return;
|
|
10157
10548
|
}
|
|
10158
|
-
const markerPath =
|
|
10159
|
-
const isDemoRoot =
|
|
10549
|
+
const markerPath = join11(root, markerFile);
|
|
10550
|
+
const isDemoRoot = existsSync9(markerPath) && readFileSync8(markerPath, "utf8").trim() === "hello-linzumi";
|
|
10160
10551
|
if (isDemoRoot && reset) {
|
|
10161
10552
|
rmSync2(root, { recursive: true, force: true });
|
|
10162
10553
|
return;
|
|
@@ -10655,27 +11046,30 @@ To kick the agent off:
|
|
|
10655
11046
|
|
|
10656
11047
|
// src/commanderDaemon.ts
|
|
10657
11048
|
import {
|
|
10658
|
-
existsSync as
|
|
10659
|
-
closeSync,
|
|
10660
|
-
mkdirSync as
|
|
10661
|
-
openSync as
|
|
10662
|
-
readFileSync as
|
|
11049
|
+
existsSync as existsSync10,
|
|
11050
|
+
closeSync as closeSync2,
|
|
11051
|
+
mkdirSync as mkdirSync10,
|
|
11052
|
+
openSync as openSync3,
|
|
11053
|
+
readFileSync as readFileSync9,
|
|
10663
11054
|
watch,
|
|
10664
11055
|
writeFileSync as writeFileSync8
|
|
10665
11056
|
} from "node:fs";
|
|
10666
11057
|
import { homedir as homedir8 } from "node:os";
|
|
10667
|
-
import { dirname as
|
|
11058
|
+
import { dirname as dirname9, join as join12, resolve as resolve8 } from "node:path";
|
|
10668
11059
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
10669
11060
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10670
|
-
var
|
|
11061
|
+
var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
|
|
10671
11062
|
function commanderStatusDir() {
|
|
10672
|
-
return
|
|
11063
|
+
return join12(homedir8(), ".linzumi", "commanders");
|
|
10673
11064
|
}
|
|
10674
11065
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10675
|
-
return
|
|
11066
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
10676
11067
|
}
|
|
10677
11068
|
function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10678
|
-
return
|
|
11069
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.log`);
|
|
11070
|
+
}
|
|
11071
|
+
function commanderLogIsConnected(log) {
|
|
11072
|
+
return connectedMarkers.some((marker) => log.includes(marker));
|
|
10679
11073
|
}
|
|
10680
11074
|
function startCommanderDaemon(options) {
|
|
10681
11075
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
@@ -10693,10 +11087,10 @@ function startCommanderDaemon(options) {
|
|
|
10693
11087
|
"--log-file",
|
|
10694
11088
|
logFile
|
|
10695
11089
|
];
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
const out =
|
|
10699
|
-
const err =
|
|
11090
|
+
mkdirSync10(statusDir, { recursive: true });
|
|
11091
|
+
mkdirSync10(dirname9(logFile), { recursive: true });
|
|
11092
|
+
const out = openSync3(logFile, "a");
|
|
11093
|
+
const err = openSync3(logFile, "a");
|
|
10700
11094
|
writeCliAuditEvent("process.spawn", {
|
|
10701
11095
|
command: nodeBin,
|
|
10702
11096
|
args: command,
|
|
@@ -10715,8 +11109,8 @@ function startCommanderDaemon(options) {
|
|
|
10715
11109
|
pid: child.pid,
|
|
10716
11110
|
purpose: "commander_daemon.start"
|
|
10717
11111
|
}, { sessionId: options.runnerId });
|
|
10718
|
-
|
|
10719
|
-
|
|
11112
|
+
closeSync2(out);
|
|
11113
|
+
closeSync2(err);
|
|
10720
11114
|
child.unref();
|
|
10721
11115
|
if (child.pid === undefined) {
|
|
10722
11116
|
throw new Error("commander daemon did not report a pid");
|
|
@@ -10738,15 +11132,15 @@ function startCommanderDaemon(options) {
|
|
|
10738
11132
|
}
|
|
10739
11133
|
function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
|
|
10740
11134
|
const statusFile = commanderStatusFile(runnerId, statusDir);
|
|
10741
|
-
if (!
|
|
11135
|
+
if (!existsSync10(statusFile)) {
|
|
10742
11136
|
return { status: "missing", runnerId, statusFile };
|
|
10743
11137
|
}
|
|
10744
|
-
const record = parseRecord(
|
|
11138
|
+
const record = parseRecord(readFileSync9(statusFile, "utf8"));
|
|
10745
11139
|
return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
|
|
10746
11140
|
}
|
|
10747
11141
|
async function waitForCommanderDaemon(options) {
|
|
10748
11142
|
const now = options.now ?? (() => Date.now());
|
|
10749
|
-
const readTextFile = options.readTextFile ?? ((path) =>
|
|
11143
|
+
const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync9(path, "utf8") : undefined);
|
|
10750
11144
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
10751
11145
|
const deadline = now() + options.timeoutMs;
|
|
10752
11146
|
while (now() <= deadline) {
|
|
@@ -10761,12 +11155,12 @@ async function waitForCommanderDaemon(options) {
|
|
|
10761
11155
|
if (log === undefined) {
|
|
10762
11156
|
return { ok: false, reason: "timeout" };
|
|
10763
11157
|
}
|
|
10764
|
-
if (log
|
|
11158
|
+
if (commanderLogIsConnected(log)) {
|
|
10765
11159
|
return { ok: true, record: status.record };
|
|
10766
11160
|
}
|
|
10767
11161
|
await waitForFileChangeOrTimeout(status.record.logFile, deadline, now, () => {
|
|
10768
11162
|
const updatedLog = readTextFile(status.record.logFile);
|
|
10769
|
-
return updatedLog !== undefined && updatedLog
|
|
11163
|
+
return updatedLog !== undefined && commanderLogIsConnected(updatedLog);
|
|
10770
11164
|
});
|
|
10771
11165
|
}
|
|
10772
11166
|
}
|
|
@@ -10976,7 +11370,7 @@ async function main(args) {
|
|
|
10976
11370
|
process.stdout.write(connectGuideText());
|
|
10977
11371
|
return;
|
|
10978
11372
|
case "version":
|
|
10979
|
-
process.stdout.write(
|
|
11373
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
10980
11374
|
`);
|
|
10981
11375
|
return;
|
|
10982
11376
|
case "auth":
|
|
@@ -10996,18 +11390,18 @@ async function main(args) {
|
|
|
10996
11390
|
return;
|
|
10997
11391
|
case "agentRunner": {
|
|
10998
11392
|
const options = await parseAgentRunnerArgs(parsed.args);
|
|
10999
|
-
await runLocalCodexRunner(options);
|
|
11393
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11000
11394
|
return;
|
|
11001
11395
|
}
|
|
11002
11396
|
case "start": {
|
|
11003
11397
|
const options = await parseStartRunnerArgs(parsed.args);
|
|
11004
11398
|
addAllowedCwd(options.cwd);
|
|
11005
|
-
await runLocalCodexRunner(options);
|
|
11399
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11006
11400
|
return;
|
|
11007
11401
|
}
|
|
11008
11402
|
case "run": {
|
|
11009
11403
|
const options = await parseRunnerArgs(parsed.args);
|
|
11010
|
-
await runLocalCodexRunner(options);
|
|
11404
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
11011
11405
|
return;
|
|
11012
11406
|
}
|
|
11013
11407
|
}
|
|
@@ -11318,7 +11712,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
11318
11712
|
return {
|
|
11319
11713
|
kandanUrl,
|
|
11320
11714
|
token: targetToken,
|
|
11321
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11715
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11322
11716
|
workspaceSlug: target.workspaceSlug,
|
|
11323
11717
|
cwd,
|
|
11324
11718
|
codexBin,
|
|
@@ -11406,7 +11800,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11406
11800
|
return {
|
|
11407
11801
|
kandanUrl,
|
|
11408
11802
|
token: tokenFile.commanderToken,
|
|
11409
|
-
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${
|
|
11803
|
+
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${randomUUID4()}`,
|
|
11410
11804
|
workspaceSlug: tokenFile.workspaceId,
|
|
11411
11805
|
cwd,
|
|
11412
11806
|
codexBin,
|
|
@@ -11426,7 +11820,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11426
11820
|
};
|
|
11427
11821
|
}
|
|
11428
11822
|
function readAgentTokenTextFile(path) {
|
|
11429
|
-
return
|
|
11823
|
+
return existsSync11(path) ? readFileSync10(path, "utf8") : undefined;
|
|
11430
11824
|
}
|
|
11431
11825
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
11432
11826
|
const unsupportedFlags = [
|
|
@@ -11502,12 +11896,12 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11502
11896
|
process.exit(0);
|
|
11503
11897
|
}
|
|
11504
11898
|
if (values.get("version") === true) {
|
|
11505
|
-
process.stdout.write(
|
|
11899
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
11506
11900
|
`);
|
|
11507
11901
|
process.exit(0);
|
|
11508
11902
|
}
|
|
11509
|
-
const
|
|
11510
|
-
const kandanUrl =
|
|
11903
|
+
const connectTarget = parseConnectTarget(values);
|
|
11904
|
+
const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
11511
11905
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
11512
11906
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
11513
11907
|
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetails();
|
|
@@ -11518,8 +11912,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11518
11912
|
const token = await deps.resolveToken({
|
|
11519
11913
|
kandanUrl,
|
|
11520
11914
|
explicitToken,
|
|
11521
|
-
workspaceSlug:
|
|
11522
|
-
channelSlug:
|
|
11915
|
+
workspaceSlug: connectTarget?.workspaceSlug,
|
|
11916
|
+
channelSlug: connectTarget?.channelSlug,
|
|
11523
11917
|
authFilePath: stringValue3(values, "auth-file"),
|
|
11524
11918
|
callbackHost: stringValue3(values, "oauth-callback-host"),
|
|
11525
11919
|
reportRejectedCachedToken: () => {
|
|
@@ -11527,7 +11921,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11527
11921
|
`);
|
|
11528
11922
|
}
|
|
11529
11923
|
});
|
|
11530
|
-
const channelSession = parseChannelSession(values, token,
|
|
11924
|
+
const channelSession = parseChannelSession(values, token, connectTarget);
|
|
11531
11925
|
const editorRuntime = await deps.resolveEditorRuntime({
|
|
11532
11926
|
kandanUrl,
|
|
11533
11927
|
token,
|
|
@@ -11544,7 +11938,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11544
11938
|
return {
|
|
11545
11939
|
kandanUrl,
|
|
11546
11940
|
token,
|
|
11547
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11941
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11548
11942
|
cwd,
|
|
11549
11943
|
codexBin,
|
|
11550
11944
|
codexUrl: stringValue3(values, "codex-url"),
|
|
@@ -11558,7 +11952,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11558
11952
|
editorRuntime: editorRuntime.runtime,
|
|
11559
11953
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11560
11954
|
dependencyStatus,
|
|
11561
|
-
workspaceSlug:
|
|
11955
|
+
workspaceSlug: connectTarget?.workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
11562
11956
|
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11563
11957
|
channelSession
|
|
11564
11958
|
};
|
|
@@ -11670,7 +12064,7 @@ function resolveUserPath(pathValue) {
|
|
|
11670
12064
|
return resolve9(pathValue);
|
|
11671
12065
|
}
|
|
11672
12066
|
function parseChannelSession(values, token, target) {
|
|
11673
|
-
if (target === undefined) {
|
|
12067
|
+
if (target === undefined || target.channelSlug === undefined) {
|
|
11674
12068
|
return;
|
|
11675
12069
|
}
|
|
11676
12070
|
const listenUser = stringValue3(values, "listen-user") ?? defaultListenUserFromToken(token);
|
|
@@ -11686,7 +12080,7 @@ function parseChannelSession(values, token, target) {
|
|
|
11686
12080
|
streamFlushMs: positiveIntegerValue(values, "stream-flush-ms")
|
|
11687
12081
|
};
|
|
11688
12082
|
}
|
|
11689
|
-
function
|
|
12083
|
+
function parseConnectTarget(values) {
|
|
11690
12084
|
return parseOptionalChannelTarget(values);
|
|
11691
12085
|
}
|
|
11692
12086
|
function defaultListenUserFromToken(token) {
|
|
@@ -11699,12 +12093,15 @@ function defaultListenUserFromToken(token) {
|
|
|
11699
12093
|
function parseOptionalChannelTarget(values) {
|
|
11700
12094
|
const channel = stringValue3(values, "channel");
|
|
11701
12095
|
const workspace = stringValue3(values, "workspace");
|
|
11702
|
-
if (channel === undefined
|
|
11703
|
-
|
|
12096
|
+
if (channel === undefined) {
|
|
12097
|
+
if (workspace === undefined) {
|
|
12098
|
+
return;
|
|
12099
|
+
}
|
|
12100
|
+
return { workspaceSlug: workspace };
|
|
11704
12101
|
}
|
|
11705
|
-
return channel
|
|
12102
|
+
return channel.includes("/") ? parseChannelPath(channel) : {
|
|
11706
12103
|
workspaceSlug: workspace ?? required(values, "workspace"),
|
|
11707
|
-
channelSlug: channel
|
|
12104
|
+
channelSlug: channel
|
|
11708
12105
|
};
|
|
11709
12106
|
}
|
|
11710
12107
|
function parseChannelPath(channel) {
|
|
@@ -11717,6 +12114,12 @@ function parseChannelPath(channel) {
|
|
|
11717
12114
|
channelSlug: channelSlug.trim()
|
|
11718
12115
|
};
|
|
11719
12116
|
}
|
|
12117
|
+
function withLocalMachineId(options) {
|
|
12118
|
+
return {
|
|
12119
|
+
...options,
|
|
12120
|
+
machineId: ensureLocalMachineId()
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
11720
12123
|
function required(values, key) {
|
|
11721
12124
|
const value = stringValue3(values, key);
|
|
11722
12125
|
if (value === undefined) {
|
|
@@ -11773,19 +12176,19 @@ Usage:
|
|
|
11773
12176
|
linzumi commander <folder> [options]
|
|
11774
12177
|
linzumi start <folder> [options]
|
|
11775
12178
|
linzumi paths list|add|remove [path]
|
|
11776
|
-
linzumi connect --
|
|
12179
|
+
linzumi connect --workspace <slug> [--channel <slug>] [options]
|
|
11777
12180
|
linzumi auth --linzumi-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
11778
12181
|
|
|
11779
|
-
|
|
12182
|
+
Connection:
|
|
11780
12183
|
--linzumi-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
11781
12184
|
(deprecated alias: --kandan-url)
|
|
11782
12185
|
--token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
|
|
11783
12186
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
11784
12187
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
11785
12188
|
|
|
11786
|
-
|
|
12189
|
+
Workspace and optional channel binding:
|
|
11787
12190
|
--workspace <slug> Workspace slug
|
|
11788
|
-
--channel <slug|w/c>
|
|
12191
|
+
--channel <slug|w/c> Optional channel slug, or workspace/channel
|
|
11789
12192
|
--linzumi-thread-id <uuid> Resume an existing Linzumi thread instead of announcing a new root
|
|
11790
12193
|
(deprecated alias: --kandan-thread-id)
|
|
11791
12194
|
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
@@ -11818,8 +12221,9 @@ Examples:
|
|
|
11818
12221
|
linzumi commander daemon --runner-id launch-commander
|
|
11819
12222
|
linzumi start ~/
|
|
11820
12223
|
linzumi start ~/code/my-app
|
|
11821
|
-
linzumi connect --workspace <your-workspace> --
|
|
11822
|
-
linzumi connect --workspace <your-workspace> --
|
|
12224
|
+
linzumi connect --workspace <your-workspace> --launch-tui
|
|
12225
|
+
linzumi connect --workspace <your-workspace> --model gpt-5 --reasoning-effort low --fast --launch-tui
|
|
12226
|
+
linzumi connect --workspace <your-workspace> --channel <your-channel>
|
|
11823
12227
|
linzumi auth --workspace <your-workspace> --channel <your-channel>
|
|
11824
12228
|
linzumi paths add ~/code/my-app
|
|
11825
12229
|
linzumi paths list
|
|
@@ -11829,9 +12233,9 @@ Examples:
|
|
|
11829
12233
|
Missing --listen-user and authenticated user is unavailable.
|
|
11830
12234
|
linzumi connect --token "$TOKEN" --listen-users sean
|
|
11831
12235
|
Invalid flag: use --listen-user.
|
|
11832
|
-
linzumi connect --workspace <your-workspace> --
|
|
12236
|
+
linzumi connect --workspace <your-workspace> --allowed-cwd /does/not/exist
|
|
11833
12237
|
Invalid --allowed-cwd: allowed cwd roots must exist locally.
|
|
11834
|
-
linzumi connect --workspace <your-workspace> --
|
|
12238
|
+
linzumi connect --workspace <your-workspace> --forward-port vite
|
|
11835
12239
|
Invalid --forward-port: value must be a TCP port from 1 to 65535.
|
|
11836
12240
|
`;
|
|
11837
12241
|
}
|
|
@@ -11989,7 +12393,7 @@ space, persists this folder to your trusted-paths list, and starts this
|
|
|
11989
12393
|
computer as a local Codex runner.
|
|
11990
12394
|
|
|
11991
12395
|
Advanced (when you already know your workspace and channel):
|
|
11992
|
-
linzumi connect --workspace <your-workspace>
|
|
12396
|
+
linzumi connect --workspace <your-workspace>
|
|
11993
12397
|
|
|
11994
12398
|
For help:
|
|
11995
12399
|
linzumi connect --help
|
package/package.json
CHANGED