@linzumi/cli 0.0.32-beta → 0.0.34-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 +30 -292
- package/dist/index.js +647 -151
- 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);
|
|
@@ -4458,6 +4659,7 @@ function openLocalWebSocket(control, sockets, pushEvent, scheme) {
|
|
|
4458
4659
|
const url = localForwardUrl(scheme === "ws" ? "http" : "https", control.port, control.path, control.queryString).replace(/^http/, scheme);
|
|
4459
4660
|
const protocols = webSocketProtocols(control.headers);
|
|
4460
4661
|
const websocket = protocols === undefined ? new WebSocket(url) : new WebSocket(url, protocols);
|
|
4662
|
+
websocket.binaryType = "arraybuffer";
|
|
4461
4663
|
sockets.set(control.socketId, websocket);
|
|
4462
4664
|
websocket.addEventListener("open", () => {
|
|
4463
4665
|
opened = true;
|
|
@@ -4641,13 +4843,13 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
4641
4843
|
import {
|
|
4642
4844
|
cpSync,
|
|
4643
4845
|
existsSync as existsSync2,
|
|
4644
|
-
mkdirSync as
|
|
4846
|
+
mkdirSync as mkdirSync3,
|
|
4645
4847
|
mkdtempSync,
|
|
4646
4848
|
realpathSync as realpathSync3,
|
|
4647
4849
|
writeFileSync as writeFileSync2
|
|
4648
4850
|
} from "node:fs";
|
|
4649
4851
|
import { tmpdir } from "node:os";
|
|
4650
|
-
import { basename as basename3, delimiter, dirname, join as
|
|
4852
|
+
import { basename as basename3, delimiter, dirname as dirname2, join as join4 } from "node:path";
|
|
4651
4853
|
function isStartLocalEditorControl(control) {
|
|
4652
4854
|
return control.type === "start_local_editor";
|
|
4653
4855
|
}
|
|
@@ -4727,7 +4929,7 @@ async function startLocalEditor(control, options) {
|
|
|
4727
4929
|
reason: launchResult.reason
|
|
4728
4930
|
};
|
|
4729
4931
|
}
|
|
4730
|
-
const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime);
|
|
4932
|
+
const collaborationResult = await startCollaborationSidecar(collaboration, profileResult, options.editorRuntime, options.runnerId);
|
|
4731
4933
|
if (!collaborationResult.ok) {
|
|
4732
4934
|
return {
|
|
4733
4935
|
ok: false,
|
|
@@ -4735,11 +4937,35 @@ async function startLocalEditor(control, options) {
|
|
|
4735
4937
|
reason: "code_server_spawn_failed"
|
|
4736
4938
|
};
|
|
4737
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 });
|
|
4738
4946
|
const child = spawn2(launchResult.command, [...launchResult.args], {
|
|
4739
4947
|
cwd: cwdDecision.cwd,
|
|
4740
|
-
env: codeServerEnv(process.env, profileResult.userDataDir, collaborationResult.collaboration),
|
|
4948
|
+
env: codeServerEnv(process.env, cwdDecision.cwd, profileResult.userDataDir, collaborationResult.collaboration),
|
|
4741
4949
|
stdio: ["ignore", "inherit", "inherit"]
|
|
4742
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
|
+
});
|
|
4743
4969
|
const exited = waitForCodeServerExit(child);
|
|
4744
4970
|
const spawnResult = await waitForCodeServerSpawn(child);
|
|
4745
4971
|
if (spawnResult === "failed") {
|
|
@@ -4810,17 +5036,17 @@ function codeServerArgs(port, cwd, userDataDir, extensionsDir) {
|
|
|
4810
5036
|
}
|
|
4811
5037
|
function prepareCodeServerProfile(collaboration, editorRuntime) {
|
|
4812
5038
|
try {
|
|
4813
|
-
const userDataDir = mkdtempSync(
|
|
4814
|
-
const extensionsDir =
|
|
4815
|
-
const collaborationServerDir =
|
|
4816
|
-
const userSettingsDir =
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
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 });
|
|
4820
5046
|
if (editorRuntime !== undefined) {
|
|
4821
|
-
installDirectory(editorRuntime.assets.documentStateExtensionDir,
|
|
5047
|
+
installDirectory(editorRuntime.assets.documentStateExtensionDir, join4(extensionsDir, "kandan.document-state-telemetry"));
|
|
4822
5048
|
}
|
|
4823
|
-
writeFileSync2(
|
|
5049
|
+
writeFileSync2(join4(userSettingsDir, "settings.json"), JSON.stringify(codeServerSettings(collaboration), null, 2));
|
|
4824
5050
|
return { ok: true, userDataDir, extensionsDir, collaborationServerDir };
|
|
4825
5051
|
} catch (_error) {
|
|
4826
5052
|
return { ok: false, reason: "code_server_spawn_failed" };
|
|
@@ -4886,13 +5112,16 @@ function prepareLinuxCodeServerLaunch(options) {
|
|
|
4886
5112
|
"/tmp",
|
|
4887
5113
|
"--setenv",
|
|
4888
5114
|
"HOME",
|
|
4889
|
-
options.
|
|
5115
|
+
options.cwd,
|
|
5116
|
+
"--setenv",
|
|
5117
|
+
"PWD",
|
|
5118
|
+
options.cwd,
|
|
4890
5119
|
"--setenv",
|
|
4891
5120
|
"XDG_DATA_HOME",
|
|
4892
|
-
|
|
5121
|
+
join4(options.userDataDir, "data"),
|
|
4893
5122
|
"--setenv",
|
|
4894
5123
|
"XDG_CONFIG_HOME",
|
|
4895
|
-
|
|
5124
|
+
join4(options.userDataDir, "config"),
|
|
4896
5125
|
...readOnlyRoots.flatMap((path) => ["--ro-bind-try", path, path]),
|
|
4897
5126
|
"--bind",
|
|
4898
5127
|
options.cwd,
|
|
@@ -4965,12 +5194,12 @@ function resolveCodeServerExecutable(command, envPath) {
|
|
|
4965
5194
|
if (directory.trim() === "") {
|
|
4966
5195
|
continue;
|
|
4967
5196
|
}
|
|
4968
|
-
const candidate =
|
|
5197
|
+
const candidate = join4(directory, command);
|
|
4969
5198
|
if (!existsSync2(candidate)) {
|
|
4970
5199
|
continue;
|
|
4971
5200
|
}
|
|
4972
5201
|
const realpath = realpathSync3(candidate);
|
|
4973
|
-
return { ok: true, command: realpath, directory:
|
|
5202
|
+
return { ok: true, command: realpath, directory: dirname2(realpath) };
|
|
4974
5203
|
}
|
|
4975
5204
|
return { ok: false };
|
|
4976
5205
|
}
|
|
@@ -4979,10 +5208,10 @@ function hasPathSeparator(path) {
|
|
|
4979
5208
|
}
|
|
4980
5209
|
function safeRealpathDir(path) {
|
|
4981
5210
|
try {
|
|
4982
|
-
const directory =
|
|
5211
|
+
const directory = dirname2(realpathSync3(path));
|
|
4983
5212
|
return directory === "/" ? undefined : directory;
|
|
4984
5213
|
} catch (_error) {
|
|
4985
|
-
const directory =
|
|
5214
|
+
const directory = dirname2(path);
|
|
4986
5215
|
return directory === "." || directory === "/" ? undefined : directory;
|
|
4987
5216
|
}
|
|
4988
5217
|
}
|
|
@@ -5032,7 +5261,7 @@ function codeServerSettings(collaboration) {
|
|
|
5032
5261
|
"workbench.welcomePage.walkthroughs.openOnInstall": false
|
|
5033
5262
|
};
|
|
5034
5263
|
}
|
|
5035
|
-
async function startCollaborationSidecar(collaboration, profile, editorRuntime) {
|
|
5264
|
+
async function startCollaborationSidecar(collaboration, profile, editorRuntime, runnerId) {
|
|
5036
5265
|
if (collaboration === undefined) {
|
|
5037
5266
|
return { ok: true, ready: Promise.resolve("ready") };
|
|
5038
5267
|
}
|
|
@@ -5041,13 +5270,22 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
|
|
|
5041
5270
|
installLocalTarball(editorRuntime.assets.collaborationExtensionTarball, profile.extensionsDir),
|
|
5042
5271
|
installLocalTarball(editorRuntime.assets.collaborationServerTarball, profile.collaborationServerDir)
|
|
5043
5272
|
]);
|
|
5044
|
-
const
|
|
5045
|
-
|
|
5273
|
+
const command = nodeRuntimeExecutable();
|
|
5274
|
+
const args = [
|
|
5275
|
+
join4(profile.collaborationServerDir, "open-collaboration-server", "bundle", "app.js"),
|
|
5046
5276
|
"--hostname",
|
|
5047
5277
|
"127.0.0.1",
|
|
5048
5278
|
"--port",
|
|
5049
5279
|
String(collaboration.serverPort)
|
|
5050
|
-
]
|
|
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, {
|
|
5051
5289
|
env: {
|
|
5052
5290
|
...process.env,
|
|
5053
5291
|
OCT_ACTIVATE_SIMPLE_LOGIN: "true",
|
|
@@ -5055,6 +5293,26 @@ async function startCollaborationSidecar(collaboration, profile, editorRuntime)
|
|
|
5055
5293
|
},
|
|
5056
5294
|
stdio: ["ignore", "inherit", "inherit"]
|
|
5057
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
|
+
});
|
|
5058
5316
|
const exited = waitForCodeServerExit(child);
|
|
5059
5317
|
const spawnResult = await waitForCodeServerSpawn(child);
|
|
5060
5318
|
if (spawnResult === "failed") {
|
|
@@ -5088,21 +5346,22 @@ function nodeRuntimeExecutable(env = process.env, execPath = process.execPath) {
|
|
|
5088
5346
|
return basename3(execPath).toLowerCase().includes("bun") ? "node" : execPath;
|
|
5089
5347
|
}
|
|
5090
5348
|
async function installLocalTarball(archivePath, destinationDir) {
|
|
5091
|
-
|
|
5349
|
+
mkdirSync3(destinationDir, { recursive: true });
|
|
5092
5350
|
await runProcess("tar", ["-xzf", archivePath, "-C", destinationDir]);
|
|
5093
5351
|
}
|
|
5094
5352
|
function installDirectory(sourceDir, destinationDir) {
|
|
5095
|
-
|
|
5353
|
+
mkdirSync3(dirname2(destinationDir), { recursive: true });
|
|
5096
5354
|
cpSync(sourceDir, destinationDir, { recursive: true });
|
|
5097
5355
|
}
|
|
5098
|
-
function codeServerEnv(env, userDataDir, collaboration) {
|
|
5356
|
+
function codeServerEnv(env, cwd, userDataDir, collaboration) {
|
|
5099
5357
|
const { PORT: _port, ...hostEnv } = env;
|
|
5100
5358
|
const base = {
|
|
5101
5359
|
...hostEnv,
|
|
5102
|
-
HOME:
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
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")
|
|
5106
5365
|
};
|
|
5107
5366
|
if (collaboration === undefined) {
|
|
5108
5367
|
return base;
|
|
@@ -5136,11 +5395,30 @@ function sameCollaboration(running, requested) {
|
|
|
5136
5395
|
}
|
|
5137
5396
|
function runProcess(command, args) {
|
|
5138
5397
|
return new Promise((resolve4, reject) => {
|
|
5398
|
+
writeCliAuditEvent("process.spawn", {
|
|
5399
|
+
command,
|
|
5400
|
+
args,
|
|
5401
|
+
purpose: "local_editor.install_process"
|
|
5402
|
+
});
|
|
5139
5403
|
const child = spawn2(command, [...args], {
|
|
5140
5404
|
stdio: ["ignore", "ignore", "inherit"]
|
|
5141
5405
|
});
|
|
5406
|
+
writeCliAuditEvent("process.spawned", {
|
|
5407
|
+
command,
|
|
5408
|
+
args,
|
|
5409
|
+
pid: child.pid,
|
|
5410
|
+
purpose: "local_editor.install_process"
|
|
5411
|
+
});
|
|
5142
5412
|
child.once("error", reject);
|
|
5143
|
-
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
|
+
});
|
|
5144
5422
|
if (code === 0) {
|
|
5145
5423
|
resolve4();
|
|
5146
5424
|
} else {
|
|
@@ -5292,17 +5570,17 @@ import { spawn as spawn4 } from "node:child_process";
|
|
|
5292
5570
|
import { createHash as createHash2 } from "node:crypto";
|
|
5293
5571
|
import {
|
|
5294
5572
|
createReadStream,
|
|
5295
|
-
createWriteStream,
|
|
5573
|
+
createWriteStream as createWriteStream2,
|
|
5296
5574
|
existsSync as existsSync3,
|
|
5297
|
-
mkdirSync as
|
|
5575
|
+
mkdirSync as mkdirSync4,
|
|
5298
5576
|
mkdtempSync as mkdtempSync2,
|
|
5299
5577
|
readFileSync as readFileSync2,
|
|
5300
5578
|
renameSync,
|
|
5301
5579
|
rmSync,
|
|
5302
5580
|
writeFileSync as writeFileSync3
|
|
5303
5581
|
} from "node:fs";
|
|
5304
|
-
import { homedir as
|
|
5305
|
-
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";
|
|
5306
5584
|
import { Readable } from "node:stream";
|
|
5307
5585
|
import { pipeline } from "node:stream/promises";
|
|
5308
5586
|
|
|
@@ -5489,8 +5767,8 @@ function startCallbackServer(args) {
|
|
|
5489
5767
|
if (error !== null && error.trim() !== "") {
|
|
5490
5768
|
rejectCallback?.(new Error(`local runner OAuth failed: ${error}`));
|
|
5491
5769
|
writeOauthResult(response, {
|
|
5492
|
-
title: "
|
|
5493
|
-
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.",
|
|
5494
5772
|
status: 403
|
|
5495
5773
|
});
|
|
5496
5774
|
return;
|
|
@@ -5498,22 +5776,22 @@ function startCallbackServer(args) {
|
|
|
5498
5776
|
if (code === null || state === null || code.trim() === "" || state.trim() === "") {
|
|
5499
5777
|
writeOauthResult(response, {
|
|
5500
5778
|
title: "Authorization callback was incomplete",
|
|
5501
|
-
body: "
|
|
5779
|
+
body: "We didn't receive the authorization code. Return to your terminal and try again.",
|
|
5502
5780
|
status: 400
|
|
5503
5781
|
});
|
|
5504
5782
|
return;
|
|
5505
5783
|
}
|
|
5506
5784
|
resolveCallback?.({ code, state });
|
|
5507
5785
|
writeOauthResult(response, {
|
|
5508
|
-
title: "
|
|
5509
|
-
body: "You can close this tab
|
|
5786
|
+
title: "Connected",
|
|
5787
|
+
body: "You can close this tab. Your terminal will finish the setup.",
|
|
5510
5788
|
status: 200
|
|
5511
5789
|
});
|
|
5512
5790
|
} catch (error) {
|
|
5513
5791
|
rejectCallback?.(error);
|
|
5514
5792
|
writeOauthResult(response, {
|
|
5515
5793
|
title: "Authorization callback failed",
|
|
5516
|
-
body: "Return to your terminal and rerun
|
|
5794
|
+
body: "Return to your terminal and rerun the bootstrap command.",
|
|
5517
5795
|
status: 500
|
|
5518
5796
|
});
|
|
5519
5797
|
}
|
|
@@ -5577,7 +5855,18 @@ function openBrowser(url) {
|
|
|
5577
5855
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
5578
5856
|
const args = process.platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
5579
5857
|
return new Promise((resolve4) => {
|
|
5858
|
+
writeCliAuditEvent("process.spawn", {
|
|
5859
|
+
command,
|
|
5860
|
+
args,
|
|
5861
|
+
purpose: "oauth.open_browser"
|
|
5862
|
+
});
|
|
5580
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
|
+
});
|
|
5581
5870
|
child.on("error", () => resolve4());
|
|
5582
5871
|
child.on("spawn", () => {
|
|
5583
5872
|
child.unref();
|
|
@@ -5764,8 +6053,8 @@ function normalizeRuntimeAssets(value) {
|
|
|
5764
6053
|
}
|
|
5765
6054
|
function installedRuntime(cacheRoot, manifest) {
|
|
5766
6055
|
const runtimeRoot = runtimeInstallRoot(cacheRoot, manifest);
|
|
5767
|
-
const manifestPath =
|
|
5768
|
-
const codeServerBin =
|
|
6056
|
+
const manifestPath = join5(runtimeRoot, manifest.manifestPath);
|
|
6057
|
+
const codeServerBin = join5(runtimeRoot, manifest.codeServerBinPath);
|
|
5769
6058
|
const assets = verifiedRuntimeAssetPaths(runtimeRoot, manifest);
|
|
5770
6059
|
if (!existsSync3(manifestPath) || !existsSync3(codeServerBin) || assets === undefined) {
|
|
5771
6060
|
return { ok: false };
|
|
@@ -5789,10 +6078,10 @@ function installedRuntime(cacheRoot, manifest) {
|
|
|
5789
6078
|
return { ok: false };
|
|
5790
6079
|
}
|
|
5791
6080
|
async function installRuntime(args) {
|
|
5792
|
-
|
|
5793
|
-
const tempRoot = mkdtempSync2(
|
|
5794
|
-
const archivePath =
|
|
5795
|
-
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");
|
|
5796
6085
|
try {
|
|
5797
6086
|
const downloaded = await downloadArchive({
|
|
5798
6087
|
kandanUrl: args.kandanUrl,
|
|
@@ -5804,7 +6093,7 @@ async function installRuntime(args) {
|
|
|
5804
6093
|
if (!downloaded.ok) {
|
|
5805
6094
|
return downloaded;
|
|
5806
6095
|
}
|
|
5807
|
-
|
|
6096
|
+
mkdirSync4(extractRoot, { recursive: true });
|
|
5808
6097
|
if (!await args.extractArchive(archivePath, extractRoot)) {
|
|
5809
6098
|
return { ok: false, reason: "archive_extract_failed" };
|
|
5810
6099
|
}
|
|
@@ -5817,13 +6106,13 @@ async function installRuntime(args) {
|
|
|
5817
6106
|
if (!assetsInstalled) {
|
|
5818
6107
|
return { ok: false, reason: "install_failed" };
|
|
5819
6108
|
}
|
|
5820
|
-
const manifestPath =
|
|
5821
|
-
const codeServerBin =
|
|
6109
|
+
const manifestPath = join5(extractRoot, args.manifest.manifestPath);
|
|
6110
|
+
const codeServerBin = join5(extractRoot, args.manifest.codeServerBinPath);
|
|
5822
6111
|
const assets = verifiedRuntimeAssetPaths(extractRoot, args.manifest);
|
|
5823
6112
|
if (!existsSync3(codeServerBin) || assets === undefined) {
|
|
5824
6113
|
return { ok: false, reason: "invalid_archive" };
|
|
5825
6114
|
}
|
|
5826
|
-
|
|
6115
|
+
mkdirSync4(dirname3(manifestPath), { recursive: true });
|
|
5827
6116
|
writeFileSync3(manifestPath, JSON.stringify({
|
|
5828
6117
|
version: args.manifest.version,
|
|
5829
6118
|
platform: args.manifest.platform,
|
|
@@ -5835,18 +6124,18 @@ async function installRuntime(args) {
|
|
|
5835
6124
|
}, null, 2));
|
|
5836
6125
|
const targetRoot = runtimeInstallRoot(args.cacheRoot, args.manifest);
|
|
5837
6126
|
rmSync(targetRoot, { recursive: true, force: true });
|
|
5838
|
-
|
|
6127
|
+
mkdirSync4(dirname3(targetRoot), { recursive: true });
|
|
5839
6128
|
renameSync(extractRoot, targetRoot);
|
|
5840
6129
|
return {
|
|
5841
6130
|
ok: true,
|
|
5842
6131
|
runtime: {
|
|
5843
6132
|
mode: "server_managed",
|
|
5844
6133
|
root: targetRoot,
|
|
5845
|
-
codeServerBin:
|
|
6134
|
+
codeServerBin: join5(targetRoot, args.manifest.codeServerBinPath),
|
|
5846
6135
|
assets: {
|
|
5847
|
-
collaborationExtensionTarball:
|
|
5848
|
-
collaborationServerTarball:
|
|
5849
|
-
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")
|
|
5850
6139
|
}
|
|
5851
6140
|
}
|
|
5852
6141
|
};
|
|
@@ -5858,7 +6147,7 @@ async function installRuntime(args) {
|
|
|
5858
6147
|
}
|
|
5859
6148
|
async function materializeRuntimeAssets(args) {
|
|
5860
6149
|
for (const asset of args.manifest.assets) {
|
|
5861
|
-
const targetPath =
|
|
6150
|
+
const targetPath = join5(args.runtimeRoot, asset.path);
|
|
5862
6151
|
try {
|
|
5863
6152
|
const bytes = await runtimeAssetBytes({
|
|
5864
6153
|
kandanUrl: args.kandanUrl,
|
|
@@ -5868,7 +6157,7 @@ async function materializeRuntimeAssets(args) {
|
|
|
5868
6157
|
if (bytes === undefined) {
|
|
5869
6158
|
continue;
|
|
5870
6159
|
}
|
|
5871
|
-
|
|
6160
|
+
mkdirSync4(dirname3(targetPath), { recursive: true });
|
|
5872
6161
|
writeFileSync3(targetPath, bytes);
|
|
5873
6162
|
} catch (_error) {
|
|
5874
6163
|
return false;
|
|
@@ -5899,7 +6188,7 @@ async function downloadArchive(args) {
|
|
|
5899
6188
|
if (response.status !== 200 || response.body === null) {
|
|
5900
6189
|
return { ok: false, reason: "download_failed" };
|
|
5901
6190
|
}
|
|
5902
|
-
await pipeline(Readable.fromWeb(response.body),
|
|
6191
|
+
await pipeline(Readable.fromWeb(response.body), createWriteStream2(args.archivePath));
|
|
5903
6192
|
const sha256 = await fileSha256(args.archivePath);
|
|
5904
6193
|
if (sha256 !== args.manifest.archiveSha256) {
|
|
5905
6194
|
return { ok: false, reason: "checksum_mismatch" };
|
|
@@ -5911,11 +6200,34 @@ function sameOrigin(left, right) {
|
|
|
5911
6200
|
}
|
|
5912
6201
|
function extractTarGz(archivePath, destination) {
|
|
5913
6202
|
return new Promise((resolveExtract) => {
|
|
5914
|
-
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, {
|
|
5915
6211
|
stdio: ["ignore", "ignore", "ignore"]
|
|
5916
6212
|
});
|
|
6213
|
+
writeCliAuditEvent("process.spawned", {
|
|
6214
|
+
command,
|
|
6215
|
+
args,
|
|
6216
|
+
pid: child.pid,
|
|
6217
|
+
purpose: "editor_runtime.extract"
|
|
6218
|
+
});
|
|
5917
6219
|
child.on("error", () => resolveExtract(false));
|
|
5918
|
-
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
|
+
});
|
|
5919
6231
|
});
|
|
5920
6232
|
}
|
|
5921
6233
|
function fileSha256(path) {
|
|
@@ -5931,14 +6243,14 @@ function runtimeInstallRoot(cacheRoot, manifest) {
|
|
|
5931
6243
|
return resolve4(cacheRoot, manifest.platform, manifest.archiveSha256);
|
|
5932
6244
|
}
|
|
5933
6245
|
function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
5934
|
-
const collaborationExtensionTarball =
|
|
5935
|
-
const collaborationServerTarball =
|
|
5936
|
-
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");
|
|
5937
6249
|
const codeServerRoot = codeServerRuntimeRoot(manifest.codeServerBinPath);
|
|
5938
6250
|
const requiredPaths = [
|
|
5939
6251
|
manifest.codeServerBinPath,
|
|
5940
|
-
|
|
5941
|
-
|
|
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"),
|
|
5942
6254
|
"kandan/editor_extensions/typefox.open-collaboration-tools.tar.gz",
|
|
5943
6255
|
"kandan/editor_servers/open-collaboration-server.tar.gz",
|
|
5944
6256
|
"kandan/editor_extensions/kandan.document-state-telemetry/package.json",
|
|
@@ -5950,7 +6262,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
|
5950
6262
|
if (expectedSha256 === undefined && relativePath !== manifest.codeServerBinPath) {
|
|
5951
6263
|
return;
|
|
5952
6264
|
}
|
|
5953
|
-
const absolutePath =
|
|
6265
|
+
const absolutePath = join5(runtimeRoot, relativePath);
|
|
5954
6266
|
if (!existsSync3(absolutePath)) {
|
|
5955
6267
|
return;
|
|
5956
6268
|
}
|
|
@@ -5969,7 +6281,7 @@ function verifiedRuntimeAssetPaths(runtimeRoot, manifest) {
|
|
|
5969
6281
|
}
|
|
5970
6282
|
function codeServerRuntimeRoot(codeServerBinPath) {
|
|
5971
6283
|
const normalized = codeServerBinPath.replaceAll("\\", "/");
|
|
5972
|
-
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);
|
|
5973
6285
|
}
|
|
5974
6286
|
function manifestAssetChecksums(assets) {
|
|
5975
6287
|
const checksums = new Map;
|
|
@@ -5982,7 +6294,7 @@ function fileSha256Sync(path) {
|
|
|
5982
6294
|
return createHash2("sha256").update(readFileSync2(path)).digest("hex");
|
|
5983
6295
|
}
|
|
5984
6296
|
function defaultEditorRuntimeCacheRoot() {
|
|
5985
|
-
return
|
|
6297
|
+
return join5(homedir4(), ".linzumi", "editor-runtimes");
|
|
5986
6298
|
}
|
|
5987
6299
|
function nonEmptyString(value) {
|
|
5988
6300
|
return typeof value === "string" && value.trim() !== "" ? value.trim() : undefined;
|
|
@@ -5995,10 +6307,24 @@ function sha256String(value) {
|
|
|
5995
6307
|
// src/dependencyStatus.ts
|
|
5996
6308
|
function probeTool(command, cwd) {
|
|
5997
6309
|
return new Promise((resolve5) => {
|
|
5998
|
-
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, {
|
|
5999
6318
|
cwd,
|
|
6000
6319
|
stdio: ["ignore", "pipe", "pipe"]
|
|
6001
6320
|
});
|
|
6321
|
+
writeCliAuditEvent("process.spawned", {
|
|
6322
|
+
command,
|
|
6323
|
+
args,
|
|
6324
|
+
cwd,
|
|
6325
|
+
pid: child.pid,
|
|
6326
|
+
purpose: "dependency_probe"
|
|
6327
|
+
});
|
|
6002
6328
|
let stdout = "";
|
|
6003
6329
|
let stderr = "";
|
|
6004
6330
|
let resolved = false;
|
|
@@ -6012,6 +6338,15 @@ function probeTool(command, cwd) {
|
|
|
6012
6338
|
};
|
|
6013
6339
|
const timeout = setTimeout(() => {
|
|
6014
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
|
+
});
|
|
6015
6350
|
finish({ command, available: false });
|
|
6016
6351
|
}, 1000);
|
|
6017
6352
|
child.stdout?.on("data", (chunk) => {
|
|
@@ -6021,9 +6356,28 @@ function probeTool(command, cwd) {
|
|
|
6021
6356
|
stderr += chunk.toString();
|
|
6022
6357
|
});
|
|
6023
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
|
+
});
|
|
6024
6369
|
finish({ command, available: false });
|
|
6025
6370
|
});
|
|
6026
|
-
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
|
+
});
|
|
6027
6381
|
if (code !== 0) {
|
|
6028
6382
|
finish({ command, available: false });
|
|
6029
6383
|
return;
|
|
@@ -6072,7 +6426,7 @@ function codeServerDependencyStatus(args) {
|
|
|
6072
6426
|
}
|
|
6073
6427
|
function assertStartDependencies(status) {
|
|
6074
6428
|
if (!status.bun.available) {
|
|
6075
|
-
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.");
|
|
6076
6430
|
}
|
|
6077
6431
|
if (!status.codex.available) {
|
|
6078
6432
|
throw new Error(`Codex is not available at ${status.codex.command}. Install Codex or pass --codex-bin <path>.`);
|
|
@@ -6336,35 +6690,6 @@ function waitForOpen2(websocket) {
|
|
|
6336
6690
|
});
|
|
6337
6691
|
}
|
|
6338
6692
|
|
|
6339
|
-
// src/runnerLogger.ts
|
|
6340
|
-
import { openSync } from "node:fs";
|
|
6341
|
-
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
6342
|
-
import { dirname as dirname3 } from "node:path";
|
|
6343
|
-
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
6344
|
-
function createRunnerLogger(logFile, consoleReporter) {
|
|
6345
|
-
mkdirSync4(dirname3(logFile), { recursive: true });
|
|
6346
|
-
const fd = openSync(logFile, "a");
|
|
6347
|
-
const stream = createWriteStream2("", { fd, flags: "a", autoClose: true });
|
|
6348
|
-
const logger = (event, payload) => {
|
|
6349
|
-
stream.write(`${JSON.stringify({ ts: new Date().toISOString(), event, ...payload })}
|
|
6350
|
-
`, "utf8");
|
|
6351
|
-
consoleReporter?.(event, payload);
|
|
6352
|
-
};
|
|
6353
|
-
Object.defineProperty(logger, "close", {
|
|
6354
|
-
value: () => closeStream(stream)
|
|
6355
|
-
});
|
|
6356
|
-
return logger;
|
|
6357
|
-
}
|
|
6358
|
-
function closeStream(stream) {
|
|
6359
|
-
if (stream.closed || stream.destroyed) {
|
|
6360
|
-
return Promise.resolve();
|
|
6361
|
-
}
|
|
6362
|
-
return new Promise((resolve5, reject) => {
|
|
6363
|
-
stream.once("error", reject);
|
|
6364
|
-
stream.end(resolve5);
|
|
6365
|
-
});
|
|
6366
|
-
}
|
|
6367
|
-
|
|
6368
6693
|
// src/runnerConsoleReporter.ts
|
|
6369
6694
|
function reportRunnerConsoleEvent(event, payload) {
|
|
6370
6695
|
const line = formatRunnerConsoleEvent(event, payload);
|
|
@@ -6486,6 +6811,26 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6486
6811
|
const allowedForwardPorts = options.allowedForwardPorts ?? [];
|
|
6487
6812
|
const liveForwardPorts = new Set(allowedForwardPorts);
|
|
6488
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
|
+
});
|
|
6489
6834
|
const allowedCwds = { value: [...options.allowedCwds] };
|
|
6490
6835
|
const localEditorState = {
|
|
6491
6836
|
value: { status: "disabled" }
|
|
@@ -6505,6 +6850,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6505
6850
|
allowedCwdSuggestions: allowedCwdSuggestions(options.cwd, allowedCwds.value),
|
|
6506
6851
|
portForwarding: liveForwardPorts.size > 0,
|
|
6507
6852
|
allowedPorts: Array.from(liveForwardPorts).sort((left, right) => left - right),
|
|
6853
|
+
forwardedPortAttributions: buildForwardPortAttributionPayload(),
|
|
6508
6854
|
toolStatus: options.dependencyStatus === undefined ? null : dependencyStatusPayload(options.dependencyStatus),
|
|
6509
6855
|
editorRuntime: options.dependencyStatus?.editorRuntime === undefined ? null : dependencyStatusPayload(options.dependencyStatus).editorRuntime,
|
|
6510
6856
|
...localEditorCapabilities(options.editorRuntime, allowedCwds.value, localEditorState.value)
|
|
@@ -6612,12 +6958,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6612
6958
|
enablePortForwardWatch: true,
|
|
6613
6959
|
initialForwardPorts: allowedForwardPorts,
|
|
6614
6960
|
suppressedForwardPorts: () => Array.from(managedForwardPorts),
|
|
6615
|
-
onForwardPortApproved: (port) => {
|
|
6961
|
+
onForwardPortApproved: (port, attribution) => {
|
|
6616
6962
|
liveForwardPorts.add(port);
|
|
6963
|
+
setForwardPortAttribution(port, attribution);
|
|
6617
6964
|
return capabilitiesPayload();
|
|
6618
6965
|
},
|
|
6619
6966
|
onForwardPortRevoked: (port) => {
|
|
6620
6967
|
liveForwardPorts.delete(port);
|
|
6968
|
+
clearForwardPortAttribution(port);
|
|
6621
6969
|
return capabilitiesPayload();
|
|
6622
6970
|
},
|
|
6623
6971
|
channelSession: options.channelSession
|
|
@@ -6659,12 +7007,14 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6659
7007
|
enablePortForwardWatch: true,
|
|
6660
7008
|
initialForwardPorts: allowedForwardPorts,
|
|
6661
7009
|
suppressedForwardPorts: () => Array.from(managedForwardPorts),
|
|
6662
|
-
onForwardPortApproved: (port) => {
|
|
7010
|
+
onForwardPortApproved: (port, attribution) => {
|
|
6663
7011
|
liveForwardPorts.add(port);
|
|
7012
|
+
setForwardPortAttribution(port, attribution);
|
|
6664
7013
|
return capabilitiesPayload();
|
|
6665
7014
|
},
|
|
6666
7015
|
onForwardPortRevoked: (port) => {
|
|
6667
7016
|
liveForwardPorts.delete(port);
|
|
7017
|
+
clearForwardPortAttribution(port);
|
|
6668
7018
|
return capabilitiesPayload();
|
|
6669
7019
|
},
|
|
6670
7020
|
channelSession: {
|
|
@@ -6760,7 +7110,17 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
6760
7110
|
return;
|
|
6761
7111
|
}
|
|
6762
7112
|
if (isForwardHttpRequestControl(control)) {
|
|
6763
|
-
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", {
|
|
6764
7124
|
requestId: control.requestId,
|
|
6765
7125
|
ok: false,
|
|
6766
7126
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -6927,7 +7287,7 @@ function normalizedWorkDescription(value) {
|
|
|
6927
7287
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
6928
7288
|
}
|
|
6929
7289
|
function makeRunnerLogger(options) {
|
|
6930
|
-
return createRunnerLogger(options.logFile ??
|
|
7290
|
+
return createRunnerLogger(options.logFile ?? join6(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
|
|
6931
7291
|
}
|
|
6932
7292
|
function installCleanupHandlers(close) {
|
|
6933
7293
|
const closeAndExit = () => {
|
|
@@ -6952,11 +7312,44 @@ function installCleanupHandlers(close) {
|
|
|
6952
7312
|
};
|
|
6953
7313
|
}
|
|
6954
7314
|
function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
|
|
6955
|
-
|
|
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, {
|
|
6956
7323
|
cwd,
|
|
6957
7324
|
env: process.env,
|
|
6958
7325
|
stdio: "inherit"
|
|
6959
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;
|
|
6960
7353
|
}
|
|
6961
7354
|
function codexTuiArgs(codexUrl, codexThreadId, session, fast) {
|
|
6962
7355
|
const overrides = codexTuiConfigArgs(session, fast);
|
|
@@ -7205,11 +7598,11 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
7205
7598
|
|
|
7206
7599
|
// src/authCache.ts
|
|
7207
7600
|
import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7208
|
-
import { homedir as
|
|
7209
|
-
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";
|
|
7210
7603
|
function defaultAuthFilePath() {
|
|
7211
|
-
const base = process.env.KANDAN_HOME ??
|
|
7212
|
-
return
|
|
7604
|
+
const base = process.env.KANDAN_HOME ?? join7(homedir5(), ".kandan");
|
|
7605
|
+
return join7(base, "auth.json");
|
|
7213
7606
|
}
|
|
7214
7607
|
function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
|
|
7215
7608
|
if (!existsSync4(authFilePath)) {
|
|
@@ -7344,11 +7737,11 @@ async function acquireAndCacheToken(args) {
|
|
|
7344
7737
|
|
|
7345
7738
|
// src/localConfig.ts
|
|
7346
7739
|
import { existsSync as existsSync5, mkdirSync as mkdirSync6, readFileSync as readFileSync4, realpathSync as realpathSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
7347
|
-
import { homedir as
|
|
7740
|
+
import { homedir as homedir6 } from "node:os";
|
|
7348
7741
|
import { dirname as dirname5, resolve as resolve5 } from "node:path";
|
|
7349
7742
|
function localConfigPath(env = process.env) {
|
|
7350
7743
|
const override = env.LINZUMI_CONFIG_FILE;
|
|
7351
|
-
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(
|
|
7744
|
+
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir6(), ".linzumi", "config.json");
|
|
7352
7745
|
}
|
|
7353
7746
|
function readLocalConfig(path = localConfigPath()) {
|
|
7354
7747
|
if (!existsSync5(path)) {
|
|
@@ -7409,6 +7802,10 @@ function realpathOrResolved(pathValue) {
|
|
|
7409
7802
|
}
|
|
7410
7803
|
}
|
|
7411
7804
|
|
|
7805
|
+
// src/defaultUrls.ts
|
|
7806
|
+
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
7807
|
+
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
7808
|
+
|
|
7412
7809
|
// src/kandanTls.ts
|
|
7413
7810
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
7414
7811
|
import { Agent } from "undici";
|
|
@@ -7454,9 +7851,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
7454
7851
|
|
|
7455
7852
|
// src/agentBootstrap.ts
|
|
7456
7853
|
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7457
|
-
import { dirname as dirname6, join as
|
|
7458
|
-
import { homedir as
|
|
7459
|
-
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";
|
|
7460
7856
|
async function runAgentCliCommand(args, deps = {
|
|
7461
7857
|
fetchImpl: fetch,
|
|
7462
7858
|
stdout: process.stdout,
|
|
@@ -7474,6 +7870,12 @@ async function runAgentCliCommand(args, deps = {
|
|
|
7474
7870
|
case "claim":
|
|
7475
7871
|
await runClaim(command, deps);
|
|
7476
7872
|
return;
|
|
7873
|
+
case "loginLinkIssue":
|
|
7874
|
+
await runLoginLinkIssue(command, deps);
|
|
7875
|
+
return;
|
|
7876
|
+
case "loginLinkClaim":
|
|
7877
|
+
await runLoginLinkClaim(command, deps);
|
|
7878
|
+
return;
|
|
7477
7879
|
case "threadNew":
|
|
7478
7880
|
await runThreadNew(command, deps);
|
|
7479
7881
|
return;
|
|
@@ -7528,6 +7930,28 @@ function parseAgentCommand(args) {
|
|
|
7528
7930
|
tokenFile: agentTokenFile(parsed.flags)
|
|
7529
7931
|
};
|
|
7530
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
|
+
}
|
|
7531
7955
|
case "thread": {
|
|
7532
7956
|
const [subcommand, ...subcommandArgs] = rest;
|
|
7533
7957
|
if (subcommand !== "new") {
|
|
@@ -7790,6 +8214,7 @@ async function runClaim(command, deps) {
|
|
|
7790
8214
|
`);
|
|
7791
8215
|
deps.stdout.write(`login_url: ${tokenFile.loginUrl}
|
|
7792
8216
|
`);
|
|
8217
|
+
writeHumanLoginUrlWarning(deps);
|
|
7793
8218
|
deps.stdout.write(`channel_url: ${tokenFile.channelUrl}
|
|
7794
8219
|
`);
|
|
7795
8220
|
deps.stdout.write(`support_channel_id: ${tokenFile.supportChannelId}
|
|
@@ -7797,6 +8222,37 @@ async function runClaim(command, deps) {
|
|
|
7797
8222
|
deps.stdout.write(`support_channel_url: ${tokenFile.supportChannelUrl}
|
|
7798
8223
|
`);
|
|
7799
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
|
+
}
|
|
7800
8256
|
async function runThreadNew(command, deps) {
|
|
7801
8257
|
const tokenFile = readTokenFile(command.tokenFile, deps.readTextFile);
|
|
7802
8258
|
const response = await postJson(command.apiUrl, "/agent/threads", {
|
|
@@ -7987,13 +8443,13 @@ function numberValue2(value) {
|
|
|
7987
8443
|
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
7988
8444
|
}
|
|
7989
8445
|
function agentApiUrl(flags) {
|
|
7990
|
-
return flags.get("api-url") ??
|
|
8446
|
+
return flags.get("api-url") ?? defaultLinzumiHttpUrl;
|
|
7991
8447
|
}
|
|
7992
8448
|
function agentTokenFile(flags) {
|
|
7993
8449
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
7994
8450
|
}
|
|
7995
8451
|
function defaultAgentTokenFilePath() {
|
|
7996
|
-
return
|
|
8452
|
+
return join8(homedir7(), ".linzumi", "agent-token.json");
|
|
7997
8453
|
}
|
|
7998
8454
|
function normalizedApiUrl(apiUrl) {
|
|
7999
8455
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -8044,12 +8500,20 @@ function writeTokenFile(path, tokenFile, writeTextFile2) {
|
|
|
8044
8500
|
writeTextFile2(path, `${JSON.stringify(tokenFile, null, 2)}
|
|
8045
8501
|
`);
|
|
8046
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
|
+
}
|
|
8047
8509
|
function agentHelpText() {
|
|
8048
8510
|
return `Linzumi agent bootstrap
|
|
8049
8511
|
|
|
8050
8512
|
Usage:
|
|
8051
8513
|
linzumi signup --email <email> --agent-name <name> [--workspace-name <name>] [--api-url <url>]
|
|
8052
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>]
|
|
8053
8517
|
linzumi thread new <title> --message <message> [--api-url <url>]
|
|
8054
8518
|
linzumi channel post <channel_id> <message> [--api-url <url>]
|
|
8055
8519
|
linzumi post <thread_id> <message> [--kind progress|question]
|
|
@@ -8060,10 +8524,14 @@ Usage:
|
|
|
8060
8524
|
linzumi editor open <thread_id> --runner <runner_id> --cwd <path>
|
|
8061
8525
|
|
|
8062
8526
|
Options:
|
|
8063
|
-
--api-url <url> Agent API origin, default ${
|
|
8527
|
+
--api-url <url> Agent API origin, default ${defaultLinzumiHttpUrl}
|
|
8064
8528
|
--agent-token-file <path> Token cache path, default ~/.linzumi/agent-token.json
|
|
8065
8529
|
--workspace-name <name> Human-readable workspace name requested during signup
|
|
8066
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
|
+
|
|
8067
8535
|
Launch target:
|
|
8068
8536
|
zero-to-hello-world-pr+editor in under 3 minutes.
|
|
8069
8537
|
`;
|
|
@@ -8071,7 +8539,7 @@ Launch target:
|
|
|
8071
8539
|
|
|
8072
8540
|
// src/helloLinzumiProject.ts
|
|
8073
8541
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8074
|
-
import { dirname as dirname7, join as
|
|
8542
|
+
import { dirname as dirname7, join as join9, resolve as resolve6 } from "node:path";
|
|
8075
8543
|
import { fileURLToPath } from "node:url";
|
|
8076
8544
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
8077
8545
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -8080,7 +8548,7 @@ var defaultHelloLinzumiPort = 8787;
|
|
|
8080
8548
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
8081
8549
|
var markerFile = ".linzumi-demo-project";
|
|
8082
8550
|
var moduleDir = dirname7(fileURLToPath(import.meta.url));
|
|
8083
|
-
var linzumiLogoSvg = readFileSync7(
|
|
8551
|
+
var linzumiLogoSvg = readFileSync7(join9(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
8084
8552
|
function createHelloLinzumiProject(input = {}) {
|
|
8085
8553
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
8086
8554
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -8088,9 +8556,9 @@ function createHelloLinzumiProject(input = {}) {
|
|
|
8088
8556
|
const host = normalizeHost(options.host);
|
|
8089
8557
|
assertTcpPort(port);
|
|
8090
8558
|
assertWritableDemoRoot(root, options.reset === true);
|
|
8091
|
-
mkdirSync8(
|
|
8559
|
+
mkdirSync8(join9(root, "src"), { recursive: true });
|
|
8092
8560
|
for (const file of demoFiles({ root, port, host })) {
|
|
8093
|
-
writeFileSync7(
|
|
8561
|
+
writeFileSync7(join9(root, file.path), file.content, "utf8");
|
|
8094
8562
|
}
|
|
8095
8563
|
return {
|
|
8096
8564
|
root,
|
|
@@ -8134,7 +8602,7 @@ function assertWritableDemoRoot(root, reset) {
|
|
|
8134
8602
|
if (!existsSync8(root)) {
|
|
8135
8603
|
return;
|
|
8136
8604
|
}
|
|
8137
|
-
const markerPath =
|
|
8605
|
+
const markerPath = join9(root, markerFile);
|
|
8138
8606
|
const isDemoRoot = existsSync8(markerPath) && readFileSync7(markerPath, "utf8").trim() === "hello-linzumi";
|
|
8139
8607
|
if (isDemoRoot && reset) {
|
|
8140
8608
|
rmSync2(root, { recursive: true, force: true });
|
|
@@ -8642,19 +9110,19 @@ import {
|
|
|
8642
9110
|
watch,
|
|
8643
9111
|
writeFileSync as writeFileSync8
|
|
8644
9112
|
} from "node:fs";
|
|
8645
|
-
import { homedir as
|
|
8646
|
-
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";
|
|
8647
9115
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
8648
9116
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8649
9117
|
var connectedMarker = "Runner connected:";
|
|
8650
9118
|
function commanderStatusDir() {
|
|
8651
|
-
return
|
|
9119
|
+
return join10(homedir8(), ".linzumi", "commanders");
|
|
8652
9120
|
}
|
|
8653
9121
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
8654
|
-
return
|
|
9122
|
+
return join10(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
8655
9123
|
}
|
|
8656
9124
|
function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
8657
|
-
return
|
|
9125
|
+
return join10(statusDir, `${safeRunnerId(runnerId)}.log`);
|
|
8658
9126
|
}
|
|
8659
9127
|
function startCommanderDaemon(options) {
|
|
8660
9128
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
@@ -8676,11 +9144,24 @@ function startCommanderDaemon(options) {
|
|
|
8676
9144
|
mkdirSync9(dirname8(logFile), { recursive: true });
|
|
8677
9145
|
const out = openSync2(logFile, "a");
|
|
8678
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 });
|
|
8679
9153
|
const child = (options.spawnImpl ?? spawn7)(nodeBin, command, {
|
|
8680
9154
|
detached: true,
|
|
8681
9155
|
stdio: ["ignore", out, err],
|
|
8682
9156
|
env: process.env
|
|
8683
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 });
|
|
8684
9165
|
closeSync(out);
|
|
8685
9166
|
closeSync(err);
|
|
8686
9167
|
child.unref();
|
|
@@ -8804,10 +9285,22 @@ function processMatchesRecord(record, processIdentityReader) {
|
|
|
8804
9285
|
}
|
|
8805
9286
|
function readProcessIdentity(pid) {
|
|
8806
9287
|
try {
|
|
8807
|
-
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, {
|
|
8808
9296
|
encoding: "utf8",
|
|
8809
9297
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8810
9298
|
}).trim();
|
|
9299
|
+
writeCliAuditEvent("process.exec_file_completed", {
|
|
9300
|
+
command,
|
|
9301
|
+
args,
|
|
9302
|
+
purpose: "commander_daemon.process_identity"
|
|
9303
|
+
});
|
|
8811
9304
|
if (output === "") {
|
|
8812
9305
|
return;
|
|
8813
9306
|
}
|
|
@@ -8922,7 +9415,7 @@ async function main(args) {
|
|
|
8922
9415
|
process.stdout.write(connectGuideText());
|
|
8923
9416
|
return;
|
|
8924
9417
|
case "version":
|
|
8925
|
-
process.stdout.write(`linzumi 0.0.
|
|
9418
|
+
process.stdout.write(`linzumi 0.0.34-beta
|
|
8926
9419
|
`);
|
|
8927
9420
|
return;
|
|
8928
9421
|
case "auth":
|
|
@@ -8993,6 +9486,7 @@ function parseCommand(args) {
|
|
|
8993
9486
|
return { command: "commanderDaemon", args: ["stop", ...rest] };
|
|
8994
9487
|
case "signup":
|
|
8995
9488
|
case "claim":
|
|
9489
|
+
case "login-link":
|
|
8996
9490
|
case "thread":
|
|
8997
9491
|
case "channel":
|
|
8998
9492
|
case "post":
|
|
@@ -9198,7 +9692,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
9198
9692
|
process.exit(0);
|
|
9199
9693
|
}
|
|
9200
9694
|
rejectStartTargetingFlags(values);
|
|
9201
|
-
const kandanUrl = stringValue3(values, "kandan-url") ??
|
|
9695
|
+
const kandanUrl = stringValue3(values, "kandan-url") ?? defaultLinzumiWebSocketUrl;
|
|
9202
9696
|
const requestedCwd = resolveUserPath(cwdArg ?? process.cwd());
|
|
9203
9697
|
const cwd = assertConfiguredAllowedCwds([requestedCwd])[0] ?? requestedCwd;
|
|
9204
9698
|
const explicitAllowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [];
|
|
@@ -9435,7 +9929,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
9435
9929
|
process.exit(0);
|
|
9436
9930
|
}
|
|
9437
9931
|
if (values.get("version") === true) {
|
|
9438
|
-
process.stdout.write(`linzumi 0.0.
|
|
9932
|
+
process.stdout.write(`linzumi 0.0.34-beta
|
|
9439
9933
|
`);
|
|
9440
9934
|
process.exit(0);
|
|
9441
9935
|
}
|
|
@@ -9580,10 +10074,10 @@ function rejectStartTargetingFlags(values) {
|
|
|
9580
10074
|
}
|
|
9581
10075
|
function resolveUserPath(pathValue) {
|
|
9582
10076
|
if (pathValue === "~") {
|
|
9583
|
-
return
|
|
10077
|
+
return homedir9();
|
|
9584
10078
|
}
|
|
9585
10079
|
if (pathValue.startsWith("~/")) {
|
|
9586
|
-
return resolve8(
|
|
10080
|
+
return resolve8(homedir9(), pathValue.slice(2));
|
|
9587
10081
|
}
|
|
9588
10082
|
return resolve8(pathValue);
|
|
9589
10083
|
}
|
|
@@ -9678,6 +10172,8 @@ Usage:
|
|
|
9678
10172
|
linzumi
|
|
9679
10173
|
linzumi signup --email <email> --agent-name <name> [--workspace-name <name>]
|
|
9680
10174
|
linzumi claim --pending <pending_id> --code <XXXX-XXXX>
|
|
10175
|
+
linzumi login-link issue --email <email> --workspace <workspace_id>
|
|
10176
|
+
linzumi login-link claim --pending <pending_id> --code <XXXX-XXXX>
|
|
9681
10177
|
linzumi thread new <title> --message <message>
|
|
9682
10178
|
linzumi post <thread_id> <message>
|
|
9683
10179
|
linzumi inbox <thread_id> --since-last
|
|
@@ -9693,7 +10189,7 @@ Usage:
|
|
|
9693
10189
|
linzumi auth --kandan-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
9694
10190
|
|
|
9695
10191
|
Required:
|
|
9696
|
-
--kandan-url <ws-url> Linzumi backend URL, default
|
|
10192
|
+
--kandan-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
9697
10193
|
--token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
|
|
9698
10194
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
9699
10195
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
@@ -9830,7 +10326,7 @@ What it does:
|
|
|
9830
10326
|
previews, and the browser VS Code editor.
|
|
9831
10327
|
|
|
9832
10328
|
Options:
|
|
9833
|
-
--kandan-url <ws-url> Linzumi backend URL, default
|
|
10329
|
+
--kandan-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
9834
10330
|
--token <jwt> Optional scoped local-runner token override
|
|
9835
10331
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
9836
10332
|
--oauth-callback-host <ip> Callback host reachable by your browser
|