@linzumi/cli 0.0.33-beta → 0.0.35-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 +62 -266
- package/dist/index.js +680 -174
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3
3
|
import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync5 } from "node:fs";
|
|
4
|
-
import { homedir as
|
|
4
|
+
import { homedir as homedir9 } from "node:os";
|
|
5
5
|
import { resolve as resolve8 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7
7
|
|
|
@@ -9,7 +9,7 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
|
9
9
|
import { spawn as spawn6 } from "node:child_process";
|
|
10
10
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11
11
|
import { hostname as hostname2 } from "node:os";
|
|
12
|
-
import { join as
|
|
12
|
+
import { join as join6 } from "node:path";
|
|
13
13
|
|
|
14
14
|
// src/channelSessionSupport.ts
|
|
15
15
|
import { spawnSync } from "node:child_process";
|
|
@@ -1872,7 +1872,11 @@ async function resolvePendingPortForwardRequest(args, state, payloadContext, con
|
|
|
1872
1872
|
}
|
|
1873
1873
|
state.approvedForwardPorts.add(request.port);
|
|
1874
1874
|
state.approvedForwardTargets.set(request.port, approvedTargetFromRequest(request));
|
|
1875
|
-
const capabilities = args.options.onForwardPortApproved?.(request.port
|
|
1875
|
+
const capabilities = args.options.onForwardPortApproved?.(request.port, {
|
|
1876
|
+
kandanThreadId: state.kandanThreadId ?? null,
|
|
1877
|
+
codexThreadId: state.codexThreadId ?? null,
|
|
1878
|
+
channelSlug: args.options.channelSession.channelSlug ?? null
|
|
1879
|
+
});
|
|
1876
1880
|
await publishForwardPortResolvedEvent(args, request, capabilities);
|
|
1877
1881
|
await publishMessageStateForPortForwardResult(args, state, request, "processed");
|
|
1878
1882
|
await publishPortForwardReadyMessage(args, state, payloadContext, request);
|
|
@@ -3959,6 +3963,179 @@ async function pushOptional(kandan, topic, event, payload, log) {
|
|
|
3959
3963
|
// src/codexAppServer.ts
|
|
3960
3964
|
import { spawn } from "node:child_process";
|
|
3961
3965
|
import { createServer } from "node:net";
|
|
3966
|
+
|
|
3967
|
+
// src/runnerLogger.ts
|
|
3968
|
+
import { appendFileSync, openSync } from "node:fs";
|
|
3969
|
+
import { createWriteStream } from "node:fs";
|
|
3970
|
+
import { homedir } from "node:os";
|
|
3971
|
+
import { dirname, join as join2 } from "node:path";
|
|
3972
|
+
import { mkdirSync } from "node:fs";
|
|
3973
|
+
var sensitiveMarker = "<SENSITIVE_DATA>";
|
|
3974
|
+
var sensitiveQueryParams = new Set([
|
|
3975
|
+
"access_token",
|
|
3976
|
+
"authorization",
|
|
3977
|
+
"code",
|
|
3978
|
+
"cookie",
|
|
3979
|
+
"kandan_preview_ticket",
|
|
3980
|
+
"refresh_token",
|
|
3981
|
+
"token"
|
|
3982
|
+
]);
|
|
3983
|
+
var sensitiveArgFlags = new Set([
|
|
3984
|
+
"--access-token",
|
|
3985
|
+
"--api-key",
|
|
3986
|
+
"--authorization",
|
|
3987
|
+
"--cookie",
|
|
3988
|
+
"--oauth-code",
|
|
3989
|
+
"--password",
|
|
3990
|
+
"--refresh-token",
|
|
3991
|
+
"--secret",
|
|
3992
|
+
"--token"
|
|
3993
|
+
]);
|
|
3994
|
+
function createRunnerLogger(logFile, consoleReporter) {
|
|
3995
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
3996
|
+
const fd = openSync(logFile, "a");
|
|
3997
|
+
const stream = createWriteStream("", { fd, flags: "a", autoClose: true });
|
|
3998
|
+
const logger = (event, payload) => {
|
|
3999
|
+
const redacted = redactForCliLog(payload);
|
|
4000
|
+
stream.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...redacted })}
|
|
4001
|
+
`, "utf8");
|
|
4002
|
+
consoleReporter?.(event, redacted);
|
|
4003
|
+
};
|
|
4004
|
+
Object.defineProperty(logger, "close", {
|
|
4005
|
+
value: () => closeStream(stream)
|
|
4006
|
+
});
|
|
4007
|
+
return logger;
|
|
4008
|
+
}
|
|
4009
|
+
function writeCliAuditEvent(event, payload, options = {}) {
|
|
4010
|
+
const logFile = options.logFile ?? defaultCliAuditLogFile();
|
|
4011
|
+
try {
|
|
4012
|
+
mkdirSync(dirname(logFile), { recursive: true });
|
|
4013
|
+
appendFileSync(logFile, `${JSON.stringify({
|
|
4014
|
+
ts: new Date().toISOString(),
|
|
4015
|
+
event,
|
|
4016
|
+
...options.sessionId === undefined ? {} : { sessionId: options.sessionId },
|
|
4017
|
+
...redactForCliLog(payload)
|
|
4018
|
+
})}
|
|
4019
|
+
`, "utf8");
|
|
4020
|
+
} catch (_error) {
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
function defaultCliAuditLogFile() {
|
|
4025
|
+
const override = process.env.LINZUMI_CLI_AUDIT_LOG?.trim();
|
|
4026
|
+
return override === undefined || override === "" ? join2(homedir(), ".linzumi", "logs", "command-events.jsonl") : override;
|
|
4027
|
+
}
|
|
4028
|
+
function redactForCliLog(value) {
|
|
4029
|
+
return redactValue(value, undefined);
|
|
4030
|
+
}
|
|
4031
|
+
function redactValue(value, key) {
|
|
4032
|
+
if (sensitiveKey(key)) {
|
|
4033
|
+
return sensitiveMarker;
|
|
4034
|
+
}
|
|
4035
|
+
if (typeof value === "string") {
|
|
4036
|
+
return redactString(value);
|
|
4037
|
+
}
|
|
4038
|
+
if (Array.isArray(value)) {
|
|
4039
|
+
return key === "args" ? redactArgs(value) : value.map((item) => redactValue(item, undefined));
|
|
4040
|
+
}
|
|
4041
|
+
if (value !== null && typeof value === "object") {
|
|
4042
|
+
return redactObject(value);
|
|
4043
|
+
}
|
|
4044
|
+
return value;
|
|
4045
|
+
}
|
|
4046
|
+
function redactObject(value) {
|
|
4047
|
+
const headerName = typeof value.name === "string" ? value.name.toLowerCase() : undefined;
|
|
4048
|
+
const shouldRedactHeaderValue = headerName === "authorization" || headerName === "cookie" || headerName === "set-cookie";
|
|
4049
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
4050
|
+
key,
|
|
4051
|
+
shouldRedactHeaderValue && key === "value" ? sensitiveMarker : redactValue(entry, key)
|
|
4052
|
+
]));
|
|
4053
|
+
}
|
|
4054
|
+
function redactArgs(args) {
|
|
4055
|
+
let redactNext = false;
|
|
4056
|
+
return args.map((arg) => {
|
|
4057
|
+
if (typeof arg !== "string") {
|
|
4058
|
+
redactNext = false;
|
|
4059
|
+
return redactValue(arg, undefined);
|
|
4060
|
+
}
|
|
4061
|
+
if (redactNext) {
|
|
4062
|
+
redactNext = false;
|
|
4063
|
+
return sensitiveMarker;
|
|
4064
|
+
}
|
|
4065
|
+
const [flag, value] = splitArgAssignment(arg);
|
|
4066
|
+
if (sensitiveArgFlags.has(flag)) {
|
|
4067
|
+
if (value === undefined) {
|
|
4068
|
+
redactNext = true;
|
|
4069
|
+
return arg;
|
|
4070
|
+
}
|
|
4071
|
+
return `${flag}=${sensitiveMarker}`;
|
|
4072
|
+
}
|
|
4073
|
+
return redactString(arg);
|
|
4074
|
+
});
|
|
4075
|
+
}
|
|
4076
|
+
function splitArgAssignment(value) {
|
|
4077
|
+
const index = value.indexOf("=");
|
|
4078
|
+
return index === -1 ? [value, undefined] : [value.slice(0, index), value.slice(index + 1)];
|
|
4079
|
+
}
|
|
4080
|
+
function sensitiveKey(key) {
|
|
4081
|
+
if (key === undefined) {
|
|
4082
|
+
return false;
|
|
4083
|
+
}
|
|
4084
|
+
return /^(authorization|cookie|set-cookie|password)$/i.test(key) || /(^|[_-])(access[_-]?token|api[_-]?key|auth[_-]?token|oauth[_-]?code|refresh[_-]?token|secret|token)$/i.test(key);
|
|
4085
|
+
}
|
|
4086
|
+
function redactString(value) {
|
|
4087
|
+
const withRedactedQuery = redactUrlQuery(value);
|
|
4088
|
+
return withRedactedQuery.replace(/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, sensitiveMarker);
|
|
4089
|
+
}
|
|
4090
|
+
function redactUrlQuery(value) {
|
|
4091
|
+
let parsed;
|
|
4092
|
+
try {
|
|
4093
|
+
parsed = new URL(value);
|
|
4094
|
+
} catch (_error) {
|
|
4095
|
+
return redactRelativeUrlQuery(value);
|
|
4096
|
+
}
|
|
4097
|
+
for (const name of [...parsed.searchParams.keys()]) {
|
|
4098
|
+
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
4099
|
+
parsed.searchParams.set(name, sensitiveMarker);
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
return parsed.toString();
|
|
4103
|
+
}
|
|
4104
|
+
function redactRelativeUrlQuery(value) {
|
|
4105
|
+
const queryStart = value.indexOf("?");
|
|
4106
|
+
const hashStart = value.indexOf("#");
|
|
4107
|
+
if (queryStart === -1 || hashStart !== -1 && hashStart < queryStart) {
|
|
4108
|
+
return value;
|
|
4109
|
+
}
|
|
4110
|
+
const path = value.slice(0, queryStart);
|
|
4111
|
+
const query = hashStart === -1 ? value.slice(queryStart + 1) : value.slice(queryStart + 1, hashStart);
|
|
4112
|
+
const hash = hashStart === -1 ? "" : value.slice(hashStart);
|
|
4113
|
+
const searchParams = new URLSearchParams(query);
|
|
4114
|
+
let redacted = false;
|
|
4115
|
+
for (const name of [...searchParams.keys()]) {
|
|
4116
|
+
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
4117
|
+
searchParams.set(name, sensitiveMarker);
|
|
4118
|
+
redacted = true;
|
|
4119
|
+
}
|
|
4120
|
+
}
|
|
4121
|
+
switch (redacted) {
|
|
4122
|
+
case true:
|
|
4123
|
+
return `${path}?${searchParams.toString()}${hash}`;
|
|
4124
|
+
case false:
|
|
4125
|
+
return value;
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
function closeStream(stream) {
|
|
4129
|
+
if (stream.closed || stream.destroyed) {
|
|
4130
|
+
return Promise.resolve();
|
|
4131
|
+
}
|
|
4132
|
+
return new Promise((resolve2, reject) => {
|
|
4133
|
+
stream.once("error", reject);
|
|
4134
|
+
stream.end(resolve2);
|
|
4135
|
+
});
|
|
4136
|
+
}
|
|
4137
|
+
|
|
4138
|
+
// src/codexAppServer.ts
|
|
3962
4139
|
async function chooseLoopbackPort() {
|
|
3963
4140
|
return new Promise((resolve2, reject) => {
|
|
3964
4141
|
const server = createServer();
|
|
@@ -3984,12 +4161,35 @@ async function chooseLoopbackPort() {
|
|
|
3984
4161
|
async function startCodexAppServer(codexBin, cwd, options = {}) {
|
|
3985
4162
|
const port = await chooseLoopbackPort();
|
|
3986
4163
|
const url = `ws://127.0.0.1:${port}`;
|
|
3987
|
-
const
|
|
4164
|
+
const args = codexAppServerArgs(url, options);
|
|
4165
|
+
writeCliAuditEvent("process.spawn", {
|
|
4166
|
+
command: codexBin,
|
|
4167
|
+
args,
|
|
4168
|
+
cwd,
|
|
4169
|
+
purpose: "codex.app_server"
|
|
4170
|
+
});
|
|
4171
|
+
const child = spawn(codexBin, args, {
|
|
3988
4172
|
cwd,
|
|
3989
4173
|
env: process.env,
|
|
3990
4174
|
stdio: ["ignore", "inherit", "inherit"]
|
|
3991
4175
|
});
|
|
3992
|
-
|
|
4176
|
+
writeCliAuditEvent("process.spawned", {
|
|
4177
|
+
command: codexBin,
|
|
4178
|
+
args,
|
|
4179
|
+
cwd,
|
|
4180
|
+
pid: child.pid,
|
|
4181
|
+
purpose: "codex.app_server"
|
|
4182
|
+
});
|
|
4183
|
+
child.once("exit", (code, signal) => {
|
|
4184
|
+
writeCliAuditEvent("process.exit", {
|
|
4185
|
+
command: codexBin,
|
|
4186
|
+
args,
|
|
4187
|
+
cwd,
|
|
4188
|
+
pid: child.pid,
|
|
4189
|
+
code,
|
|
4190
|
+
signal,
|
|
4191
|
+
purpose: "codex.app_server"
|
|
4192
|
+
});
|
|
3993
4193
|
if (code !== 0) {
|
|
3994
4194
|
process.stderr.write(`codex app-server exited with code ${code ?? "signal"}
|
|
3995
4195
|
`);
|
|
@@ -4218,21 +4418,21 @@ function readyzUrlForWebsocket(websocketUrl) {
|
|
|
4218
4418
|
// src/codexProjectTrust.ts
|
|
4219
4419
|
import {
|
|
4220
4420
|
existsSync,
|
|
4221
|
-
mkdirSync,
|
|
4421
|
+
mkdirSync as mkdirSync2,
|
|
4222
4422
|
readFileSync,
|
|
4223
4423
|
realpathSync,
|
|
4224
4424
|
writeFileSync
|
|
4225
4425
|
} from "node:fs";
|
|
4226
|
-
import { homedir } from "node:os";
|
|
4227
|
-
import { join as
|
|
4426
|
+
import { homedir as homedir2 } from "node:os";
|
|
4427
|
+
import { join as join3, resolve as resolve2 } from "node:path";
|
|
4228
4428
|
function ensureCodexProjectTrusted(projectPath, options = {}) {
|
|
4229
4429
|
const trustedPath = realpathSync(resolve2(projectPath));
|
|
4230
|
-
const configHome = options.configHome ?? process.env.CODEX_HOME ??
|
|
4231
|
-
const configPath =
|
|
4430
|
+
const configHome = options.configHome ?? process.env.CODEX_HOME ?? join3(homedir2(), ".codex");
|
|
4431
|
+
const configPath = join3(configHome, "config.toml");
|
|
4232
4432
|
const currentConfig = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
|
|
4233
4433
|
const nextConfig = codexConfigWithTrustedProject(currentConfig, trustedPath);
|
|
4234
4434
|
if (nextConfig !== currentConfig) {
|
|
4235
|
-
|
|
4435
|
+
mkdirSync2(configHome, { recursive: true });
|
|
4236
4436
|
writeFileSync(configPath, nextConfig);
|
|
4237
4437
|
}
|
|
4238
4438
|
return trustedPath;
|
|
@@ -4279,7 +4479,7 @@ function trimTrailingNewlines(value) {
|
|
|
4279
4479
|
|
|
4280
4480
|
// src/localCapabilities.ts
|
|
4281
4481
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
4282
|
-
import { homedir as
|
|
4482
|
+
import { homedir as homedir3 } from "node:os";
|
|
4283
4483
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3 } from "node:path";
|
|
4284
4484
|
function parseAllowedCwdList(value) {
|
|
4285
4485
|
if (value === undefined) {
|
|
@@ -4319,10 +4519,10 @@ function assertConfiguredAllowedCwds(paths) {
|
|
|
4319
4519
|
}
|
|
4320
4520
|
function expandUserPath(pathValue) {
|
|
4321
4521
|
if (pathValue === "~") {
|
|
4322
|
-
return
|
|
4522
|
+
return homedir3();
|
|
4323
4523
|
}
|
|
4324
4524
|
if (pathValue.startsWith("~/")) {
|
|
4325
|
-
return resolve3(
|
|
4525
|
+
return resolve3(homedir3(), pathValue.slice(2));
|
|
4326
4526
|
}
|
|
4327
4527
|
return pathValue;
|
|
4328
4528
|
}
|
|
@@ -4368,6 +4568,7 @@ async function handleForwardHttpRequest(control, allowedPorts) {
|
|
|
4368
4568
|
const request = {
|
|
4369
4569
|
method: control.method,
|
|
4370
4570
|
headers: requestHeaders(control.headers),
|
|
4571
|
+
redirect: "manual",
|
|
4371
4572
|
...bodyDecision.body === undefined ? {} : { body: bodyDecision.body }
|
|
4372
4573
|
};
|
|
4373
4574
|
const response = await fetchWithHttpsFallback(control.port, control.path, control.queryString, request);
|
|
@@ -4642,13 +4843,13 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
4642
4843
|
import {
|
|
4643
4844
|
cpSync,
|
|
4644
4845
|
existsSync as existsSync2,
|
|
4645
|
-
mkdirSync as
|
|
4846
|
+
mkdirSync as mkdirSync3,
|
|
4646
4847
|
mkdtempSync,
|
|
4647
4848
|
realpathSync as realpathSync3,
|
|
4648
4849
|
writeFileSync as writeFileSync2
|
|
4649
4850
|
} from "node:fs";
|
|
4650
4851
|
import { tmpdir } from "node:os";
|
|
4651
|
-
import { basename as basename3, delimiter, dirname, join as
|
|
4852
|
+
import { basename as basename3, delimiter, dirname as dirname2, join as join4 } from "node:path";
|
|
4652
4853
|
function isStartLocalEditorControl(control) {
|
|
4653
4854
|
return control.type === "start_local_editor";
|
|
4654
4855
|
}
|
|
@@ -4728,7 +4929,7 @@ async function startLocalEditor(control, options) {
|
|
|
4728
4929
|
reason: launchResult.reason
|
|
4729
4930
|
};
|
|
4730
4931
|
}
|
|
4731
|
-
const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime);
|
|
4932
|
+
const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime, options.runnerId);
|
|
4732
4933
|
if (!collaborationResult.ok) {
|
|
4733
4934
|
return {
|
|
4734
4935
|
ok: false,
|
|
@@ -4736,11 +4937,35 @@ async function startLocalEditor(control, options) {
|
|
|
4736
4937
|
reason: "code_server_spawn_failed"
|
|
4737
4938
|
};
|
|
4738
4939
|
}
|
|
4940
|
+
writeCliAuditEvent("process.spawn", {
|
|
4941
|
+
command: launchResult.command,
|
|
4942
|
+
args: launchResult.args,
|
|
4943
|
+
cwd: cwdDecision.cwd,
|
|
4944
|
+
purpose: "local_editor.code_server"
|
|
4945
|
+
}, { sessionId: options.runnerId });
|
|
4739
4946
|
const child = spawn2(launchResult.command, [...launchResult.args], {
|
|
4740
4947
|
cwd: cwdDecision.cwd,
|
|
4741
|
-
env: codeServerEnv(process.env, profileResult.userDataDir, collaborationResult.collaboration),
|
|
4948
|
+
env: codeServerEnv(process.env, cwdDecision.cwd, profileResult.userDataDir, collaborationResult.collaboration),
|
|
4742
4949
|
stdio: ["ignore", "inherit", "inherit"]
|
|
4743
4950
|
});
|
|
4951
|
+
writeCliAuditEvent("process.spawned", {
|
|
4952
|
+
command: launchResult.command,
|
|
4953
|
+
args: launchResult.args,
|
|
4954
|
+
cwd: cwdDecision.cwd,
|
|
4955
|
+
pid: child.pid,
|
|
4956
|
+
purpose: "local_editor.code_server"
|
|
4957
|
+
}, { sessionId: options.runnerId });
|
|
4958
|
+
child.once("exit", (code, signal) => {
|
|
4959
|
+
writeCliAuditEvent("process.exit", {
|
|
4960
|
+
command: launchResult.command,
|
|
4961
|
+
args: launchResult.args,
|
|
4962
|
+
cwd: cwdDecision.cwd,
|
|
4963
|
+
pid: child.pid,
|
|
4964
|
+
code,
|
|
4965
|
+
signal,
|
|
4966
|
+
purpose: "local_editor.code_server"
|
|
4967
|
+
}, { sessionId: options.runnerId });
|
|
4968
|
+
});
|
|
4744
4969
|
const exited = waitForCodeServerExit(child);
|
|
4745
4970
|
const spawnResult = await waitForCodeServerSpawn(child);
|
|
4746
4971
|
if (spawnResult === "failed") {
|
|
@@ -4811,17 +5036,17 @@ function codeServerArgs(port, cwd, userDataDir, extensionsDir) {
|
|
|
4811
5036
|
}
|
|
4812
5037
|
function prepareCodeServerProfile(collaboration, editorRuntime) {
|
|
4813
5038
|
try {
|
|
4814
|
-
const userDataDir = mkdtempSync(
|
|
4815
|
-
const extensionsDir =
|
|
4816
|
-
const collaborationServerDir =
|
|
4817
|
-
const userSettingsDir =
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
5039
|
+
const userDataDir = mkdtempSync(join4(tmpdir(), "kandan-local-editor-"));
|
|
5040
|
+
const extensionsDir = join4(userDataDir, "extensions");
|
|
5041
|
+
const collaborationServerDir = join4(userDataDir, "collaboration-server");
|
|
5042
|
+
const userSettingsDir = join4(userDataDir, "User");
|
|
5043
|
+
mkdirSync3(userSettingsDir, { recursive: true });
|
|
5044
|
+
mkdirSync3(extensionsDir, { recursive: true });
|
|
5045
|
+
mkdirSync3(collaborationServerDir, { recursive: true });
|
|
4821
5046
|
if (editorRuntime !== undefined) {
|
|
4822
|
-
installDirectory(editorRuntime.assets.documentStateExtensionDir,
|
|
5047
|
+
installDirectory(editorRuntime.assets.documentStateExtensionDir, join4(extensionsDir, "kandan.document-state-telemetry"));
|
|
4823
5048
|
}
|
|
4824
|
-
writeFileSync2(
|
|
5049
|
+
writeFileSync2(join4(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
|
|
4825
5050
|
return { ok: true, userDataDir, extensionsDir, collaborationServerDir };
|
|
4826
5051
|
} catch (_error) {
|
|
4827
5052
|
return { ok: false, reason: "code_server_spawn_failed" };
|
|
@@ -4887,13 +5112,16 @@ function prepareLinuxCodeServerLaunch(options) {
|
|
|
4887
5112
|
"/tmp",
|
|
4888
5113
|
"--setenv",
|
|
4889
5114
|
"HOME",
|
|
4890
|
-
options.
|
|
5115
|
+
options.cwd,
|
|
5116
|
+
"--setenv",
|
|
5117
|
+
"PWD",
|
|
5118
|
+
options.cwd,
|
|
4891
5119
|
"--setenv",
|
|
4892
5120
|
"XDG_DATA_HOME",
|
|
4893
|
-
|
|
5121
|
+
join4(options.userDataDir, "data"),
|
|
4894
5122
|
"--setenv",
|
|
4895
5123
|
"XDG_CONFIG_HOME",
|
|
4896
|
-
|
|
5124
|
+
join4(options.userDataDir, "config"),
|
|
4897
5125
|
...readOnlyRoots.flatMap((path) => ["--ro-bind-try", path, path]),
|
|
4898
5126
|
"--bind",
|
|
4899
5127
|
options.cwd,
|
|
@@ -4966,12 +5194,12 @@ function resolveCodeServerExecutable(command, envPath) {
|
|
|
4966
5194
|
if (directory.trim() === "") {
|
|
4967
5195
|
continue;
|
|
4968
5196
|
}
|
|
4969
|
-
const candidate =
|
|
5197
|
+
const candidate = join4(directory, command);
|
|
4970
5198
|
if (!existsSync2(candidate)) {
|
|
4971
5199
|
continue;
|
|
4972
5200
|
}
|
|
4973
5201
|
const realpath = realpathSync3(candidate);
|
|
4974
|
-
return { ok: true, command: realpath, directory:
|
|
5202
|
+
return { ok: true, command: realpath, directory: dirname2(realpath) };
|
|
4975
5203
|
}
|
|
4976
5204
|
return { ok: false };
|
|
4977
5205
|
}
|
|
@@ -4980,10 +5208,10 @@ function hasPathSeparator(path) {
|
|
|
4980
5208
|
}
|
|
4981
5209
|
function safeRealpathDir(path) {
|
|
4982
5210
|
try {
|
|
4983
|
-
const directory =
|
|
5211
|
+
const directory = dirname2(realpathSync3(path));
|
|
4984
5212
|
return directory === "/" ? undefined : directory;
|
|
4985
5213
|
} catch (_error) {
|
|
4986
|
-
const directory =
|
|
5214
|
+
const directory = dirname2(path);
|
|
4987
5215
|
return directory === "." || directory === "/" ? undefined : directory;
|
|
4988
5216
|
}
|
|
4989
5217
|
}
|
|
@@ -5033,7 +5261,7 @@ function codeServerSettings(collaboration) {
|
|
|
5033
5261
|
"workbench.welcomePage.walkthroughs.openOnInstall": false
|
|
5034
5262
|
};
|
|
5035
5263
|
}
|
|
5036
|
-
async function startCollaborationSidecar(collaboration, profile, editorRuntime) {
|
|
5264
|
+
async function startCollaborationSidecar(collaboration, profile, editorRuntime, runnerId) {
|
|
5037
5265
|
if (collaboration === undefined) {
|
|
5038
5266
|
return { ok: true, ready: Promise.resolve("ready") };
|
|
5039
5267
|
}
|
|
@@ -5042,13 +5270,22 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
|
|
|
5042
5270
|
installLocalTarball(editorRuntime.assets.collaborationExtensionTarball, profile.extensionsDir),
|
|
5043
5271
|
installLocalTarball(editorRuntime.assets.collaborationServerTarball, profile.collaborationServerDir)
|
|
5044
5272
|
]);
|
|
5045
|
-
const
|
|
5046
|
-
|
|
5273
|
+
const command = nodeRuntimeExecutable();
|
|
5274
|
+
const args = [
|
|
5275
|
+
join4(profile.collaborationServerDir, "open-collaboration-server", "bundle", "app.js"),
|
|
5047
5276
|
"--hostname",
|
|
5048
5277
|
"127.0.0.1",
|
|
5049
5278
|
"--port",
|
|
5050
5279
|
String(collaboration.serverPort)
|
|
5051
|
-
]
|
|
5280
|
+
];
|
|
5281
|
+
writeCliAuditEvent("process.spawn", {
|
|
5282
|
+
command,
|
|
5283
|
+
args,
|
|
5284
|
+
purpose: "local_editor.collaboration_sidecar",
|
|
5285
|
+
editorSessionId: collaboration.editorSessionId,
|
|
5286
|
+
runtimeSessionId: collaboration.runtimeSessionId
|
|
5287
|
+
}, { sessionId: runnerId });
|
|
5288
|
+
const child = spawn2(command, args, {
|
|
5052
5289
|
env: {
|
|
5053
5290
|
...process.env,
|
|
5054
5291
|
OCT_ACTIVATE_SIMPLE_LOGIN: "true",
|
|
@@ -5056,6 +5293,26 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
|
|
|
5056
5293
|
},
|
|
5057
5294
|
stdio: ["ignore", "inherit", "inherit"]
|
|
5058
5295
|
});
|
|
5296
|
+
writeCliAuditEvent("process.spawned", {
|
|
5297
|
+
command,
|
|
5298
|
+
args,
|
|
5299
|
+
pid: child.pid,
|
|
5300
|
+
purpose: "local_editor.collaboration_sidecar",
|
|
5301
|
+
editorSessionId: collaboration.editorSessionId,
|
|
5302
|
+
runtimeSessionId: collaboration.runtimeSessionId
|
|
5303
|
+
}, { sessionId: runnerId });
|
|
5304
|
+
child.once("exit", (code, signal) => {
|
|
5305
|
+
writeCliAuditEvent("process.exit", {
|
|
5306
|
+
command,
|
|
5307
|
+
args,
|
|
5308
|
+
pid: child.pid,
|
|
5309
|
+
code,
|
|
5310
|
+
signal,
|
|
5311
|
+
purpose: "local_editor.collaboration_sidecar",
|
|
5312
|
+
editorSessionId: collaboration.editorSessionId,
|
|
5313
|
+
runtimeSessionId: collaboration.runtimeSessionId
|
|
5314
|
+
}, { sessionId: runnerId });
|
|
5315
|
+
});
|
|
5059
5316
|
const exited = waitForCodeServerExit(child);
|
|
5060
5317
|
const spawnResult = await waitForCodeServerSpawn(child);
|
|
5061
5318
|
if (spawnResult === "failed") {
|
|
@@ -5089,21 +5346,22 @@ function nodeRuntimeExecutable(env = process.env, execPath = process.execPath) {
|
|
|
5089
5346
|
return basename3(execPath).toLowerCase().includes("bun") ? "node" : execPath;
|
|
5090
5347
|
}
|
|
5091
5348
|
async function installLocalTarball(archivePath, destinationDir) {
|
|
5092
|
-
|
|
5349
|
+
mkdirSync3(destinationDir, { recursive: true });
|
|
5093
5350
|
await runProcess("tar", ["-xzf", archivePath, "-C", destinationDir]);
|
|
5094
5351
|
}
|
|
5095
5352
|
function installDirectory(sourceDir, destinationDir) {
|
|
5096
|
-
|
|
5353
|
+
mkdirSync3(dirname2(destinationDir), { recursive: true });
|
|
5097
5354
|
cpSync(sourceDir, destinationDir, { recursive: true });
|
|
5098
5355
|
}
|
|
5099
|
-
function codeServerEnv(env, userDataDir, collaboration) {
|
|
5356
|
+
function codeServerEnv(env, cwd, userDataDir, collaboration) {
|
|
5100
5357
|
const { PORT: _port, ...hostEnv } = env;
|
|
5101
5358
|
const base = {
|
|
5102
5359
|
...hostEnv,
|
|
5103
|
-
HOME:
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5360
|
+
HOME: cwd,
|
|
5361
|
+
PWD: cwd,
|
|
5362
|
+
XDG_CACHE_HOME: join4(userDataDir, "xdg-cache"),
|
|
5363
|
+
XDG_CONFIG_HOME: join4(userDataDir, "xdg-config"),
|
|
5364
|
+
XDG_DATA_HOME: join4(userDataDir, "xdg-data")
|
|
5107
5365
|
};
|
|
5108
5366
|
if (collaboration === undefined) {
|
|
5109
5367
|
return base;
|
|
@@ -5137,11 +5395,30 @@ function sameCollaboration(running, requested) {
|
|
|
5137
5395
|
}
|
|
5138
5396
|
function runProcess(command, args) {
|
|
5139
5397
|
return new Promise((resolve4, reject) => {
|
|
5398
|
+
writeCliAuditEvent("process.spawn", {
|
|
5399
|
+
command,
|
|
5400
|
+
args,
|
|
5401
|
+
purpose: "local_editor.install_process"
|
|
5402
|
+
});
|
|
5140
5403
|
const child = spawn2(command, [...args], {
|
|
5141
5404
|
stdio: ["ignore", "ignore", "inherit"]
|
|
5142
5405
|
});
|
|
5406
|
+
writeCliAuditEvent("process.spawned", {
|
|
5407
|
+
command,
|
|
5408
|
+
args,
|
|
5409
|
+
pid: child.pid,
|
|
5410
|
+
purpose: "local_editor.install_process"
|
|
5411
|
+
});
|
|
5143
5412
|
child.once("error", reject);
|
|
5144
|
-
child.once("exit", (code) => {
|
|
5413
|
+
child.once("exit", (code, signal) => {
|
|
5414
|
+
writeCliAuditEvent("process.exit", {
|
|
5415
|
+
command,
|
|
5416
|
+
args,
|
|
5417
|
+
pid: child.pid,
|
|
5418
|
+
code,
|
|
5419
|
+
signal,
|
|
5420
|
+
purpose: "local_editor.install_process"
|
|
5421
|
+
});
|
|
5145
5422
|
if (code === 0) {
|
|
5146
5423
|
resolve4();
|
|
5147
5424
|
} else {
|
|
@@ -5293,17 +5570,17 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
5293
5570
|
import { createHash as createHash2 } from "node:crypto";
|
|
5294
5571
|
import {
|
|
5295
5572
|
createReadStream,
|
|
5296
|
-
createWriteStream,
|
|
5573
|
+
createWriteStream as createWriteStream2,
|
|
5297
5574
|
existsSync as existsSync3,
|
|
5298
|
-
mkdirSync as
|
|
5575
|
+
mkdirSync as mkdirSync4,
|
|
5299
5576
|
mkdtempSync as mkdtempSync2,
|
|
5300
5577
|
readFileSync as readFileSync2,
|
|
5301
5578
|
renameSync,
|
|
5302
5579
|
rmSync,
|
|
5303
5580
|
writeFileSync as writeFileSync3
|
|
5304
5581
|
} from "node:fs";
|
|
5305
|
-
import { homedir as
|
|
5306
|
-
import { dirname as
|
|
5582
|
+
import { homedir as homedir4 } from "node:os";
|
|
5583
|
+
import { dirname as dirname3, join as join5, resolve as resolve4 } from "node:path";
|
|
5307
5584
|
import { Readable } from "node:stream";
|
|
5308
5585
|
import { pipeline } from "node:stream/promises";
|
|
5309
5586
|
|
|
@@ -5403,7 +5680,7 @@ function kandanHttpBaseUrl(kandanUrl) {
|
|
|
5403
5680
|
case "https:":
|
|
5404
5681
|
break;
|
|
5405
5682
|
default:
|
|
5406
|
-
throw new Error("--
|
|
5683
|
+
throw new Error("--linzumi-url must be ws://, wss://, http://, or https://");
|
|
5407
5684
|
}
|
|
5408
5685
|
parsed.pathname = "";
|
|
5409
5686
|
parsed.search = "";
|
|
@@ -5490,8 +5767,8 @@ function startCallbackServer(args) {
|
|
|
5490
5767
|
if (error !== null && error.trim() !== "") {
|
|
5491
5768
|
rejectCallback?.(new Error(`local runner OAuth failed: ${error}`));
|
|
5492
5769
|
writeOauthResult(response, {
|
|
5493
|
-
title: "
|
|
5494
|
-
body: "You denied the request.
|
|
5770
|
+
title: "Not authorized",
|
|
5771
|
+
body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
|
|
5495
5772
|
status: 403
|
|
5496
5773
|
});
|
|
5497
5774
|
return;
|
|
@@ -5499,22 +5776,22 @@ function startCallbackServer(args) {
|
|
|
5499
5776
|
if (code === null || state === null || code.trim() === "" || state.trim() === "") {
|
|
5500
5777
|
writeOauthResult(response, {
|
|
5501
5778
|
title: "Authorization callback was incomplete",
|
|
5502
|
-
body: "
|
|
5779
|
+
body: "We didn't receive the authorization code. Return to your terminal and try again.",
|
|
5503
5780
|
status: 400
|
|
5504
5781
|
});
|
|
5505
5782
|
return;
|
|
5506
5783
|
}
|
|
5507
5784
|
resolveCallback?.({ code, state });
|
|
5508
5785
|
writeOauthResult(response, {
|
|
5509
|
-
title: "
|
|
5510
|
-
body: "You can close this tab
|
|
5786
|
+
title: "Connected",
|
|
5787
|
+
body: "You can close this tab. Your terminal will finish the setup.",
|
|
5511
5788
|
status: 200
|
|
5512
5789
|
});
|
|
5513
5790
|
} catch (error) {
|
|
5514
5791
|
rejectCallback?.(error);
|
|
5515
5792
|
writeOauthResult(response, {
|
|
5516
5793
|
title: "Authorization callback failed",
|
|
5517
|
-
body: "Return to your terminal and rerun
|
|
5794
|
+
body: "Return to your terminal and rerun the bootstrap command.",
|
|
5518
5795
|
status: 500
|
|
5519
5796
|
});
|
|
5520
5797
|
}
|
|
@@ -5578,7 +5855,18 @@ function openBrowser(url) {
|
|
|
5578
5855
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
5579
5856
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
5580
5857
|
return new Promise((resolve4) => {
|
|
5858
|
+
writeCliAuditEvent("process.spawn", {
|
|
5859
|
+
command,
|
|
5860
|
+
args,
|
|
5861
|
+
purpose: "oauth.open_browser"
|
|
5862
|
+
});
|
|
5581
5863
|
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
5864
|
+
writeCliAuditEvent("process.spawned", {
|
|
5865
|
+
command,
|
|
5866
|
+
args,
|
|
5867
|
+
pid: child.pid,
|
|
5868
|
+
purpose: "oauth.open_browser"
|
|
5869
|
+
});
|
|
5582
5870
|
child.on("error", () => resolve4());
|
|
5583
5871
|
child.on("spawn", () => {
|
|
5584
5872
|
child.unref();
|
|
@@ -5765,8 +6053,8 @@ function normalizeRuntimeAssets(value) {
|
|
|
5765
6053
|
}
|
|
5766
6054
|
function installedRuntime(cacheRoot, manifest) {
|
|
5767
6055
|
const runtimeRoot = runtimeInstallRoot(cacheRoot, manifest);
|
|
5768
|
-
const manifestPath =
|
|
5769
|
-
const codeServerBin =
|
|
6056
|
+
const manifestPath = join5(runtimeRoot, manifest.manifestPath);
|
|
6057
|
+
const codeServerBin = join5(runtimeRoot, manifest.codeServerBinPath);
|
|
5770
6058
|
const assets = verifiedRuntimeAssetPaths(runtimeRoot, manifest);
|
|
5771
6059
|
if (!existsSync3(manifestPath) || !existsSync3(codeServerBin) || assets === undefined) {
|
|
5772
6060
|
return { ok: false };
|
|
@@ -5790,10 +6078,10 @@ function installedRuntime(cacheRoot, manifest) {
|
|
|
5790
6078
|
return { ok: false };
|
|
5791
6079
|
}
|
|
5792
6080
|
async function installRuntime(args) {
|
|
5793
|
-
|
|
5794
|
-
const tempRoot = mkdtempSync2(
|
|
5795
|
-
const archivePath =
|
|
5796
|
-
const extractRoot =
|
|
6081
|
+
mkdirSync4(args.cacheRoot, { recursive: true });
|
|
6082
|
+
const tempRoot = mkdtempSync2(join5(args.cacheRoot, ".install-"));
|
|
6083
|
+
const archivePath = join5(tempRoot, "runtime.tar.gz");
|
|
6084
|
+
const extractRoot = join5(tempRoot, "runtime");
|
|
5797
6085
|
try {
|
|
5798
6086
|
const downloaded = await downloadArchive({
|
|
5799
6087
|
kandanUrl: args.kandanUrl,
|
|
@@ -5805,7 +6093,7 @@ async function installRuntime(args) {
|
|
|
5805
6093
|
if (!downloaded.ok) {
|
|
5806
6094
|
return downloaded;
|
|
5807
6095
|
}
|
|
5808
|
-
|
|
6096
|
+
mkdirSync4(extractRoot, { recursive: true });
|
|
5809
6097
|
if (!await args.extractArchive(archivePath, extractRoot)) {
|
|
5810
6098
|
return { ok: false, reason: "archive_extract_failed" };
|
|
5811
6099
|
}
|
|
@@ -5818,13 +6106,13 @@ async function installRuntime(args) {
|
|
|
5818
6106
|
if (!assetsInstalled) {
|
|
5819
6107
|
return { ok: false, reason: "install_failed" };
|
|
5820
6108
|
}
|
|
5821
|
-
const manifestPath =
|
|
5822
|
-
const codeServerBin =
|
|
6109
|
+
const manifestPath = join5(extractRoot, args.manifest.manifestPath);
|
|
6110
|
+
const codeServerBin = join5(extractRoot, args.manifest.codeServerBinPath);
|
|
5823
6111
|
const assets = verifiedRuntimeAssetPaths(extractRoot, args.manifest);
|
|
5824
6112
|
if (!existsSync3(codeServerBin) || assets === undefined) {
|
|
5825
6113
|
return { ok: false, reason: "invalid_archive" };
|
|
5826
6114
|
}
|
|
5827
|
-
|
|
6115
|
+
mkdirSync4(dirname3(manifestPath), { recursive: true });
|
|
5828
6116
|
writeFileSync3(manifestPath, JSON.stringify({
|
|
5829
6117
|
version: args.manifest.version,
|
|
5830
6118
|
platform: args.manifest.platform,
|
|
@@ -5836,18 +6124,18 @@ async function installRuntime(args) {
|
|
|
5836
6124
|
}, null, 2));
|
|
5837
6125
|
const targetRoot = runtimeInstallRoot(args.cacheRoot, args.manifest);
|
|
5838
6126
|
rmSync(targetRoot, { recursive: true, force: true });
|
|
5839
|
-
|
|
6127
|
+
mkdirSync4(dirname3(targetRoot), { recursive: true });
|
|
5840
6128
|
renameSync(extractRoot, targetRoot);
|
|
5841
6129
|
return {
|
|
5842
6130
|
ok: true,
|
|
5843
6131
|
runtime: {
|
|
5844
6132
|
mode: "server_managed",
|
|
5845
6133
|
root: targetRoot,
|
|
5846
|
-
codeServerBin:
|
|
6134
|
+
codeServerBin: join5(targetRoot, args.manifest.codeServerBinPath),
|
|
5847
6135
|
assets: {
|
|
5848
|
-
collaborationExtensionTarball:
|
|
5849
|
-
collaborationServerTarball:
|
|
5850
|
-
documentStateExtensionDir:
|
|
6136
|
+
collaborationExtensionTarball: join5(targetRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz"),
|
|
6137
|
+
collaborationServerTarball: join5(targetRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz"),
|
|
6138
|
+
documentStateExtensionDir: join5(targetRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry")
|
|
5851
6139
|
}
|
|
5852
6140
|
}
|
|
5853
6141
|
};
|
|
@@ -5859,7 +6147,7 @@ async function installRuntime(args) {
|
|
|
5859
6147
|
}
|
|
5860
6148
|
async function materializeRuntimeAssets(args) {
|
|
5861
6149
|
for (const asset of args.manifest.assets) {
|
|
5862
|
-
const targetPath =
|
|
6150
|
+
const targetPath = join5(args.runtimeRoot, asset.path);
|
|
5863
6151
|
try {
|
|
5864
6152
|
const bytes = await runtimeAssetBytes({
|
|
5865
6153
|
kandanUrl: args.kandanUrl,
|
|
@@ -5869,7 +6157,7 @@ async function materializeRuntimeAssets(args) {
|
|
|
5869
6157
|
if (bytes === undefined) {
|
|
5870
6158
|
continue;
|
|
5871
6159
|
}
|
|
5872
|
-
|
|
6160
|
+
mkdirSync4(dirname3(targetPath), { recursive: true });
|
|
5873
6161
|
writeFileSync3(targetPath, bytes);
|
|
5874
6162
|
} catch (_error) {
|
|
5875
6163
|
return false;
|
|
@@ -5900,7 +6188,7 @@ async function downloadArchive(args) {
|
|
|
5900
6188
|
if (response.status !== 200 || response.body === null) {
|
|
5901
6189
|
return { ok: false, reason: "download_failed" };
|
|
5902
6190
|
}
|
|
5903
|
-
await pipeline(Readable.fromWeb(response.body),
|
|
6191
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream2(args.archivePath));
|
|
5904
6192
|
const sha256 = await fileSha256(args.archivePath);
|
|
5905
6193
|
if (sha256 !== args.manifest.archiveSha256) {
|
|
5906
6194
|
return { ok: false, reason: "checksum_mismatch" };
|
|
@@ -5912,11 +6200,34 @@ function sameOrigin(left, right) {
|
|
|
5912
6200
|
}
|
|
5913
6201
|
function extractTarGz(archivePath, destination) {
|
|
5914
6202
|
return new Promise((resolveExtract) => {
|
|
5915
|
-
const
|
|
6203
|
+
const command = "tar";
|
|
6204
|
+
const args = ["-xzf", archivePath, "-C", destination];
|
|
6205
|
+
writeCliAuditEvent("process.spawn", {
|
|
6206
|
+
command,
|
|
6207
|
+
args,
|
|
6208
|
+
purpose: "editor_runtime.extract"
|
|
6209
|
+
});
|
|
6210
|
+
const child = spawn4(command, args, {
|
|
5916
6211
|
stdio: ["ignore", "ignore", "ignore"]
|
|
5917
6212
|
});
|
|
6213
|
+
writeCliAuditEvent("process.spawned", {
|
|
6214
|
+
command,
|
|
6215
|
+
args,
|
|
6216
|
+
pid: child.pid,
|
|
6217
|
+
purpose: "editor_runtime.extract"
|
|
6218
|
+
});
|
|
5918
6219
|
child.on("error", () => resolveExtract(false));
|
|
5919
|
-
child.on("exit", (code) =>
|
|
6220
|
+
child.on("exit", (code, signal) => {
|
|
6221
|
+
writeCliAuditEvent("process.exit", {
|
|
6222
|
+
command,
|
|
6223
|
+
args,
|
|
6224
|
+
pid: child.pid,
|
|
6225
|
+
code,
|
|
6226
|
+
signal,
|
|
6227
|
+
purpose: "editor_runtime.extract"
|
|
6228
|
+
});
|
|
6229
|
+
resolveExtract(code === 0);
|
|
6230
|
+
});
|
|
5920
6231
|
});
|
|
5921
6232
|
}
|
|
5922
6233
|
function fileSha256(path) {
|
|
@@ -5932,14 +6243,14 @@ function runtimeInstallRoot(cacheRoot, manifest) {
|
|
|
5932
6243
|
return resolve4(cacheRoot, manifest.platform, manifest.archiveSha256);
|
|
5933
6244
|
}
|
|
5934
6245
|
function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
5935
|
-
const collaborationExtensionTarball =
|
|
5936
|
-
const collaborationServerTarball =
|
|
5937
|
-
const documentStateExtensionDir =
|
|
6246
|
+
const collaborationExtensionTarball = join5(runtimeRoot, "kandan", "editor_extensions", "typefox.open-collaboration-tools.tar.gz");
|
|
6247
|
+
const collaborationServerTarball = join5(runtimeRoot, "kandan", "editor_servers", "open-collaboration-server.tar.gz");
|
|
6248
|
+
const documentStateExtensionDir = join5(runtimeRoot, "kandan", "editor_extensions", "kandan.document-state-telemetry");
|
|
5938
6249
|
const codeServerRoot = codeServerRuntimeRoot(manifest.codeServerBinPath);
|
|
5939
6250
|
const requiredPaths = [
|
|
5940
6251
|
manifest.codeServerBinPath,
|
|
5941
|
-
|
|
5942
|
-
|
|
6252
|
+
join5(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda.js"),
|
|
6253
|
+
join5(codeServerRoot, "lib", "vscode", "node_modules", "vsda", "rust", "web", "vsda_bg.wasm"),
|
|
5943
6254
|
"kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz",
|
|
5944
6255
|
"kandan/editor_servers/open-collaboration-server.tar.gz",
|
|
5945
6256
|
"kandan/editor_extensions/kandan.document-state-telemetry/package.json",
|
|
@@ -5951,7 +6262,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
|
5951
6262
|
if (expectedSha256 === undefined && relativePath !== manifest.codeServerBinPath) {
|
|
5952
6263
|
return;
|
|
5953
6264
|
}
|
|
5954
|
-
const absolutePath =
|
|
6265
|
+
const absolutePath = join5(runtimeRoot, relativePath);
|
|
5955
6266
|
if (!existsSync3(absolutePath)) {
|
|
5956
6267
|
return;
|
|
5957
6268
|
}
|
|
@@ -5970,7 +6281,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
|
5970
6281
|
}
|
|
5971
6282
|
function codeServerRuntimeRoot(codeServerBinPath) {
|
|
5972
6283
|
const normalized = codeServerBinPath.replaceAll("\\", "/");
|
|
5973
|
-
return normalized === "bin/code-server" ? "." : normalized.endsWith("/bin/code-server") ? normalized.slice(0, -"/bin/code-server".length) || "." :
|
|
6284
|
+
return normalized === "bin/code-server" ? "." : normalized.endsWith("/bin/code-server") ? normalized.slice(0, -"/bin/code-server".length) || "." : dirname3(normalized);
|
|
5974
6285
|
}
|
|
5975
6286
|
function manifestAssetChecksums(assets) {
|
|
5976
6287
|
const checksums = new Map;
|
|
@@ -5983,7 +6294,7 @@ function fileSha256Sync(path) {
|
|
|
5983
6294
|
return createHash2("sha256").update(readFileSync2(path)).digest("hex");
|
|
5984
6295
|
}
|
|
5985
6296
|
function defaultEditorRuntimeCacheRoot() {
|
|
5986
|
-
return
|
|
6297
|
+
return join5(homedir4(), ".linzumi", "editor-runtimes");
|
|
5987
6298
|
}
|
|
5988
6299
|
function nonEmptyString(value) {
|
|
5989
6300
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
|
|
@@ -5996,10 +6307,24 @@ function sha256String(value) {
|
|
|
5996
6307
|
// src/dependencyStatus.ts
|
|
5997
6308
|
function probeTool(command, cwd) {
|
|
5998
6309
|
return new Promise((resolve5) => {
|
|
5999
|
-
const
|
|
6310
|
+
const args = ["--version"];
|
|
6311
|
+
writeCliAuditEvent("process.spawn", {
|
|
6312
|
+
command,
|
|
6313
|
+
args,
|
|
6314
|
+
cwd,
|
|
6315
|
+
purpose: "dependency_probe"
|
|
6316
|
+
});
|
|
6317
|
+
const child = spawn5(command, args, {
|
|
6000
6318
|
cwd,
|
|
6001
6319
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6002
6320
|
});
|
|
6321
|
+
writeCliAuditEvent("process.spawned", {
|
|
6322
|
+
command,
|
|
6323
|
+
args,
|
|
6324
|
+
cwd,
|
|
6325
|
+
pid: child.pid,
|
|
6326
|
+
purpose: "dependency_probe"
|
|
6327
|
+
});
|
|
6003
6328
|
let stdout = "";
|
|
6004
6329
|
let stderr = "";
|
|
6005
6330
|
let resolved = false;
|
|
@@ -6013,6 +6338,15 @@ function probeTool(command, cwd) {
|
|
|
6013
6338
|
};
|
|
6014
6339
|
const timeout = setTimeout(() => {
|
|
6015
6340
|
child.kill("SIGKILL");
|
|
6341
|
+
writeCliAuditEvent("process.exit", {
|
|
6342
|
+
command,
|
|
6343
|
+
args,
|
|
6344
|
+
cwd,
|
|
6345
|
+
pid: child.pid,
|
|
6346
|
+
code: null,
|
|
6347
|
+
signal: "SIGKILL",
|
|
6348
|
+
purpose: "dependency_probe"
|
|
6349
|
+
});
|
|
6016
6350
|
finish({ command, available: false });
|
|
6017
6351
|
}, 1000);
|
|
6018
6352
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -6022,9 +6356,28 @@ function probeTool(command, cwd) {
|
|
|
6022
6356
|
stderr += chunk.toString();
|
|
6023
6357
|
});
|
|
6024
6358
|
child.on("error", () => {
|
|
6359
|
+
writeCliAuditEvent("process.exit", {
|
|
6360
|
+
command,
|
|
6361
|
+
args,
|
|
6362
|
+
cwd,
|
|
6363
|
+
pid: child.pid,
|
|
6364
|
+
code: null,
|
|
6365
|
+
signal: null,
|
|
6366
|
+
error: "spawn_failed",
|
|
6367
|
+
purpose: "dependency_probe"
|
|
6368
|
+
});
|
|
6025
6369
|
finish({ command, available: false });
|
|
6026
6370
|
});
|
|
6027
|
-
child.on("exit", (code) => {
|
|
6371
|
+
child.on("exit", (code, signal) => {
|
|
6372
|
+
writeCliAuditEvent("process.exit", {
|
|
6373
|
+
command,
|
|
6374
|
+
args,
|
|
6375
|
+
cwd,
|
|
6376
|
+
pid: child.pid,
|
|
6377
|
+
code,
|
|
6378
|
+
signal,
|
|
6379
|
+
purpose: "dependency_probe"
|
|
6380
|
+
});
|
|
6028
6381
|
if (code !== 0) {
|
|
6029
6382
|
finish({ command, available: false });
|
|
6030
6383
|
return;
|
|
@@ -6073,7 +6426,7 @@ function codeServerDependencyStatus(args) {
|
|
|
6073
6426
|
}
|
|
6074
6427
|
function assertStartDependencies(status) {
|
|
6075
6428
|
if (!status.bun.available) {
|
|
6076
|
-
throw new Error("Node.js is not available. Install Node.js 20+, then rerun
|
|
6429
|
+
throw new Error("Node.js is not available. Install Node.js 20+, then rerun the bootstrap command.");
|
|
6077
6430
|
}
|
|
6078
6431
|
if (!status.codex.available) {
|
|
6079
6432
|
throw new Error(`Codex is not available at ${status.codex.command}. Install Codex or pass --codex-bin <path>.`);
|
|
@@ -6337,35 +6690,6 @@ function waitForOpen2(websocket) {
|
|
|
6337
6690
|
});
|
|
6338
6691
|
}
|
|
6339
6692
|
|
|
6340
|
-
// src/runnerLogger.ts
|
|
6341
|
-
import { openSync } from "node:fs";
|
|
6342
|
-
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
6343
|
-
import { dirname as dirname3 } from "node:path";
|
|
6344
|
-
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
6345
|
-
function createRunnerLogger(logFile, consoleReporter) {
|
|
6346
|
-
mkdirSync4(dirname3(logFile), { recursive: true });
|
|
6347
|
-
const fd = openSync(logFile, "a");
|
|
6348
|
-
const stream = createWriteStream2("", { fd, flags: "a", autoClose: true });
|
|
6349
|
-
const logger = (event, payload) => {
|
|
6350
|
-
stream.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...payload })}
|
|
6351
|
-
`, "utf8");
|
|
6352
|
-
consoleReporter?.(event, payload);
|
|
6353
|
-
};
|
|
6354
|
-
Object.defineProperty(logger, "close", {
|
|
6355
|
-
value: () => closeStream(stream)
|
|
6356
|
-
});
|
|
6357
|
-
return logger;
|
|
6358
|
-
}
|
|
6359
|
-
function closeStream(stream) {
|
|
6360
|
-
if (stream.closed || stream.destroyed) {
|
|
6361
|
-
return Promise.resolve();
|
|
6362
|
-
}
|
|
6363
|
-
return new Promise((resolve5, reject) => {
|
|
6364
|
-
stream.once("error", reject);
|
|
6365
|
-
stream.end(resolve5);
|
|
6366
|
-
});
|
|
6367
|
-
}
|
|
6368
|
-
|
|
6369
6693
|
// src/runnerConsoleReporter.ts
|
|
6370
6694
|
function reportRunnerConsoleEvent(event, payload) {
|
|
6371
6695
|
const line = formatRunnerConsoleEvent(event, payload);
|
|
@@ -6487,6 +6811,26 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6487
6811
|
const allowedForwardPorts = options.allowedForwardPorts ?? [];
|
|
6488
6812
|
const liveForwardPorts = new Set(allowedForwardPorts);
|
|
6489
6813
|
const managedForwardPorts = new Set;
|
|
6814
|
+
const forwardPortAttributions = new Map;
|
|
6815
|
+
const setForwardPortAttribution = (port, attribution) => {
|
|
6816
|
+
forwardPortAttributions.set(port, {
|
|
6817
|
+
kandanThreadId: attribution.kandanThreadId ?? null,
|
|
6818
|
+
codexThreadId: attribution.codexThreadId ?? null,
|
|
6819
|
+
channelSlug: attribution.channelSlug ?? null
|
|
6820
|
+
});
|
|
6821
|
+
};
|
|
6822
|
+
const clearForwardPortAttribution = (port) => {
|
|
6823
|
+
forwardPortAttributions.delete(port);
|
|
6824
|
+
};
|
|
6825
|
+
const buildForwardPortAttributionPayload = () => Array.from(liveForwardPorts).sort((left, right) => left - right).map((port) => {
|
|
6826
|
+
const attribution = forwardPortAttributions.get(port);
|
|
6827
|
+
return {
|
|
6828
|
+
port,
|
|
6829
|
+
kandanThreadId: attribution?.kandanThreadId ?? null,
|
|
6830
|
+
codexThreadId: attribution?.codexThreadId ?? null,
|
|
6831
|
+
channelSlug: attribution?.channelSlug ?? null
|
|
6832
|
+
};
|
|
6833
|
+
});
|
|
6490
6834
|
const allowedCwds = { value: [...options.allowedCwds] };
|
|
6491
6835
|
const localEditorState = {
|
|
6492
6836
|
value: { status: "disabled" }
|
|
@@ -6506,6 +6850,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6506
6850
|
allowedCwdSuggestions: allowedCwdSuggestions(options.cwd, allowedCwds.value),
|
|
6507
6851
|
portForwarding: liveForwardPorts.size > 0,
|
|
6508
6852
|
allowedPorts: Array.from(liveForwardPorts).sort((left, right) => left - right),
|
|
6853
|
+
forwardedPortAttributions: buildForwardPortAttributionPayload(),
|
|
6509
6854
|
toolStatus: options.dependencyStatus === undefined ? null : dependencyStatusPayload(options.dependencyStatus),
|
|
6510
6855
|
editorRuntime: options.dependencyStatus?.editorRuntime === undefined ? null : dependencyStatusPayload(options.dependencyStatus).editorRuntime,
|
|
6511
6856
|
...localEditorCapabilities(options.editorRuntime, allowedCwds.value, localEditorState.value)
|
|
@@ -6613,12 +6958,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6613
6958
|
enablePortForwardWatch: true,
|
|
6614
6959
|
initialForwardPorts: allowedForwardPorts,
|
|
6615
6960
|
suppressedForwardPorts: () => Array.from(managedForwardPorts),
|
|
6616
|
-
onForwardPortApproved: (port) => {
|
|
6961
|
+
onForwardPortApproved: (port, attribution) => {
|
|
6617
6962
|
liveForwardPorts.add(port);
|
|
6963
|
+
setForwardPortAttribution(port, attribution);
|
|
6618
6964
|
return capabilitiesPayload();
|
|
6619
6965
|
},
|
|
6620
6966
|
onForwardPortRevoked: (port) => {
|
|
6621
6967
|
liveForwardPorts.delete(port);
|
|
6968
|
+
clearForwardPortAttribution(port);
|
|
6622
6969
|
return capabilitiesPayload();
|
|
6623
6970
|
},
|
|
6624
6971
|
channelSession: options.channelSession
|
|
@@ -6660,12 +7007,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6660
7007
|
enablePortForwardWatch: true,
|
|
6661
7008
|
initialForwardPorts: allowedForwardPorts,
|
|
6662
7009
|
suppressedForwardPorts: () => Array.from(managedForwardPorts),
|
|
6663
|
-
onForwardPortApproved: (port) => {
|
|
7010
|
+
onForwardPortApproved: (port, attribution) => {
|
|
6664
7011
|
liveForwardPorts.add(port);
|
|
7012
|
+
setForwardPortAttribution(port, attribution);
|
|
6665
7013
|
return capabilitiesPayload();
|
|
6666
7014
|
},
|
|
6667
7015
|
onForwardPortRevoked: (port) => {
|
|
6668
7016
|
liveForwardPorts.delete(port);
|
|
7017
|
+
clearForwardPortAttribution(port);
|
|
6669
7018
|
return capabilitiesPayload();
|
|
6670
7019
|
},
|
|
6671
7020
|
channelSession: {
|
|
@@ -6761,7 +7110,17 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6761
7110
|
return;
|
|
6762
7111
|
}
|
|
6763
7112
|
if (isForwardHttpRequestControl(control)) {
|
|
6764
|
-
handleForwardHttpRequest(control, Array.from(liveForwardPorts)).then((response) =>
|
|
7113
|
+
handleForwardHttpRequest(control, Array.from(liveForwardPorts)).then((response) => {
|
|
7114
|
+
log("kandan.forward_response", {
|
|
7115
|
+
requestId: control.requestId,
|
|
7116
|
+
port: control.port,
|
|
7117
|
+
path: control.path,
|
|
7118
|
+
status: response.status,
|
|
7119
|
+
ok: response.ok,
|
|
7120
|
+
location: forwardedHeaderValue(response.headers, "location")
|
|
7121
|
+
});
|
|
7122
|
+
return kandan.push(topic, "forward:http_response", response);
|
|
7123
|
+
}).catch((error) => kandan.push(topic, "forward:http_response", {
|
|
6765
7124
|
requestId: control.requestId,
|
|
6766
7125
|
ok: false,
|
|
6767
7126
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -6928,7 +7287,7 @@ function normalizedWorkDescription(value) {
|
|
|
6928
7287
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
6929
7288
|
}
|
|
6930
7289
|
function makeRunnerLogger(options) {
|
|
6931
|
-
return createRunnerLogger(options.logFile ??
|
|
7290
|
+
return createRunnerLogger(options.logFile ?? join6(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
|
|
6932
7291
|
}
|
|
6933
7292
|
function installCleanupHandlers(close) {
|
|
6934
7293
|
const closeAndExit = () => {
|
|
@@ -6953,11 +7312,44 @@ function installCleanupHandlers(close) {
|
|
|
6953
7312
|
};
|
|
6954
7313
|
}
|
|
6955
7314
|
function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
|
|
6956
|
-
|
|
7315
|
+
const args = codexTuiArgs(codexUrl, codexThreadId, session, fast);
|
|
7316
|
+
writeCliAuditEvent("process.spawn", {
|
|
7317
|
+
command: codexBin,
|
|
7318
|
+
args,
|
|
7319
|
+
cwd,
|
|
7320
|
+
purpose: "codex.tui"
|
|
7321
|
+
});
|
|
7322
|
+
const child = spawn6(codexBin, args, {
|
|
6957
7323
|
cwd,
|
|
6958
7324
|
env: process.env,
|
|
6959
7325
|
stdio: "inherit"
|
|
6960
7326
|
});
|
|
7327
|
+
writeCliAuditEvent("process.spawned", {
|
|
7328
|
+
command: codexBin,
|
|
7329
|
+
args,
|
|
7330
|
+
cwd,
|
|
7331
|
+
pid: child.pid,
|
|
7332
|
+
purpose: "codex.tui"
|
|
7333
|
+
});
|
|
7334
|
+
child.once("exit", (code, signal) => {
|
|
7335
|
+
writeCliAuditEvent("process.exit", {
|
|
7336
|
+
command: codexBin,
|
|
7337
|
+
args,
|
|
7338
|
+
cwd,
|
|
7339
|
+
pid: child.pid,
|
|
7340
|
+
code,
|
|
7341
|
+
signal,
|
|
7342
|
+
purpose: "codex.tui"
|
|
7343
|
+
});
|
|
7344
|
+
});
|
|
7345
|
+
return child;
|
|
7346
|
+
}
|
|
7347
|
+
function forwardedHeaderValue(headers, name) {
|
|
7348
|
+
if (!Array.isArray(headers)) {
|
|
7349
|
+
return;
|
|
7350
|
+
}
|
|
7351
|
+
const header = headers.find((value) => isJsonObject(value) && typeof value.name === "string" && value.name.toLowerCase() === name.toLowerCase() && typeof value.value === "string");
|
|
7352
|
+
return isJsonObject(header) && typeof header.value === "string" ? header.value : undefined;
|
|
6961
7353
|
}
|
|
6962
7354
|
function codexTuiArgs(codexUrl, codexThreadId, session, fast) {
|
|
6963
7355
|
const overrides = codexTuiConfigArgs(session, fast);
|
|
@@ -7206,11 +7598,11 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
7206
7598
|
|
|
7207
7599
|
// src/authCache.ts
|
|
7208
7600
|
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7209
|
-
import { homedir as
|
|
7210
|
-
import { dirname as dirname4, join as
|
|
7601
|
+
import { homedir as homedir5 } from "node:os";
|
|
7602
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
7211
7603
|
function defaultAuthFilePath() {
|
|
7212
|
-
const base = process.env.KANDAN_HOME ??
|
|
7213
|
-
return
|
|
7604
|
+
const base = process.env.KANDAN_HOME ?? join7(homedir5(), ".kandan");
|
|
7605
|
+
return join7(base, "auth.json");
|
|
7214
7606
|
}
|
|
7215
7607
|
function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
|
|
7216
7608
|
if (!existsSync4(authFilePath)) {
|
|
@@ -7345,11 +7737,11 @@ async function acquireAndCacheToken(args) {
|
|
|
7345
7737
|
|
|
7346
7738
|
// src/localConfig.ts
|
|
7347
7739
|
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync4, realpathSync as realpathSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7348
|
-
import { homedir as
|
|
7740
|
+
import { homedir as homedir6 } from "node:os";
|
|
7349
7741
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
7350
7742
|
function localConfigPath(env = process.env) {
|
|
7351
7743
|
const override = env.LINZUMI_CONFIG_FILE;
|
|
7352
|
-
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(
|
|
7744
|
+
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir6(), ".linzumi", "config.json");
|
|
7353
7745
|
}
|
|
7354
7746
|
function readLocalConfig(path = localConfigPath()) {
|
|
7355
7747
|
if (!existsSync5(path)) {
|
|
@@ -7410,6 +7802,10 @@ function realpathOrResolved(pathValue) {
|
|
|
7410
7802
|
}
|
|
7411
7803
|
}
|
|
7412
7804
|
|
|
7805
|
+
// src/defaultUrls.ts
|
|
7806
|
+
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
7807
|
+
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
7808
|
+
|
|
7413
7809
|
// src/kandanTls.ts
|
|
7414
7810
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
7415
7811
|
import { Agent } from "undici";
|
|
@@ -7455,9 +7851,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
7455
7851
|
|
|
7456
7852
|
// src/agentBootstrap.ts
|
|
7457
7853
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7458
|
-
import { dirname as dirname6, join as
|
|
7459
|
-
import { homedir as
|
|
7460
|
-
var defaultApiUrl = "https://serve.linzumi.com";
|
|
7854
|
+
import { dirname as dirname6, join as join8 } from "node:path";
|
|
7855
|
+
import { homedir as homedir7 } from "node:os";
|
|
7461
7856
|
async function runAgentCliCommand(args, deps = {
|
|
7462
7857
|
fetchImpl: fetch,
|
|
7463
7858
|
stdout: process.stdout,
|
|
@@ -7475,6 +7870,12 @@ async function runAgentCliCommand(args, deps = {
|
|
|
7475
7870
|
case "claim":
|
|
7476
7871
|
await runClaim(command, deps);
|
|
7477
7872
|
return;
|
|
7873
|
+
case "loginLinkIssue":
|
|
7874
|
+
await runLoginLinkIssue(command, deps);
|
|
7875
|
+
return;
|
|
7876
|
+
case "loginLinkClaim":
|
|
7877
|
+
await runLoginLinkClaim(command, deps);
|
|
7878
|
+
return;
|
|
7478
7879
|
case "threadNew":
|
|
7479
7880
|
await runThreadNew(command, deps);
|
|
7480
7881
|
return;
|
|
@@ -7529,6 +7930,28 @@ function parseAgentCommand(args) {
|
|
|
7529
7930
|
tokenFile: agentTokenFile(parsed.flags)
|
|
7530
7931
|
};
|
|
7531
7932
|
}
|
|
7933
|
+
case "login-link": {
|
|
7934
|
+
const [subcommand, ...subcommandArgs] = rest;
|
|
7935
|
+
const parsed = parseAgentArgs(subcommandArgs);
|
|
7936
|
+
switch (subcommand) {
|
|
7937
|
+
case "issue":
|
|
7938
|
+
return {
|
|
7939
|
+
kind: "loginLinkIssue",
|
|
7940
|
+
apiUrl: agentApiUrl(parsed.flags),
|
|
7941
|
+
email: requiredFlag(parsed.flags, "email"),
|
|
7942
|
+
workspaceId: requiredFlag(parsed.flags, "workspace")
|
|
7943
|
+
};
|
|
7944
|
+
case "claim":
|
|
7945
|
+
return {
|
|
7946
|
+
kind: "loginLinkClaim",
|
|
7947
|
+
apiUrl: agentApiUrl(parsed.flags),
|
|
7948
|
+
pendingId: requiredFlag(parsed.flags, "pending"),
|
|
7949
|
+
code: requiredFlag(parsed.flags, "code")
|
|
7950
|
+
};
|
|
7951
|
+
default:
|
|
7952
|
+
throw new Error("linzumi login-link supports: issue, claim");
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7532
7955
|
case "thread": {
|
|
7533
7956
|
const [subcommand, ...subcommandArgs] = rest;
|
|
7534
7957
|
if (subcommand !== "new") {
|
|
@@ -7791,6 +8214,7 @@ async function runClaim(command, deps) {
|
|
|
7791
8214
|
`);
|
|
7792
8215
|
deps.stdout.write(`login_url: ${tokenFile.loginUrl}
|
|
7793
8216
|
`);
|
|
8217
|
+
writeHumanLoginUrlWarning(deps);
|
|
7794
8218
|
deps.stdout.write(`channel_url: ${tokenFile.channelUrl}
|
|
7795
8219
|
`);
|
|
7796
8220
|
deps.stdout.write(`support_channel_id: ${tokenFile.supportChannelId}
|
|
@@ -7798,6 +8222,37 @@ async function runClaim(command, deps) {
|
|
|
7798
8222
|
deps.stdout.write(`support_channel_url: ${tokenFile.supportChannelUrl}
|
|
7799
8223
|
`);
|
|
7800
8224
|
}
|
|
8225
|
+
async function runLoginLinkIssue(command, deps) {
|
|
8226
|
+
const response = await postJson(command.apiUrl, "/agent/login-link/issue", {
|
|
8227
|
+
email: command.email,
|
|
8228
|
+
workspace_id: command.workspaceId
|
|
8229
|
+
}, undefined, deps.fetchImpl);
|
|
8230
|
+
const pendingId = requiredString(response, "pending_id");
|
|
8231
|
+
const expiresInSeconds = numberValue2(response.expires_in_seconds) ?? 600;
|
|
8232
|
+
const codeFormatHint = stringValue(response.code_format_hint) ?? "XXXX-XXXX";
|
|
8233
|
+
deps.stdout.write(`pending_id: ${pendingId}
|
|
8234
|
+
`);
|
|
8235
|
+
deps.stdout.write(`Code emailed to ${command.email} (format ${codeFormatHint}, expires in ${Math.ceil(expiresInSeconds / 60)}m)
|
|
8236
|
+
`);
|
|
8237
|
+
}
|
|
8238
|
+
async function runLoginLinkClaim(command, deps) {
|
|
8239
|
+
const response = await postJson(command.apiUrl, "/agent/login-link/claim", {
|
|
8240
|
+
pending_id: command.pendingId,
|
|
8241
|
+
code: command.code
|
|
8242
|
+
}, undefined, deps.fetchImpl);
|
|
8243
|
+
deps.stdout.write(`workspace_id: ${requiredString(response, "workspace_id")}
|
|
8244
|
+
`);
|
|
8245
|
+
const workspaceName = stringValue(response.workspace_name);
|
|
8246
|
+
if (workspaceName !== undefined) {
|
|
8247
|
+
deps.stdout.write(`workspace_name: ${workspaceName}
|
|
8248
|
+
`);
|
|
8249
|
+
}
|
|
8250
|
+
deps.stdout.write(`login_url: ${requiredString(response, "login_url")}
|
|
8251
|
+
`);
|
|
8252
|
+
writeHumanLoginUrlWarning(deps);
|
|
8253
|
+
deps.stdout.write(`channel_url: ${requiredString(response, "channel_url")}
|
|
8254
|
+
`);
|
|
8255
|
+
}
|
|
7801
8256
|
async function runThreadNew(command, deps) {
|
|
7802
8257
|
const tokenFile = readTokenFile(command.tokenFile, deps.readTextFile);
|
|
7803
8258
|
const response = await postJson(command.apiUrl, "/agent/threads", {
|
|
@@ -7988,13 +8443,13 @@ function numberValue2(value) {
|
|
|
7988
8443
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
7989
8444
|
}
|
|
7990
8445
|
function agentApiUrl(flags) {
|
|
7991
|
-
return flags.get("api-url") ??
|
|
8446
|
+
return flags.get("api-url") ?? defaultLinzumiHttpUrl;
|
|
7992
8447
|
}
|
|
7993
8448
|
function agentTokenFile(flags) {
|
|
7994
8449
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
7995
8450
|
}
|
|
7996
8451
|
function defaultAgentTokenFilePath() {
|
|
7997
|
-
return
|
|
8452
|
+
return join8(homedir7(), ".linzumi", "agent-token.json");
|
|
7998
8453
|
}
|
|
7999
8454
|
function normalizedApiUrl(apiUrl) {
|
|
8000
8455
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -8045,12 +8500,20 @@ function writeTokenFile(path, tokenFile, writeTextFile2) {
|
|
|
8045
8500
|
writeTextFile2(path, `${JSON.stringify(tokenFile, null, 2)}
|
|
8046
8501
|
`);
|
|
8047
8502
|
}
|
|
8503
|
+
function writeHumanLoginUrlWarning(deps) {
|
|
8504
|
+
deps.stdout.write(`This is a one-time human login link. Do not open it from automation.
|
|
8505
|
+
`);
|
|
8506
|
+
deps.stdout.write(`Give it directly to the human who will use the workspace.
|
|
8507
|
+
`);
|
|
8508
|
+
}
|
|
8048
8509
|
function agentHelpText() {
|
|
8049
8510
|
return `Linzumi agent bootstrap
|
|
8050
8511
|
|
|
8051
8512
|
Usage:
|
|
8052
8513
|
linzumi signup --email <email> --agent-name <name> [--workspace-name <name>] [--api-url <url>]
|
|
8053
8514
|
linzumi claim --pending <pending_id> --code <XXXX-XXXX> [--api-url <url>]
|
|
8515
|
+
linzumi login-link issue --email <email> --workspace <workspace_id> [--api-url <url>]
|
|
8516
|
+
linzumi login-link claim --pending <pending_id> --code <XXXX-XXXX> [--api-url <url>]
|
|
8054
8517
|
linzumi thread new <title> --message <message> [--api-url <url>]
|
|
8055
8518
|
linzumi channel post <channel_id> <message> [--api-url <url>]
|
|
8056
8519
|
linzumi post <thread_id> <message> [--kind progress|question]
|
|
@@ -8061,10 +8524,14 @@ Usage:
|
|
|
8061
8524
|
linzumi editor open <thread_id> --runner <runner_id> --cwd <path>
|
|
8062
8525
|
|
|
8063
8526
|
Options:
|
|
8064
|
-
--api-url <url> Agent API origin, default ${
|
|
8527
|
+
--api-url <url> Agent API origin, default ${defaultLinzumiHttpUrl}
|
|
8065
8528
|
--agent-token-file <path> Token cache path, default ~/.linzumi/agent-token.json
|
|
8066
8529
|
--workspace-name <name> Human-readable workspace name requested during signup
|
|
8067
8530
|
|
|
8531
|
+
Human login:
|
|
8532
|
+
Human browser login recovery; prints one-time links for the human only.
|
|
8533
|
+
Do not open login_url from Codex, CLI automation, or an unconfirmed browser profile.
|
|
8534
|
+
|
|
8068
8535
|
Launch target:
|
|
8069
8536
|
zero-to-hello-world-pr+editor in under 3 minutes.
|
|
8070
8537
|
`;
|
|
@@ -8072,7 +8539,7 @@ Launch target:
|
|
|
8072
8539
|
|
|
8073
8540
|
// src/helloLinzumiProject.ts
|
|
8074
8541
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8075
|
-
import { dirname as dirname7, join as
|
|
8542
|
+
import { dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
|
|
8076
8543
|
import { fileURLToPath } from "node:url";
|
|
8077
8544
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
8078
8545
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -8081,7 +8548,7 @@ var defaultHelloLinzumiPort = 8787;
|
|
|
8081
8548
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
8082
8549
|
var markerFile = ".linzumi-demo-project";
|
|
8083
8550
|
var moduleDir = dirname7(fileURLToPath(import.meta.url));
|
|
8084
|
-
var linzumiLogoSvg = readFileSync7(
|
|
8551
|
+
var linzumiLogoSvg = readFileSync7(join9(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
8085
8552
|
function createHelloLinzumiProject(input = {}) {
|
|
8086
8553
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
8087
8554
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -8089,9 +8556,9 @@ function createHelloLinzumiProject(input = {}) {
|
|
|
8089
8556
|
const host = normalizeHost(options.host);
|
|
8090
8557
|
assertTcpPort(port);
|
|
8091
8558
|
assertWritableDemoRoot(root, options.reset === true);
|
|
8092
|
-
mkdirSync8(
|
|
8559
|
+
mkdirSync8(join9(root, "src"), { recursive: true });
|
|
8093
8560
|
for (const file of demoFiles({ root, port, host })) {
|
|
8094
|
-
writeFileSync7(
|
|
8561
|
+
writeFileSync7(join9(root, file.path), file.content, "utf8");
|
|
8095
8562
|
}
|
|
8096
8563
|
return {
|
|
8097
8564
|
root,
|
|
@@ -8135,7 +8602,7 @@ function assertWritableDemoRoot(root, reset) {
|
|
|
8135
8602
|
if (!existsSync8(root)) {
|
|
8136
8603
|
return;
|
|
8137
8604
|
}
|
|
8138
|
-
const markerPath =
|
|
8605
|
+
const markerPath = join9(root, markerFile);
|
|
8139
8606
|
const isDemoRoot = existsSync8(markerPath) && readFileSync7(markerPath, "utf8").trim() === "hello-linzumi";
|
|
8140
8607
|
if (isDemoRoot && reset) {
|
|
8141
8608
|
rmSync2(root, { recursive: true, force: true });
|
|
@@ -8643,19 +9110,19 @@ import {
|
|
|
8643
9110
|
watch,
|
|
8644
9111
|
writeFileSync as writeFileSync8
|
|
8645
9112
|
} from "node:fs";
|
|
8646
|
-
import { homedir as
|
|
8647
|
-
import { dirname as dirname8, join as
|
|
9113
|
+
import { homedir as homedir8 } from "node:os";
|
|
9114
|
+
import { dirname as dirname8, join as join10, resolve as resolve7 } from "node:path";
|
|
8648
9115
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
8649
9116
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8650
9117
|
var connectedMarker = "Runner connected:";
|
|
8651
9118
|
function commanderStatusDir() {
|
|
8652
|
-
return
|
|
9119
|
+
return join10(homedir8(), ".linzumi", "commanders");
|
|
8653
9120
|
}
|
|
8654
9121
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
8655
|
-
return
|
|
9122
|
+
return join10(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
8656
9123
|
}
|
|
8657
9124
|
function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
8658
|
-
return
|
|
9125
|
+
return join10(statusDir, `${safeRunnerId(runnerId)}.log`);
|
|
8659
9126
|
}
|
|
8660
9127
|
function startCommanderDaemon(options) {
|
|
8661
9128
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
@@ -8677,11 +9144,24 @@ function startCommanderDaemon(options) {
|
|
|
8677
9144
|
mkdirSync9(dirname8(logFile), { recursive: true });
|
|
8678
9145
|
const out = openSync2(logFile, "a");
|
|
8679
9146
|
const err = openSync2(logFile, "a");
|
|
9147
|
+
writeCliAuditEvent("process.spawn", {
|
|
9148
|
+
command: nodeBin,
|
|
9149
|
+
args: command,
|
|
9150
|
+
cwd: options.cwd,
|
|
9151
|
+
purpose: "commander_daemon.start"
|
|
9152
|
+
}, { sessionId: options.runnerId });
|
|
8680
9153
|
const child = (options.spawnImpl ?? spawn7)(nodeBin, command, {
|
|
8681
9154
|
detached: true,
|
|
8682
9155
|
stdio: ["ignore", out, err],
|
|
8683
9156
|
env: process.env
|
|
8684
9157
|
});
|
|
9158
|
+
writeCliAuditEvent("process.spawned", {
|
|
9159
|
+
command: nodeBin,
|
|
9160
|
+
args: command,
|
|
9161
|
+
cwd: options.cwd,
|
|
9162
|
+
pid: child.pid,
|
|
9163
|
+
purpose: "commander_daemon.start"
|
|
9164
|
+
}, { sessionId: options.runnerId });
|
|
8685
9165
|
closeSync(out);
|
|
8686
9166
|
closeSync(err);
|
|
8687
9167
|
child.unref();
|
|
@@ -8805,10 +9285,22 @@ function processMatchesRecord(record, processIdentityReader) {
|
|
|
8805
9285
|
}
|
|
8806
9286
|
function readProcessIdentity(pid) {
|
|
8807
9287
|
try {
|
|
8808
|
-
const
|
|
9288
|
+
const command = "ps";
|
|
9289
|
+
const args = ["-p", String(pid), "-o", "lstart=", "-o", "command="];
|
|
9290
|
+
writeCliAuditEvent("process.exec_file", {
|
|
9291
|
+
command,
|
|
9292
|
+
args,
|
|
9293
|
+
purpose: "commander_daemon.process_identity"
|
|
9294
|
+
});
|
|
9295
|
+
const output = execFileSync(command, args, {
|
|
8809
9296
|
encoding: "utf8",
|
|
8810
9297
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8811
9298
|
}).trim();
|
|
9299
|
+
writeCliAuditEvent("process.exec_file_completed", {
|
|
9300
|
+
command,
|
|
9301
|
+
args,
|
|
9302
|
+
purpose: "commander_daemon.process_identity"
|
|
9303
|
+
});
|
|
8812
9304
|
if (output === "") {
|
|
8813
9305
|
return;
|
|
8814
9306
|
}
|
|
@@ -8862,6 +9354,7 @@ async function waitForFileChangeOrTimeout(path, deadline, now, ready2 = () => fa
|
|
|
8862
9354
|
// src/index.ts
|
|
8863
9355
|
var flagDefinitions = new Map([
|
|
8864
9356
|
["version", { kind: "boolean" }],
|
|
9357
|
+
["linzumi-url", { kind: "value" }],
|
|
8865
9358
|
["kandan-url", { kind: "value" }],
|
|
8866
9359
|
["token", { kind: "value" }],
|
|
8867
9360
|
["runner-id", { kind: "value" }],
|
|
@@ -8871,6 +9364,7 @@ var flagDefinitions = new Map([
|
|
|
8871
9364
|
["launch-tui", { kind: "boolean" }],
|
|
8872
9365
|
["workspace", { kind: "value" }],
|
|
8873
9366
|
["channel", { kind: "value" }],
|
|
9367
|
+
["linzumi-thread-id", { kind: "value" }],
|
|
8874
9368
|
["kandan-thread-id", { kind: "value" }],
|
|
8875
9369
|
["listen-user", { kind: "value" }],
|
|
8876
9370
|
["model", { kind: "value" }],
|
|
@@ -8890,6 +9384,10 @@ var flagDefinitions = new Map([
|
|
|
8890
9384
|
["oauth-callback-host", { kind: "value" }],
|
|
8891
9385
|
["help", { kind: "boolean" }]
|
|
8892
9386
|
]);
|
|
9387
|
+
var flagAliases = new Map([
|
|
9388
|
+
["kandan-url", "linzumi-url"],
|
|
9389
|
+
["kandan-thread-id", "linzumi-thread-id"]
|
|
9390
|
+
]);
|
|
8893
9391
|
var helloFlagDefinitions = new Map([
|
|
8894
9392
|
["dir", { kind: "value" }],
|
|
8895
9393
|
["parent-dir", { kind: "value" }],
|
|
@@ -8923,7 +9421,7 @@ async function main(args) {
|
|
|
8923
9421
|
process.stdout.write(connectGuideText());
|
|
8924
9422
|
return;
|
|
8925
9423
|
case "version":
|
|
8926
|
-
process.stdout.write(`linzumi 0.0.
|
|
9424
|
+
process.stdout.write(`linzumi 0.0.35-beta
|
|
8927
9425
|
`);
|
|
8928
9426
|
return;
|
|
8929
9427
|
case "auth":
|
|
@@ -8994,6 +9492,7 @@ function parseCommand(args) {
|
|
|
8994
9492
|
return { command: "commanderDaemon", args: ["stop", ...rest] };
|
|
8995
9493
|
case "signup":
|
|
8996
9494
|
case "claim":
|
|
9495
|
+
case "login-link":
|
|
8997
9496
|
case "thread":
|
|
8998
9497
|
case "channel":
|
|
8999
9498
|
case "post":
|
|
@@ -9168,7 +9667,7 @@ async function runAuthCommand(args) {
|
|
|
9168
9667
|
process.stdout.write(helpText());
|
|
9169
9668
|
return;
|
|
9170
9669
|
}
|
|
9171
|
-
const kandanUrl = required(values, "
|
|
9670
|
+
const kandanUrl = required(values, "linzumi-url");
|
|
9172
9671
|
const target = parseOptionalChannelTarget(values);
|
|
9173
9672
|
const token = await acquireLocalRunnerTokenDetails({
|
|
9174
9673
|
kandanUrl,
|
|
@@ -9182,7 +9681,7 @@ async function runAuthCommand(args) {
|
|
|
9182
9681
|
expiresInSeconds: token.expiresInSeconds,
|
|
9183
9682
|
authFilePath: stringValue3(values, "auth-file")
|
|
9184
9683
|
});
|
|
9185
|
-
process.stdout.write(`Saved
|
|
9684
|
+
process.stdout.write(`Saved Linzumi local runner auth for ${cached.kandanBaseUrl}
|
|
9186
9685
|
`);
|
|
9187
9686
|
}
|
|
9188
9687
|
async function parseStartRunnerArgs(args, deps = {
|
|
@@ -9199,7 +9698,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
9199
9698
|
process.exit(0);
|
|
9200
9699
|
}
|
|
9201
9700
|
rejectStartTargetingFlags(values);
|
|
9202
|
-
const kandanUrl = stringValue3(values, "
|
|
9701
|
+
const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
9203
9702
|
const requestedCwd = resolveUserPath(cwdArg ?? process.cwd());
|
|
9204
9703
|
const cwd = assertConfiguredAllowedCwds([requestedCwd])[0] ?? requestedCwd;
|
|
9205
9704
|
const explicitAllowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [];
|
|
@@ -9216,7 +9715,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
9216
9715
|
const authFilePath = stringValue3(values, "auth-file");
|
|
9217
9716
|
const callbackHost = stringValue3(values, "oauth-callback-host");
|
|
9218
9717
|
const reportRejectedCachedToken = () => {
|
|
9219
|
-
process.stderr.write(`Cached
|
|
9718
|
+
process.stderr.write(`Cached Linzumi local runner auth was rejected; starting OAuth.
|
|
9220
9719
|
`);
|
|
9221
9720
|
};
|
|
9222
9721
|
const token = await deps.resolveToken({
|
|
@@ -9275,7 +9774,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
9275
9774
|
channelSession: {
|
|
9276
9775
|
workspaceSlug: target.workspaceSlug,
|
|
9277
9776
|
channelSlug: target.channelSlug,
|
|
9278
|
-
kandanThreadId: stringValue3(values, "
|
|
9777
|
+
kandanThreadId: stringValue3(values, "linzumi-thread-id"),
|
|
9279
9778
|
listenUser: stringValue3(values, "listen-user") ?? defaultListenUserFromToken(targetToken),
|
|
9280
9779
|
model: stringValue3(values, "model"),
|
|
9281
9780
|
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
@@ -9304,7 +9803,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
9304
9803
|
const tokenFile = readStoredAgentTokenFile(tokenFilePath, deps.readTextFile);
|
|
9305
9804
|
const channelSlug = requiredStoredAgentChannel(tokenFile.channelId);
|
|
9306
9805
|
const listenUser = stringValue3(values, "listen-user") ?? requiredStoredOwnerUsername(tokenFile.ownerUsername);
|
|
9307
|
-
const kandanUrl = stringValue3(values, "
|
|
9806
|
+
const kandanUrl = stringValue3(values, "linzumi-url") ?? agentApiUrlToKandanUrl(tokenFile.apiUrl);
|
|
9308
9807
|
const requestedCwdValue = cwdArg ?? stringValue3(values, "cwd");
|
|
9309
9808
|
const requestedCwd = resolveUserPath(requestedCwdValue ?? process.cwd());
|
|
9310
9809
|
const allowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : requestedCwdValue === undefined ? readConfiguredAllowedCwds() : assertConfiguredAllowedCwds([requestedCwd]);
|
|
@@ -9349,7 +9848,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
9349
9848
|
channelSession: {
|
|
9350
9849
|
workspaceSlug: tokenFile.workspaceId,
|
|
9351
9850
|
channelSlug,
|
|
9352
|
-
kandanThreadId: stringValue3(values, "
|
|
9851
|
+
kandanThreadId: stringValue3(values, "linzumi-thread-id"),
|
|
9353
9852
|
listenUser,
|
|
9354
9853
|
model: stringValue3(values, "model"),
|
|
9355
9854
|
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
@@ -9436,12 +9935,12 @@ async function parseRunnerArgs(args, deps = {
|
|
|
9436
9935
|
process.exit(0);
|
|
9437
9936
|
}
|
|
9438
9937
|
if (values.get("version") === true) {
|
|
9439
|
-
process.stdout.write(`linzumi 0.0.
|
|
9938
|
+
process.stdout.write(`linzumi 0.0.35-beta
|
|
9440
9939
|
`);
|
|
9441
9940
|
process.exit(0);
|
|
9442
9941
|
}
|
|
9443
9942
|
const channelTarget = parseChannelSessionTarget(values);
|
|
9444
|
-
const kandanUrl = required(values, "
|
|
9943
|
+
const kandanUrl = required(values, "linzumi-url");
|
|
9445
9944
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
9446
9945
|
const codexBin = stringValue3(values, "codex-bin") ?? "codex";
|
|
9447
9946
|
const customCodeServerBin = stringValue3(values, "code-server-bin");
|
|
@@ -9454,7 +9953,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
9454
9953
|
authFilePath: stringValue3(values, "auth-file"),
|
|
9455
9954
|
callbackHost: stringValue3(values, "oauth-callback-host"),
|
|
9456
9955
|
reportRejectedCachedToken: () => {
|
|
9457
|
-
process.stderr.write(`Cached
|
|
9956
|
+
process.stderr.write(`Cached Linzumi local runner auth was rejected; starting OAuth.
|
|
9458
9957
|
`);
|
|
9459
9958
|
}
|
|
9460
9959
|
});
|
|
@@ -9498,18 +9997,19 @@ function strictFlagValues(args, definitions = flagDefinitions) {
|
|
|
9498
9997
|
if (arg === undefined || !arg.startsWith("--")) {
|
|
9499
9998
|
throw new Error(`invalid argument: ${arg ?? ""}`);
|
|
9500
9999
|
}
|
|
9501
|
-
const
|
|
9502
|
-
const definition = definitions.get(
|
|
10000
|
+
const rawKey = arg.slice(2);
|
|
10001
|
+
const definition = definitions.get(rawKey);
|
|
9503
10002
|
if (definition === undefined) {
|
|
9504
|
-
throw new Error(`invalid flag: --${
|
|
10003
|
+
throw new Error(`invalid flag: --${rawKey}`);
|
|
9505
10004
|
}
|
|
10005
|
+
const key = flagAliases.get(rawKey) ?? rawKey;
|
|
9506
10006
|
if (definition.kind === "boolean") {
|
|
9507
10007
|
values.set(key, true);
|
|
9508
10008
|
continue;
|
|
9509
10009
|
}
|
|
9510
10010
|
const next = args[index + 1];
|
|
9511
10011
|
if (next === undefined || next.startsWith("--")) {
|
|
9512
|
-
throw new Error(`missing value for --${
|
|
10012
|
+
throw new Error(`missing value for --${rawKey}`);
|
|
9513
10013
|
}
|
|
9514
10014
|
values.set(key, next);
|
|
9515
10015
|
index += 1;
|
|
@@ -9581,10 +10081,10 @@ function rejectStartTargetingFlags(values) {
|
|
|
9581
10081
|
}
|
|
9582
10082
|
function resolveUserPath(pathValue) {
|
|
9583
10083
|
if (pathValue === "~") {
|
|
9584
|
-
return
|
|
10084
|
+
return homedir9();
|
|
9585
10085
|
}
|
|
9586
10086
|
if (pathValue.startsWith("~/")) {
|
|
9587
|
-
return resolve8(
|
|
10087
|
+
return resolve8(homedir9(), pathValue.slice(2));
|
|
9588
10088
|
}
|
|
9589
10089
|
return resolve8(pathValue);
|
|
9590
10090
|
}
|
|
@@ -9596,7 +10096,7 @@ function parseChannelSession(values, token, target) {
|
|
|
9596
10096
|
return {
|
|
9597
10097
|
workspaceSlug: target.workspaceSlug,
|
|
9598
10098
|
channelSlug: target.channelSlug,
|
|
9599
|
-
kandanThreadId: stringValue3(values, "
|
|
10099
|
+
kandanThreadId: stringValue3(values, "linzumi-thread-id"),
|
|
9600
10100
|
listenUser,
|
|
9601
10101
|
model: stringValue3(values, "model"),
|
|
9602
10102
|
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
@@ -9679,6 +10179,8 @@ Usage:
|
|
|
9679
10179
|
linzumi
|
|
9680
10180
|
linzumi signup --email <email> --agent-name <name> [--workspace-name <name>]
|
|
9681
10181
|
linzumi claim --pending <pending_id> --code <XXXX-XXXX>
|
|
10182
|
+
linzumi login-link issue --email <email> --workspace <workspace_id>
|
|
10183
|
+
linzumi login-link claim --pending <pending_id> --code <XXXX-XXXX>
|
|
9682
10184
|
linzumi thread new <title> --message <message>
|
|
9683
10185
|
linzumi post <thread_id> <message>
|
|
9684
10186
|
linzumi inbox <thread_id> --since-last
|
|
@@ -9690,11 +10192,12 @@ Usage:
|
|
|
9690
10192
|
linzumi commander <folder> [options]
|
|
9691
10193
|
linzumi start <folder> [options]
|
|
9692
10194
|
linzumi paths list|add|remove [path]
|
|
9693
|
-
linzumi connect --
|
|
9694
|
-
linzumi auth --
|
|
10195
|
+
linzumi connect --linzumi-url <ws-url> --workspace <slug> --channel <slug> [options]
|
|
10196
|
+
linzumi auth --linzumi-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
9695
10197
|
|
|
9696
10198
|
Required:
|
|
9697
|
-
--
|
|
10199
|
+
--linzumi-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
10200
|
+
(deprecated alias: --kandan-url)
|
|
9698
10201
|
--token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
|
|
9699
10202
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
9700
10203
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
@@ -9702,7 +10205,8 @@ Required:
|
|
|
9702
10205
|
Channel binding:
|
|
9703
10206
|
--workspace <slug> Workspace slug
|
|
9704
10207
|
--channel <slug|w/c> Channel slug, or workspace/channel
|
|
9705
|
-
--
|
|
10208
|
+
--linzumi-thread-id <uuid> Resume an existing Linzumi thread instead of announcing a new root
|
|
10209
|
+
(deprecated alias: --kandan-thread-id)
|
|
9706
10210
|
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
9707
10211
|
|
|
9708
10212
|
Codex:
|
|
@@ -9831,7 +10335,8 @@ What it does:
|
|
|
9831
10335
|
previews, and the browser VS Code editor.
|
|
9832
10336
|
|
|
9833
10337
|
Options:
|
|
9834
|
-
--
|
|
10338
|
+
--linzumi-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
10339
|
+
(deprecated alias: --kandan-url)
|
|
9835
10340
|
--token <jwt> Optional scoped local-runner token override
|
|
9836
10341
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
9837
10342
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
@@ -9871,23 +10376,24 @@ What it does:
|
|
|
9871
10376
|
|
|
9872
10377
|
Options:
|
|
9873
10378
|
--agent-token-file <path> Agent token cache, default ~/.linzumi/agent-token.json
|
|
9874
|
-
--
|
|
10379
|
+
--linzumi-url <ws-url> Linzumi websocket base URL. Defaults deterministically from the stored apiUrl.
|
|
10380
|
+
(deprecated alias: --kandan-url)
|
|
9875
10381
|
--runner-id <id> Stable Commander id
|
|
9876
10382
|
--codex-bin <path> Codex executable, default codex
|
|
9877
|
-
--code-server-bin <path> Custom development code-server executable. By default
|
|
10383
|
+
--code-server-bin <path> Custom development code-server executable. By default Linzumi installs the approved editor runtime.
|
|
9878
10384
|
--listen-user <user> Human whose replies Codex may accept, default owner from claim
|
|
9879
10385
|
--model <name> Model requested for Codex sessions
|
|
9880
10386
|
--reasoning-effort <value> Reasoning effort requested for Codex sessions
|
|
9881
|
-
--sandbox <value> Sandbox metadata shown in
|
|
9882
|
-
--approval-policy <value> Approval-policy metadata shown in
|
|
9883
|
-
--forward-port <ports> Comma-separated local TCP ports
|
|
10387
|
+
--sandbox <value> Sandbox metadata shown in Linzumi
|
|
10388
|
+
--approval-policy <value> Approval-policy metadata shown in Linzumi
|
|
10389
|
+
--forward-port <ports> Comma-separated local TCP ports Linzumi may expose as previews
|
|
9884
10390
|
--allowed-cwd <paths> Override ~/.linzumi/config.json or selected folder with comma-separated trusted roots
|
|
9885
10391
|
--fast Mark this Commander as low-latency/fast in Linzumi
|
|
9886
10392
|
|
|
9887
10393
|
Examples:
|
|
9888
10394
|
linzumi paths add "$PWD"
|
|
9889
10395
|
linzumi commander daemon --runner-id hello-world-commander
|
|
9890
|
-
linzumi commander ~/code/my-app --
|
|
10396
|
+
linzumi commander ~/code/my-app --linzumi-url ws://127.0.0.1:4162 --runner-id local-qa-commander
|
|
9891
10397
|
`;
|
|
9892
10398
|
}
|
|
9893
10399
|
function connectGuideText() {
|