@linzumi/cli 0.0.85-beta → 0.0.86-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +1013 -296
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -39,6 +39,70 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
39
39
|
mod
|
|
40
40
|
));
|
|
41
41
|
|
|
42
|
+
// src/onboardingPlaceResponsiveness.ts
|
|
43
|
+
import { Worker } from "node:worker_threads";
|
|
44
|
+
function isPlaceResponsive(path2, deps = {}) {
|
|
45
|
+
const timeoutMs = deps.timeoutMs ?? defaultPlaceProbeTimeoutMs;
|
|
46
|
+
if (deps.probe !== void 0) {
|
|
47
|
+
return deps.probe(path2, timeoutMs);
|
|
48
|
+
}
|
|
49
|
+
return statProbe(path2, timeoutMs);
|
|
50
|
+
}
|
|
51
|
+
function statProbe(path2, timeoutMs) {
|
|
52
|
+
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
|
|
53
|
+
const status = new Int32Array(sharedBuffer);
|
|
54
|
+
Atomics.store(status, 0, probeStatusPending);
|
|
55
|
+
let worker;
|
|
56
|
+
try {
|
|
57
|
+
worker = new Worker(probeWorkerSource, {
|
|
58
|
+
eval: true,
|
|
59
|
+
workerData: { sharedBuffer, path: path2 }
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
worker.unref();
|
|
65
|
+
worker.on("error", () => {
|
|
66
|
+
if (Atomics.load(status, 0) === probeStatusPending) {
|
|
67
|
+
Atomics.store(status, 0, probeStatusUnresponsive);
|
|
68
|
+
Atomics.notify(status, 0);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
Atomics.wait(status, 0, probeStatusPending, timeoutMs);
|
|
72
|
+
const result = Atomics.load(status, 0);
|
|
73
|
+
void worker.terminate().catch(() => {
|
|
74
|
+
});
|
|
75
|
+
return result === probeStatusResponsive;
|
|
76
|
+
}
|
|
77
|
+
var defaultPlaceProbeTimeoutMs, probeStatusPending, probeStatusResponsive, probeStatusUnresponsive, probeWorkerSource;
|
|
78
|
+
var init_onboardingPlaceResponsiveness = __esm({
|
|
79
|
+
"src/onboardingPlaceResponsiveness.ts"() {
|
|
80
|
+
"use strict";
|
|
81
|
+
defaultPlaceProbeTimeoutMs = 1e3;
|
|
82
|
+
probeStatusPending = 0;
|
|
83
|
+
probeStatusResponsive = 1;
|
|
84
|
+
probeStatusUnresponsive = 2;
|
|
85
|
+
probeWorkerSource = `
|
|
86
|
+
const { workerData, parentPort } = require('node:worker_threads');
|
|
87
|
+
const { statSync } = require('node:fs');
|
|
88
|
+
const status = new Int32Array(workerData.sharedBuffer);
|
|
89
|
+
let result = ${probeStatusUnresponsive};
|
|
90
|
+
try {
|
|
91
|
+
statSync(workerData.path);
|
|
92
|
+
result = ${probeStatusResponsive};
|
|
93
|
+
} catch {
|
|
94
|
+
// A path that throws quickly (ENOENT/EACCES/ENOTDIR) is still a *responsive*
|
|
95
|
+
// volume - discovery's own existence checks will handle it. Only a wedged
|
|
96
|
+
// syscall (which never returns here) counts as unresponsive.
|
|
97
|
+
result = ${probeStatusResponsive};
|
|
98
|
+
}
|
|
99
|
+
Atomics.store(status, 0, result);
|
|
100
|
+
Atomics.notify(status, 0);
|
|
101
|
+
if (parentPort) parentPort.postMessage(result);
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
42
106
|
// src/onboardingProjectDiscovery.ts
|
|
43
107
|
var onboardingProjectDiscovery_exports = {};
|
|
44
108
|
__export(onboardingProjectDiscovery_exports, {
|
|
@@ -50,10 +114,12 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
50
114
|
import { basename, dirname, join } from "node:path";
|
|
51
115
|
function discoverCurrentGitProject(args) {
|
|
52
116
|
const startedAtMs = args.nowMs ?? Date.now();
|
|
117
|
+
const responsiveness = args.placeResponsiveness;
|
|
53
118
|
const candidates = /* @__PURE__ */ new Map();
|
|
54
119
|
const searchStats = [];
|
|
120
|
+
const cwdResponsive = isPlaceResponsive(args.cwd, responsiveness);
|
|
55
121
|
const gitStartedAtMs = Date.now();
|
|
56
|
-
const topLevel = gitOutput(args.cwd, ["rev-parse", "--show-toplevel"]);
|
|
122
|
+
const topLevel = cwdResponsive ? gitOutput(args.cwd, ["rev-parse", "--show-toplevel"]) : void 0;
|
|
57
123
|
const gitDurationMs = Date.now() - gitStartedAtMs;
|
|
58
124
|
if (topLevel !== void 0) {
|
|
59
125
|
mergeCandidate(
|
|
@@ -65,12 +131,28 @@ function discoverCurrentGitProject(args) {
|
|
|
65
131
|
place: args.cwd,
|
|
66
132
|
source: "git",
|
|
67
133
|
duration_ms: gitDurationMs,
|
|
68
|
-
result_count: topLevel === void 0 ? 0 : 1
|
|
134
|
+
result_count: topLevel === void 0 ? 0 : 1,
|
|
135
|
+
skipped_unresponsive: cwdResponsive ? void 0 : true
|
|
69
136
|
});
|
|
70
137
|
const sourceRoots = args.sourceRoots ?? defaultLocalProjectSourceRoots(args.cwd);
|
|
71
138
|
let placesSearched = 1;
|
|
72
139
|
for (const sourceRoot of sourceRoots) {
|
|
73
140
|
const sourceRootStartedAtMs = Date.now();
|
|
141
|
+
const rootResponsive = isPlaceResponsive(sourceRoot, responsiveness);
|
|
142
|
+
if (!rootResponsive) {
|
|
143
|
+
searchStats.push({
|
|
144
|
+
place: sourceRoot,
|
|
145
|
+
source: "git",
|
|
146
|
+
duration_ms: Date.now() - sourceRootStartedAtMs,
|
|
147
|
+
result_count: 0,
|
|
148
|
+
skipped_unresponsive: true
|
|
149
|
+
});
|
|
150
|
+
placesSearched += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!isDirectory(sourceRoot)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
74
156
|
const foundInRoot = discoverGitWorktreesInSourceRoot(sourceRoot);
|
|
75
157
|
for (const worktreePath of foundInRoot) {
|
|
76
158
|
mergeCandidate(
|
|
@@ -218,7 +300,7 @@ function defaultLocalProjectSourceRoots(cwd) {
|
|
|
218
300
|
...ancestorSourceRoots(cwd),
|
|
219
301
|
...homeSourceRoots(),
|
|
220
302
|
...externalVolumeSourceRoots()
|
|
221
|
-
])
|
|
303
|
+
]);
|
|
222
304
|
}
|
|
223
305
|
function ancestorSourceRoots(cwd) {
|
|
224
306
|
const sourceRootNames = /* @__PURE__ */ new Set([
|
|
@@ -331,6 +413,7 @@ var worktreePathSampleLimit, gitSpawnTimeoutMs;
|
|
|
331
413
|
var init_onboardingProjectDiscovery = __esm({
|
|
332
414
|
"src/onboardingProjectDiscovery.ts"() {
|
|
333
415
|
"use strict";
|
|
416
|
+
init_onboardingPlaceResponsiveness();
|
|
334
417
|
worktreePathSampleLimit = 25;
|
|
335
418
|
gitSpawnTimeoutMs = 4e3;
|
|
336
419
|
}
|
|
@@ -6977,6 +7060,7 @@ function startPortForwardWatcher(options) {
|
|
|
6977
7060
|
const scanProcessCwds = options.scanProcessCwds ?? readProcessCwdRows;
|
|
6978
7061
|
const nowMs = options.nowMs ?? Date.now;
|
|
6979
7062
|
const commanderBoundPids = validPidSet(options.commanderBoundPids ?? []);
|
|
7063
|
+
const commanderBoundPorts = validPortSet(options.commanderBoundPorts ?? []);
|
|
6980
7064
|
const candidateStabilityByPort = /* @__PURE__ */ new Map();
|
|
6981
7065
|
const emittedByPort = /* @__PURE__ */ new Map();
|
|
6982
7066
|
const missingByPort = /* @__PURE__ */ new Map();
|
|
@@ -6990,12 +7074,15 @@ function startPortForwardWatcher(options) {
|
|
|
6990
7074
|
const descendants = descendantPidSet(scanProcesses(), rootPid);
|
|
6991
7075
|
const sockets = scanListenSockets();
|
|
6992
7076
|
const observedPids = /* @__PURE__ */ new Set([...descendants, ...commanderBoundPids]);
|
|
6993
|
-
const candidatePids = sockets.filter(
|
|
7077
|
+
const candidatePids = sockets.filter(
|
|
7078
|
+
(socket) => observedPids.has(socket.pid) || commanderBoundPorts.has(socket.port)
|
|
7079
|
+
).map((socket) => socket.pid);
|
|
6994
7080
|
const candidates = detectedForwardCandidates(
|
|
6995
7081
|
sockets,
|
|
6996
7082
|
observedPids,
|
|
6997
7083
|
scanProcessCwds(candidatePids),
|
|
6998
|
-
commanderBoundPids
|
|
7084
|
+
commanderBoundPids,
|
|
7085
|
+
commanderBoundPorts
|
|
6999
7086
|
);
|
|
7000
7087
|
const scanTimeMs = nowMs();
|
|
7001
7088
|
const stable = stableForwardCandidates(
|
|
@@ -7113,10 +7200,12 @@ function descendantPidSet(rows, rootPid) {
|
|
|
7113
7200
|
}
|
|
7114
7201
|
return descendants;
|
|
7115
7202
|
}
|
|
7116
|
-
function detectedForwardCandidates(sockets, descendantPids, processCwds = /* @__PURE__ */ new Map(), commanderBoundPids = /* @__PURE__ */ new Set()) {
|
|
7117
|
-
return sockets.filter(
|
|
7203
|
+
function detectedForwardCandidates(sockets, descendantPids, processCwds = /* @__PURE__ */ new Map(), commanderBoundPids = /* @__PURE__ */ new Set(), commanderBoundPorts = /* @__PURE__ */ new Set()) {
|
|
7204
|
+
return sockets.filter(
|
|
7205
|
+
(socket) => descendantPids.has(socket.pid) || commanderBoundPorts.has(socket.port)
|
|
7206
|
+
).filter((socket) => socket.port > 0 && socket.port < 65536).sort((left, right) => left.port - right.port).map((socket) => {
|
|
7118
7207
|
const cwd = processCwds.get(socket.pid);
|
|
7119
|
-
const portKind = commanderBoundPids.size === 0 ? void 0 : commanderBoundPids.has(socket.pid) ? "commander_bound" : "descendant";
|
|
7208
|
+
const portKind = commanderBoundPids.size === 0 && commanderBoundPorts.size === 0 ? void 0 : commanderBoundPids.has(socket.pid) || commanderBoundPorts.has(socket.port) ? "commander_bound" : "descendant";
|
|
7120
7209
|
return {
|
|
7121
7210
|
port: socket.port,
|
|
7122
7211
|
pid: socket.pid,
|
|
@@ -7129,6 +7218,11 @@ function detectedForwardCandidates(sockets, descendantPids, processCwds = /* @__
|
|
|
7129
7218
|
function validPidSet(pids) {
|
|
7130
7219
|
return new Set(pids.filter((pid) => Number.isInteger(pid) && pid > 0));
|
|
7131
7220
|
}
|
|
7221
|
+
function validPortSet(ports) {
|
|
7222
|
+
return new Set(
|
|
7223
|
+
ports.filter((port) => Number.isInteger(port) && port > 0 && port < 65536)
|
|
7224
|
+
);
|
|
7225
|
+
}
|
|
7132
7226
|
function normalizedPortKind(portKind) {
|
|
7133
7227
|
return portKind ?? "descendant";
|
|
7134
7228
|
}
|
|
@@ -11964,14 +12058,336 @@ var init_claudeCodePlanMirror = __esm({
|
|
|
11964
12058
|
}
|
|
11965
12059
|
});
|
|
11966
12060
|
|
|
12061
|
+
// src/runnerLogger.ts
|
|
12062
|
+
import { appendFileSync, openSync as openSync2 } from "node:fs";
|
|
12063
|
+
import { createWriteStream } from "node:fs";
|
|
12064
|
+
import { homedir as homedir5 } from "node:os";
|
|
12065
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
12066
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
12067
|
+
function createRunnerLogger(logFile, consoleReporter) {
|
|
12068
|
+
mkdirSync2(dirname3(logFile), { recursive: true });
|
|
12069
|
+
const fd = openSync2(logFile, "a");
|
|
12070
|
+
const stream = createWriteStream("", { fd, flags: "a", autoClose: true });
|
|
12071
|
+
const logger = ((event, payload) => {
|
|
12072
|
+
const redacted = redactForCliLog(payload);
|
|
12073
|
+
stream.write(
|
|
12074
|
+
`${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event, ...redacted })}
|
|
12075
|
+
`,
|
|
12076
|
+
"utf8"
|
|
12077
|
+
);
|
|
12078
|
+
consoleReporter?.(event, runnerConsolePayload(redacted, payload));
|
|
12079
|
+
});
|
|
12080
|
+
Object.defineProperty(logger, "close", {
|
|
12081
|
+
value: () => closeStream(stream)
|
|
12082
|
+
});
|
|
12083
|
+
return logger;
|
|
12084
|
+
}
|
|
12085
|
+
function writeCliAuditEvent(event, payload, options = {}) {
|
|
12086
|
+
const logFile = options.logFile ?? defaultCliAuditLogFile();
|
|
12087
|
+
try {
|
|
12088
|
+
mkdirSync2(dirname3(logFile), { recursive: true });
|
|
12089
|
+
appendFileSync(
|
|
12090
|
+
logFile,
|
|
12091
|
+
`${JSON.stringify({
|
|
12092
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12093
|
+
event,
|
|
12094
|
+
...options.sessionId === void 0 ? {} : { sessionId: options.sessionId },
|
|
12095
|
+
...redactForCliLog(payload)
|
|
12096
|
+
})}
|
|
12097
|
+
`,
|
|
12098
|
+
"utf8"
|
|
12099
|
+
);
|
|
12100
|
+
} catch (_error) {
|
|
12101
|
+
return;
|
|
12102
|
+
}
|
|
12103
|
+
}
|
|
12104
|
+
function defaultCliAuditLogFile() {
|
|
12105
|
+
const override = process.env.LINZUMI_CLI_AUDIT_LOG?.trim();
|
|
12106
|
+
return override === void 0 || override === "" ? join6(homedir5(), ".linzumi", "logs", "command-events.jsonl") : override;
|
|
12107
|
+
}
|
|
12108
|
+
function defaultRunnerLogFile() {
|
|
12109
|
+
return join6(homedir5(), ".linzumi", "logs", "linzumi-runner.log");
|
|
12110
|
+
}
|
|
12111
|
+
function redactForCliLog(value) {
|
|
12112
|
+
return redactObject(value);
|
|
12113
|
+
}
|
|
12114
|
+
function redactValue(value, key) {
|
|
12115
|
+
if (sensitiveKey(key)) {
|
|
12116
|
+
return sensitiveMarker;
|
|
12117
|
+
}
|
|
12118
|
+
if (typeof value === "string") {
|
|
12119
|
+
return redactString(value);
|
|
12120
|
+
}
|
|
12121
|
+
if (Array.isArray(value)) {
|
|
12122
|
+
return key === "args" ? redactArgs(value) : value.map((item) => redactValue(item, void 0));
|
|
12123
|
+
}
|
|
12124
|
+
if (value !== null && typeof value === "object") {
|
|
12125
|
+
return redactObject(value);
|
|
12126
|
+
}
|
|
12127
|
+
return value;
|
|
12128
|
+
}
|
|
12129
|
+
function redactObject(value) {
|
|
12130
|
+
const headerName = typeof value.name === "string" ? value.name.toLowerCase() : void 0;
|
|
12131
|
+
const shouldRedactHeaderValue = headerName === "authorization" || headerName === "cookie" || headerName === "set-cookie";
|
|
12132
|
+
return Object.fromEntries(
|
|
12133
|
+
Object.entries(value).filter(([key]) => key !== "runner_console").map(([key, entry]) => [
|
|
12134
|
+
key,
|
|
12135
|
+
shouldRedactHeaderValue && key === "value" ? sensitiveMarker : redactValue(entry, key)
|
|
12136
|
+
])
|
|
12137
|
+
);
|
|
12138
|
+
}
|
|
12139
|
+
function runnerConsolePayload(redacted, original) {
|
|
12140
|
+
const consoleFields = objectValue3(original.runner_console);
|
|
12141
|
+
return consoleFields === void 0 ? redacted : { ...redacted, ...redactForCliLog(consoleFields) };
|
|
12142
|
+
}
|
|
12143
|
+
function objectValue3(value) {
|
|
12144
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
12145
|
+
}
|
|
12146
|
+
function redactArgs(args) {
|
|
12147
|
+
let redactNext = false;
|
|
12148
|
+
return args.map((arg) => {
|
|
12149
|
+
if (typeof arg !== "string") {
|
|
12150
|
+
redactNext = false;
|
|
12151
|
+
return redactValue(arg, void 0);
|
|
12152
|
+
}
|
|
12153
|
+
if (redactNext) {
|
|
12154
|
+
redactNext = false;
|
|
12155
|
+
return sensitiveMarker;
|
|
12156
|
+
}
|
|
12157
|
+
const [flag, value] = splitArgAssignment(arg);
|
|
12158
|
+
if (sensitiveArgFlags.has(flag)) {
|
|
12159
|
+
if (value === void 0) {
|
|
12160
|
+
redactNext = true;
|
|
12161
|
+
return arg;
|
|
12162
|
+
}
|
|
12163
|
+
return `${flag}=${sensitiveMarker}`;
|
|
12164
|
+
}
|
|
12165
|
+
return redactString(arg);
|
|
12166
|
+
});
|
|
12167
|
+
}
|
|
12168
|
+
function splitArgAssignment(value) {
|
|
12169
|
+
const index = value.indexOf("=");
|
|
12170
|
+
return index === -1 ? [value, void 0] : [value.slice(0, index), value.slice(index + 1)];
|
|
12171
|
+
}
|
|
12172
|
+
function sensitiveKey(key) {
|
|
12173
|
+
if (key === void 0) {
|
|
12174
|
+
return false;
|
|
12175
|
+
}
|
|
12176
|
+
return /^(authorization|cookie|set-cookie|password)$/i.test(key) || /(^|[_-])(access[_-]?token|api[_-]?key|auth[_-]?token|oauth[_-]?code|refresh[_-]?token|secret|token)$/i.test(
|
|
12177
|
+
key
|
|
12178
|
+
);
|
|
12179
|
+
}
|
|
12180
|
+
function redactString(value) {
|
|
12181
|
+
const withRedactedQuery = redactUrlQuery(value);
|
|
12182
|
+
return withRedactedQuery.replace(
|
|
12183
|
+
/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
12184
|
+
sensitiveMarker
|
|
12185
|
+
);
|
|
12186
|
+
}
|
|
12187
|
+
function redactUrlQuery(value) {
|
|
12188
|
+
let parsed;
|
|
12189
|
+
try {
|
|
12190
|
+
parsed = new URL(value);
|
|
12191
|
+
} catch (_error) {
|
|
12192
|
+
return redactRelativeUrlQuery(value);
|
|
12193
|
+
}
|
|
12194
|
+
for (const name of [...parsed.searchParams.keys()]) {
|
|
12195
|
+
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
12196
|
+
parsed.searchParams.set(name, sensitiveMarker);
|
|
12197
|
+
}
|
|
12198
|
+
}
|
|
12199
|
+
return parsed.toString();
|
|
12200
|
+
}
|
|
12201
|
+
function redactRelativeUrlQuery(value) {
|
|
12202
|
+
const queryStart = value.indexOf("?");
|
|
12203
|
+
const hashStart = value.indexOf("#");
|
|
12204
|
+
if (queryStart === -1 || hashStart !== -1 && hashStart < queryStart) {
|
|
12205
|
+
return value;
|
|
12206
|
+
}
|
|
12207
|
+
const path2 = value.slice(0, queryStart);
|
|
12208
|
+
const query = hashStart === -1 ? value.slice(queryStart + 1) : value.slice(queryStart + 1, hashStart);
|
|
12209
|
+
const hash = hashStart === -1 ? "" : value.slice(hashStart);
|
|
12210
|
+
const searchParams = new URLSearchParams(query);
|
|
12211
|
+
let redacted = false;
|
|
12212
|
+
for (const name of [...searchParams.keys()]) {
|
|
12213
|
+
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
12214
|
+
searchParams.set(name, sensitiveMarker);
|
|
12215
|
+
redacted = true;
|
|
12216
|
+
}
|
|
12217
|
+
}
|
|
12218
|
+
switch (redacted) {
|
|
12219
|
+
case true:
|
|
12220
|
+
return `${path2}?${searchParams.toString()}${hash}`;
|
|
12221
|
+
case false:
|
|
12222
|
+
return value;
|
|
12223
|
+
}
|
|
12224
|
+
}
|
|
12225
|
+
function closeStream(stream) {
|
|
12226
|
+
if (stream.closed || stream.destroyed) {
|
|
12227
|
+
return Promise.resolve();
|
|
12228
|
+
}
|
|
12229
|
+
return new Promise((resolve12, reject) => {
|
|
12230
|
+
stream.once("error", reject);
|
|
12231
|
+
stream.end(resolve12);
|
|
12232
|
+
});
|
|
12233
|
+
}
|
|
12234
|
+
var sensitiveMarker, sensitiveQueryParams, sensitiveArgFlags;
|
|
12235
|
+
var init_runnerLogger = __esm({
|
|
12236
|
+
"src/runnerLogger.ts"() {
|
|
12237
|
+
"use strict";
|
|
12238
|
+
sensitiveMarker = "<SENSITIVE_DATA>";
|
|
12239
|
+
sensitiveQueryParams = /* @__PURE__ */ new Set([
|
|
12240
|
+
"access_token",
|
|
12241
|
+
"authorization",
|
|
12242
|
+
"code",
|
|
12243
|
+
"cookie",
|
|
12244
|
+
"kandan_preview_ticket",
|
|
12245
|
+
"refresh_token",
|
|
12246
|
+
"token"
|
|
12247
|
+
]);
|
|
12248
|
+
sensitiveArgFlags = /* @__PURE__ */ new Set([
|
|
12249
|
+
"--access-token",
|
|
12250
|
+
"--api-key",
|
|
12251
|
+
"--authorization",
|
|
12252
|
+
"--cookie",
|
|
12253
|
+
"--oauth-code",
|
|
12254
|
+
"--password",
|
|
12255
|
+
"--refresh-token",
|
|
12256
|
+
"--secret",
|
|
12257
|
+
"--token"
|
|
12258
|
+
]);
|
|
12259
|
+
}
|
|
12260
|
+
});
|
|
12261
|
+
|
|
12262
|
+
// src/engineChildReaper.ts
|
|
12263
|
+
function registerEngineChild(registration, _killProcess = process.kill) {
|
|
12264
|
+
reaperState.children.add(registration);
|
|
12265
|
+
ensureExitHandlersInstalled();
|
|
12266
|
+
return {
|
|
12267
|
+
unregister: () => {
|
|
12268
|
+
reaperState.children.delete(registration);
|
|
12269
|
+
}
|
|
12270
|
+
};
|
|
12271
|
+
}
|
|
12272
|
+
function reapAllEngineChildren(killProcess = process.kill, reason = "exit") {
|
|
12273
|
+
const children = [...reaperState.children];
|
|
12274
|
+
reaperState.children.clear();
|
|
12275
|
+
for (const child of children) {
|
|
12276
|
+
reapEngineChild(child, killProcess, reason);
|
|
12277
|
+
}
|
|
12278
|
+
}
|
|
12279
|
+
function reapEngineChild(child, killProcess, reason) {
|
|
12280
|
+
try {
|
|
12281
|
+
child.stop();
|
|
12282
|
+
} catch (error) {
|
|
12283
|
+
auditReapFailure(child, reason, "stop", error);
|
|
12284
|
+
}
|
|
12285
|
+
if (child.pid === void 0) {
|
|
12286
|
+
return;
|
|
12287
|
+
}
|
|
12288
|
+
if (child.ownProcessGroup) {
|
|
12289
|
+
try {
|
|
12290
|
+
killProcess(-child.pid, reapSignal);
|
|
12291
|
+
return;
|
|
12292
|
+
} catch (error) {
|
|
12293
|
+
if (processSignalErrorCode(error) === "ESRCH") {
|
|
12294
|
+
return;
|
|
12295
|
+
}
|
|
12296
|
+
auditReapFailure(child, reason, "group_kill", error);
|
|
12297
|
+
}
|
|
12298
|
+
}
|
|
12299
|
+
try {
|
|
12300
|
+
killProcess(child.pid, reapSignal);
|
|
12301
|
+
} catch (error) {
|
|
12302
|
+
if (processSignalErrorCode(error) === "ESRCH") {
|
|
12303
|
+
return;
|
|
12304
|
+
}
|
|
12305
|
+
auditReapFailure(child, reason, "pid_kill", error);
|
|
12306
|
+
}
|
|
12307
|
+
}
|
|
12308
|
+
function ensureExitHandlersInstalled() {
|
|
12309
|
+
if (reaperState.handlersInstalled) {
|
|
12310
|
+
return;
|
|
12311
|
+
}
|
|
12312
|
+
reaperState.handlersInstalled = true;
|
|
12313
|
+
const onExit2 = () => {
|
|
12314
|
+
reapAllEngineChildren(process.kill, "process_exit");
|
|
12315
|
+
};
|
|
12316
|
+
process.on("exit", onExit2);
|
|
12317
|
+
const ownsUncaught = process.listenerCount("uncaughtException") === 0;
|
|
12318
|
+
const ownsRejection = process.listenerCount("unhandledRejection") === 0;
|
|
12319
|
+
const onUncaughtException = (error) => {
|
|
12320
|
+
reapAllEngineChildren(process.kill, "uncaught_exception");
|
|
12321
|
+
process.stderr.write(
|
|
12322
|
+
`linzumi runner uncaughtException: ${error instanceof Error ? error.stack ?? error.message : String(error)}
|
|
12323
|
+
`
|
|
12324
|
+
);
|
|
12325
|
+
process.exit(1);
|
|
12326
|
+
};
|
|
12327
|
+
const onUnhandledRejection = (reason) => {
|
|
12328
|
+
reapAllEngineChildren(process.kill, "unhandled_rejection");
|
|
12329
|
+
process.stderr.write(
|
|
12330
|
+
`linzumi runner unhandledRejection: ${reason instanceof Error ? reason.stack ?? reason.message : String(reason)}
|
|
12331
|
+
`
|
|
12332
|
+
);
|
|
12333
|
+
process.exit(1);
|
|
12334
|
+
};
|
|
12335
|
+
if (ownsUncaught) {
|
|
12336
|
+
process.on("uncaughtException", onUncaughtException);
|
|
12337
|
+
}
|
|
12338
|
+
if (ownsRejection) {
|
|
12339
|
+
process.on("unhandledRejection", onUnhandledRejection);
|
|
12340
|
+
}
|
|
12341
|
+
reaperState.removeHandlers = () => {
|
|
12342
|
+
process.off("exit", onExit2);
|
|
12343
|
+
if (ownsUncaught) {
|
|
12344
|
+
process.off("uncaughtException", onUncaughtException);
|
|
12345
|
+
}
|
|
12346
|
+
if (ownsRejection) {
|
|
12347
|
+
process.off("unhandledRejection", onUnhandledRejection);
|
|
12348
|
+
}
|
|
12349
|
+
};
|
|
12350
|
+
}
|
|
12351
|
+
function auditReapFailure(child, reason, stage, error) {
|
|
12352
|
+
writeCliAuditEvent("process.reap_failed", {
|
|
12353
|
+
purpose: child.kind,
|
|
12354
|
+
pid: child.pid ?? null,
|
|
12355
|
+
ownProcessGroup: child.ownProcessGroup,
|
|
12356
|
+
reason,
|
|
12357
|
+
stage,
|
|
12358
|
+
code: processSignalErrorCode(error) ?? null,
|
|
12359
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12360
|
+
});
|
|
12361
|
+
}
|
|
12362
|
+
function processSignalErrorCode(error) {
|
|
12363
|
+
if (error !== null && typeof error === "object" && "code" in error) {
|
|
12364
|
+
const code = error.code;
|
|
12365
|
+
return typeof code === "string" ? code : void 0;
|
|
12366
|
+
}
|
|
12367
|
+
return void 0;
|
|
12368
|
+
}
|
|
12369
|
+
var reaperState, reapSignal;
|
|
12370
|
+
var init_engineChildReaper = __esm({
|
|
12371
|
+
"src/engineChildReaper.ts"() {
|
|
12372
|
+
"use strict";
|
|
12373
|
+
init_runnerLogger();
|
|
12374
|
+
reaperState = {
|
|
12375
|
+
children: /* @__PURE__ */ new Set(),
|
|
12376
|
+
handlersInstalled: false,
|
|
12377
|
+
removeHandlers: void 0
|
|
12378
|
+
};
|
|
12379
|
+
reapSignal = "SIGKILL";
|
|
12380
|
+
}
|
|
12381
|
+
});
|
|
12382
|
+
|
|
11967
12383
|
// src/claudeCodeLiveBashOutput.ts
|
|
11968
12384
|
import {
|
|
11969
|
-
mkdirSync as
|
|
12385
|
+
mkdirSync as mkdirSync3,
|
|
11970
12386
|
rmSync,
|
|
11971
12387
|
writeFileSync,
|
|
11972
12388
|
promises as fsPromises
|
|
11973
12389
|
} from "node:fs";
|
|
11974
|
-
import { join as
|
|
12390
|
+
import { join as join7 } from "node:path";
|
|
11975
12391
|
import { StringDecoder } from "node:string_decoder";
|
|
11976
12392
|
function shellSingleQuoted(value) {
|
|
11977
12393
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
@@ -12096,12 +12512,12 @@ function createClaudeCodeLiveBashCapture(host) {
|
|
|
12096
12512
|
isClaudeLiveBashWrappedCommand(command)) {
|
|
12097
12513
|
return void 0;
|
|
12098
12514
|
}
|
|
12099
|
-
const file =
|
|
12515
|
+
const file = join7(
|
|
12100
12516
|
host.captureDir,
|
|
12101
12517
|
`${toolUseId.replaceAll(/[^\w-]/g, "_")}.out`
|
|
12102
12518
|
);
|
|
12103
12519
|
try {
|
|
12104
|
-
|
|
12520
|
+
mkdirSync3(host.captureDir, { recursive: true });
|
|
12105
12521
|
writeFileSync(file, "");
|
|
12106
12522
|
} catch (error) {
|
|
12107
12523
|
host.log?.("claude_live_bash.capture_file_failed", {
|
|
@@ -12183,8 +12599,8 @@ var init_claudeCodeLiveBashOutput = __esm({
|
|
|
12183
12599
|
|
|
12184
12600
|
// src/claudeCodeSession.ts
|
|
12185
12601
|
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
12186
|
-
import { homedir as
|
|
12187
|
-
import { join as
|
|
12602
|
+
import { homedir as homedir6 } from "node:os";
|
|
12603
|
+
import { join as join8 } from "node:path";
|
|
12188
12604
|
function claudeCodeSettingSources() {
|
|
12189
12605
|
return ["user", "project", "local"];
|
|
12190
12606
|
}
|
|
@@ -12328,7 +12744,7 @@ function claudeCodeRateLimitSummaryText(rateLimit, nowMs) {
|
|
|
12328
12744
|
async function probeClaudeCodeAvailability(args) {
|
|
12329
12745
|
if (!hasClaudeCodeAuthHint(process.env, {
|
|
12330
12746
|
cwd: args.cwd,
|
|
12331
|
-
homeDir:
|
|
12747
|
+
homeDir: homedir6(),
|
|
12332
12748
|
platform: process.platform,
|
|
12333
12749
|
fileExists: existsSync4,
|
|
12334
12750
|
readTextFile: readTextFileIfPresent
|
|
@@ -12361,7 +12777,7 @@ async function probeClaudeCodeAvailability(args) {
|
|
|
12361
12777
|
}
|
|
12362
12778
|
}
|
|
12363
12779
|
function hasClaudeCodeAuthHint(env, deps) {
|
|
12364
|
-
const configDir = env.CLAUDE_CONFIG_DIR ??
|
|
12780
|
+
const configDir = env.CLAUDE_CONFIG_DIR ?? join8(deps.homeDir, ".claude");
|
|
12365
12781
|
return hasAnthropicCredentialEnv(env) || hasClaudeCloudProviderEnv(env) || hasClaudeCodeFileCredential(configDir, deps) || hasClaudeCodeApiKeyHelper(configDir, deps) || hasMacClaudeCodeKeychainAnchor(deps);
|
|
12366
12782
|
}
|
|
12367
12783
|
function claudeCodePolicyDeferHooks() {
|
|
@@ -12396,14 +12812,14 @@ function hasClaudeCloudProviderEnv(env) {
|
|
|
12396
12812
|
return trueishEnv(env.CLAUDE_CODE_USE_BEDROCK) || trueishEnv(env.CLAUDE_CODE_USE_VERTEX) || trueishEnv(env.CLAUDE_CODE_USE_FOUNDRY) || trueishEnv(env.CLAUDE_CODE_USE_ANTHROPIC_AWS) || trueishEnv(env.CLAUDE_CODE_USE_MANTLE);
|
|
12397
12813
|
}
|
|
12398
12814
|
function hasClaudeCodeFileCredential(configDir, deps) {
|
|
12399
|
-
return deps.fileExists(
|
|
12815
|
+
return deps.fileExists(join8(configDir, ".credentials.json")) || deps.fileExists(join8(configDir, ".claude.json")) || deps.fileExists(join8(deps.homeDir, ".claude.json"));
|
|
12400
12816
|
}
|
|
12401
12817
|
function hasClaudeCodeApiKeyHelper(configDir, deps) {
|
|
12402
12818
|
return [
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12819
|
+
join8(configDir, "settings.json"),
|
|
12820
|
+
join8(configDir, "settings.local.json"),
|
|
12821
|
+
join8(deps.cwd, ".claude", "settings.json"),
|
|
12822
|
+
join8(deps.cwd, ".claude", "settings.local.json")
|
|
12407
12823
|
].some((path2) => settingsFileHasApiKeyHelper(path2, deps));
|
|
12408
12824
|
}
|
|
12409
12825
|
function settingsFileHasApiKeyHelper(path2, deps) {
|
|
@@ -12423,9 +12839,9 @@ function hasMacClaudeCodeKeychainAnchor(deps) {
|
|
|
12423
12839
|
return false;
|
|
12424
12840
|
}
|
|
12425
12841
|
return [
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12842
|
+
join8(deps.homeDir, "Library", "Application Support", "claude-cli-nodejs"),
|
|
12843
|
+
join8(deps.homeDir, "Library", "Application Support", "Claude"),
|
|
12844
|
+
join8(deps.homeDir, "Library", "Preferences", "claude-cli-nodejs")
|
|
12429
12845
|
].some((path2) => deps.fileExists(path2));
|
|
12430
12846
|
}
|
|
12431
12847
|
function readTextFileIfPresent(path2) {
|
|
@@ -13076,207 +13492,6 @@ var init_claudeCodeSession = __esm({
|
|
|
13076
13492
|
}
|
|
13077
13493
|
});
|
|
13078
13494
|
|
|
13079
|
-
// src/runnerLogger.ts
|
|
13080
|
-
import { appendFileSync, openSync as openSync2 } from "node:fs";
|
|
13081
|
-
import { createWriteStream } from "node:fs";
|
|
13082
|
-
import { homedir as homedir6 } from "node:os";
|
|
13083
|
-
import { dirname as dirname3, join as join8 } from "node:path";
|
|
13084
|
-
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
13085
|
-
function createRunnerLogger(logFile, consoleReporter) {
|
|
13086
|
-
mkdirSync3(dirname3(logFile), { recursive: true });
|
|
13087
|
-
const fd = openSync2(logFile, "a");
|
|
13088
|
-
const stream = createWriteStream("", { fd, flags: "a", autoClose: true });
|
|
13089
|
-
const logger = ((event, payload) => {
|
|
13090
|
-
const redacted = redactForCliLog(payload);
|
|
13091
|
-
stream.write(
|
|
13092
|
-
`${JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), event, ...redacted })}
|
|
13093
|
-
`,
|
|
13094
|
-
"utf8"
|
|
13095
|
-
);
|
|
13096
|
-
consoleReporter?.(event, runnerConsolePayload(redacted, payload));
|
|
13097
|
-
});
|
|
13098
|
-
Object.defineProperty(logger, "close", {
|
|
13099
|
-
value: () => closeStream(stream)
|
|
13100
|
-
});
|
|
13101
|
-
return logger;
|
|
13102
|
-
}
|
|
13103
|
-
function writeCliAuditEvent(event, payload, options = {}) {
|
|
13104
|
-
const logFile = options.logFile ?? defaultCliAuditLogFile();
|
|
13105
|
-
try {
|
|
13106
|
-
mkdirSync3(dirname3(logFile), { recursive: true });
|
|
13107
|
-
appendFileSync(
|
|
13108
|
-
logFile,
|
|
13109
|
-
`${JSON.stringify({
|
|
13110
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13111
|
-
event,
|
|
13112
|
-
...options.sessionId === void 0 ? {} : { sessionId: options.sessionId },
|
|
13113
|
-
...redactForCliLog(payload)
|
|
13114
|
-
})}
|
|
13115
|
-
`,
|
|
13116
|
-
"utf8"
|
|
13117
|
-
);
|
|
13118
|
-
} catch (_error) {
|
|
13119
|
-
return;
|
|
13120
|
-
}
|
|
13121
|
-
}
|
|
13122
|
-
function defaultCliAuditLogFile() {
|
|
13123
|
-
const override = process.env.LINZUMI_CLI_AUDIT_LOG?.trim();
|
|
13124
|
-
return override === void 0 || override === "" ? join8(homedir6(), ".linzumi", "logs", "command-events.jsonl") : override;
|
|
13125
|
-
}
|
|
13126
|
-
function defaultRunnerLogFile() {
|
|
13127
|
-
return join8(homedir6(), ".linzumi", "logs", "linzumi-runner.log");
|
|
13128
|
-
}
|
|
13129
|
-
function redactForCliLog(value) {
|
|
13130
|
-
return redactObject(value);
|
|
13131
|
-
}
|
|
13132
|
-
function redactValue(value, key) {
|
|
13133
|
-
if (sensitiveKey(key)) {
|
|
13134
|
-
return sensitiveMarker;
|
|
13135
|
-
}
|
|
13136
|
-
if (typeof value === "string") {
|
|
13137
|
-
return redactString(value);
|
|
13138
|
-
}
|
|
13139
|
-
if (Array.isArray(value)) {
|
|
13140
|
-
return key === "args" ? redactArgs(value) : value.map((item) => redactValue(item, void 0));
|
|
13141
|
-
}
|
|
13142
|
-
if (value !== null && typeof value === "object") {
|
|
13143
|
-
return redactObject(value);
|
|
13144
|
-
}
|
|
13145
|
-
return value;
|
|
13146
|
-
}
|
|
13147
|
-
function redactObject(value) {
|
|
13148
|
-
const headerName = typeof value.name === "string" ? value.name.toLowerCase() : void 0;
|
|
13149
|
-
const shouldRedactHeaderValue = headerName === "authorization" || headerName === "cookie" || headerName === "set-cookie";
|
|
13150
|
-
return Object.fromEntries(
|
|
13151
|
-
Object.entries(value).filter(([key]) => key !== "runner_console").map(([key, entry]) => [
|
|
13152
|
-
key,
|
|
13153
|
-
shouldRedactHeaderValue && key === "value" ? sensitiveMarker : redactValue(entry, key)
|
|
13154
|
-
])
|
|
13155
|
-
);
|
|
13156
|
-
}
|
|
13157
|
-
function runnerConsolePayload(redacted, original) {
|
|
13158
|
-
const consoleFields = objectValue3(original.runner_console);
|
|
13159
|
-
return consoleFields === void 0 ? redacted : { ...redacted, ...redactForCliLog(consoleFields) };
|
|
13160
|
-
}
|
|
13161
|
-
function objectValue3(value) {
|
|
13162
|
-
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
13163
|
-
}
|
|
13164
|
-
function redactArgs(args) {
|
|
13165
|
-
let redactNext = false;
|
|
13166
|
-
return args.map((arg) => {
|
|
13167
|
-
if (typeof arg !== "string") {
|
|
13168
|
-
redactNext = false;
|
|
13169
|
-
return redactValue(arg, void 0);
|
|
13170
|
-
}
|
|
13171
|
-
if (redactNext) {
|
|
13172
|
-
redactNext = false;
|
|
13173
|
-
return sensitiveMarker;
|
|
13174
|
-
}
|
|
13175
|
-
const [flag, value] = splitArgAssignment(arg);
|
|
13176
|
-
if (sensitiveArgFlags.has(flag)) {
|
|
13177
|
-
if (value === void 0) {
|
|
13178
|
-
redactNext = true;
|
|
13179
|
-
return arg;
|
|
13180
|
-
}
|
|
13181
|
-
return `${flag}=${sensitiveMarker}`;
|
|
13182
|
-
}
|
|
13183
|
-
return redactString(arg);
|
|
13184
|
-
});
|
|
13185
|
-
}
|
|
13186
|
-
function splitArgAssignment(value) {
|
|
13187
|
-
const index = value.indexOf("=");
|
|
13188
|
-
return index === -1 ? [value, void 0] : [value.slice(0, index), value.slice(index + 1)];
|
|
13189
|
-
}
|
|
13190
|
-
function sensitiveKey(key) {
|
|
13191
|
-
if (key === void 0) {
|
|
13192
|
-
return false;
|
|
13193
|
-
}
|
|
13194
|
-
return /^(authorization|cookie|set-cookie|password)$/i.test(key) || /(^|[_-])(access[_-]?token|api[_-]?key|auth[_-]?token|oauth[_-]?code|refresh[_-]?token|secret|token)$/i.test(
|
|
13195
|
-
key
|
|
13196
|
-
);
|
|
13197
|
-
}
|
|
13198
|
-
function redactString(value) {
|
|
13199
|
-
const withRedactedQuery = redactUrlQuery(value);
|
|
13200
|
-
return withRedactedQuery.replace(
|
|
13201
|
-
/\beyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g,
|
|
13202
|
-
sensitiveMarker
|
|
13203
|
-
);
|
|
13204
|
-
}
|
|
13205
|
-
function redactUrlQuery(value) {
|
|
13206
|
-
let parsed;
|
|
13207
|
-
try {
|
|
13208
|
-
parsed = new URL(value);
|
|
13209
|
-
} catch (_error) {
|
|
13210
|
-
return redactRelativeUrlQuery(value);
|
|
13211
|
-
}
|
|
13212
|
-
for (const name of [...parsed.searchParams.keys()]) {
|
|
13213
|
-
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
13214
|
-
parsed.searchParams.set(name, sensitiveMarker);
|
|
13215
|
-
}
|
|
13216
|
-
}
|
|
13217
|
-
return parsed.toString();
|
|
13218
|
-
}
|
|
13219
|
-
function redactRelativeUrlQuery(value) {
|
|
13220
|
-
const queryStart = value.indexOf("?");
|
|
13221
|
-
const hashStart = value.indexOf("#");
|
|
13222
|
-
if (queryStart === -1 || hashStart !== -1 && hashStart < queryStart) {
|
|
13223
|
-
return value;
|
|
13224
|
-
}
|
|
13225
|
-
const path2 = value.slice(0, queryStart);
|
|
13226
|
-
const query = hashStart === -1 ? value.slice(queryStart + 1) : value.slice(queryStart + 1, hashStart);
|
|
13227
|
-
const hash = hashStart === -1 ? "" : value.slice(hashStart);
|
|
13228
|
-
const searchParams = new URLSearchParams(query);
|
|
13229
|
-
let redacted = false;
|
|
13230
|
-
for (const name of [...searchParams.keys()]) {
|
|
13231
|
-
if (sensitiveQueryParams.has(name.toLowerCase())) {
|
|
13232
|
-
searchParams.set(name, sensitiveMarker);
|
|
13233
|
-
redacted = true;
|
|
13234
|
-
}
|
|
13235
|
-
}
|
|
13236
|
-
switch (redacted) {
|
|
13237
|
-
case true:
|
|
13238
|
-
return `${path2}?${searchParams.toString()}${hash}`;
|
|
13239
|
-
case false:
|
|
13240
|
-
return value;
|
|
13241
|
-
}
|
|
13242
|
-
}
|
|
13243
|
-
function closeStream(stream) {
|
|
13244
|
-
if (stream.closed || stream.destroyed) {
|
|
13245
|
-
return Promise.resolve();
|
|
13246
|
-
}
|
|
13247
|
-
return new Promise((resolve12, reject) => {
|
|
13248
|
-
stream.once("error", reject);
|
|
13249
|
-
stream.end(resolve12);
|
|
13250
|
-
});
|
|
13251
|
-
}
|
|
13252
|
-
var sensitiveMarker, sensitiveQueryParams, sensitiveArgFlags;
|
|
13253
|
-
var init_runnerLogger = __esm({
|
|
13254
|
-
"src/runnerLogger.ts"() {
|
|
13255
|
-
"use strict";
|
|
13256
|
-
sensitiveMarker = "<SENSITIVE_DATA>";
|
|
13257
|
-
sensitiveQueryParams = /* @__PURE__ */ new Set([
|
|
13258
|
-
"access_token",
|
|
13259
|
-
"authorization",
|
|
13260
|
-
"code",
|
|
13261
|
-
"cookie",
|
|
13262
|
-
"kandan_preview_ticket",
|
|
13263
|
-
"refresh_token",
|
|
13264
|
-
"token"
|
|
13265
|
-
]);
|
|
13266
|
-
sensitiveArgFlags = /* @__PURE__ */ new Set([
|
|
13267
|
-
"--access-token",
|
|
13268
|
-
"--api-key",
|
|
13269
|
-
"--authorization",
|
|
13270
|
-
"--cookie",
|
|
13271
|
-
"--oauth-code",
|
|
13272
|
-
"--password",
|
|
13273
|
-
"--refresh-token",
|
|
13274
|
-
"--secret",
|
|
13275
|
-
"--token"
|
|
13276
|
-
]);
|
|
13277
|
-
}
|
|
13278
|
-
});
|
|
13279
|
-
|
|
13280
13495
|
// src/mcpConfig.ts
|
|
13281
13496
|
function linzumiMcpServerConfig(options) {
|
|
13282
13497
|
return {
|
|
@@ -13403,6 +13618,76 @@ var init_mcpConfig = __esm({
|
|
|
13403
13618
|
}
|
|
13404
13619
|
});
|
|
13405
13620
|
|
|
13621
|
+
// src/engineParentDeathWatchdog.ts
|
|
13622
|
+
function encodeParentDeathWatchdogConfig(config) {
|
|
13623
|
+
return JSON.stringify(config);
|
|
13624
|
+
}
|
|
13625
|
+
function parentDeathWatchdogProgram() {
|
|
13626
|
+
return `
|
|
13627
|
+
const { spawn } = require('node:child_process');
|
|
13628
|
+
const config = JSON.parse(process.argv[process.argv.length - 1]);
|
|
13629
|
+
const originalParentPid = process.ppid;
|
|
13630
|
+
const child = spawn(config.command, config.args, { stdio: 'inherit' });
|
|
13631
|
+
let done = false;
|
|
13632
|
+
function shutdown(signal) {
|
|
13633
|
+
if (done) return;
|
|
13634
|
+
done = true;
|
|
13635
|
+
try { process.kill(-process.pid, signal); }
|
|
13636
|
+
catch (_e) { try { child.kill(signal); } catch (_e2) {} }
|
|
13637
|
+
}
|
|
13638
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
13639
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
13640
|
+
process.on('SIGHUP', () => shutdown('SIGTERM'));
|
|
13641
|
+
child.on('exit', (code, signal) => {
|
|
13642
|
+
clearInterval(watchdog);
|
|
13643
|
+
// The child is gone; the wrapper must follow it down rather than linger as an
|
|
13644
|
+
// orphan. On the forwarded-signal path (shutdown() above caught SIGINT/SIGTERM
|
|
13645
|
+
// /SIGHUP and propagated it to the child), the wrapper still has its own
|
|
13646
|
+
// handler installed for that signal, so a bare process.kill(self, signal)
|
|
13647
|
+
// would just re-enter shutdown() - a no-op once done is set - and the wrapper
|
|
13648
|
+
// would never exit. Remove the handlers first so the signal's DEFAULT
|
|
13649
|
+
// disposition (terminate) applies, re-raise to mirror the child's death
|
|
13650
|
+
// signal, then exit() as a belt-and-suspenders fallback in case re-raising
|
|
13651
|
+
// does not terminate (e.g. a signal with no default-terminate disposition).
|
|
13652
|
+
process.removeAllListeners('SIGINT');
|
|
13653
|
+
process.removeAllListeners('SIGTERM');
|
|
13654
|
+
process.removeAllListeners('SIGHUP');
|
|
13655
|
+
if (signal) { try { process.kill(process.pid, signal); } catch (_e) {} }
|
|
13656
|
+
process.exit(signal ? 1 : code == null ? 0 : code);
|
|
13657
|
+
});
|
|
13658
|
+
child.on('error', (err) => {
|
|
13659
|
+
process.stderr.write('engine watchdog spawn failed: ' + (err && err.message) + '\\n');
|
|
13660
|
+
process.exit(1);
|
|
13661
|
+
});
|
|
13662
|
+
const watchdog = setInterval(() => {
|
|
13663
|
+
// Inlined parentDeathWatchdogShouldReap (keep in sync): reap PURELY on a ppid
|
|
13664
|
+
// change from boot - covers reparent-to-subreaper AND reparent-to-init. NO
|
|
13665
|
+
// \`=== 1\` arm: a runner that is PID 1 captures originalParentPid === 1, so
|
|
13666
|
+
// such an arm would false-fire on the first poll and kill a healthy codex.
|
|
13667
|
+
if (process.ppid !== originalParentPid) {
|
|
13668
|
+
try { child.kill('SIGKILL'); } catch (_e) {}
|
|
13669
|
+
try { process.kill(-process.pid, 'SIGKILL'); } catch (_e) {}
|
|
13670
|
+
process.exit(0);
|
|
13671
|
+
}
|
|
13672
|
+
}, config.pollIntervalMs);
|
|
13673
|
+
`.trim();
|
|
13674
|
+
}
|
|
13675
|
+
function parentDeathWatchdogSpawn(nodeExecPath, config) {
|
|
13676
|
+
return {
|
|
13677
|
+
command: nodeExecPath,
|
|
13678
|
+
args: [
|
|
13679
|
+
"-e",
|
|
13680
|
+
parentDeathWatchdogProgram(),
|
|
13681
|
+
encodeParentDeathWatchdogConfig(config)
|
|
13682
|
+
]
|
|
13683
|
+
};
|
|
13684
|
+
}
|
|
13685
|
+
var init_engineParentDeathWatchdog = __esm({
|
|
13686
|
+
"src/engineParentDeathWatchdog.ts"() {
|
|
13687
|
+
"use strict";
|
|
13688
|
+
}
|
|
13689
|
+
});
|
|
13690
|
+
|
|
13406
13691
|
// src/codexAppServer.ts
|
|
13407
13692
|
import {
|
|
13408
13693
|
spawn as spawn2
|
|
@@ -13444,13 +13729,18 @@ async function startCodexAppServerAttempt(codexBin, cwd, options, attempt) {
|
|
|
13444
13729
|
let stderrText = "";
|
|
13445
13730
|
const configuredStdio = codexAppServerStdio(process.stdout.isTTY === true);
|
|
13446
13731
|
const stdio = [configuredStdio[0], configuredStdio[1], "pipe"];
|
|
13732
|
+
const watchdogSpawn = parentDeathWatchdogSpawn(process.execPath, {
|
|
13733
|
+
command: codexBin,
|
|
13734
|
+
args,
|
|
13735
|
+
pollIntervalMs: codexAppServerWatchdogPollMs
|
|
13736
|
+
});
|
|
13447
13737
|
writeCliAuditEvent("process.spawn", {
|
|
13448
13738
|
command: codexBin,
|
|
13449
13739
|
args,
|
|
13450
13740
|
cwd,
|
|
13451
13741
|
purpose: "codex.app_server"
|
|
13452
13742
|
});
|
|
13453
|
-
const child = spawn2(
|
|
13743
|
+
const child = spawn2(watchdogSpawn.command, [...watchdogSpawn.args], {
|
|
13454
13744
|
cwd,
|
|
13455
13745
|
env: codexAppServerEnv(options.env, options.inheritEnv ?? true),
|
|
13456
13746
|
stdio,
|
|
@@ -13467,7 +13757,17 @@ async function startCodexAppServerAttempt(codexBin, cwd, options, attempt) {
|
|
|
13467
13757
|
}
|
|
13468
13758
|
});
|
|
13469
13759
|
}
|
|
13470
|
-
const
|
|
13760
|
+
const registered = registerEngineChild({
|
|
13761
|
+
stop: () => stopCodexAppServerProcess(child),
|
|
13762
|
+
pid: child.pid,
|
|
13763
|
+
ownProcessGroup: true,
|
|
13764
|
+
kind: "codex.app_server"
|
|
13765
|
+
});
|
|
13766
|
+
child.once("exit", () => registered.unregister());
|
|
13767
|
+
const stop = () => {
|
|
13768
|
+
registered.unregister();
|
|
13769
|
+
stopCodexAppServerProcess(child);
|
|
13770
|
+
};
|
|
13471
13771
|
writeCliAuditEvent("process.spawned", {
|
|
13472
13772
|
command: codexBin,
|
|
13473
13773
|
args,
|
|
@@ -13571,7 +13871,7 @@ function stopCodexAppServerProcess(child, killProcess = process.kill) {
|
|
|
13571
13871
|
child.kill("SIGINT");
|
|
13572
13872
|
}
|
|
13573
13873
|
function logProcessGroupSignalFailure(pid, error) {
|
|
13574
|
-
const code =
|
|
13874
|
+
const code = processSignalErrorCode2(error);
|
|
13575
13875
|
const message = error instanceof Error ? error.message : String(error);
|
|
13576
13876
|
const event = code === "EPERM" ? "process.group_signal_denied" : code === "ESRCH" ? "process.group_signal_missing" : "process.group_signal_failed";
|
|
13577
13877
|
writeCliAuditEvent(event, {
|
|
@@ -13589,7 +13889,7 @@ function logProcessGroupSignalFailure(pid, error) {
|
|
|
13589
13889
|
);
|
|
13590
13890
|
}
|
|
13591
13891
|
}
|
|
13592
|
-
function
|
|
13892
|
+
function processSignalErrorCode2(error) {
|
|
13593
13893
|
if (error !== null && typeof error === "object" && "code" in error) {
|
|
13594
13894
|
const code = error.code;
|
|
13595
13895
|
return typeof code === "string" ? code : void 0;
|
|
@@ -13973,13 +14273,16 @@ function readyzUrlForWebsocket(websocketUrl) {
|
|
|
13973
14273
|
parsed.hash = "";
|
|
13974
14274
|
return parsed.toString();
|
|
13975
14275
|
}
|
|
13976
|
-
var blockedCodexAppServerEnvKeys;
|
|
14276
|
+
var codexAppServerWatchdogPollMs, blockedCodexAppServerEnvKeys;
|
|
13977
14277
|
var init_codexAppServer = __esm({
|
|
13978
14278
|
"src/codexAppServer.ts"() {
|
|
13979
14279
|
"use strict";
|
|
13980
14280
|
init_protocol();
|
|
13981
14281
|
init_runnerLogger();
|
|
13982
14282
|
init_mcpConfig();
|
|
14283
|
+
init_engineChildReaper();
|
|
14284
|
+
init_engineParentDeathWatchdog();
|
|
14285
|
+
codexAppServerWatchdogPollMs = 2e3;
|
|
13983
14286
|
blockedCodexAppServerEnvKeys = [
|
|
13984
14287
|
"LINZUMI_MCP_ACCESS_TOKEN",
|
|
13985
14288
|
"LINZUMI_MCP_OWNER_USERNAME"
|
|
@@ -18127,7 +18430,7 @@ var linzumiCliVersion, linzumiCliVersionText;
|
|
|
18127
18430
|
var init_version = __esm({
|
|
18128
18431
|
"src/version.ts"() {
|
|
18129
18432
|
"use strict";
|
|
18130
|
-
linzumiCliVersion = "0.0.
|
|
18433
|
+
linzumiCliVersion = "0.0.86-beta";
|
|
18131
18434
|
linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
18132
18435
|
}
|
|
18133
18436
|
});
|
|
@@ -18406,6 +18709,9 @@ function releaseRunnerLock(path2, record) {
|
|
|
18406
18709
|
}
|
|
18407
18710
|
}
|
|
18408
18711
|
function readRunnerLockForRelease(path2) {
|
|
18712
|
+
return readRunnerLockOwner(path2);
|
|
18713
|
+
}
|
|
18714
|
+
function readRunnerLockOwner(path2) {
|
|
18409
18715
|
try {
|
|
18410
18716
|
return readRunnerLockIfPresent(path2);
|
|
18411
18717
|
} catch (_error) {
|
|
@@ -18526,8 +18832,216 @@ var init_runnerLock = __esm({
|
|
|
18526
18832
|
}
|
|
18527
18833
|
});
|
|
18528
18834
|
|
|
18529
|
-
// src/
|
|
18835
|
+
// src/runnerLockTakeover.ts
|
|
18836
|
+
function runnerLockTakeoverSleep(ms) {
|
|
18837
|
+
return new Promise((resolve12) => {
|
|
18838
|
+
setTimeout(resolve12, ms);
|
|
18839
|
+
});
|
|
18840
|
+
}
|
|
18841
|
+
function runnerLockConflictReport(baseMessage) {
|
|
18842
|
+
const bar = "=".repeat(64);
|
|
18843
|
+
return [
|
|
18844
|
+
"",
|
|
18845
|
+
bar,
|
|
18846
|
+
"ERROR: linzumi connect could not start - workspace already in use",
|
|
18847
|
+
bar,
|
|
18848
|
+
baseMessage,
|
|
18849
|
+
bar,
|
|
18850
|
+
""
|
|
18851
|
+
].join("\n");
|
|
18852
|
+
}
|
|
18853
|
+
function isRunnerLockConflictReportedError(error) {
|
|
18854
|
+
return error instanceof RunnerLockConflictReportedError || error instanceof Error && error.name === runnerLockConflictReportedErrorName;
|
|
18855
|
+
}
|
|
18856
|
+
function shouldResolveRunnerLockConflict(lockTakeover) {
|
|
18857
|
+
return lockTakeover !== void 0;
|
|
18858
|
+
}
|
|
18859
|
+
async function resolveRunnerLockConflict(args) {
|
|
18860
|
+
const report = runnerLockConflictReport(args.baseMessage);
|
|
18861
|
+
if (args.takeOverWithoutPrompt) {
|
|
18862
|
+
args.prompt.writeReport(report);
|
|
18863
|
+
await stopHolderAndAwaitRelease(args.holder, args.lockPath, args.takeover);
|
|
18864
|
+
return { outcome: "take_over" };
|
|
18865
|
+
}
|
|
18866
|
+
if (!args.prompt.isInteractive()) {
|
|
18867
|
+
args.prompt.writeReport(report);
|
|
18868
|
+
return { outcome: "declined", report };
|
|
18869
|
+
}
|
|
18870
|
+
args.prompt.writeReport(report);
|
|
18871
|
+
const yes = await args.prompt.promptYesNo(
|
|
18872
|
+
`Another runner (pid ${args.holder.pid}) is already connected to this workspace. Stop it and run here instead? [y/N] `
|
|
18873
|
+
);
|
|
18874
|
+
if (!yes) {
|
|
18875
|
+
return { outcome: "declined", report };
|
|
18876
|
+
}
|
|
18877
|
+
await stopHolderAndAwaitRelease(args.holder, args.lockPath, args.takeover);
|
|
18878
|
+
return { outcome: "take_over" };
|
|
18879
|
+
}
|
|
18880
|
+
async function stopHolderAndAwaitRelease(holder, lockPath, deps) {
|
|
18881
|
+
deps.log("runner.lock_takeover_requested", {
|
|
18882
|
+
holderPid: holder.pid,
|
|
18883
|
+
holderRunnerId: holder.runnerId,
|
|
18884
|
+
lockPath
|
|
18885
|
+
});
|
|
18886
|
+
if (!deps.isPidAlive(holder.pid)) {
|
|
18887
|
+
deps.log("runner.lock_takeover_holder_already_gone", {
|
|
18888
|
+
holderPid: holder.pid,
|
|
18889
|
+
lockPath
|
|
18890
|
+
});
|
|
18891
|
+
return;
|
|
18892
|
+
}
|
|
18893
|
+
if (!stillHeldBy(deps, holder)) {
|
|
18894
|
+
deps.log("runner.lock_takeover_owner_changed", {
|
|
18895
|
+
holderPid: holder.pid,
|
|
18896
|
+
lockPath
|
|
18897
|
+
});
|
|
18898
|
+
return;
|
|
18899
|
+
}
|
|
18900
|
+
sendSignalTolerant(deps, holder.pid, "SIGTERM");
|
|
18901
|
+
if (await waitForHolderRelease(holder, deps, runnerLockTakeoverGracefulStopMs)) {
|
|
18902
|
+
deps.log("runner.lock_takeover_graceful", {
|
|
18903
|
+
holderPid: holder.pid,
|
|
18904
|
+
lockPath
|
|
18905
|
+
});
|
|
18906
|
+
return;
|
|
18907
|
+
}
|
|
18908
|
+
if (!stillHeldBy(deps, holder)) {
|
|
18909
|
+
deps.log("runner.lock_takeover_owner_changed", {
|
|
18910
|
+
holderPid: holder.pid,
|
|
18911
|
+
lockPath
|
|
18912
|
+
});
|
|
18913
|
+
return;
|
|
18914
|
+
}
|
|
18915
|
+
deps.log("runner.lock_takeover_escalating_sigkill", {
|
|
18916
|
+
holderPid: holder.pid,
|
|
18917
|
+
lockPath
|
|
18918
|
+
});
|
|
18919
|
+
sendSignalTolerant(deps, holder.pid, "SIGKILL");
|
|
18920
|
+
if (await waitForHolderRelease(holder, deps, runnerLockTakeoverForcefulStopMs)) {
|
|
18921
|
+
deps.log("runner.lock_takeover_forceful", {
|
|
18922
|
+
holderPid: holder.pid,
|
|
18923
|
+
lockPath
|
|
18924
|
+
});
|
|
18925
|
+
return;
|
|
18926
|
+
}
|
|
18927
|
+
throw new Error(
|
|
18928
|
+
`Could not stop the runner holding this workspace (pid ${holder.pid}). It did not exit after SIGTERM and SIGKILL within the timeout. Stop it manually or remove the lock file (${lockPath}) and retry.`
|
|
18929
|
+
);
|
|
18930
|
+
}
|
|
18931
|
+
async function waitForHolderRelease(holder, deps, timeoutMs) {
|
|
18932
|
+
const deadline = deps.now() + timeoutMs;
|
|
18933
|
+
while (deps.now() < deadline) {
|
|
18934
|
+
if (!deps.isPidAlive(holder.pid) || deps.lockReleased()) {
|
|
18935
|
+
return true;
|
|
18936
|
+
}
|
|
18937
|
+
await deps.sleep(runnerLockTakeoverPollMs);
|
|
18938
|
+
}
|
|
18939
|
+
return !deps.isPidAlive(holder.pid) || deps.lockReleased();
|
|
18940
|
+
}
|
|
18941
|
+
function stillHeldBy(deps, holder) {
|
|
18942
|
+
const current = deps.currentLockOwner();
|
|
18943
|
+
return current !== void 0 && current.machineId === holder.machineId && current.runnerId === holder.runnerId && current.pid === holder.pid && current.startedAt === holder.startedAt;
|
|
18944
|
+
}
|
|
18945
|
+
function sendSignalTolerant(deps, pid, signal) {
|
|
18946
|
+
try {
|
|
18947
|
+
deps.signalPid(pid, signal);
|
|
18948
|
+
} catch (error) {
|
|
18949
|
+
const code = error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
18950
|
+
if (code !== "ESRCH") {
|
|
18951
|
+
deps.log("runner.lock_takeover_signal_failed", {
|
|
18952
|
+
holderPid: pid,
|
|
18953
|
+
signal,
|
|
18954
|
+
code: typeof code === "string" ? code : null,
|
|
18955
|
+
message: error instanceof Error ? error.message : String(error)
|
|
18956
|
+
});
|
|
18957
|
+
}
|
|
18958
|
+
}
|
|
18959
|
+
}
|
|
18960
|
+
var runnerLockTakeoverGracefulStopMs, runnerLockTakeoverForcefulStopMs, runnerLockTakeoverPollMs, runnerLockConflictReportedErrorName, RunnerLockConflictReportedError;
|
|
18961
|
+
var init_runnerLockTakeover = __esm({
|
|
18962
|
+
"src/runnerLockTakeover.ts"() {
|
|
18963
|
+
"use strict";
|
|
18964
|
+
runnerLockTakeoverGracefulStopMs = 1e4;
|
|
18965
|
+
runnerLockTakeoverForcefulStopMs = 5e3;
|
|
18966
|
+
runnerLockTakeoverPollMs = 200;
|
|
18967
|
+
runnerLockConflictReportedErrorName = "RunnerLockConflictReportedError";
|
|
18968
|
+
RunnerLockConflictReportedError = class extends Error {
|
|
18969
|
+
constructor() {
|
|
18970
|
+
super("");
|
|
18971
|
+
this.name = runnerLockConflictReportedErrorName;
|
|
18972
|
+
}
|
|
18973
|
+
};
|
|
18974
|
+
}
|
|
18975
|
+
});
|
|
18976
|
+
|
|
18977
|
+
// src/blessedTputSetulcShim.ts
|
|
18530
18978
|
import blessed from "blessed";
|
|
18979
|
+
function balanceTerminfoConditionals(cap) {
|
|
18980
|
+
if (cap.indexOf("%;") === -1) {
|
|
18981
|
+
return cap;
|
|
18982
|
+
}
|
|
18983
|
+
let depth = 0;
|
|
18984
|
+
let repaired = "";
|
|
18985
|
+
let index = 0;
|
|
18986
|
+
while (index < cap.length) {
|
|
18987
|
+
const here = cap[index];
|
|
18988
|
+
const next = cap[index + 1];
|
|
18989
|
+
if (here === "%" && next === "%") {
|
|
18990
|
+
repaired += "%%";
|
|
18991
|
+
index += 2;
|
|
18992
|
+
continue;
|
|
18993
|
+
}
|
|
18994
|
+
if (here === "%" && next === "?") {
|
|
18995
|
+
depth += 1;
|
|
18996
|
+
repaired += "%?";
|
|
18997
|
+
index += 2;
|
|
18998
|
+
continue;
|
|
18999
|
+
}
|
|
19000
|
+
if (here === "%" && next === ";") {
|
|
19001
|
+
if (depth > 0) {
|
|
19002
|
+
depth -= 1;
|
|
19003
|
+
repaired += "%;";
|
|
19004
|
+
}
|
|
19005
|
+
index += 2;
|
|
19006
|
+
continue;
|
|
19007
|
+
}
|
|
19008
|
+
repaired += here;
|
|
19009
|
+
index += 1;
|
|
19010
|
+
}
|
|
19011
|
+
return repaired;
|
|
19012
|
+
}
|
|
19013
|
+
function installBlessedSetulcShim(blessedModule = blessed) {
|
|
19014
|
+
const tput = blessedModule.Tput;
|
|
19015
|
+
const proto = tput?.prototype;
|
|
19016
|
+
const original = proto?._compile;
|
|
19017
|
+
if (proto === void 0 || typeof original !== "function") {
|
|
19018
|
+
return;
|
|
19019
|
+
}
|
|
19020
|
+
if (proto[SHIM_FLAG] === true) {
|
|
19021
|
+
return;
|
|
19022
|
+
}
|
|
19023
|
+
const patched = function patchedCompile(info, key, str) {
|
|
19024
|
+
const safeStr = typeof str === "string" ? balanceTerminfoConditionals(str) : str;
|
|
19025
|
+
return original.call(this, info, key, safeStr);
|
|
19026
|
+
};
|
|
19027
|
+
proto._compile = patched;
|
|
19028
|
+
Object.defineProperty(proto, SHIM_FLAG, {
|
|
19029
|
+
value: true,
|
|
19030
|
+
enumerable: false,
|
|
19031
|
+
configurable: true,
|
|
19032
|
+
writable: true
|
|
19033
|
+
});
|
|
19034
|
+
}
|
|
19035
|
+
var SHIM_FLAG;
|
|
19036
|
+
var init_blessedTputSetulcShim = __esm({
|
|
19037
|
+
"src/blessedTputSetulcShim.ts"() {
|
|
19038
|
+
"use strict";
|
|
19039
|
+
SHIM_FLAG = "__linzumiSetulcShimInstalled";
|
|
19040
|
+
}
|
|
19041
|
+
});
|
|
19042
|
+
|
|
19043
|
+
// src/runnerConsoleReporter.ts
|
|
19044
|
+
import blessed2 from "blessed";
|
|
18531
19045
|
function reportRunnerConsoleEvent(event, payload) {
|
|
18532
19046
|
if (shouldRenderDashboard()) {
|
|
18533
19047
|
const tui = initializeDashboardTui(dashboardState);
|
|
@@ -19512,20 +20026,21 @@ function handleDashboardKey(state, key, exitProcess = () => process.kill(process
|
|
|
19512
20026
|
function createRunnerConsoleDashboardTui(state, exitProcess = () => process.kill(process.pid, "SIGINT")) {
|
|
19513
20027
|
let rendering = false;
|
|
19514
20028
|
let rawScrollAnchor;
|
|
19515
|
-
|
|
20029
|
+
installBlessedSetulcShim();
|
|
20030
|
+
const screen = blessed2.screen({
|
|
19516
20031
|
title: "Linzumi Commander",
|
|
19517
20032
|
smartCSR: true,
|
|
19518
20033
|
fullUnicode: true,
|
|
19519
20034
|
mouse: true
|
|
19520
20035
|
});
|
|
19521
|
-
const header =
|
|
20036
|
+
const header = blessed2.box({
|
|
19522
20037
|
top: 0,
|
|
19523
20038
|
left: 0,
|
|
19524
20039
|
width: "100%",
|
|
19525
20040
|
height: 1,
|
|
19526
20041
|
tags: false
|
|
19527
20042
|
});
|
|
19528
|
-
const tableElement =
|
|
20043
|
+
const tableElement = blessed2.listtable({
|
|
19529
20044
|
top: 1,
|
|
19530
20045
|
left: 0,
|
|
19531
20046
|
width: "100%",
|
|
@@ -19554,7 +20069,7 @@ function createRunnerConsoleDashboardTui(state, exitProcess = () => process.kill
|
|
|
19554
20069
|
}
|
|
19555
20070
|
}
|
|
19556
20071
|
});
|
|
19557
|
-
const rawTitle =
|
|
20072
|
+
const rawTitle = blessed2.box({
|
|
19558
20073
|
top: 0,
|
|
19559
20074
|
left: 12,
|
|
19560
20075
|
right: 0,
|
|
@@ -19562,7 +20077,7 @@ function createRunnerConsoleDashboardTui(state, exitProcess = () => process.kill
|
|
|
19562
20077
|
tags: false,
|
|
19563
20078
|
hidden: true
|
|
19564
20079
|
});
|
|
19565
|
-
const backButton =
|
|
20080
|
+
const backButton = blessed2.button({
|
|
19566
20081
|
top: 0,
|
|
19567
20082
|
left: 0,
|
|
19568
20083
|
width: 10,
|
|
@@ -19577,7 +20092,7 @@ function createRunnerConsoleDashboardTui(state, exitProcess = () => process.kill
|
|
|
19577
20092
|
hover: { inverse: true }
|
|
19578
20093
|
}
|
|
19579
20094
|
});
|
|
19580
|
-
const rawBox =
|
|
20095
|
+
const rawBox = blessed2.box({
|
|
19581
20096
|
top: 3,
|
|
19582
20097
|
left: 0,
|
|
19583
20098
|
width: "100%",
|
|
@@ -20056,6 +20571,7 @@ var dashboardState, maxRawLines, escapeKey, ctrlCKey, enterKey, upKey, downKey,
|
|
|
20056
20571
|
var init_runnerConsoleReporter = __esm({
|
|
20057
20572
|
"src/runnerConsoleReporter.ts"() {
|
|
20058
20573
|
"use strict";
|
|
20574
|
+
init_blessedTputSetulcShim();
|
|
20059
20575
|
dashboardState = {
|
|
20060
20576
|
jobs: /* @__PURE__ */ new Map(),
|
|
20061
20577
|
discovery: /* @__PURE__ */ new Map(),
|
|
@@ -21381,6 +21897,7 @@ import { spawn as spawn9, spawnSync as spawnSync5 } from "node:child_process";
|
|
|
21381
21897
|
import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
|
|
21382
21898
|
import {
|
|
21383
21899
|
chmodSync as chmodSync2,
|
|
21900
|
+
existsSync as existsSync13,
|
|
21384
21901
|
lstatSync,
|
|
21385
21902
|
mkdirSync as mkdirSync12,
|
|
21386
21903
|
mkdtempSync as mkdtempSync4,
|
|
@@ -21395,6 +21912,7 @@ import {
|
|
|
21395
21912
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
21396
21913
|
import { createServer as createServer3 } from "node:http";
|
|
21397
21914
|
import { homedir as homedir13, hostname as hostname2, tmpdir as tmpdir3 } from "node:os";
|
|
21915
|
+
import { createInterface } from "node:readline";
|
|
21398
21916
|
import {
|
|
21399
21917
|
basename as basename8,
|
|
21400
21918
|
dirname as dirname13,
|
|
@@ -21419,42 +21937,35 @@ async function runLocalCodexRunner(options) {
|
|
|
21419
21937
|
});
|
|
21420
21938
|
try {
|
|
21421
21939
|
if (options.machineId !== void 0) {
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21428
|
-
|
|
21429
|
-
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
|
|
21437
|
-
|
|
21438
|
-
log2("runner.lock_takeover", {
|
|
21439
|
-
runnerId: options.runnerId,
|
|
21440
|
-
holderRunnerId: takeover.holderRunnerId,
|
|
21441
|
-
holderPid: takeover.holderPid,
|
|
21442
|
-
reason: takeover.reason,
|
|
21443
|
-
lockPath: takeover.lockPath
|
|
21444
|
-
});
|
|
21445
|
-
}
|
|
21446
|
-
});
|
|
21447
|
-
} catch (error) {
|
|
21448
|
-
if (isRunnerLockHeldError(error)) {
|
|
21449
|
-
log2("runner.lock_held_by_live_process", {
|
|
21940
|
+
const machineId = options.machineId;
|
|
21941
|
+
const acquire = () => acquireRunnerLock({
|
|
21942
|
+
machineId,
|
|
21943
|
+
runnerId: options.runnerId,
|
|
21944
|
+
cwd: options.cwd,
|
|
21945
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
21946
|
+
linzumiUrl: options.kandanUrl,
|
|
21947
|
+
launchSource: options.launchSource,
|
|
21948
|
+
configPath: options.runnerLockConfigPath,
|
|
21949
|
+
// Wedged-runner takeover: a lock holder whose pid is alive but
|
|
21950
|
+
// whose lock heartbeat went stale (>3 min) gets SIGKILLed and its
|
|
21951
|
+
// lock replaced, instead of blocking recovery until someone runs
|
|
21952
|
+
// kill -9 by hand. Log it so the runner log explains where the old
|
|
21953
|
+
// pid went.
|
|
21954
|
+
onTakeover: (takeover) => {
|
|
21955
|
+
log2("runner.lock_takeover", {
|
|
21450
21956
|
runnerId: options.runnerId,
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21957
|
+
holderRunnerId: takeover.holderRunnerId,
|
|
21958
|
+
holderPid: takeover.holderPid,
|
|
21959
|
+
reason: takeover.reason,
|
|
21960
|
+
lockPath: takeover.lockPath
|
|
21454
21961
|
});
|
|
21455
21962
|
}
|
|
21456
|
-
|
|
21457
|
-
|
|
21963
|
+
});
|
|
21964
|
+
const runnerLock = await acquireRunnerLockWithConflictResolution(
|
|
21965
|
+
acquire,
|
|
21966
|
+
options,
|
|
21967
|
+
log2
|
|
21968
|
+
);
|
|
21458
21969
|
cleanup.actions.push(() => runnerLock.release());
|
|
21459
21970
|
log2("runner.lock_acquired", {
|
|
21460
21971
|
path: runnerLock.path,
|
|
@@ -22474,6 +22985,11 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
22474
22985
|
if (options.channelSession !== void 0 && codex === void 0) {
|
|
22475
22986
|
throw new Error("channel session requires a Codex app-server connection");
|
|
22476
22987
|
}
|
|
22988
|
+
const ownsRespawnableAppServer = runnerOwnsRespawnableAppServer({
|
|
22989
|
+
codexUrl: options.codexUrl,
|
|
22990
|
+
threadProcessRole: options.threadProcess?.role,
|
|
22991
|
+
ownsStartedAppServer: started !== void 0
|
|
22992
|
+
});
|
|
22477
22993
|
const seq = { value: 0 };
|
|
22478
22994
|
const discoveredCodexThreads = { value: [] };
|
|
22479
22995
|
const runtimeDefaults = runnerRuntimeDefaults(options);
|
|
@@ -22565,6 +23081,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
22565
23081
|
const dynamicChannelSessions = /* @__PURE__ */ new Map();
|
|
22566
23082
|
const dynamicChannelSessionCodexClients = /* @__PURE__ */ new Map();
|
|
22567
23083
|
const codexTurnFailureGuards = /* @__PURE__ */ new Map();
|
|
23084
|
+
const lossReportPushes = [];
|
|
22568
23085
|
const codexTurnFailureGuardFor = (client) => {
|
|
22569
23086
|
const existing = codexTurnFailureGuards.get(client);
|
|
22570
23087
|
if (existing !== void 0) {
|
|
@@ -22577,7 +23094,36 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
22577
23094
|
([codexThreadId]) => dynamicChannelSessionCodexClients.get(codexThreadId) === client
|
|
22578
23095
|
).map(([, session]) => session)
|
|
22579
23096
|
],
|
|
22580
|
-
log: log2
|
|
23097
|
+
log: log2,
|
|
23098
|
+
// Spec: plans/2026-06-13-orphaned-mid-turn-reconnect-recovery-spec.md
|
|
23099
|
+
// The runner process and its channel survive an app-server death, so the
|
|
23100
|
+
// server otherwise keeps these thread bindings "connected" and never
|
|
23101
|
+
// offers reconnect. Report the loss per affected thread (best-effort)
|
|
23102
|
+
// so the bindings flip to disconnected/reconnectable upstream.
|
|
23103
|
+
//
|
|
23104
|
+
// Only do this for a runner that owns a respawnable app-server. For an
|
|
23105
|
+
// external/remote `codexUrl` backend (or a thread-process worker) this
|
|
23106
|
+
// runner cannot relaunch the dead app-server, so a reconnect could never
|
|
23107
|
+
// recover the thread - marking it reconnectable would be misleading. In
|
|
23108
|
+
// that case we still fail the in-flight turns terminally, but leave the
|
|
23109
|
+
// binding as-is rather than offering a reconnect that cannot succeed.
|
|
23110
|
+
onActiveThreadsFailed: ownsRespawnableAppServer ? (threadIds) => {
|
|
23111
|
+
for (const codexThreadId of threadIds) {
|
|
23112
|
+
lossReportPushes.push(
|
|
23113
|
+
kandan.push(topic, "session:app_server_lost", {
|
|
23114
|
+
codex_thread_id: codexThreadId
|
|
23115
|
+
}).then(
|
|
23116
|
+
() => void 0,
|
|
23117
|
+
(error) => {
|
|
23118
|
+
log2("kandan.session_app_server_lost_push_failed", {
|
|
23119
|
+
codex_thread_id: codexThreadId,
|
|
23120
|
+
message: error instanceof Error ? error.message : String(error)
|
|
23121
|
+
});
|
|
23122
|
+
}
|
|
23123
|
+
)
|
|
23124
|
+
);
|
|
23125
|
+
}
|
|
23126
|
+
} : void 0
|
|
22581
23127
|
});
|
|
22582
23128
|
codexTurnFailureGuards.set(client, guard);
|
|
22583
23129
|
return guard;
|
|
@@ -22681,7 +23227,8 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
22681
23227
|
portForwardWatcher: channelSessionPortForwardWatcherOptions({
|
|
22682
23228
|
rootPid: portForwardWatcherRootPid,
|
|
22683
23229
|
start: options.portForwardWatcher,
|
|
22684
|
-
commanderBoundPids: args.commanderBoundPids
|
|
23230
|
+
commanderBoundPids: args.commanderBoundPids,
|
|
23231
|
+
commanderBoundPorts: args.commanderBoundPorts
|
|
22685
23232
|
}),
|
|
22686
23233
|
suppressedForwardPorts,
|
|
22687
23234
|
onForwardPortApproved: (port, attribution) => {
|
|
@@ -22876,6 +23423,12 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
22876
23423
|
portForwardWatcherRootPid: handle.processPid,
|
|
22877
23424
|
commanderBoundPids: (handle.commanderManagedPorts ?? []).flatMap(
|
|
22878
23425
|
(port) => port.pid === void 0 ? [] : [port.pid]
|
|
23426
|
+
),
|
|
23427
|
+
// The exact codex app-server ports are commander-managed even though
|
|
23428
|
+
// their listening pid is the wrapped grandchild, not the recorded
|
|
23429
|
+
// wrapper pid (Codex P1).
|
|
23430
|
+
commanderBoundPorts: (handle.commanderManagedPorts ?? []).map(
|
|
23431
|
+
(port) => port.port
|
|
22879
23432
|
)
|
|
22880
23433
|
});
|
|
22881
23434
|
switch (control.type) {
|
|
@@ -23025,6 +23578,7 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
23025
23578
|
}
|
|
23026
23579
|
});
|
|
23027
23580
|
if (sharedCodexTurnFailureGuard !== void 0) {
|
|
23581
|
+
const appServerLost = { handled: false };
|
|
23028
23582
|
const failCodexSessionTurns = (cause) => {
|
|
23029
23583
|
if (cleanup.closePromise !== void 0) {
|
|
23030
23584
|
return;
|
|
@@ -23032,6 +23586,27 @@ async function openLocalCodexRunner(options, log2, cleanup, close) {
|
|
|
23032
23586
|
log2("codex.app_server_connection_lost", { cause });
|
|
23033
23587
|
sharedCodexTurnFailureGuard.failActiveTurns(cause);
|
|
23034
23588
|
started?.stop();
|
|
23589
|
+
if (ownsRespawnableAppServer && !appServerLost.handled) {
|
|
23590
|
+
appServerLost.handled = true;
|
|
23591
|
+
console.error(
|
|
23592
|
+
`Codex app-server connection was lost (${cause}); exiting so a fresh runner can relaunch it. Run \`linzumi connect\` to reconnect.`
|
|
23593
|
+
);
|
|
23594
|
+
const sessionDrains = [
|
|
23595
|
+
...channelSession !== void 0 ? [channelSession] : [],
|
|
23596
|
+
...dynamicChannelSessions.values()
|
|
23597
|
+
].map(
|
|
23598
|
+
(session) => session.close().catch((error) => {
|
|
23599
|
+
log2("codex.app_server_lost_session_drain_failed", {
|
|
23600
|
+
message: error instanceof Error ? error.message : String(error)
|
|
23601
|
+
});
|
|
23602
|
+
})
|
|
23603
|
+
);
|
|
23604
|
+
void awaitLossReportsThenExit({
|
|
23605
|
+
pushes: [...lossReportPushes, ...sessionDrains],
|
|
23606
|
+
exit: () => process.exit(75),
|
|
23607
|
+
safetyCapMs: 5e3
|
|
23608
|
+
});
|
|
23609
|
+
}
|
|
23035
23610
|
};
|
|
23036
23611
|
codex?.onClose?.((error) => failCodexSessionTurns(error.message));
|
|
23037
23612
|
started?.process.once(
|
|
@@ -23787,6 +24362,19 @@ async function resolveSessionControl(channelSession, dynamicChannelSessions, con
|
|
|
23787
24362
|
}
|
|
23788
24363
|
return void 0;
|
|
23789
24364
|
}
|
|
24365
|
+
function runnerOwnsRespawnableAppServer(args) {
|
|
24366
|
+
return args.codexUrl === void 0 && args.threadProcessRole !== "thread" && args.ownsStartedAppServer;
|
|
24367
|
+
}
|
|
24368
|
+
async function awaitLossReportsThenExit(args) {
|
|
24369
|
+
const setTimeoutImpl = args.setTimeoutFn ?? setTimeout;
|
|
24370
|
+
const drained = Promise.allSettled(args.pushes).then(() => void 0);
|
|
24371
|
+
const safetyCap = new Promise((resolve12) => {
|
|
24372
|
+
const handle = setTimeoutImpl(() => resolve12(), args.safetyCapMs);
|
|
24373
|
+
handle?.unref?.();
|
|
24374
|
+
});
|
|
24375
|
+
await Promise.race([drained, safetyCap]);
|
|
24376
|
+
args.exit();
|
|
24377
|
+
}
|
|
23790
24378
|
function createCodexTurnFailureGuard(args) {
|
|
23791
24379
|
const activeTurnsByThread = /* @__PURE__ */ new Map();
|
|
23792
24380
|
const state = { failed: false };
|
|
@@ -23822,6 +24410,7 @@ function createCodexTurnFailureGuard(args) {
|
|
|
23822
24410
|
if (activeTurns.length === 0) {
|
|
23823
24411
|
return;
|
|
23824
24412
|
}
|
|
24413
|
+
args.onActiveThreadsFailed?.(activeTurns.map(([threadId]) => threadId));
|
|
23825
24414
|
const sessions = Array.from(args.sessions());
|
|
23826
24415
|
for (const [threadId, turnId] of activeTurns) {
|
|
23827
24416
|
args.log("codex.active_turn_failed_on_connection_loss", {
|
|
@@ -23979,6 +24568,101 @@ function installCleanupHandlers(close) {
|
|
|
23979
24568
|
process.off("exit", closeOnExit);
|
|
23980
24569
|
};
|
|
23981
24570
|
}
|
|
24571
|
+
async function acquireRunnerLockWithConflictResolution(acquire, options, log2) {
|
|
24572
|
+
try {
|
|
24573
|
+
return acquire();
|
|
24574
|
+
} catch (error) {
|
|
24575
|
+
if (!isRunnerLockHeldError(error)) {
|
|
24576
|
+
throw error;
|
|
24577
|
+
}
|
|
24578
|
+
log2("runner.lock_held_by_live_process", {
|
|
24579
|
+
runnerId: options.runnerId,
|
|
24580
|
+
heldByRunnerId: error.heldBy.runnerId,
|
|
24581
|
+
heldByPid: error.heldBy.pid,
|
|
24582
|
+
lockPath: error.lockPath
|
|
24583
|
+
});
|
|
24584
|
+
const lockTakeover = options.lockTakeover;
|
|
24585
|
+
if (!shouldResolveRunnerLockConflict(lockTakeover)) {
|
|
24586
|
+
throw error;
|
|
24587
|
+
}
|
|
24588
|
+
const promptDeps = lockTakeover.prompt ?? defaultRunnerLockTakeoverPromptDeps(lockTakeover);
|
|
24589
|
+
const takeoverDeps = resolveRunnerLockTakeoverDeps(
|
|
24590
|
+
error.lockPath,
|
|
24591
|
+
lockTakeover.takeover,
|
|
24592
|
+
log2
|
|
24593
|
+
);
|
|
24594
|
+
const resolution = await resolveRunnerLockConflict({
|
|
24595
|
+
holder: error.heldBy,
|
|
24596
|
+
lockPath: error.lockPath,
|
|
24597
|
+
baseMessage: error.message,
|
|
24598
|
+
takeOverWithoutPrompt: lockTakeover.takeOverWithoutPrompt === true,
|
|
24599
|
+
prompt: promptDeps,
|
|
24600
|
+
takeover: takeoverDeps
|
|
24601
|
+
});
|
|
24602
|
+
if (resolution.outcome === "declined") {
|
|
24603
|
+
throw new RunnerLockConflictReportedError();
|
|
24604
|
+
}
|
|
24605
|
+
log2("runner.lock_takeover_interactive", {
|
|
24606
|
+
runnerId: options.runnerId,
|
|
24607
|
+
heldByRunnerId: error.heldBy.runnerId,
|
|
24608
|
+
heldByPid: error.heldBy.pid,
|
|
24609
|
+
lockPath: error.lockPath
|
|
24610
|
+
});
|
|
24611
|
+
return acquire();
|
|
24612
|
+
}
|
|
24613
|
+
}
|
|
24614
|
+
function defaultRunnerLockTakeoverPromptDeps(takeover) {
|
|
24615
|
+
return {
|
|
24616
|
+
isInteractive: () => takeover?.forceNonInteractive !== true && process.stdin.isTTY === true && process.stderr.isTTY === true,
|
|
24617
|
+
writeReport: (text2) => {
|
|
24618
|
+
process.stderr.write(`${text2}
|
|
24619
|
+
`);
|
|
24620
|
+
},
|
|
24621
|
+
promptYesNo: (question) => promptYesNoOnStdin(question)
|
|
24622
|
+
};
|
|
24623
|
+
}
|
|
24624
|
+
function promptYesNoOnStdin(question) {
|
|
24625
|
+
return new Promise((resolve12) => {
|
|
24626
|
+
const rl = createInterface({
|
|
24627
|
+
input: process.stdin,
|
|
24628
|
+
output: process.stderr
|
|
24629
|
+
});
|
|
24630
|
+
rl.question(question, (answer) => {
|
|
24631
|
+
rl.close();
|
|
24632
|
+
resolve12(/^y(es)?$/i.test(answer.trim()));
|
|
24633
|
+
});
|
|
24634
|
+
});
|
|
24635
|
+
}
|
|
24636
|
+
function resolveRunnerLockTakeoverDeps(lockPath, overrides, log2) {
|
|
24637
|
+
return {
|
|
24638
|
+
isPidAlive: overrides?.isPidAlive ?? runnerLockTakeoverPidIsAlive,
|
|
24639
|
+
signalPid: overrides?.signalPid ?? ((pid, signal) => {
|
|
24640
|
+
process.kill(pid, signal);
|
|
24641
|
+
}),
|
|
24642
|
+
lockReleased: overrides?.lockReleased ?? (() => !existsSync13(lockPath)),
|
|
24643
|
+
// Re-reads the on-disk owner so the takeover flow can revalidate identity
|
|
24644
|
+
// before signaling (never throws on a torn/partial lock file).
|
|
24645
|
+
currentLockOwner: overrides?.currentLockOwner ?? (() => readRunnerLockOwner(lockPath)),
|
|
24646
|
+
// runnerLockTakeoverSleep is deliberately NOT unref'ed - see its definition;
|
|
24647
|
+
// an unref'ed timer here would let `linzumi connect` exit before retrying
|
|
24648
|
+
// the lock acquisition during a takeover.
|
|
24649
|
+
sleep: overrides?.sleep ?? runnerLockTakeoverSleep,
|
|
24650
|
+
now: overrides?.now ?? (() => Date.now()),
|
|
24651
|
+
log: overrides?.log ?? ((event, payload) => {
|
|
24652
|
+
log2(event, payload);
|
|
24653
|
+
writeCliAuditEvent(event, payload);
|
|
24654
|
+
})
|
|
24655
|
+
};
|
|
24656
|
+
}
|
|
24657
|
+
function runnerLockTakeoverPidIsAlive(pid) {
|
|
24658
|
+
try {
|
|
24659
|
+
process.kill(pid, 0);
|
|
24660
|
+
return true;
|
|
24661
|
+
} catch (error) {
|
|
24662
|
+
const code = error !== null && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
24663
|
+
return code !== "ESRCH";
|
|
24664
|
+
}
|
|
24665
|
+
}
|
|
23982
24666
|
function launchCodexTui(codexBin, codexUrl, cwd, codexThreadId, session, fast) {
|
|
23983
24667
|
const args = codexTuiArgs(codexUrl, codexThreadId, session, fast);
|
|
23984
24668
|
writeCliAuditEvent("process.spawn", {
|
|
@@ -25856,6 +26540,16 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
25856
26540
|
codexVersion: void 0
|
|
25857
26541
|
};
|
|
25858
26542
|
const abortController = new AbortController();
|
|
26543
|
+
const reaperRegistration = registerEngineChild({
|
|
26544
|
+
stop: () => {
|
|
26545
|
+
if (!abortController.signal.aborted) {
|
|
26546
|
+
abortController.abort(new Error("engine child reaper teardown"));
|
|
26547
|
+
}
|
|
26548
|
+
},
|
|
26549
|
+
pid: void 0,
|
|
26550
|
+
ownProcessGroup: false,
|
|
26551
|
+
kind: "claude_code"
|
|
26552
|
+
});
|
|
25859
26553
|
const acceptedSourceSeqs = new Set(
|
|
25860
26554
|
sourceSeq === void 0 ? [] : [sourceSeq]
|
|
25861
26555
|
);
|
|
@@ -26260,6 +26954,7 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
26260
26954
|
return result2;
|
|
26261
26955
|
};
|
|
26262
26956
|
const sessionWork = runSession();
|
|
26957
|
+
void sessionWork.finally(() => reaperRegistration.unregister()).catch(() => void 0);
|
|
26263
26958
|
sessionWorkHandle.value = sessionWork;
|
|
26264
26959
|
const settled = await Promise.race([
|
|
26265
26960
|
sessionWork.then((result2) => ({
|
|
@@ -26698,9 +27393,11 @@ function channelSessionPortForwardWatcherOptions(args) {
|
|
|
26698
27393
|
...args.rootPid === void 0 ? [] : [args.rootPid],
|
|
26699
27394
|
...args.commanderBoundPids ?? []
|
|
26700
27395
|
];
|
|
27396
|
+
const commanderBoundPorts = args.commanderBoundPorts ?? [];
|
|
26701
27397
|
return {
|
|
26702
27398
|
...args.rootPid === void 0 ? {} : { rootPid: args.rootPid },
|
|
26703
27399
|
...commanderBoundPids.length === 0 ? {} : { commanderBoundPids },
|
|
27400
|
+
...commanderBoundPorts.length === 0 ? {} : { commanderBoundPorts },
|
|
26704
27401
|
...args.start === void 0 ? {} : { start: args.start }
|
|
26705
27402
|
};
|
|
26706
27403
|
}
|
|
@@ -28502,6 +29199,7 @@ var init_runner = __esm({
|
|
|
28502
29199
|
init_commanderAttachments();
|
|
28503
29200
|
init_claudeCodePipeline();
|
|
28504
29201
|
init_claudeCodePlanMirror();
|
|
29202
|
+
init_engineChildReaper();
|
|
28505
29203
|
init_claudeCodeLiveBashOutput();
|
|
28506
29204
|
init_claudeCodeSession();
|
|
28507
29205
|
init_codexAppServer();
|
|
@@ -28524,6 +29222,7 @@ var init_runner = __esm({
|
|
|
28524
29222
|
init_protocol();
|
|
28525
29223
|
init_runnerLogger();
|
|
28526
29224
|
init_runnerLock();
|
|
29225
|
+
init_runnerLockTakeover();
|
|
28527
29226
|
init_runnerConsoleReporter();
|
|
28528
29227
|
init_version();
|
|
28529
29228
|
init_telemetry();
|
|
@@ -28564,7 +29263,7 @@ var init_runner = __esm({
|
|
|
28564
29263
|
});
|
|
28565
29264
|
|
|
28566
29265
|
// src/kandanTls.ts
|
|
28567
|
-
import { existsSync as
|
|
29266
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "node:fs";
|
|
28568
29267
|
import { Agent } from "undici";
|
|
28569
29268
|
import WsWebSocket from "ws";
|
|
28570
29269
|
function kandanTlsTrustFromEnv() {
|
|
@@ -28575,7 +29274,7 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
28575
29274
|
return void 0;
|
|
28576
29275
|
}
|
|
28577
29276
|
const trimmed = caFile.trim();
|
|
28578
|
-
if (!
|
|
29277
|
+
if (!existsSync14(trimmed)) {
|
|
28579
29278
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
28580
29279
|
}
|
|
28581
29280
|
const ca = readFileSync17(trimmed, "utf8");
|
|
@@ -48853,7 +49552,7 @@ __export(signupFlow_exports, {
|
|
|
48853
49552
|
});
|
|
48854
49553
|
import { spawn as spawn12, spawnSync as spawnSync7 } from "node:child_process";
|
|
48855
49554
|
import {
|
|
48856
|
-
existsSync as
|
|
49555
|
+
existsSync as existsSync17,
|
|
48857
49556
|
constants as fsConstants,
|
|
48858
49557
|
mkdirSync as mkdirSync15,
|
|
48859
49558
|
mkdtempSync as mkdtempSync5,
|
|
@@ -48981,7 +49680,7 @@ function defaultSignupDraftStore(serviceUrl) {
|
|
|
48981
49680
|
const path2 = defaultSignupDraftPath(serviceUrl);
|
|
48982
49681
|
return {
|
|
48983
49682
|
read: () => {
|
|
48984
|
-
if (!
|
|
49683
|
+
if (!existsSync17(path2)) {
|
|
48985
49684
|
return void 0;
|
|
48986
49685
|
}
|
|
48987
49686
|
let parsed;
|
|
@@ -49060,7 +49759,7 @@ function defaultSignupTaskCachePath(serviceUrl) {
|
|
|
49060
49759
|
);
|
|
49061
49760
|
}
|
|
49062
49761
|
function readSignupTaskCache(path2) {
|
|
49063
|
-
if (!
|
|
49762
|
+
if (!existsSync17(path2)) {
|
|
49064
49763
|
return { version: 1, entries: {} };
|
|
49065
49764
|
}
|
|
49066
49765
|
let parsed;
|
|
@@ -51888,7 +52587,7 @@ async function discoverCodeRoots(homeDir) {
|
|
|
51888
52587
|
const candidates = ["src", "code", "projects"].map(
|
|
51889
52588
|
(name) => join23(homeDir, name)
|
|
51890
52589
|
);
|
|
51891
|
-
return candidates.filter((path2) =>
|
|
52590
|
+
return candidates.filter((path2) => existsSync17(path2)).flatMap((path2) => discoveredProjectNames(path2));
|
|
51892
52591
|
}
|
|
51893
52592
|
function discoveredProjectNames(root) {
|
|
51894
52593
|
try {
|
|
@@ -51992,25 +52691,25 @@ function looksLikeProject(path2) {
|
|
|
51992
52691
|
"pnpm-lock.yaml",
|
|
51993
52692
|
"yarn.lock",
|
|
51994
52693
|
"package-lock.json"
|
|
51995
|
-
].some((name) =>
|
|
52694
|
+
].some((name) => existsSync17(join23(path2, name)));
|
|
51996
52695
|
}
|
|
51997
52696
|
function detectProjectLanguage(path2) {
|
|
51998
|
-
if (
|
|
52697
|
+
if (existsSync17(join23(path2, "pyproject.toml")) || existsSync17(join23(path2, "requirements.txt"))) {
|
|
51999
52698
|
return "Python";
|
|
52000
52699
|
}
|
|
52001
|
-
if (
|
|
52700
|
+
if (existsSync17(join23(path2, "Cargo.toml"))) {
|
|
52002
52701
|
return "Rust";
|
|
52003
52702
|
}
|
|
52004
|
-
if (
|
|
52703
|
+
if (existsSync17(join23(path2, "go.mod"))) {
|
|
52005
52704
|
return "Go";
|
|
52006
52705
|
}
|
|
52007
|
-
if (
|
|
52706
|
+
if (existsSync17(join23(path2, "mix.exs"))) {
|
|
52008
52707
|
return "Elixir";
|
|
52009
52708
|
}
|
|
52010
|
-
if (
|
|
52709
|
+
if (existsSync17(join23(path2, "tsconfig.json")) || packageJsonMentionsTypeScript(path2)) {
|
|
52011
52710
|
return "TypeScript";
|
|
52012
52711
|
}
|
|
52013
|
-
if (
|
|
52712
|
+
if (existsSync17(join23(path2, "package.json"))) {
|
|
52014
52713
|
return "JavaScript";
|
|
52015
52714
|
}
|
|
52016
52715
|
return "Project";
|
|
@@ -52026,7 +52725,7 @@ function packageJsonMentionsTypeScript(path2) {
|
|
|
52026
52725
|
}
|
|
52027
52726
|
}
|
|
52028
52727
|
function hasGitMetadata(path2) {
|
|
52029
|
-
return
|
|
52728
|
+
return existsSync17(join23(path2, ".git"));
|
|
52030
52729
|
}
|
|
52031
52730
|
function childDirectories(root) {
|
|
52032
52731
|
try {
|
|
@@ -52147,9 +52846,10 @@ secure mission control for all your agents on your computers
|
|
|
52147
52846
|
// src/index.ts
|
|
52148
52847
|
init_onboardingDiscoveryChildProcess();
|
|
52149
52848
|
init_runner();
|
|
52849
|
+
init_runnerLockTakeover();
|
|
52150
52850
|
init_claudeCodeSession();
|
|
52151
52851
|
init_authCache();
|
|
52152
|
-
import { existsSync as
|
|
52852
|
+
import { existsSync as existsSync18, readFileSync as readFileSync22, realpathSync as realpathSync7 } from "node:fs";
|
|
52153
52853
|
import { homedir as homedir17 } from "node:os";
|
|
52154
52854
|
import { resolve as resolve11 } from "node:path";
|
|
52155
52855
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
@@ -52245,7 +52945,7 @@ init_kandanTls();
|
|
|
52245
52945
|
init_protocol();
|
|
52246
52946
|
init_json();
|
|
52247
52947
|
init_defaultUrls();
|
|
52248
|
-
import { existsSync as
|
|
52948
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync13, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "node:fs";
|
|
52249
52949
|
import { dirname as dirname14, join as join21 } from "node:path";
|
|
52250
52950
|
import { homedir as homedir14 } from "node:os";
|
|
52251
52951
|
async function runAgentCliCommand(args, deps = {
|
|
@@ -52964,7 +53664,7 @@ function authorizationHeaders(token) {
|
|
|
52964
53664
|
return { authorization: `Bearer ${token}` };
|
|
52965
53665
|
}
|
|
52966
53666
|
function readOptionalTextFile(path2) {
|
|
52967
|
-
return
|
|
53667
|
+
return existsSync15(path2) ? readFileSync18(path2, "utf8") : void 0;
|
|
52968
53668
|
}
|
|
52969
53669
|
function writeTextFile(path2, content) {
|
|
52970
53670
|
mkdirSync13(dirname14(path2), { recursive: true });
|
|
@@ -53055,7 +53755,7 @@ init_helloLinzumiProject();
|
|
|
53055
53755
|
// src/commanderDaemon.ts
|
|
53056
53756
|
init_runnerLogger();
|
|
53057
53757
|
import {
|
|
53058
|
-
existsSync as
|
|
53758
|
+
existsSync as existsSync16,
|
|
53059
53759
|
closeSync as closeSync3,
|
|
53060
53760
|
mkdirSync as mkdirSync14,
|
|
53061
53761
|
openSync as openSync4,
|
|
@@ -53149,7 +53849,7 @@ function startCommanderDaemon(options) {
|
|
|
53149
53849
|
}
|
|
53150
53850
|
function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
|
|
53151
53851
|
const statusFile = commanderStatusFile(runnerId, statusDir);
|
|
53152
|
-
if (!
|
|
53852
|
+
if (!existsSync16(statusFile)) {
|
|
53153
53853
|
return { status: "missing", runnerId, statusFile };
|
|
53154
53854
|
}
|
|
53155
53855
|
const record = parseRecord(readFileSync19(statusFile, "utf8"));
|
|
@@ -53157,7 +53857,7 @@ function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), proce
|
|
|
53157
53857
|
}
|
|
53158
53858
|
async function waitForCommanderDaemon(options) {
|
|
53159
53859
|
const now = options.now ?? (() => Date.now());
|
|
53160
|
-
const readTextFile = options.readTextFile ?? ((path2) =>
|
|
53860
|
+
const readTextFile = options.readTextFile ?? ((path2) => existsSync16(path2) ? readFileSync19(path2, "utf8") : void 0);
|
|
53161
53861
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
53162
53862
|
const deadline = now() + options.timeoutMs;
|
|
53163
53863
|
while (now() <= deadline) {
|
|
@@ -66372,6 +67072,11 @@ var flagDefinitions = /* @__PURE__ */ new Map([
|
|
|
66372
67072
|
["command", { kind: "value" }],
|
|
66373
67073
|
["owner-username", { kind: "value" }],
|
|
66374
67074
|
["include-token", { kind: "boolean" }],
|
|
67075
|
+
// Interactive lock-conflict resolution on connect: --take-over stops a live
|
|
67076
|
+
// holder without a prompt (automation); --no-input forces non-interactive so
|
|
67077
|
+
// a TTY never blocks on the takeover prompt (falls back to report + exit 1).
|
|
67078
|
+
["take-over", { kind: "boolean" }],
|
|
67079
|
+
["no-input", { kind: "boolean" }],
|
|
66375
67080
|
["help", { kind: "boolean" }]
|
|
66376
67081
|
]);
|
|
66377
67082
|
var helloFlagDefinitions = /* @__PURE__ */ new Map([
|
|
@@ -66388,8 +67093,11 @@ if (isMainModule()) {
|
|
|
66388
67093
|
try {
|
|
66389
67094
|
await main(process.argv.slice(2));
|
|
66390
67095
|
} catch (error) {
|
|
66391
|
-
|
|
67096
|
+
const message = cliErrorMessage(error);
|
|
67097
|
+
if (message !== "") {
|
|
67098
|
+
process.stderr.write(`${message}
|
|
66392
67099
|
`);
|
|
67100
|
+
}
|
|
66393
67101
|
process.exit(1);
|
|
66394
67102
|
}
|
|
66395
67103
|
}
|
|
@@ -66812,6 +67520,9 @@ async function runCommanderDaemonCommand(args) {
|
|
|
66812
67520
|
}
|
|
66813
67521
|
}
|
|
66814
67522
|
function cliErrorMessage(error) {
|
|
67523
|
+
if (isRunnerLockConflictReportedError(error)) {
|
|
67524
|
+
return "";
|
|
67525
|
+
}
|
|
66815
67526
|
return signupPromptCancelMessage2(error) ?? userFacingErrorMessage(error, { includeSupportLine: true });
|
|
66816
67527
|
}
|
|
66817
67528
|
function signupPromptCancelMessage2(error) {
|
|
@@ -67099,7 +67810,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
67099
67810
|
};
|
|
67100
67811
|
}
|
|
67101
67812
|
function readAgentTokenTextFile(path2) {
|
|
67102
|
-
return
|
|
67813
|
+
return existsSync18(path2) ? readFileSync22(path2, "utf8") : void 0;
|
|
67103
67814
|
}
|
|
67104
67815
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
67105
67816
|
const unsupportedFlags = [
|
|
@@ -67274,7 +67985,11 @@ async function parseRunnerArgs(args, deps = {
|
|
|
67274
67985
|
workspaceSlug: workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
67275
67986
|
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
67276
67987
|
onboardingDiscovery,
|
|
67277
|
-
channelSession: void 0
|
|
67988
|
+
channelSession: void 0,
|
|
67989
|
+
lockTakeover: {
|
|
67990
|
+
takeOverWithoutPrompt: values.get("take-over") === true,
|
|
67991
|
+
forceNonInteractive: values.get("no-input") === true
|
|
67992
|
+
}
|
|
67278
67993
|
};
|
|
67279
67994
|
}
|
|
67280
67995
|
function runnerRuntimeDefaultsFromValues(values) {
|
|
@@ -67624,6 +68339,8 @@ Connection:
|
|
|
67624
68339
|
|
|
67625
68340
|
Workspace:
|
|
67626
68341
|
--workspace <slug> Workspace slug
|
|
68342
|
+
--take-over If another runner already holds this workspace, stop it and run here (no prompt)
|
|
68343
|
+
--no-input Never prompt; if the workspace is held, print the conflict and exit
|
|
67627
68344
|
|
|
67628
68345
|
Codex:
|
|
67629
68346
|
--cwd <path> Working directory for Codex, default current directory
|