@linzumi/cli 0.0.40-beta → 0.0.42-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +823 -324
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { randomUUID as
|
|
3
|
-
import { existsSync as
|
|
2
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
3
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10, realpathSync as realpathSync6 } from "node:fs";
|
|
4
4
|
import { homedir as homedir9 } from "node:os";
|
|
5
5
|
import { resolve as resolve9 } from "node:path";
|
|
6
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/runner.ts
|
|
9
9
|
import { spawn as spawn6 } from "node:child_process";
|
|
10
|
-
import { randomUUID as
|
|
11
|
-
import { realpathSync as
|
|
10
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
11
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
12
12
|
import { hostname as hostname2 } from "node:os";
|
|
13
|
-
import { join as
|
|
13
|
+
import { join as join8, resolve as resolve6 } from "node:path";
|
|
14
14
|
|
|
15
15
|
// src/channelSessionSupport.ts
|
|
16
16
|
import { spawnSync } from "node:child_process";
|
|
@@ -248,7 +248,7 @@ function codexThreadRuntimeOverrides(options) {
|
|
|
248
248
|
...session.model === undefined ? {} : { model: session.model },
|
|
249
249
|
...session.reasoningEffort === undefined ? {} : { reasoningEffort: session.reasoningEffort },
|
|
250
250
|
...options.fast === true ? { serviceTier: "fast" } : {},
|
|
251
|
-
|
|
251
|
+
approvalPolicy: codexApprovalPolicyForRequest(session.approvalPolicy, session.sandbox),
|
|
252
252
|
...session.sandbox === undefined ? {} : { sandbox: session.sandbox }
|
|
253
253
|
};
|
|
254
254
|
}
|
|
@@ -259,10 +259,30 @@ function codexTurnRuntimeOverrides(options) {
|
|
|
259
259
|
...session.model === undefined ? {} : { model: session.model },
|
|
260
260
|
...session.reasoningEffort === undefined ? {} : { effort: session.reasoningEffort },
|
|
261
261
|
...options.fast === true ? { serviceTier: "fast" } : {},
|
|
262
|
-
|
|
262
|
+
approvalPolicy: codexApprovalPolicyForRequest(session.approvalPolicy, session.sandbox),
|
|
263
263
|
...session.sandbox === undefined ? {} : { sandboxPolicy: codexSandboxPolicy(session.sandbox, options.cwd) }
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
+
function codexApprovalPolicySetting(approvalPolicy, sandbox) {
|
|
267
|
+
if (approvalPolicy === undefined || approvalPolicy === "default") {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
return codexApprovalPolicyForRequest(approvalPolicy, sandbox);
|
|
271
|
+
}
|
|
272
|
+
function codexApprovalPolicyForRequest(approvalPolicy, sandbox) {
|
|
273
|
+
switch (approvalPolicy) {
|
|
274
|
+
case undefined:
|
|
275
|
+
case "default":
|
|
276
|
+
return "on-request";
|
|
277
|
+
case "never":
|
|
278
|
+
return sandbox === "danger-full-access" ? "never" : "on-request";
|
|
279
|
+
case "on-request":
|
|
280
|
+
case "on-failure":
|
|
281
|
+
return approvalPolicy;
|
|
282
|
+
default:
|
|
283
|
+
throw new Error(`unsupported Codex approval policy: ${approvalPolicy}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
266
286
|
function codexSandboxPolicy(sandbox, cwd) {
|
|
267
287
|
switch (sandbox) {
|
|
268
288
|
case "danger-full-access":
|
|
@@ -290,6 +310,9 @@ function codexSandboxPolicy(sandbox, cwd) {
|
|
|
290
310
|
// src/codexOutput.ts
|
|
291
311
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
292
312
|
var maxVisibleWebSearchQueries = 6;
|
|
313
|
+
var maxPersistedCommandOutputChars = 14000;
|
|
314
|
+
var maxPersistedCommandLabelChars = 512;
|
|
315
|
+
var commandOutputTruncationReason = "phoenix_insert_body_limit";
|
|
293
316
|
function codexOutputMessagesForTurn(response, turnId) {
|
|
294
317
|
if ("error" in response) {
|
|
295
318
|
return [];
|
|
@@ -355,21 +378,20 @@ function codexOutputMessagesForItem(item, index) {
|
|
|
355
378
|
case "commandExecution": {
|
|
356
379
|
const command = stringValue(item.command) ?? "command";
|
|
357
380
|
const output = nonBlankStringValue(item.aggregatedOutput) ?? "";
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
`);
|
|
381
|
+
const projection = codexCommandOutputProjection(command, output);
|
|
361
382
|
return [
|
|
362
383
|
{
|
|
363
384
|
itemKey,
|
|
364
|
-
body,
|
|
385
|
+
body: projection.body,
|
|
365
386
|
structured: baseStructured("codex_command_execution", {
|
|
366
|
-
command,
|
|
387
|
+
command: projection.command,
|
|
367
388
|
cwd: stringValue(item.cwd) ?? "",
|
|
368
389
|
status: stringValue(item.status) ?? "",
|
|
369
390
|
process_id: stringValue(item.processId) ?? "",
|
|
370
391
|
duration_ms: integerValue(item.durationMs) ?? null,
|
|
371
392
|
exit_code: integerValue(item.exitCode) ?? null,
|
|
372
|
-
output
|
|
393
|
+
output: projection.output,
|
|
394
|
+
...commandOutputTruncationMetadata(projection)
|
|
373
395
|
})
|
|
374
396
|
}
|
|
375
397
|
];
|
|
@@ -501,21 +523,29 @@ function codexCommandOutputDeltaFromNotification(params) {
|
|
|
501
523
|
};
|
|
502
524
|
}
|
|
503
525
|
function codexCommandExecutionStructuredMessage(itemKey, fields, streamState) {
|
|
526
|
+
const projection = codexCommandOutputProjection(fields.command, fields.output);
|
|
527
|
+
return codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection);
|
|
528
|
+
}
|
|
529
|
+
function codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection) {
|
|
504
530
|
return {
|
|
505
531
|
kind: "codex_command_execution",
|
|
506
532
|
item_id: itemKey,
|
|
507
533
|
transcript_unit_id: `codex_command_execution:${itemKey}`,
|
|
508
534
|
stream_state: streamState,
|
|
509
|
-
command:
|
|
535
|
+
command: projection.command,
|
|
510
536
|
cwd: fields.cwd ?? "",
|
|
511
537
|
status: fields.status ?? streamState,
|
|
512
538
|
process_id: fields.processId ?? "",
|
|
513
539
|
duration_ms: fields.durationMs ?? null,
|
|
514
540
|
exit_code: fields.exitCode ?? null,
|
|
515
541
|
stream: fields.stream ?? "",
|
|
516
|
-
output:
|
|
542
|
+
output: projection.output,
|
|
543
|
+
...commandOutputTruncationMetadata(projection)
|
|
517
544
|
};
|
|
518
545
|
}
|
|
546
|
+
function codexCommandOutputBody(command, output) {
|
|
547
|
+
return codexCommandOutputProjection(command, output).body;
|
|
548
|
+
}
|
|
519
549
|
function codexFileChangeDeltaFromNotification(params) {
|
|
520
550
|
const patchText = textDeltaFromParams(params);
|
|
521
551
|
if (patchText === undefined || patchText === "") {
|
|
@@ -618,6 +648,65 @@ function assistantVisibleText(item) {
|
|
|
618
648
|
function assistantDeltaText(params) {
|
|
619
649
|
return textDeltaFromParams(params);
|
|
620
650
|
}
|
|
651
|
+
function codexCommandOutputProjection(command, output) {
|
|
652
|
+
const normalizedCommand = truncatePersistedText(sanitizePersistedText(command), maxPersistedCommandLabelChars, "command label");
|
|
653
|
+
const normalizedOutput = truncateCommandOutput(sanitizePersistedText(output));
|
|
654
|
+
const body = commandOutputBodyForProjection(normalizedCommand.text, normalizedOutput.text);
|
|
655
|
+
return {
|
|
656
|
+
command: normalizedCommand.text,
|
|
657
|
+
output: normalizedOutput.text,
|
|
658
|
+
body,
|
|
659
|
+
truncated: normalizedCommand.truncated || normalizedOutput.truncated,
|
|
660
|
+
originalLength: output.length,
|
|
661
|
+
maxOutputChars: maxPersistedCommandOutputChars,
|
|
662
|
+
truncationReason: commandOutputTruncationReason
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function commandOutputTruncationMetadata(projection) {
|
|
666
|
+
return projection.truncated ? {
|
|
667
|
+
output_truncated: true,
|
|
668
|
+
output_original_length: projection.originalLength,
|
|
669
|
+
output_max_chars: projection.maxOutputChars,
|
|
670
|
+
output_truncation_reason: projection.truncationReason
|
|
671
|
+
} : {};
|
|
672
|
+
}
|
|
673
|
+
function commandOutputBodyForProjection(command, output) {
|
|
674
|
+
return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
|
|
675
|
+
|
|
676
|
+
`);
|
|
677
|
+
}
|
|
678
|
+
function truncateCommandOutput(output) {
|
|
679
|
+
if (output.length <= maxPersistedCommandOutputChars) {
|
|
680
|
+
return { text: output, truncated: false };
|
|
681
|
+
}
|
|
682
|
+
const marker = `
|
|
683
|
+
|
|
684
|
+
[Linzumi truncated command output: output was too large to persist fully. Owners should search runner logs for codex.command_output_truncated failure_class=phoenix_insert_body_limit.]
|
|
685
|
+
|
|
686
|
+
`;
|
|
687
|
+
const availableChars = Math.max(0, maxPersistedCommandOutputChars - marker.length);
|
|
688
|
+
const headChars = Math.min(2000, Math.floor(availableChars / 3));
|
|
689
|
+
const tailChars = availableChars - headChars;
|
|
690
|
+
const text = `${output.slice(0, headChars)}${marker}${output.slice(-tailChars)}`;
|
|
691
|
+
return {
|
|
692
|
+
text: text.slice(0, maxPersistedCommandOutputChars),
|
|
693
|
+
truncated: true
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
function truncatePersistedText(text, maxChars, label) {
|
|
697
|
+
if (text.length <= maxChars) {
|
|
698
|
+
return { text, truncated: false };
|
|
699
|
+
}
|
|
700
|
+
const marker = `[Linzumi truncated ${label}]`;
|
|
701
|
+
const availableChars = Math.max(0, maxChars - marker.length - 1);
|
|
702
|
+
return {
|
|
703
|
+
text: `${text.slice(0, availableChars)} ${marker}`.slice(0, maxChars),
|
|
704
|
+
truncated: true
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
function sanitizePersistedText(text) {
|
|
708
|
+
return text.replaceAll("\x00", "�");
|
|
709
|
+
}
|
|
621
710
|
function textDeltaFromParams(params) {
|
|
622
711
|
const delta = params.delta;
|
|
623
712
|
if (typeof delta === "string") {
|
|
@@ -641,57 +730,67 @@ function commandMessageForFunctionCall(item, output, index) {
|
|
|
641
730
|
const command = name === "exec_command" ? nonBlankStringValue(args?.cmd) ?? name : `${name}${toolArgumentsSummary(item.arguments)}`;
|
|
642
731
|
const cwd = nonBlankStringValue(args?.cwd) ?? "";
|
|
643
732
|
const outputText = toolOutputText(output);
|
|
644
|
-
const
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
733
|
+
const projection = codexCommandOutputProjection(command, outputText);
|
|
734
|
+
return [
|
|
735
|
+
{
|
|
736
|
+
itemKey,
|
|
737
|
+
body: projection.body,
|
|
738
|
+
structured: codexCommandExecutionStructuredMessage(itemKey, {
|
|
739
|
+
command,
|
|
740
|
+
cwd,
|
|
741
|
+
output: outputText,
|
|
742
|
+
status: output === undefined ? "started" : "completed"
|
|
743
|
+
}, "completed")
|
|
744
|
+
}
|
|
745
|
+
];
|
|
657
746
|
}
|
|
658
747
|
function messageForCustomToolCall(item, output, index) {
|
|
659
748
|
const name = stringValue(item.name) ?? "custom_tool_call";
|
|
660
749
|
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
661
750
|
const input = nonBlankStringValue(item.input) ?? nonBlankStringValue(item.arguments) ?? "";
|
|
662
751
|
if (name === "apply_patch") {
|
|
663
|
-
return [
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
752
|
+
return [
|
|
753
|
+
{
|
|
754
|
+
itemKey,
|
|
755
|
+
body: input,
|
|
756
|
+
structured: {
|
|
757
|
+
kind: "codex_file_change",
|
|
758
|
+
item_id: itemKey,
|
|
759
|
+
transcript_unit_id: `codex_file_change:${itemKey}`,
|
|
760
|
+
stream_state: "completed",
|
|
761
|
+
status: output === undefined ? "started" : "completed",
|
|
762
|
+
patch_text: input,
|
|
763
|
+
changes: fileChangesFromPatch(input)
|
|
764
|
+
}
|
|
674
765
|
}
|
|
675
|
-
|
|
766
|
+
];
|
|
676
767
|
}
|
|
677
768
|
const outputText = toolOutputText(output);
|
|
678
769
|
const command = `${name}${toolArgumentsSummary(item.input)}`;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
770
|
+
const projection = codexCommandOutputProjection(command, outputText);
|
|
771
|
+
return [
|
|
772
|
+
{
|
|
773
|
+
itemKey,
|
|
774
|
+
body: projection.body,
|
|
775
|
+
structured: codexCommandExecutionStructuredMessage(itemKey, {
|
|
776
|
+
command,
|
|
777
|
+
output: outputText,
|
|
778
|
+
status: output === undefined ? "started" : "completed"
|
|
779
|
+
}, "completed")
|
|
780
|
+
}
|
|
781
|
+
];
|
|
686
782
|
}
|
|
687
783
|
function commandMessageForUnpairedToolOutput(item, index) {
|
|
688
784
|
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
689
785
|
const output = toolOutputText(item);
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
786
|
+
const projection = codexCommandOutputProjection("tool output", output);
|
|
787
|
+
return [
|
|
788
|
+
{
|
|
789
|
+
itemKey,
|
|
790
|
+
body: projection.output,
|
|
791
|
+
structured: codexCommandExecutionStructuredMessageFromProjection(itemKey, { status: "completed" }, "completed", projection)
|
|
792
|
+
}
|
|
793
|
+
];
|
|
695
794
|
}
|
|
696
795
|
function toolArgumentsSummary(value) {
|
|
697
796
|
const text = nonBlankStringValue(value);
|
|
@@ -710,13 +809,34 @@ function fileChangesFromPatch(patchText) {
|
|
|
710
809
|
const addPrefix = "*** Add File: ";
|
|
711
810
|
const deletePrefix = "*** Delete File: ";
|
|
712
811
|
if (line.startsWith(updatePrefix)) {
|
|
713
|
-
return [
|
|
812
|
+
return [
|
|
813
|
+
{
|
|
814
|
+
path: line.slice(updatePrefix.length).trim(),
|
|
815
|
+
diff: "",
|
|
816
|
+
kind: "update",
|
|
817
|
+
move_path: ""
|
|
818
|
+
}
|
|
819
|
+
];
|
|
714
820
|
}
|
|
715
821
|
if (line.startsWith(addPrefix)) {
|
|
716
|
-
return [
|
|
822
|
+
return [
|
|
823
|
+
{
|
|
824
|
+
path: line.slice(addPrefix.length).trim(),
|
|
825
|
+
diff: "",
|
|
826
|
+
kind: "add",
|
|
827
|
+
move_path: ""
|
|
828
|
+
}
|
|
829
|
+
];
|
|
717
830
|
}
|
|
718
831
|
if (line.startsWith(deletePrefix)) {
|
|
719
|
-
return [
|
|
832
|
+
return [
|
|
833
|
+
{
|
|
834
|
+
path: line.slice(deletePrefix.length).trim(),
|
|
835
|
+
diff: "",
|
|
836
|
+
kind: "delete",
|
|
837
|
+
move_path: ""
|
|
838
|
+
}
|
|
839
|
+
];
|
|
720
840
|
}
|
|
721
841
|
return [];
|
|
722
842
|
});
|
|
@@ -2345,8 +2465,8 @@ async function attachChannelSession(args) {
|
|
|
2345
2465
|
const session = args.options.channelSession;
|
|
2346
2466
|
const chatTopic = `chat:${session.workspaceSlug}:${session.channelSlug}`;
|
|
2347
2467
|
const state = initialChannelSessionState(0, session.rootSeq, session.kandanThreadId, session.codexThreadId, args.options);
|
|
2348
|
-
const joined = await args.kandan.join(chatTopic,
|
|
2349
|
-
rejoinPayload: () => (
|
|
2468
|
+
const joined = await args.kandan.join(chatTopic, chatJoinPayload(args.options.runnerId, state, 0), {
|
|
2469
|
+
rejoinPayload: () => chatJoinPayload(args.options.runnerId, state, state.minSeq)
|
|
2350
2470
|
});
|
|
2351
2471
|
const cursor = integerValue(joined.cursor) ?? 0;
|
|
2352
2472
|
const runnerIdentity = identityFromAccessToken(args.options.token);
|
|
@@ -2492,6 +2612,19 @@ async function attachChannelSession(args) {
|
|
|
2492
2612
|
}
|
|
2493
2613
|
};
|
|
2494
2614
|
}
|
|
2615
|
+
function chatJoinPayload(runnerId, state, lastSeq) {
|
|
2616
|
+
const base = { last_seq: lastSeq };
|
|
2617
|
+
switch (state.kandanThreadId) {
|
|
2618
|
+
case undefined:
|
|
2619
|
+
return base;
|
|
2620
|
+
default:
|
|
2621
|
+
return {
|
|
2622
|
+
...base,
|
|
2623
|
+
runner_id: runnerId,
|
|
2624
|
+
thread_id: state.kandanThreadId
|
|
2625
|
+
};
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2495
2628
|
async function bindCurrentCodexThread(args, state) {
|
|
2496
2629
|
if (state.codexThreadId === undefined) {
|
|
2497
2630
|
return;
|
|
@@ -3498,7 +3631,7 @@ async function handleCodexServerRequest(args, state, payloadContext, request) {
|
|
|
3498
3631
|
throw new Error(message);
|
|
3499
3632
|
}
|
|
3500
3633
|
function codexApprovalRequestCanAutoAccept(settings, method) {
|
|
3501
|
-
return settings.approvalPolicy === "never" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3634
|
+
return settings.approvalPolicy === "never" && settings.sandbox === "danger-full-access" && (method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval");
|
|
3502
3635
|
}
|
|
3503
3636
|
function codexApprovalRequestCanSurface(method) {
|
|
3504
3637
|
return method === "item/commandExecution/requestApproval" || method === "item/fileChange/requestApproval";
|
|
@@ -3972,13 +4105,25 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
3972
4105
|
}
|
|
3973
4106
|
const existing = findStreamingCommandOutput(state, delta.itemKey);
|
|
3974
4107
|
const output = `${existing?.output ?? ""}${delta.delta}`;
|
|
3975
|
-
const body = commandOutputBody("command output", output);
|
|
3976
4108
|
const structured = codexCommandExecutionStructuredMessage(delta.itemKey, {
|
|
3977
4109
|
command: "command output",
|
|
3978
4110
|
output,
|
|
3979
4111
|
processId: delta.processId,
|
|
3980
4112
|
stream: delta.stream
|
|
3981
4113
|
}, "streaming");
|
|
4114
|
+
const persistedOutput = typeof structured.output === "string" ? structured.output : output;
|
|
4115
|
+
const body = codexCommandOutputBody("command output", output);
|
|
4116
|
+
if (structured.output_truncated === true) {
|
|
4117
|
+
args.log("codex.command_output_truncated", {
|
|
4118
|
+
item_key: delta.itemKey,
|
|
4119
|
+
turn_id: turnId,
|
|
4120
|
+
process_id: delta.processId ?? null,
|
|
4121
|
+
stream: delta.stream,
|
|
4122
|
+
failure_class: stringValue(structured.output_truncation_reason) ?? null,
|
|
4123
|
+
original_length: integerValue(structured.output_original_length) ?? null,
|
|
4124
|
+
persisted_length: persistedOutput.length
|
|
4125
|
+
});
|
|
4126
|
+
}
|
|
3982
4127
|
if (existing === undefined) {
|
|
3983
4128
|
const session = args.options.channelSession;
|
|
3984
4129
|
const reply = await pushOk(args.kandan, args.topic, "session:post_thread_message", {
|
|
@@ -4002,7 +4147,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
4002
4147
|
itemKey: delta.itemKey,
|
|
4003
4148
|
turnId,
|
|
4004
4149
|
seq,
|
|
4005
|
-
output,
|
|
4150
|
+
output: persistedOutput,
|
|
4006
4151
|
processId: delta.processId,
|
|
4007
4152
|
stream: delta.stream
|
|
4008
4153
|
});
|
|
@@ -4011,7 +4156,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
4011
4156
|
await editCodexStructuredOutput(args, state, existing.seq, body, structured);
|
|
4012
4157
|
rememberStreamingCommandOutput(state, {
|
|
4013
4158
|
...existing,
|
|
4014
|
-
output,
|
|
4159
|
+
output: persistedOutput,
|
|
4015
4160
|
processId: delta.processId ?? existing.processId,
|
|
4016
4161
|
stream: delta.stream
|
|
4017
4162
|
});
|
|
@@ -4021,7 +4166,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
4021
4166
|
turn_id: turnId,
|
|
4022
4167
|
process_id: delta.processId ?? null,
|
|
4023
4168
|
stream: delta.stream,
|
|
4024
|
-
output_length:
|
|
4169
|
+
output_length: persistedOutput.length
|
|
4025
4170
|
});
|
|
4026
4171
|
}
|
|
4027
4172
|
async function forwardFileChangeDeltaPayload(args, state, delta, payloadContext) {
|
|
@@ -4216,11 +4361,6 @@ function webSearchProgressClientMessageId(instanceId, turnId) {
|
|
|
4216
4361
|
function webSearchProgressItemKey(turnId) {
|
|
4217
4362
|
return `web-search:${turnId}`;
|
|
4218
4363
|
}
|
|
4219
|
-
function commandOutputBody(command, output) {
|
|
4220
|
-
return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
|
|
4221
|
-
|
|
4222
|
-
`);
|
|
4223
|
-
}
|
|
4224
4364
|
async function streamCompletedCodexOutput(args, state, payloadContext, params) {
|
|
4225
4365
|
if (state.kandanThreadId === undefined || state.codexThreadId === undefined) {
|
|
4226
4366
|
return;
|
|
@@ -5012,20 +5152,27 @@ function runtimeSettingsFromOptions(options) {
|
|
|
5012
5152
|
return {
|
|
5013
5153
|
model: options.channelSession.model,
|
|
5014
5154
|
reasoningEffort: options.channelSession.reasoningEffort,
|
|
5015
|
-
approvalPolicy: options.channelSession.approvalPolicy,
|
|
5155
|
+
approvalPolicy: codexApprovalPolicySetting(options.channelSession.approvalPolicy, options.channelSession.sandbox),
|
|
5016
5156
|
sandbox: options.channelSession.sandbox,
|
|
5017
5157
|
fast: options.fast
|
|
5018
5158
|
};
|
|
5019
5159
|
}
|
|
5020
5160
|
function mergeRuntimeSettings(current, update) {
|
|
5161
|
+
const sandbox = mergeOptionalStringRuntimeSetting(current.sandbox, update, "sandbox");
|
|
5021
5162
|
return {
|
|
5022
5163
|
model: mergeOptionalStringRuntimeSetting(current.model, update, "model"),
|
|
5023
5164
|
reasoningEffort: mergeOptionalStringRuntimeSetting(current.reasoningEffort, update, "reasoningEffort"),
|
|
5024
|
-
approvalPolicy:
|
|
5025
|
-
sandbox
|
|
5165
|
+
approvalPolicy: mergeOptionalApprovalPolicyRuntimeSetting(current.approvalPolicy, update, sandbox),
|
|
5166
|
+
sandbox,
|
|
5026
5167
|
fast: update.fast ?? current.fast
|
|
5027
5168
|
};
|
|
5028
5169
|
}
|
|
5170
|
+
function mergeOptionalApprovalPolicyRuntimeSetting(current, update, sandbox) {
|
|
5171
|
+
if (Object.prototype.hasOwnProperty.call(update, "approvalPolicy")) {
|
|
5172
|
+
return codexApprovalPolicySetting(update.approvalPolicy ?? undefined, sandbox);
|
|
5173
|
+
}
|
|
5174
|
+
return codexApprovalPolicySetting(current, sandbox);
|
|
5175
|
+
}
|
|
5029
5176
|
function mergeOptionalStringRuntimeSetting(current, update, key) {
|
|
5030
5177
|
if (Object.prototype.hasOwnProperty.call(update, key)) {
|
|
5031
5178
|
return update[key] ?? undefined;
|
|
@@ -6249,30 +6396,14 @@ function prepareCodeServerLaunch(options) {
|
|
|
6249
6396
|
if (platform !== "darwin") {
|
|
6250
6397
|
return filesystemSandboxUnavailable();
|
|
6251
6398
|
}
|
|
6252
|
-
const sandboxExecBin = options.sandboxExecBin ?? "/usr/bin/sandbox-exec";
|
|
6253
|
-
if (!existsSync2(sandboxExecBin)) {
|
|
6254
|
-
return filesystemSandboxUnavailable();
|
|
6255
|
-
}
|
|
6256
6399
|
const codeServerExecutable = resolveCodeServerExecutable(options.codeServerBin, options.envPath ?? process.env.PATH ?? "");
|
|
6257
6400
|
if (!codeServerExecutable.ok) {
|
|
6258
6401
|
return filesystemSandboxUnavailable();
|
|
6259
6402
|
}
|
|
6260
6403
|
return {
|
|
6261
6404
|
ok: true,
|
|
6262
|
-
command:
|
|
6263
|
-
args:
|
|
6264
|
-
"-p",
|
|
6265
|
-
codeServerSandboxProfile(options, codeServerExecutable.directory),
|
|
6266
|
-
"--",
|
|
6267
|
-
"/bin/sh",
|
|
6268
|
-
"-c",
|
|
6269
|
-
'export HOME="$1"; export PWD="$1"; export TMPDIR="$2"; export TMP="$2"; export TEMP="$2"; shift 2; exec "$@"',
|
|
6270
|
-
"kandan-code-server-env",
|
|
6271
|
-
options.cwd,
|
|
6272
|
-
join4(options.userDataDir, "tmp"),
|
|
6273
|
-
codeServerExecutable.command,
|
|
6274
|
-
...codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
|
|
6275
|
-
]
|
|
6405
|
+
command: codeServerExecutable.command,
|
|
6406
|
+
args: codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
|
|
6276
6407
|
};
|
|
6277
6408
|
}
|
|
6278
6409
|
function prepareLinuxCodeServerLaunch(options) {
|
|
@@ -6343,43 +6474,6 @@ function filesystemSandboxUnavailable() {
|
|
|
6343
6474
|
reason: "local_editor_filesystem_sandbox_unavailable"
|
|
6344
6475
|
};
|
|
6345
6476
|
}
|
|
6346
|
-
function codeServerSandboxProfile(options, codeServerBinDir) {
|
|
6347
|
-
const readOnlyRoots = uniquePaths([
|
|
6348
|
-
"/System",
|
|
6349
|
-
"/Library",
|
|
6350
|
-
"/usr",
|
|
6351
|
-
"/bin",
|
|
6352
|
-
"/sbin",
|
|
6353
|
-
"/etc",
|
|
6354
|
-
"/private/etc",
|
|
6355
|
-
"/opt/homebrew",
|
|
6356
|
-
...options.codeServerRuntimeRoot === undefined ? [] : sandboxPathAliases(options.codeServerRuntimeRoot),
|
|
6357
|
-
codeServerBinDir
|
|
6358
|
-
]);
|
|
6359
|
-
const readWriteRoots = uniquePaths([
|
|
6360
|
-
...sandboxPathAliases(options.cwd),
|
|
6361
|
-
...sandboxPathAliases(options.userDataDir),
|
|
6362
|
-
...options.extensionsDir === undefined ? [] : sandboxPathAliases(options.extensionsDir)
|
|
6363
|
-
]);
|
|
6364
|
-
return [
|
|
6365
|
-
"(version 1)",
|
|
6366
|
-
"(deny default)",
|
|
6367
|
-
"(allow process*)",
|
|
6368
|
-
"(allow signal (target self))",
|
|
6369
|
-
"(allow signal (target same-sandbox))",
|
|
6370
|
-
"(allow sysctl*)",
|
|
6371
|
-
"(allow mach*)",
|
|
6372
|
-
"(allow ipc*)",
|
|
6373
|
-
"(allow network*)",
|
|
6374
|
-
"(allow file-read-metadata)",
|
|
6375
|
-
"(allow file-map-executable)",
|
|
6376
|
-
'(allow file-read* (literal "/") (literal "/private") (literal "/private/var"))',
|
|
6377
|
-
`(allow file-read* ${readOnlyRoots.map(sandboxSubpath).join(" ")})`,
|
|
6378
|
-
'(allow file-read* file-write* (subpath "/dev"))',
|
|
6379
|
-
`(allow file-read* file-write* ${readWriteRoots.map(sandboxSubpath).join(" ")})`
|
|
6380
|
-
].join(`
|
|
6381
|
-
`);
|
|
6382
|
-
}
|
|
6383
6477
|
function resolveCodeServerExecutable(command, envPath) {
|
|
6384
6478
|
if (hasPathSeparator(command)) {
|
|
6385
6479
|
const directory = safeRealpathDir(command);
|
|
@@ -6420,9 +6514,6 @@ function sandboxPathAliases(path) {
|
|
|
6420
6514
|
function uniquePaths(paths) {
|
|
6421
6515
|
return Array.from(new Set(paths.filter((path) => path.length > 0)));
|
|
6422
6516
|
}
|
|
6423
|
-
function sandboxSubpath(path) {
|
|
6424
|
-
return `(subpath "${path.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}")`;
|
|
6425
|
-
}
|
|
6426
6517
|
function prepareLocalEditorCollaboration(collaboration, runnerId, serverPort, browserBaseUrl) {
|
|
6427
6518
|
if (collaboration === undefined || serverPort === undefined) {
|
|
6428
6519
|
return;
|
|
@@ -6548,19 +6639,11 @@ function installDirectory(sourceDir, destinationDir) {
|
|
|
6548
6639
|
mkdirSync3(dirname2(destinationDir), { recursive: true });
|
|
6549
6640
|
cpSync(sourceDir, destinationDir, { recursive: true });
|
|
6550
6641
|
}
|
|
6551
|
-
function codeServerEnv(env, cwd,
|
|
6642
|
+
function codeServerEnv(env, cwd, _userDataDir, collaboration) {
|
|
6552
6643
|
const { PORT: _port, ...hostEnv } = env;
|
|
6553
|
-
const tempDir = join4(userDataDir, "tmp");
|
|
6554
6644
|
const base = {
|
|
6555
6645
|
...hostEnv,
|
|
6556
|
-
|
|
6557
|
-
PWD: cwd,
|
|
6558
|
-
TMPDIR: tempDir,
|
|
6559
|
-
TMP: tempDir,
|
|
6560
|
-
TEMP: tempDir,
|
|
6561
|
-
XDG_CACHE_HOME: join4(userDataDir, "xdg-cache"),
|
|
6562
|
-
XDG_CONFIG_HOME: join4(userDataDir, "xdg-config"),
|
|
6563
|
-
XDG_DATA_HOME: join4(userDataDir, "xdg-data")
|
|
6646
|
+
PWD: cwd
|
|
6564
6647
|
};
|
|
6565
6648
|
if (collaboration === undefined) {
|
|
6566
6649
|
return base;
|
|
@@ -7891,6 +7974,356 @@ function waitForOpen2(websocket) {
|
|
|
7891
7974
|
});
|
|
7892
7975
|
}
|
|
7893
7976
|
|
|
7977
|
+
// src/runnerLock.ts
|
|
7978
|
+
import {
|
|
7979
|
+
closeSync,
|
|
7980
|
+
existsSync as existsSync5,
|
|
7981
|
+
mkdirSync as mkdirSync6,
|
|
7982
|
+
openSync as openSync2,
|
|
7983
|
+
readFileSync as readFileSync4,
|
|
7984
|
+
unlinkSync as unlinkSync2,
|
|
7985
|
+
writeSync
|
|
7986
|
+
} from "node:fs";
|
|
7987
|
+
import { dirname as dirname5, join as join7 } from "node:path";
|
|
7988
|
+
|
|
7989
|
+
// src/localConfig.ts
|
|
7990
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
7991
|
+
import {
|
|
7992
|
+
existsSync as existsSync4,
|
|
7993
|
+
linkSync,
|
|
7994
|
+
mkdirSync as mkdirSync5,
|
|
7995
|
+
readFileSync as readFileSync3,
|
|
7996
|
+
realpathSync as realpathSync4,
|
|
7997
|
+
unlinkSync,
|
|
7998
|
+
writeFileSync as writeFileSync4
|
|
7999
|
+
} from "node:fs";
|
|
8000
|
+
import { homedir as homedir5 } from "node:os";
|
|
8001
|
+
import { basename as basename4, dirname as dirname4, join as join6, resolve as resolve5 } from "node:path";
|
|
8002
|
+
function localConfigPath(env = process.env) {
|
|
8003
|
+
const override = env.LINZUMI_CONFIG_FILE;
|
|
8004
|
+
return override !== undefined && override.trim() !== "" ? resolve5(expandUserPath(override)) : resolve5(homedir5(), ".linzumi", "config.json");
|
|
8005
|
+
}
|
|
8006
|
+
function readLocalConfig(path = localConfigPath()) {
|
|
8007
|
+
if (!existsSync4(path)) {
|
|
8008
|
+
return { version: 1, allowedCwds: [] };
|
|
8009
|
+
}
|
|
8010
|
+
const parsed = JSON.parse(readFileSync3(path, "utf8"));
|
|
8011
|
+
if (!isConfigPayload(parsed)) {
|
|
8012
|
+
throw new Error(`invalid Linzumi config: ${path}`);
|
|
8013
|
+
}
|
|
8014
|
+
const allowedCwds = uniqueStrings(parsed.allowedCwds);
|
|
8015
|
+
return parsed.machineId === undefined ? { version: 1, allowedCwds } : { version: 1, machineId: parsed.machineId, allowedCwds };
|
|
8016
|
+
}
|
|
8017
|
+
function ensureLocalMachineId(path = localConfigPath(), createMachineId = randomUUID2) {
|
|
8018
|
+
const config = readLocalConfig(path);
|
|
8019
|
+
if (config.machineId !== undefined) {
|
|
8020
|
+
return config.machineId;
|
|
8021
|
+
}
|
|
8022
|
+
const machineId = ensureLocalMachineIdSeed(path, createMachineId);
|
|
8023
|
+
const latestConfig = readLocalConfig(path);
|
|
8024
|
+
const latestMachineId = latestConfig.machineId;
|
|
8025
|
+
if (latestMachineId !== undefined) {
|
|
8026
|
+
return latestMachineId;
|
|
8027
|
+
}
|
|
8028
|
+
writeLocalConfig({ ...latestConfig, machineId }, path);
|
|
8029
|
+
return machineId;
|
|
8030
|
+
}
|
|
8031
|
+
function localMachineIdSeedPath(configPath = localConfigPath()) {
|
|
8032
|
+
return join6(dirname4(configPath), `${basename4(configPath)}.machine-id`);
|
|
8033
|
+
}
|
|
8034
|
+
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
8035
|
+
const allowedCwds = [];
|
|
8036
|
+
const missingAllowedCwds = [];
|
|
8037
|
+
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
8038
|
+
const absolutePath = resolve5(expandUserPath(cwd));
|
|
8039
|
+
try {
|
|
8040
|
+
const realPath = realpathSync4(absolutePath);
|
|
8041
|
+
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
8042
|
+
} catch (error) {
|
|
8043
|
+
if (isMissingPathError(error)) {
|
|
8044
|
+
missingAllowedCwds.push(absolutePath);
|
|
8045
|
+
continue;
|
|
8046
|
+
}
|
|
8047
|
+
throw error;
|
|
8048
|
+
}
|
|
8049
|
+
}
|
|
8050
|
+
return {
|
|
8051
|
+
allowedCwds: uniqueStrings(allowedCwds),
|
|
8052
|
+
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
8053
|
+
};
|
|
8054
|
+
}
|
|
8055
|
+
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8056
|
+
const normalizedPath = realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8057
|
+
const config = readLocalConfig(path);
|
|
8058
|
+
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
8059
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8060
|
+
return allowedCwds;
|
|
8061
|
+
}
|
|
8062
|
+
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
8063
|
+
const requestedPath = resolve5(expandUserPath(pathValue));
|
|
8064
|
+
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
8065
|
+
const config = readLocalConfig(path);
|
|
8066
|
+
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
8067
|
+
const normalizedExisting = realpathOrResolved(cwd);
|
|
8068
|
+
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
8069
|
+
});
|
|
8070
|
+
writeLocalConfig({ ...config, version: 1, allowedCwds }, path);
|
|
8071
|
+
return allowedCwds;
|
|
8072
|
+
}
|
|
8073
|
+
function writeLocalConfig(config, path = localConfigPath()) {
|
|
8074
|
+
mkdirSync5(dirname4(path), { recursive: true });
|
|
8075
|
+
writeFileSync4(path, `${JSON.stringify(config, null, 2)}
|
|
8076
|
+
`, "utf8");
|
|
8077
|
+
}
|
|
8078
|
+
function isConfigPayload(value) {
|
|
8079
|
+
return typeof value === "object" && value !== null && value.version === 1 && Array.isArray(value.allowedCwds) && machineIdValid(value.machineId) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
|
|
8080
|
+
}
|
|
8081
|
+
function machineIdValid(value) {
|
|
8082
|
+
return value === undefined || typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
8083
|
+
}
|
|
8084
|
+
function ensureLocalMachineIdSeed(configPath, createMachineId) {
|
|
8085
|
+
const seedPath = localMachineIdSeedPath(configPath);
|
|
8086
|
+
if (existsSync4(seedPath)) {
|
|
8087
|
+
return readMachineIdSeed(seedPath);
|
|
8088
|
+
}
|
|
8089
|
+
const machineId = createMachineId();
|
|
8090
|
+
if (!machineIdValid(machineId)) {
|
|
8091
|
+
throw new Error(`invalid generated Linzumi machine id: ${machineId}`);
|
|
8092
|
+
}
|
|
8093
|
+
mkdirSync5(dirname4(seedPath), { recursive: true });
|
|
8094
|
+
const tempPath = join6(dirname4(seedPath), `.${basename4(seedPath)}.${process.pid}.${randomUUID2()}.tmp`);
|
|
8095
|
+
writeFileSync4(tempPath, `${machineId}
|
|
8096
|
+
`, { encoding: "utf8", flag: "wx" });
|
|
8097
|
+
try {
|
|
8098
|
+
linkSync(tempPath, seedPath);
|
|
8099
|
+
return machineId;
|
|
8100
|
+
} catch (error) {
|
|
8101
|
+
if (isNodeErrorCode(error, "EEXIST")) {
|
|
8102
|
+
return readMachineIdSeed(seedPath);
|
|
8103
|
+
}
|
|
8104
|
+
throw error;
|
|
8105
|
+
} finally {
|
|
8106
|
+
unlinkSync(tempPath);
|
|
8107
|
+
}
|
|
8108
|
+
}
|
|
8109
|
+
function readMachineIdSeed(seedPath) {
|
|
8110
|
+
const machineId = readFileSync3(seedPath, "utf8").trim();
|
|
8111
|
+
if (!machineIdValid(machineId)) {
|
|
8112
|
+
throw new Error(`invalid Linzumi machine id seed: ${seedPath}`);
|
|
8113
|
+
}
|
|
8114
|
+
return machineId;
|
|
8115
|
+
}
|
|
8116
|
+
function uniqueStrings(values) {
|
|
8117
|
+
return [
|
|
8118
|
+
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
8119
|
+
];
|
|
8120
|
+
}
|
|
8121
|
+
function isMissingPathError(error) {
|
|
8122
|
+
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
8123
|
+
}
|
|
8124
|
+
function isNodeErrorCode(error, code) {
|
|
8125
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8126
|
+
}
|
|
8127
|
+
function realpathOrResolved(pathValue) {
|
|
8128
|
+
try {
|
|
8129
|
+
return realpathSync4(resolve5(expandUserPath(pathValue)));
|
|
8130
|
+
} catch (_error) {
|
|
8131
|
+
return resolve5(expandUserPath(pathValue));
|
|
8132
|
+
}
|
|
8133
|
+
}
|
|
8134
|
+
|
|
8135
|
+
// src/version.ts
|
|
8136
|
+
var linzumiCliVersion = "0.0.42-beta";
|
|
8137
|
+
var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
8138
|
+
|
|
8139
|
+
// src/runnerLock.ts
|
|
8140
|
+
function runnerLockPath(machineId, configPath = localConfigPath()) {
|
|
8141
|
+
return join7(dirname5(configPath), "runners", `${encodeURIComponent(machineId)}.lock`);
|
|
8142
|
+
}
|
|
8143
|
+
function acquireRunnerLock(options) {
|
|
8144
|
+
const path = runnerLockPath(options.machineId, options.configPath);
|
|
8145
|
+
const isPidAlive = options.isPidAlive ?? processIsAlive;
|
|
8146
|
+
const record = {
|
|
8147
|
+
version: 1,
|
|
8148
|
+
machineId: options.machineId,
|
|
8149
|
+
runnerId: options.runnerId,
|
|
8150
|
+
pid: options.pid ?? process.pid,
|
|
8151
|
+
cwd: options.cwd,
|
|
8152
|
+
workspace: options.workspace,
|
|
8153
|
+
startedAt: (options.now ?? (() => new Date))().toISOString(),
|
|
8154
|
+
cliVersion: options.cliVersion ?? linzumiCliVersion
|
|
8155
|
+
};
|
|
8156
|
+
writeLockOrHandleExisting(path, record, isPidAlive, options.beforeReadExistingLock, options.beforeReplaceStaleLock);
|
|
8157
|
+
return {
|
|
8158
|
+
path,
|
|
8159
|
+
record,
|
|
8160
|
+
release: () => releaseRunnerLock(path, record)
|
|
8161
|
+
};
|
|
8162
|
+
}
|
|
8163
|
+
function writeLockOrHandleExisting(path, record, isPidAlive, beforeReadExistingLock, beforeReplaceStaleLock) {
|
|
8164
|
+
if (tryCreateLock(path, record)) {
|
|
8165
|
+
return;
|
|
8166
|
+
}
|
|
8167
|
+
beforeReadExistingLock?.();
|
|
8168
|
+
const existing = readRunnerLockIfPresent(path);
|
|
8169
|
+
if (existing === undefined) {
|
|
8170
|
+
if (tryCreateLock(path, record)) {
|
|
8171
|
+
return;
|
|
8172
|
+
}
|
|
8173
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8174
|
+
}
|
|
8175
|
+
if (isPidAlive(existing.pid)) {
|
|
8176
|
+
throw new Error(activeRunnerLockMessage(path, existing));
|
|
8177
|
+
}
|
|
8178
|
+
beforeReplaceStaleLock?.();
|
|
8179
|
+
withStaleReplacementLock(path, isPidAlive, () => {
|
|
8180
|
+
const latest = existsSync5(path) ? readRunnerLock(path) : undefined;
|
|
8181
|
+
if (latest !== undefined && isPidAlive(latest.pid)) {
|
|
8182
|
+
throw new Error(activeRunnerLockMessage(path, latest));
|
|
8183
|
+
}
|
|
8184
|
+
if (latest !== undefined) {
|
|
8185
|
+
unlinkSync2(path);
|
|
8186
|
+
}
|
|
8187
|
+
if (!tryCreateLock(path, record)) {
|
|
8188
|
+
throw new Error(`another Linzumi runner lock appeared while starting: ${path}`);
|
|
8189
|
+
}
|
|
8190
|
+
});
|
|
8191
|
+
}
|
|
8192
|
+
function tryCreateLock(path, record) {
|
|
8193
|
+
mkdirSync6(dirname5(path), { recursive: true });
|
|
8194
|
+
try {
|
|
8195
|
+
const fd = openSync2(path, "wx");
|
|
8196
|
+
try {
|
|
8197
|
+
writeSync(fd, `${JSON.stringify(record, null, 2)}
|
|
8198
|
+
`);
|
|
8199
|
+
} finally {
|
|
8200
|
+
closeSync(fd);
|
|
8201
|
+
}
|
|
8202
|
+
return true;
|
|
8203
|
+
} catch (error) {
|
|
8204
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8205
|
+
return false;
|
|
8206
|
+
}
|
|
8207
|
+
throw error;
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
function withStaleReplacementLock(path, isPidAlive, callback) {
|
|
8211
|
+
const replacementPath = `${path}.replace`;
|
|
8212
|
+
while (true) {
|
|
8213
|
+
try {
|
|
8214
|
+
const fd = openSync2(replacementPath, "wx");
|
|
8215
|
+
try {
|
|
8216
|
+
writeSync(fd, `${process.pid}
|
|
8217
|
+
`);
|
|
8218
|
+
callback();
|
|
8219
|
+
} finally {
|
|
8220
|
+
closeSync(fd);
|
|
8221
|
+
unlinkSync2(replacementPath);
|
|
8222
|
+
}
|
|
8223
|
+
return;
|
|
8224
|
+
} catch (error) {
|
|
8225
|
+
if (isNodeErrorCode2(error, "EEXIST")) {
|
|
8226
|
+
const replacementPid = readReplacementLockPidIfPresent(replacementPath);
|
|
8227
|
+
if (replacementPid === undefined) {
|
|
8228
|
+
continue;
|
|
8229
|
+
}
|
|
8230
|
+
if (isPidAlive(replacementPid)) {
|
|
8231
|
+
throw new Error([
|
|
8232
|
+
"another Linzumi runner is already replacing a stale runner lock",
|
|
8233
|
+
`lock: ${path}`,
|
|
8234
|
+
"Wait for that startup to finish, then retry."
|
|
8235
|
+
].join(`
|
|
8236
|
+
`));
|
|
8237
|
+
}
|
|
8238
|
+
unlinkSync2(replacementPath);
|
|
8239
|
+
continue;
|
|
8240
|
+
}
|
|
8241
|
+
throw error;
|
|
8242
|
+
}
|
|
8243
|
+
}
|
|
8244
|
+
}
|
|
8245
|
+
function readReplacementLockPidIfPresent(path) {
|
|
8246
|
+
let value;
|
|
8247
|
+
try {
|
|
8248
|
+
value = readFileSync4(path, "utf8").trim();
|
|
8249
|
+
} catch (error) {
|
|
8250
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8251
|
+
return;
|
|
8252
|
+
}
|
|
8253
|
+
throw error;
|
|
8254
|
+
}
|
|
8255
|
+
const pid = Number.parseInt(value, 10);
|
|
8256
|
+
if (pid.toString() !== value || pid <= 0) {
|
|
8257
|
+
throw new Error(`invalid Linzumi runner replacement lock: ${path}`);
|
|
8258
|
+
}
|
|
8259
|
+
return pid;
|
|
8260
|
+
}
|
|
8261
|
+
function releaseRunnerLock(path, record) {
|
|
8262
|
+
const current = readRunnerLockForRelease(path);
|
|
8263
|
+
if (current !== undefined && current.machineId === record.machineId && current.runnerId === record.runnerId && current.pid === record.pid) {
|
|
8264
|
+
unlinkSync2(path);
|
|
8265
|
+
}
|
|
8266
|
+
}
|
|
8267
|
+
function readRunnerLockForRelease(path) {
|
|
8268
|
+
try {
|
|
8269
|
+
return readRunnerLockIfPresent(path);
|
|
8270
|
+
} catch (_error) {
|
|
8271
|
+
return;
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
function readRunnerLockIfPresent(path) {
|
|
8275
|
+
if (!existsSync5(path)) {
|
|
8276
|
+
return;
|
|
8277
|
+
}
|
|
8278
|
+
try {
|
|
8279
|
+
return readRunnerLock(path);
|
|
8280
|
+
} catch (error) {
|
|
8281
|
+
if (isNodeErrorCode2(error, "ENOENT")) {
|
|
8282
|
+
return;
|
|
8283
|
+
}
|
|
8284
|
+
throw error;
|
|
8285
|
+
}
|
|
8286
|
+
}
|
|
8287
|
+
function readRunnerLock(path) {
|
|
8288
|
+
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
8289
|
+
if (!isRunnerLockRecord(parsed)) {
|
|
8290
|
+
throw new Error(`invalid Linzumi runner lock: ${path}`);
|
|
8291
|
+
}
|
|
8292
|
+
return parsed;
|
|
8293
|
+
}
|
|
8294
|
+
function isRunnerLockRecord(value) {
|
|
8295
|
+
return typeof value === "object" && value !== null && value.version === 1 && typeof value.machineId === "string" && value.machineId.trim() !== "" && typeof value.runnerId === "string" && value.runnerId.trim() !== "" && Number.isInteger(value.pid) && value.pid > 0 && typeof value.cwd === "string" && value.cwd.trim() !== "" && workspaceValid(value.workspace) && typeof value.startedAt === "string" && value.startedAt.trim() !== "" && typeof value.cliVersion === "string" && value.cliVersion.trim() !== "";
|
|
8296
|
+
}
|
|
8297
|
+
function workspaceValid(value) {
|
|
8298
|
+
return value === null || typeof value === "string" && value.trim() !== "";
|
|
8299
|
+
}
|
|
8300
|
+
function processIsAlive(pid) {
|
|
8301
|
+
try {
|
|
8302
|
+
process.kill(pid, 0);
|
|
8303
|
+
return true;
|
|
8304
|
+
} catch (error) {
|
|
8305
|
+
return isNodeErrorCode2(error, "ESRCH") ? false : true;
|
|
8306
|
+
}
|
|
8307
|
+
}
|
|
8308
|
+
function activeRunnerLockMessage(path, record) {
|
|
8309
|
+
const workspace = record.workspace === null ? "workspace: unknown" : `workspace: ${record.workspace}`;
|
|
8310
|
+
return [
|
|
8311
|
+
"another Linzumi runner is already running for this machine",
|
|
8312
|
+
`runner id: ${record.runnerId}`,
|
|
8313
|
+
`pid: ${record.pid}`,
|
|
8314
|
+
`cwd: ${record.cwd}`,
|
|
8315
|
+
workspace,
|
|
8316
|
+
`CLI version: ${record.cliVersion}`,
|
|
8317
|
+
`started at: ${record.startedAt}`,
|
|
8318
|
+
`lock: ${path}`,
|
|
8319
|
+
"Stop that process first, then retry. If the process has already exited, remove the stale lock file and retry."
|
|
8320
|
+
].join(`
|
|
8321
|
+
`);
|
|
8322
|
+
}
|
|
8323
|
+
function isNodeErrorCode2(error, code) {
|
|
8324
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
8325
|
+
}
|
|
8326
|
+
|
|
7894
8327
|
// src/runnerConsoleReporter.ts
|
|
7895
8328
|
function reportRunnerConsoleEvent(event, payload) {
|
|
7896
8329
|
const line = formatRunnerConsoleEvent(event, payload);
|
|
@@ -7902,7 +8335,15 @@ function reportRunnerConsoleEvent(event, payload) {
|
|
|
7902
8335
|
function formatRunnerConsoleEvent(event, payload) {
|
|
7903
8336
|
switch (event) {
|
|
7904
8337
|
case "runner.instance_started":
|
|
7905
|
-
return
|
|
8338
|
+
return connectedRunnerMessage(payload);
|
|
8339
|
+
case "runner.replaced":
|
|
8340
|
+
return [
|
|
8341
|
+
"Runner replaced: another Linzumi CLI connected from this machine.",
|
|
8342
|
+
`New runner: ${text(payload.replacementRunnerId)}`,
|
|
8343
|
+
`Version: ${text(payload.replacementVersion)}`,
|
|
8344
|
+
"This process is exiting."
|
|
8345
|
+
].join(`
|
|
8346
|
+
`);
|
|
7906
8347
|
case "kandan.message_ignored":
|
|
7907
8348
|
return `Incoming message from ${sender(payload)}: ignored for reason ${text(payload.reason)}`;
|
|
7908
8349
|
case "kandan.message_queued":
|
|
@@ -7937,6 +8378,40 @@ function formatRunnerConsoleEvent(event, payload) {
|
|
|
7937
8378
|
return;
|
|
7938
8379
|
}
|
|
7939
8380
|
}
|
|
8381
|
+
function connectedRunnerMessage(payload) {
|
|
8382
|
+
return [
|
|
8383
|
+
"Connected to Linzumi",
|
|
8384
|
+
optionalLine("Computer", payload.hostname),
|
|
8385
|
+
optionalLine("Workspace", payload.workspace),
|
|
8386
|
+
`Runner: ${text(payload.runnerId)}`,
|
|
8387
|
+
`CLI: ${text(payload.version)}`,
|
|
8388
|
+
optionalLine("Codex", payload.codexUrl),
|
|
8389
|
+
...replacementLines(payload.replacedRunners)
|
|
8390
|
+
].filter((line) => line !== undefined).join(`
|
|
8391
|
+
`);
|
|
8392
|
+
}
|
|
8393
|
+
function optionalLine(label, value) {
|
|
8394
|
+
const normalized = stringValue2(value) ?? numberValue(value)?.toString();
|
|
8395
|
+
return normalized === undefined ? undefined : `${label}: ${normalized}`;
|
|
8396
|
+
}
|
|
8397
|
+
function replacementLines(value) {
|
|
8398
|
+
if (!Array.isArray(value)) {
|
|
8399
|
+
return [];
|
|
8400
|
+
}
|
|
8401
|
+
return value.flatMap((entry) => {
|
|
8402
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
8403
|
+
return [];
|
|
8404
|
+
}
|
|
8405
|
+
const record = entry;
|
|
8406
|
+
const runnerId = stringValue2(record.runnerId);
|
|
8407
|
+
if (runnerId === undefined) {
|
|
8408
|
+
return [];
|
|
8409
|
+
}
|
|
8410
|
+
const version = stringValue2(record.version);
|
|
8411
|
+
const suffix = version === undefined ? "" : ` (CLI ${version})`;
|
|
8412
|
+
return [`Replaced older runner from this machine: ${runnerId}${suffix}`];
|
|
8413
|
+
});
|
|
8414
|
+
}
|
|
7940
8415
|
function sender(payload) {
|
|
7941
8416
|
const slug = stringValue2(payload.actor_slug);
|
|
7942
8417
|
const userId = numberValue(payload.actor_user_id);
|
|
@@ -8000,6 +8475,21 @@ async function runLocalCodexRunner(options) {
|
|
|
8000
8475
|
kandanUrl: options.kandanUrl
|
|
8001
8476
|
});
|
|
8002
8477
|
try {
|
|
8478
|
+
if (options.machineId !== undefined) {
|
|
8479
|
+
const runnerLock = acquireRunnerLock({
|
|
8480
|
+
machineId: options.machineId,
|
|
8481
|
+
runnerId: options.runnerId,
|
|
8482
|
+
cwd: options.cwd,
|
|
8483
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8484
|
+
configPath: options.runnerLockConfigPath
|
|
8485
|
+
});
|
|
8486
|
+
cleanup.actions.push(() => runnerLock.release());
|
|
8487
|
+
log("runner.lock_acquired", {
|
|
8488
|
+
path: runnerLock.path,
|
|
8489
|
+
machineId: options.machineId,
|
|
8490
|
+
runnerId: options.runnerId
|
|
8491
|
+
});
|
|
8492
|
+
}
|
|
8003
8493
|
return await openLocalCodexRunner(options, log, cleanup, close);
|
|
8004
8494
|
} catch (error) {
|
|
8005
8495
|
await close().catch(() => {
|
|
@@ -8067,9 +8557,11 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8067
8557
|
const kandan = await connectPhoenixClient(options.kandanUrl, options.token, options.socketFactory);
|
|
8068
8558
|
cleanup.actions.push(() => kandan.close());
|
|
8069
8559
|
const topic = `local_runner:${options.runnerId}`;
|
|
8560
|
+
const clientId = options.machineId ?? options.runnerId;
|
|
8070
8561
|
const joinPayload = () => ({
|
|
8071
8562
|
clientName: "kandan-local-codex-runner",
|
|
8072
|
-
|
|
8563
|
+
clientId,
|
|
8564
|
+
version: linzumiCliVersion,
|
|
8073
8565
|
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8074
8566
|
channel: options.channelSession?.channelSlug ?? null,
|
|
8075
8567
|
capabilities: capabilitiesPayload()
|
|
@@ -8084,7 +8576,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8084
8576
|
}
|
|
8085
8577
|
dispatcher(control);
|
|
8086
8578
|
});
|
|
8087
|
-
await kandan.join(topic, joinPayload(), {
|
|
8579
|
+
const joinResponse = await kandan.join(topic, joinPayload(), {
|
|
8580
|
+
rejoinPayload: joinPayload
|
|
8581
|
+
});
|
|
8582
|
+
const replacedRunners = replacementRunnerSummaries(objectValue(joinResponse)?.replaced_runners);
|
|
8088
8583
|
const started = options.codexUrl === undefined ? await startOwnedCodexAppServer(options) : undefined;
|
|
8089
8584
|
if (started !== undefined) {
|
|
8090
8585
|
cleanup.actions.push(() => {
|
|
@@ -8095,7 +8590,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8095
8590
|
if (codexUrl === undefined) {
|
|
8096
8591
|
throw new Error("missing codex app-server websocket URL");
|
|
8097
8592
|
}
|
|
8098
|
-
const instanceId = `codex-${
|
|
8593
|
+
const instanceId = `codex-${randomUUID3()}`;
|
|
8099
8594
|
const publishLocalEditorStatus = (payload) => {
|
|
8100
8595
|
kandan.push(topic, "local_editor_status", payload).catch((error) => {
|
|
8101
8596
|
log("kandan.local_editor_status_push_failed", {
|
|
@@ -8143,6 +8638,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8143
8638
|
const runtimeDefaults = runnerRuntimeDefaults(options);
|
|
8144
8639
|
const instancePayload = {
|
|
8145
8640
|
instanceId,
|
|
8641
|
+
clientId,
|
|
8146
8642
|
codexUrl,
|
|
8147
8643
|
tuiLaunched: options.launchTui,
|
|
8148
8644
|
cwd: options.cwd,
|
|
@@ -8155,7 +8651,15 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8155
8651
|
fast: options.fast ?? false
|
|
8156
8652
|
};
|
|
8157
8653
|
await kandan.push(topic, "instance_started", instancePayload);
|
|
8158
|
-
log("runner.instance_started", {
|
|
8654
|
+
log("runner.instance_started", {
|
|
8655
|
+
runnerId: options.runnerId,
|
|
8656
|
+
hostname: runnerHost,
|
|
8657
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8658
|
+
version: linzumiCliVersion,
|
|
8659
|
+
instanceId,
|
|
8660
|
+
codexUrl,
|
|
8661
|
+
replacedRunners
|
|
8662
|
+
});
|
|
8159
8663
|
const channelSession = options.channelSession === undefined ? undefined : await attachChannelSession({
|
|
8160
8664
|
kandan,
|
|
8161
8665
|
codex,
|
|
@@ -8256,6 +8760,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8256
8760
|
};
|
|
8257
8761
|
const heartbeatPayload = () => ({
|
|
8258
8762
|
instanceId,
|
|
8763
|
+
clientId,
|
|
8259
8764
|
codexUrl,
|
|
8260
8765
|
cwd: options.cwd,
|
|
8261
8766
|
hostname: runnerHost,
|
|
@@ -8334,6 +8839,20 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8334
8839
|
});
|
|
8335
8840
|
const handleControl = (control) => {
|
|
8336
8841
|
log("kandan.control", { control });
|
|
8842
|
+
if (control.type === "replace_runner") {
|
|
8843
|
+
log("runner.replaced", {
|
|
8844
|
+
runnerId: options.runnerId,
|
|
8845
|
+
reason: control.reason,
|
|
8846
|
+
replacementRunnerId: control.replacementRunnerId,
|
|
8847
|
+
replacementVersion: control.replacementVersion
|
|
8848
|
+
});
|
|
8849
|
+
close().catch((error) => {
|
|
8850
|
+
log("runner.replace_close_failed", {
|
|
8851
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8852
|
+
});
|
|
8853
|
+
});
|
|
8854
|
+
return;
|
|
8855
|
+
}
|
|
8337
8856
|
if (!controlTargetsInstance(control, instanceId)) {
|
|
8338
8857
|
log("kandan.control_ignored", {
|
|
8339
8858
|
reason: "instance_id_mismatch",
|
|
@@ -8548,8 +9067,24 @@ function normalizedWorkDescription(value) {
|
|
|
8548
9067
|
const normalized = value?.trim();
|
|
8549
9068
|
return normalized === undefined || normalized === "" ? undefined : normalized;
|
|
8550
9069
|
}
|
|
9070
|
+
function replacementRunnerSummaries(value) {
|
|
9071
|
+
const entries = arrayValue(value) ?? [];
|
|
9072
|
+
return entries.flatMap((entry) => {
|
|
9073
|
+
const record = objectValue(entry);
|
|
9074
|
+
const runnerId = stringValue(record?.runnerId);
|
|
9075
|
+
if (runnerId === undefined) {
|
|
9076
|
+
return [];
|
|
9077
|
+
}
|
|
9078
|
+
return [
|
|
9079
|
+
{
|
|
9080
|
+
runnerId,
|
|
9081
|
+
version: stringValue(record?.version) ?? null
|
|
9082
|
+
}
|
|
9083
|
+
];
|
|
9084
|
+
});
|
|
9085
|
+
}
|
|
8551
9086
|
function makeRunnerLogger(options) {
|
|
8552
|
-
return createRunnerLogger(options.logFile ??
|
|
9087
|
+
return createRunnerLogger(options.logFile ?? join8(options.cwd, ".linzumi-runner.log"), options.launchTui ? undefined : reportRunnerConsoleEvent);
|
|
8553
9088
|
}
|
|
8554
9089
|
function installCleanupHandlers(close) {
|
|
8555
9090
|
const closeAndExit = () => {
|
|
@@ -8654,6 +9189,8 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8654
9189
|
switch (control.type) {
|
|
8655
9190
|
case "start_instance": {
|
|
8656
9191
|
const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
|
|
9192
|
+
let startupStage = "starting_codex_session";
|
|
9193
|
+
let startedCodexThreadId;
|
|
8657
9194
|
if (!cwd.ok) {
|
|
8658
9195
|
return {
|
|
8659
9196
|
instanceId,
|
|
@@ -8672,8 +9209,10 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8672
9209
|
}
|
|
8673
9210
|
try {
|
|
8674
9211
|
if (options.codexUrl === undefined) {
|
|
9212
|
+
startupStage = "checking_project_trust";
|
|
8675
9213
|
ensureCodexProjectTrusted(cwd.cwd);
|
|
8676
9214
|
}
|
|
9215
|
+
startupStage = "starting_codex_session";
|
|
8677
9216
|
const developerPrompt = normalizedWorkDescription(control.developerPrompt);
|
|
8678
9217
|
const runtimeSettings = startInstanceRuntimeSettings(options, control);
|
|
8679
9218
|
const response = await codex.request("thread/start", {
|
|
@@ -8691,12 +9230,18 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8691
9230
|
...runtimeSettings.fast === true ? { serviceTier: "fast" } : {}
|
|
8692
9231
|
});
|
|
8693
9232
|
const codexThreadId = extractStartedThreadId(response);
|
|
9233
|
+
startedCodexThreadId = codexThreadId;
|
|
8694
9234
|
const workDescription = normalizedWorkDescription(control.workDescription);
|
|
8695
9235
|
if (codexThreadId !== undefined && developerPrompt !== undefined) {
|
|
8696
9236
|
await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId);
|
|
8697
9237
|
}
|
|
8698
|
-
|
|
9238
|
+
let startedThreadSession;
|
|
9239
|
+
if (codexThreadId !== undefined && onStartedThread !== undefined) {
|
|
9240
|
+
startupStage = "binding_kandan_thread";
|
|
9241
|
+
startedThreadSession = await onStartedThread(control, cwd.cwd, codexThreadId);
|
|
9242
|
+
}
|
|
8699
9243
|
if (codexThreadId !== undefined && workDescription !== undefined) {
|
|
9244
|
+
startupStage = "starting_first_turn";
|
|
8700
9245
|
const rootSeq = integerValue(control.rootSeq);
|
|
8701
9246
|
const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
|
|
8702
9247
|
if (startedThreadSession !== undefined && sourceSeq !== undefined) {
|
|
@@ -8722,8 +9267,16 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8722
9267
|
response
|
|
8723
9268
|
};
|
|
8724
9269
|
} catch (error) {
|
|
9270
|
+
const failureReason = startInstanceFailureReason(startupStage, error);
|
|
9271
|
+
log("kandan.start_instance_failed", {
|
|
9272
|
+
stage: startupStage,
|
|
9273
|
+
message: error instanceof Error ? error.message : String(error),
|
|
9274
|
+
thread_id: optionalThreadControlField(control, "threadId") ?? null,
|
|
9275
|
+
source_seq: integerValue(control.sourceSeq) ?? integerValue(control.rootSeq) ?? null,
|
|
9276
|
+
codex_thread_id: startedCodexThreadId ?? null
|
|
9277
|
+
});
|
|
8725
9278
|
try {
|
|
8726
|
-
await publishStartInstanceMessageState(kandan, topic, control, "failed",
|
|
9279
|
+
await publishStartInstanceMessageState(kandan, topic, control, "failed", failureReason, { codexThreadId: startedCodexThreadId });
|
|
8727
9280
|
} catch (_publishError) {}
|
|
8728
9281
|
throw error;
|
|
8729
9282
|
}
|
|
@@ -8835,6 +9388,26 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8835
9388
|
return { instanceId, controlType: control.type, skipped: true };
|
|
8836
9389
|
}
|
|
8837
9390
|
}
|
|
9391
|
+
function startInstanceFailureReason(stage, error) {
|
|
9392
|
+
const detail = truncateFailureDetail(error instanceof Error ? error.message : String(error));
|
|
9393
|
+
switch (stage) {
|
|
9394
|
+
case "checking_project_trust":
|
|
9395
|
+
return `Codex could not start because this folder is not trusted locally. Trust the folder with linzumi paths add, or restart the runner from a trusted folder. Detail: ${detail}`;
|
|
9396
|
+
case "starting_codex_session":
|
|
9397
|
+
return `Codex did not create a session. Restart the local runner and try again. Detail: ${detail}`;
|
|
9398
|
+
case "binding_kandan_thread":
|
|
9399
|
+
return `Codex started, but Linzumi could not attach it to this thread. Try starting the agent again; if it repeats, share this failed message with the runner owner. Detail: ${detail}`;
|
|
9400
|
+
case "starting_first_turn":
|
|
9401
|
+
return `Codex started, but the first prompt could not be delivered. Try sending the message again; if it repeats, reconnect the local runner. Detail: ${detail}`;
|
|
9402
|
+
}
|
|
9403
|
+
}
|
|
9404
|
+
function truncateFailureDetail(message) {
|
|
9405
|
+
const trimmed = message.trim();
|
|
9406
|
+
if (trimmed.length <= 240) {
|
|
9407
|
+
return trimmed;
|
|
9408
|
+
}
|
|
9409
|
+
return `${trimmed.slice(0, 237)}...`;
|
|
9410
|
+
}
|
|
8838
9411
|
function commanderDeveloperInstructions(args) {
|
|
8839
9412
|
const customPrompt = args.developerPrompt === undefined ? "" : `
|
|
8840
9413
|
<invoker_developer_prompt>
|
|
@@ -8915,14 +9488,14 @@ ${developerPrompt}`,
|
|
|
8915
9488
|
client_message_id: `codex-start-instructions-${threadId}`
|
|
8916
9489
|
});
|
|
8917
9490
|
}
|
|
8918
|
-
async function publishStartInstanceMessageState(kandan, topic, control, status, reason) {
|
|
8919
|
-
const payload = startInstanceMessageStatePayload(control, status, reason);
|
|
9491
|
+
async function publishStartInstanceMessageState(kandan, topic, control, status, reason, diagnostics = {}) {
|
|
9492
|
+
const payload = startInstanceMessageStatePayload(control, status, reason, diagnostics);
|
|
8920
9493
|
if (payload === undefined) {
|
|
8921
9494
|
return;
|
|
8922
9495
|
}
|
|
8923
9496
|
await kandan.push(topic, "message_state", payload);
|
|
8924
9497
|
}
|
|
8925
|
-
function startInstanceMessageStatePayload(control, status, reason) {
|
|
9498
|
+
function startInstanceMessageStatePayload(control, status, reason, diagnostics = {}) {
|
|
8926
9499
|
const rootSeq = integerValue(control.rootSeq);
|
|
8927
9500
|
const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
|
|
8928
9501
|
if (sourceSeq === undefined) {
|
|
@@ -8937,7 +9510,8 @@ function startInstanceMessageStatePayload(control, status, reason) {
|
|
|
8937
9510
|
thread_id: threadId,
|
|
8938
9511
|
seq: sourceSeq,
|
|
8939
9512
|
status,
|
|
8940
|
-
reason
|
|
9513
|
+
reason,
|
|
9514
|
+
...diagnostics.codexThreadId === undefined ? {} : { codex_thread_id: diagnostics.codexThreadId }
|
|
8941
9515
|
};
|
|
8942
9516
|
}
|
|
8943
9517
|
function requiredStartInstanceControlField(control, field) {
|
|
@@ -8959,11 +9533,12 @@ function optionalThreadControlField(control, field) {
|
|
|
8959
9533
|
}
|
|
8960
9534
|
function startInstanceRuntimeSettings(options, control) {
|
|
8961
9535
|
const defaults = runnerRuntimeDefaults(options);
|
|
9536
|
+
const sandbox = control.sandbox ?? defaults.sandbox;
|
|
8962
9537
|
return {
|
|
8963
9538
|
model: control.model ?? defaults.model,
|
|
8964
9539
|
reasoningEffort: control.reasoningEffort ?? defaults.reasoningEffort,
|
|
8965
|
-
approvalPolicy: control.approvalPolicy ?? defaults.approvalPolicy,
|
|
8966
|
-
sandbox
|
|
9540
|
+
approvalPolicy: codexApprovalPolicyForRequest(control.approvalPolicy ?? defaults.approvalPolicy, sandbox),
|
|
9541
|
+
sandbox,
|
|
8967
9542
|
fast: control.fast ?? options.fast
|
|
8968
9543
|
};
|
|
8969
9544
|
}
|
|
@@ -8982,11 +9557,12 @@ function runnerWorkspaceSlug(options) {
|
|
|
8982
9557
|
function runnerRuntimeDefaults(options) {
|
|
8983
9558
|
const session = options.channelSession;
|
|
8984
9559
|
const defaults = options.runtimeDefaults;
|
|
9560
|
+
const sandbox = defaults?.sandbox ?? session?.sandbox;
|
|
8985
9561
|
return {
|
|
8986
9562
|
model: defaults?.model ?? session?.model,
|
|
8987
9563
|
reasoningEffort: defaults?.reasoningEffort ?? session?.reasoningEffort,
|
|
8988
|
-
approvalPolicy: defaults?.approvalPolicy ?? session?.approvalPolicy,
|
|
8989
|
-
sandbox
|
|
9564
|
+
approvalPolicy: codexApprovalPolicySetting(defaults?.approvalPolicy ?? session?.approvalPolicy, sandbox),
|
|
9565
|
+
sandbox
|
|
8990
9566
|
};
|
|
8991
9567
|
}
|
|
8992
9568
|
function isUpdateRunnerConfigControl(control) {
|
|
@@ -9005,9 +9581,9 @@ function configuredAllowedCwds(values) {
|
|
|
9005
9581
|
const allowedCwds = [];
|
|
9006
9582
|
const missingAllowedCwds = [];
|
|
9007
9583
|
for (const value of normalizeAllowedCwds(values)) {
|
|
9008
|
-
const absolutePath =
|
|
9584
|
+
const absolutePath = resolve6(expandUserPath(value));
|
|
9009
9585
|
try {
|
|
9010
|
-
const realPath =
|
|
9586
|
+
const realPath = realpathSync5(absolutePath);
|
|
9011
9587
|
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9012
9588
|
} catch (error) {
|
|
9013
9589
|
if (isMissingAllowedCwdError(error)) {
|
|
@@ -9030,18 +9606,18 @@ function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
|
9030
9606
|
}
|
|
9031
9607
|
|
|
9032
9608
|
// src/authCache.ts
|
|
9033
|
-
import { existsSync as
|
|
9034
|
-
import { homedir as
|
|
9035
|
-
import { dirname as
|
|
9609
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9610
|
+
import { homedir as homedir6 } from "node:os";
|
|
9611
|
+
import { dirname as dirname6, join as join9 } from "node:path";
|
|
9036
9612
|
function defaultAuthFilePath() {
|
|
9037
|
-
const base = process.env.KANDAN_HOME ??
|
|
9038
|
-
return
|
|
9613
|
+
const base = process.env.KANDAN_HOME ?? join9(homedir6(), ".kandan");
|
|
9614
|
+
return join9(base, "auth.json");
|
|
9039
9615
|
}
|
|
9040
9616
|
function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePath()) {
|
|
9041
|
-
if (!
|
|
9617
|
+
if (!existsSync6(authFilePath)) {
|
|
9042
9618
|
return;
|
|
9043
9619
|
}
|
|
9044
|
-
const authFile = parseAuthFile(
|
|
9620
|
+
const authFile = parseAuthFile(readFileSync5(authFilePath, "utf8"));
|
|
9045
9621
|
const kandanBaseUrl = kandanHttpBaseUrl(kandanUrl);
|
|
9046
9622
|
const entry = authFile.local_codex_runner?.[kandanBaseUrl];
|
|
9047
9623
|
if (entry === undefined || entry.access_token.trim() === "") {
|
|
@@ -9059,7 +9635,7 @@ function readCachedLocalRunnerToken(kandanUrl, authFilePath = defaultAuthFilePat
|
|
|
9059
9635
|
}
|
|
9060
9636
|
function writeCachedLocalRunnerToken(args) {
|
|
9061
9637
|
const authFilePath = args.authFilePath ?? defaultAuthFilePath();
|
|
9062
|
-
const existing =
|
|
9638
|
+
const existing = existsSync6(authFilePath) ? parseAuthFile(readFileSync5(authFilePath, "utf8")) : { version: 1 };
|
|
9063
9639
|
const kandanBaseUrl = kandanHttpBaseUrl(args.kandanUrl);
|
|
9064
9640
|
const issuedAt = new Date;
|
|
9065
9641
|
const expiresAt = args.expiresInSeconds === undefined ? undefined : new Date(issuedAt.getTime() + args.expiresInSeconds * 1000).toISOString();
|
|
@@ -9075,8 +9651,8 @@ function writeCachedLocalRunnerToken(args) {
|
|
|
9075
9651
|
}
|
|
9076
9652
|
}
|
|
9077
9653
|
};
|
|
9078
|
-
|
|
9079
|
-
|
|
9654
|
+
mkdirSync7(dirname6(authFilePath), { recursive: true });
|
|
9655
|
+
writeFileSync5(authFilePath, `${JSON.stringify(next, null, 2)}
|
|
9080
9656
|
`, "utf8");
|
|
9081
9657
|
return {
|
|
9082
9658
|
accessToken: args.accessToken,
|
|
@@ -9168,102 +9744,12 @@ async function acquireAndCacheToken(args) {
|
|
|
9168
9744
|
return token.accessToken;
|
|
9169
9745
|
}
|
|
9170
9746
|
|
|
9171
|
-
// src/localConfig.ts
|
|
9172
|
-
import {
|
|
9173
|
-
existsSync as existsSync5,
|
|
9174
|
-
mkdirSync as mkdirSync6,
|
|
9175
|
-
readFileSync as readFileSync4,
|
|
9176
|
-
realpathSync as realpathSync5,
|
|
9177
|
-
writeFileSync as writeFileSync5
|
|
9178
|
-
} from "node:fs";
|
|
9179
|
-
import { homedir as homedir6 } from "node:os";
|
|
9180
|
-
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
9181
|
-
function localConfigPath(env = process.env) {
|
|
9182
|
-
const override = env.LINZUMI_CONFIG_FILE;
|
|
9183
|
-
return override !== undefined && override.trim() !== "" ? resolve6(expandUserPath(override)) : resolve6(homedir6(), ".linzumi", "config.json");
|
|
9184
|
-
}
|
|
9185
|
-
function readLocalConfig(path = localConfigPath()) {
|
|
9186
|
-
if (!existsSync5(path)) {
|
|
9187
|
-
return { version: 1, allowedCwds: [] };
|
|
9188
|
-
}
|
|
9189
|
-
const parsed = JSON.parse(readFileSync4(path, "utf8"));
|
|
9190
|
-
if (!isConfigPayload(parsed)) {
|
|
9191
|
-
throw new Error(`invalid Linzumi config: ${path}`);
|
|
9192
|
-
}
|
|
9193
|
-
return {
|
|
9194
|
-
version: 1,
|
|
9195
|
-
allowedCwds: uniqueStrings(parsed.allowedCwds)
|
|
9196
|
-
};
|
|
9197
|
-
}
|
|
9198
|
-
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
9199
|
-
const allowedCwds = [];
|
|
9200
|
-
const missingAllowedCwds = [];
|
|
9201
|
-
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
9202
|
-
const absolutePath = resolve6(expandUserPath(cwd));
|
|
9203
|
-
try {
|
|
9204
|
-
const realPath = realpathSync5(absolutePath);
|
|
9205
|
-
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9206
|
-
} catch (error) {
|
|
9207
|
-
if (isMissingPathError(error)) {
|
|
9208
|
-
missingAllowedCwds.push(absolutePath);
|
|
9209
|
-
continue;
|
|
9210
|
-
}
|
|
9211
|
-
throw error;
|
|
9212
|
-
}
|
|
9213
|
-
}
|
|
9214
|
-
return {
|
|
9215
|
-
allowedCwds: uniqueStrings(allowedCwds),
|
|
9216
|
-
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
9217
|
-
};
|
|
9218
|
-
}
|
|
9219
|
-
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9220
|
-
const normalizedPath = realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9221
|
-
const config = readLocalConfig(path);
|
|
9222
|
-
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
9223
|
-
writeLocalConfig({ version: 1, allowedCwds }, path);
|
|
9224
|
-
return allowedCwds;
|
|
9225
|
-
}
|
|
9226
|
-
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9227
|
-
const requestedPath = resolve6(expandUserPath(pathValue));
|
|
9228
|
-
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
9229
|
-
const config = readLocalConfig(path);
|
|
9230
|
-
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
9231
|
-
const normalizedExisting = realpathOrResolved(cwd);
|
|
9232
|
-
return cwd !== pathValue && normalizedExisting !== normalizedRequest;
|
|
9233
|
-
});
|
|
9234
|
-
writeLocalConfig({ version: 1, allowedCwds }, path);
|
|
9235
|
-
return allowedCwds;
|
|
9236
|
-
}
|
|
9237
|
-
function writeLocalConfig(config, path = localConfigPath()) {
|
|
9238
|
-
mkdirSync6(dirname5(path), { recursive: true });
|
|
9239
|
-
writeFileSync5(path, `${JSON.stringify(config, null, 2)}
|
|
9240
|
-
`, "utf8");
|
|
9241
|
-
}
|
|
9242
|
-
function isConfigPayload(value) {
|
|
9243
|
-
return typeof value === "object" && value !== null && value.version === 1 && Array.isArray(value.allowedCwds) && value.allowedCwds.every((cwd) => typeof cwd === "string" && cwd.trim() !== "");
|
|
9244
|
-
}
|
|
9245
|
-
function uniqueStrings(values) {
|
|
9246
|
-
return [
|
|
9247
|
-
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
9248
|
-
];
|
|
9249
|
-
}
|
|
9250
|
-
function isMissingPathError(error) {
|
|
9251
|
-
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
9252
|
-
}
|
|
9253
|
-
function realpathOrResolved(pathValue) {
|
|
9254
|
-
try {
|
|
9255
|
-
return realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9256
|
-
} catch (_error) {
|
|
9257
|
-
return resolve6(expandUserPath(pathValue));
|
|
9258
|
-
}
|
|
9259
|
-
}
|
|
9260
|
-
|
|
9261
9747
|
// src/defaultUrls.ts
|
|
9262
9748
|
var defaultLinzumiHttpUrl = "https://serve.linzumi.com";
|
|
9263
9749
|
var defaultLinzumiWebSocketUrl = "wss://serve.linzumi.com";
|
|
9264
9750
|
|
|
9265
9751
|
// src/kandanTls.ts
|
|
9266
|
-
import { existsSync as
|
|
9752
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
9267
9753
|
import { Agent } from "undici";
|
|
9268
9754
|
import { WebSocket as WsWebSocket } from "ws";
|
|
9269
9755
|
function kandanTlsTrustFromEnv() {
|
|
@@ -9274,10 +9760,10 @@ function kandanTlsTrustFromCaFile(caFile) {
|
|
|
9274
9760
|
return;
|
|
9275
9761
|
}
|
|
9276
9762
|
const trimmed = caFile.trim();
|
|
9277
|
-
if (!
|
|
9763
|
+
if (!existsSync7(trimmed)) {
|
|
9278
9764
|
throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
|
|
9279
9765
|
}
|
|
9280
|
-
const ca =
|
|
9766
|
+
const ca = readFileSync6(trimmed, "utf8");
|
|
9281
9767
|
return {
|
|
9282
9768
|
caFile: trimmed,
|
|
9283
9769
|
ca,
|
|
@@ -9306,8 +9792,8 @@ function trustedWebSocketFactory(trust, WebSocketImpl = WsWebSocket) {
|
|
|
9306
9792
|
}
|
|
9307
9793
|
|
|
9308
9794
|
// src/agentBootstrap.ts
|
|
9309
|
-
import { existsSync as
|
|
9310
|
-
import { dirname as
|
|
9795
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9796
|
+
import { dirname as dirname7, join as join10 } from "node:path";
|
|
9311
9797
|
import { homedir as homedir7 } from "node:os";
|
|
9312
9798
|
async function runAgentCliCommand(args, deps = {
|
|
9313
9799
|
fetchImpl: fetch,
|
|
@@ -9907,7 +10393,7 @@ function agentTokenFile(flags) {
|
|
|
9907
10393
|
return flags.get("agent-token-file") ?? defaultAgentTokenFilePath();
|
|
9908
10394
|
}
|
|
9909
10395
|
function defaultAgentTokenFilePath() {
|
|
9910
|
-
return
|
|
10396
|
+
return join10(homedir7(), ".linzumi", "agent-token.json");
|
|
9911
10397
|
}
|
|
9912
10398
|
function normalizedApiUrl(apiUrl) {
|
|
9913
10399
|
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
@@ -9916,10 +10402,10 @@ function authorizationHeaders(token) {
|
|
|
9916
10402
|
return { authorization: `Bearer ${token}` };
|
|
9917
10403
|
}
|
|
9918
10404
|
function readOptionalTextFile(path) {
|
|
9919
|
-
return
|
|
10405
|
+
return existsSync8(path) ? readFileSync7(path, "utf8") : undefined;
|
|
9920
10406
|
}
|
|
9921
10407
|
function writeTextFile(path, content) {
|
|
9922
|
-
|
|
10408
|
+
mkdirSync8(dirname7(path), { recursive: true });
|
|
9923
10409
|
writeFileSync6(path, content);
|
|
9924
10410
|
}
|
|
9925
10411
|
function readStoredAgentTokenFile(path, readTextFile = readOptionalTextFile) {
|
|
@@ -9996,8 +10482,8 @@ Launch target:
|
|
|
9996
10482
|
}
|
|
9997
10483
|
|
|
9998
10484
|
// src/helloLinzumiProject.ts
|
|
9999
|
-
import { existsSync as
|
|
10000
|
-
import { dirname as
|
|
10485
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync9, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
10486
|
+
import { dirname as dirname8, join as join11, resolve as resolve7 } from "node:path";
|
|
10001
10487
|
import { fileURLToPath } from "node:url";
|
|
10002
10488
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
10003
10489
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -10005,8 +10491,8 @@ var defaultHelloLinzumiParentDir = "/tmp";
|
|
|
10005
10491
|
var defaultHelloLinzumiPort = 8787;
|
|
10006
10492
|
var defaultHelloLinzumiHost = "0.0.0.0";
|
|
10007
10493
|
var markerFile = ".linzumi-demo-project";
|
|
10008
|
-
var moduleDir =
|
|
10009
|
-
var linzumiLogoSvg =
|
|
10494
|
+
var moduleDir = dirname8(fileURLToPath(import.meta.url));
|
|
10495
|
+
var linzumiLogoSvg = readFileSync8(join11(moduleDir, "assets", "linzumi-logo.svg"), "utf8");
|
|
10010
10496
|
function createHelloLinzumiProject(input = {}) {
|
|
10011
10497
|
const options = typeof input === "string" ? { rootPath: input } : input;
|
|
10012
10498
|
const root = resolveHelloProjectRoot(options);
|
|
@@ -10014,9 +10500,9 @@ function createHelloLinzumiProject(input = {}) {
|
|
|
10014
10500
|
const host = normalizeHost(options.host);
|
|
10015
10501
|
assertTcpPort(port);
|
|
10016
10502
|
assertWritableDemoRoot(root, options.reset === true);
|
|
10017
|
-
|
|
10503
|
+
mkdirSync9(join11(root, "src"), { recursive: true });
|
|
10018
10504
|
for (const file of demoFiles({ root, port, host })) {
|
|
10019
|
-
writeFileSync7(
|
|
10505
|
+
writeFileSync7(join11(root, file.path), file.content, "utf8");
|
|
10020
10506
|
}
|
|
10021
10507
|
return {
|
|
10022
10508
|
root,
|
|
@@ -10057,11 +10543,11 @@ function assertTcpPort(port) {
|
|
|
10057
10543
|
throw new Error("--port must be a TCP port from 1 to 65535");
|
|
10058
10544
|
}
|
|
10059
10545
|
function assertWritableDemoRoot(root, reset) {
|
|
10060
|
-
if (!
|
|
10546
|
+
if (!existsSync9(root)) {
|
|
10061
10547
|
return;
|
|
10062
10548
|
}
|
|
10063
|
-
const markerPath =
|
|
10064
|
-
const isDemoRoot =
|
|
10549
|
+
const markerPath = join11(root, markerFile);
|
|
10550
|
+
const isDemoRoot = existsSync9(markerPath) && readFileSync8(markerPath, "utf8").trim() === "hello-linzumi";
|
|
10065
10551
|
if (isDemoRoot && reset) {
|
|
10066
10552
|
rmSync2(root, { recursive: true, force: true });
|
|
10067
10553
|
return;
|
|
@@ -10560,27 +11046,30 @@ To kick the agent off:
|
|
|
10560
11046
|
|
|
10561
11047
|
// src/commanderDaemon.ts
|
|
10562
11048
|
import {
|
|
10563
|
-
existsSync as
|
|
10564
|
-
closeSync,
|
|
10565
|
-
mkdirSync as
|
|
10566
|
-
openSync as
|
|
10567
|
-
readFileSync as
|
|
11049
|
+
existsSync as existsSync10,
|
|
11050
|
+
closeSync as closeSync2,
|
|
11051
|
+
mkdirSync as mkdirSync10,
|
|
11052
|
+
openSync as openSync3,
|
|
11053
|
+
readFileSync as readFileSync9,
|
|
10568
11054
|
watch,
|
|
10569
11055
|
writeFileSync as writeFileSync8
|
|
10570
11056
|
} from "node:fs";
|
|
10571
11057
|
import { homedir as homedir8 } from "node:os";
|
|
10572
|
-
import { dirname as
|
|
11058
|
+
import { dirname as dirname9, join as join12, resolve as resolve8 } from "node:path";
|
|
10573
11059
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
10574
11060
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10575
|
-
var
|
|
11061
|
+
var connectedMarkers = ["Connected to Linzumi", "Runner connected:"];
|
|
10576
11062
|
function commanderStatusDir() {
|
|
10577
|
-
return
|
|
11063
|
+
return join12(homedir8(), ".linzumi", "commanders");
|
|
10578
11064
|
}
|
|
10579
11065
|
function commanderStatusFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10580
|
-
return
|
|
11066
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.json`);
|
|
10581
11067
|
}
|
|
10582
11068
|
function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
10583
|
-
return
|
|
11069
|
+
return join12(statusDir, `${safeRunnerId(runnerId)}.log`);
|
|
11070
|
+
}
|
|
11071
|
+
function commanderLogIsConnected(log) {
|
|
11072
|
+
return connectedMarkers.some((marker) => log.includes(marker));
|
|
10584
11073
|
}
|
|
10585
11074
|
function startCommanderDaemon(options) {
|
|
10586
11075
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
@@ -10598,10 +11087,10 @@ function startCommanderDaemon(options) {
|
|
|
10598
11087
|
"--log-file",
|
|
10599
11088
|
logFile
|
|
10600
11089
|
];
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
const out =
|
|
10604
|
-
const err =
|
|
11090
|
+
mkdirSync10(statusDir, { recursive: true });
|
|
11091
|
+
mkdirSync10(dirname9(logFile), { recursive: true });
|
|
11092
|
+
const out = openSync3(logFile, "a");
|
|
11093
|
+
const err = openSync3(logFile, "a");
|
|
10605
11094
|
writeCliAuditEvent("process.spawn", {
|
|
10606
11095
|
command: nodeBin,
|
|
10607
11096
|
args: command,
|
|
@@ -10620,8 +11109,8 @@ function startCommanderDaemon(options) {
|
|
|
10620
11109
|
pid: child.pid,
|
|
10621
11110
|
purpose: "commander_daemon.start"
|
|
10622
11111
|
}, { sessionId: options.runnerId });
|
|
10623
|
-
|
|
10624
|
-
|
|
11112
|
+
closeSync2(out);
|
|
11113
|
+
closeSync2(err);
|
|
10625
11114
|
child.unref();
|
|
10626
11115
|
if (child.pid === undefined) {
|
|
10627
11116
|
throw new Error("commander daemon did not report a pid");
|
|
@@ -10643,15 +11132,15 @@ function startCommanderDaemon(options) {
|
|
|
10643
11132
|
}
|
|
10644
11133
|
function commanderDaemonStatus(runnerId, statusDir = commanderStatusDir(), processIdentityReader = readProcessIdentity) {
|
|
10645
11134
|
const statusFile = commanderStatusFile(runnerId, statusDir);
|
|
10646
|
-
if (!
|
|
11135
|
+
if (!existsSync10(statusFile)) {
|
|
10647
11136
|
return { status: "missing", runnerId, statusFile };
|
|
10648
11137
|
}
|
|
10649
|
-
const record = parseRecord(
|
|
11138
|
+
const record = parseRecord(readFileSync9(statusFile, "utf8"));
|
|
10650
11139
|
return processIsRunning(record.pid) && processMatchesRecord(record, processIdentityReader) ? { status: "running", record } : { status: "stopped", record };
|
|
10651
11140
|
}
|
|
10652
11141
|
async function waitForCommanderDaemon(options) {
|
|
10653
11142
|
const now = options.now ?? (() => Date.now());
|
|
10654
|
-
const readTextFile = options.readTextFile ?? ((path) =>
|
|
11143
|
+
const readTextFile = options.readTextFile ?? ((path) => existsSync10(path) ? readFileSync9(path, "utf8") : undefined);
|
|
10655
11144
|
const statusImpl = options.statusImpl ?? commanderDaemonStatus;
|
|
10656
11145
|
const deadline = now() + options.timeoutMs;
|
|
10657
11146
|
while (now() <= deadline) {
|
|
@@ -10666,12 +11155,12 @@ async function waitForCommanderDaemon(options) {
|
|
|
10666
11155
|
if (log === undefined) {
|
|
10667
11156
|
return { ok: false, reason: "timeout" };
|
|
10668
11157
|
}
|
|
10669
|
-
if (log
|
|
11158
|
+
if (commanderLogIsConnected(log)) {
|
|
10670
11159
|
return { ok: true, record: status.record };
|
|
10671
11160
|
}
|
|
10672
11161
|
await waitForFileChangeOrTimeout(status.record.logFile, deadline, now, () => {
|
|
10673
11162
|
const updatedLog = readTextFile(status.record.logFile);
|
|
10674
|
-
return updatedLog !== undefined && updatedLog
|
|
11163
|
+
return updatedLog !== undefined && commanderLogIsConnected(updatedLog);
|
|
10675
11164
|
});
|
|
10676
11165
|
}
|
|
10677
11166
|
}
|
|
@@ -10881,7 +11370,7 @@ async function main(args) {
|
|
|
10881
11370
|
process.stdout.write(connectGuideText());
|
|
10882
11371
|
return;
|
|
10883
11372
|
case "version":
|
|
10884
|
-
process.stdout.write(
|
|
11373
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
10885
11374
|
`);
|
|
10886
11375
|
return;
|
|
10887
11376
|
case "auth":
|
|
@@ -10901,18 +11390,18 @@ async function main(args) {
|
|
|
10901
11390
|
return;
|
|
10902
11391
|
case "agentRunner": {
|
|
10903
11392
|
const options = await parseAgentRunnerArgs(parsed.args);
|
|
10904
|
-
await runLocalCodexRunner(options);
|
|
11393
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
10905
11394
|
return;
|
|
10906
11395
|
}
|
|
10907
11396
|
case "start": {
|
|
10908
11397
|
const options = await parseStartRunnerArgs(parsed.args);
|
|
10909
11398
|
addAllowedCwd(options.cwd);
|
|
10910
|
-
await runLocalCodexRunner(options);
|
|
11399
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
10911
11400
|
return;
|
|
10912
11401
|
}
|
|
10913
11402
|
case "run": {
|
|
10914
11403
|
const options = await parseRunnerArgs(parsed.args);
|
|
10915
|
-
await runLocalCodexRunner(options);
|
|
11404
|
+
await runLocalCodexRunner(withLocalMachineId(options));
|
|
10916
11405
|
return;
|
|
10917
11406
|
}
|
|
10918
11407
|
}
|
|
@@ -11223,7 +11712,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
11223
11712
|
return {
|
|
11224
11713
|
kandanUrl,
|
|
11225
11714
|
token: targetToken,
|
|
11226
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11715
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11227
11716
|
workspaceSlug: target.workspaceSlug,
|
|
11228
11717
|
cwd,
|
|
11229
11718
|
codexBin,
|
|
@@ -11311,7 +11800,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11311
11800
|
return {
|
|
11312
11801
|
kandanUrl,
|
|
11313
11802
|
token: tokenFile.commanderToken,
|
|
11314
|
-
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${
|
|
11803
|
+
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${randomUUID4()}`,
|
|
11315
11804
|
workspaceSlug: tokenFile.workspaceId,
|
|
11316
11805
|
cwd,
|
|
11317
11806
|
codexBin,
|
|
@@ -11331,7 +11820,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11331
11820
|
};
|
|
11332
11821
|
}
|
|
11333
11822
|
function readAgentTokenTextFile(path) {
|
|
11334
|
-
return
|
|
11823
|
+
return existsSync11(path) ? readFileSync10(path, "utf8") : undefined;
|
|
11335
11824
|
}
|
|
11336
11825
|
function rejectAgentRunnerTargetingFlags(values) {
|
|
11337
11826
|
const unsupportedFlags = [
|
|
@@ -11407,12 +11896,12 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11407
11896
|
process.exit(0);
|
|
11408
11897
|
}
|
|
11409
11898
|
if (values.get("version") === true) {
|
|
11410
|
-
process.stdout.write(
|
|
11899
|
+
process.stdout.write(`${linzumiCliVersionText}
|
|
11411
11900
|
`);
|
|
11412
11901
|
process.exit(0);
|
|
11413
11902
|
}
|
|
11414
|
-
const
|
|
11415
|
-
const kandanUrl =
|
|
11903
|
+
const connectTarget = parseConnectTarget(values);
|
|
11904
|
+
const kandanUrl = stringValue3(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
11416
11905
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
11417
11906
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
11418
11907
|
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetails();
|
|
@@ -11423,8 +11912,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11423
11912
|
const token = await deps.resolveToken({
|
|
11424
11913
|
kandanUrl,
|
|
11425
11914
|
explicitToken,
|
|
11426
|
-
workspaceSlug:
|
|
11427
|
-
channelSlug:
|
|
11915
|
+
workspaceSlug: connectTarget?.workspaceSlug,
|
|
11916
|
+
channelSlug: connectTarget?.channelSlug,
|
|
11428
11917
|
authFilePath: stringValue3(values, "auth-file"),
|
|
11429
11918
|
callbackHost: stringValue3(values, "oauth-callback-host"),
|
|
11430
11919
|
reportRejectedCachedToken: () => {
|
|
@@ -11432,7 +11921,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11432
11921
|
`);
|
|
11433
11922
|
}
|
|
11434
11923
|
});
|
|
11435
|
-
const channelSession = parseChannelSession(values, token,
|
|
11924
|
+
const channelSession = parseChannelSession(values, token, connectTarget);
|
|
11436
11925
|
const editorRuntime = await deps.resolveEditorRuntime({
|
|
11437
11926
|
kandanUrl,
|
|
11438
11927
|
token,
|
|
@@ -11449,7 +11938,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11449
11938
|
return {
|
|
11450
11939
|
kandanUrl,
|
|
11451
11940
|
token,
|
|
11452
|
-
runnerId: stringValue3(values, "runner-id") ?? `runner-${
|
|
11941
|
+
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID4()}`,
|
|
11453
11942
|
cwd,
|
|
11454
11943
|
codexBin,
|
|
11455
11944
|
codexUrl: stringValue3(values, "codex-url"),
|
|
@@ -11463,7 +11952,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11463
11952
|
editorRuntime: editorRuntime.runtime,
|
|
11464
11953
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11465
11954
|
dependencyStatus,
|
|
11466
|
-
workspaceSlug:
|
|
11955
|
+
workspaceSlug: connectTarget?.workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
11467
11956
|
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11468
11957
|
channelSession
|
|
11469
11958
|
};
|
|
@@ -11575,7 +12064,7 @@ function resolveUserPath(pathValue) {
|
|
|
11575
12064
|
return resolve9(pathValue);
|
|
11576
12065
|
}
|
|
11577
12066
|
function parseChannelSession(values, token, target) {
|
|
11578
|
-
if (target === undefined) {
|
|
12067
|
+
if (target === undefined || target.channelSlug === undefined) {
|
|
11579
12068
|
return;
|
|
11580
12069
|
}
|
|
11581
12070
|
const listenUser = stringValue3(values, "listen-user") ?? defaultListenUserFromToken(token);
|
|
@@ -11591,7 +12080,7 @@ function parseChannelSession(values, token, target) {
|
|
|
11591
12080
|
streamFlushMs: positiveIntegerValue(values, "stream-flush-ms")
|
|
11592
12081
|
};
|
|
11593
12082
|
}
|
|
11594
|
-
function
|
|
12083
|
+
function parseConnectTarget(values) {
|
|
11595
12084
|
return parseOptionalChannelTarget(values);
|
|
11596
12085
|
}
|
|
11597
12086
|
function defaultListenUserFromToken(token) {
|
|
@@ -11604,12 +12093,15 @@ function defaultListenUserFromToken(token) {
|
|
|
11604
12093
|
function parseOptionalChannelTarget(values) {
|
|
11605
12094
|
const channel = stringValue3(values, "channel");
|
|
11606
12095
|
const workspace = stringValue3(values, "workspace");
|
|
11607
|
-
if (channel === undefined
|
|
11608
|
-
|
|
12096
|
+
if (channel === undefined) {
|
|
12097
|
+
if (workspace === undefined) {
|
|
12098
|
+
return;
|
|
12099
|
+
}
|
|
12100
|
+
return { workspaceSlug: workspace };
|
|
11609
12101
|
}
|
|
11610
|
-
return channel
|
|
12102
|
+
return channel.includes("/") ? parseChannelPath(channel) : {
|
|
11611
12103
|
workspaceSlug: workspace ?? required(values, "workspace"),
|
|
11612
|
-
channelSlug: channel
|
|
12104
|
+
channelSlug: channel
|
|
11613
12105
|
};
|
|
11614
12106
|
}
|
|
11615
12107
|
function parseChannelPath(channel) {
|
|
@@ -11622,6 +12114,12 @@ function parseChannelPath(channel) {
|
|
|
11622
12114
|
channelSlug: channelSlug.trim()
|
|
11623
12115
|
};
|
|
11624
12116
|
}
|
|
12117
|
+
function withLocalMachineId(options) {
|
|
12118
|
+
return {
|
|
12119
|
+
...options,
|
|
12120
|
+
machineId: ensureLocalMachineId()
|
|
12121
|
+
};
|
|
12122
|
+
}
|
|
11625
12123
|
function required(values, key) {
|
|
11626
12124
|
const value = stringValue3(values, key);
|
|
11627
12125
|
if (value === undefined) {
|
|
@@ -11678,19 +12176,19 @@ Usage:
|
|
|
11678
12176
|
linzumi commander <folder> [options]
|
|
11679
12177
|
linzumi start <folder> [options]
|
|
11680
12178
|
linzumi paths list|add|remove [path]
|
|
11681
|
-
linzumi connect --
|
|
12179
|
+
linzumi connect --workspace <slug> [--channel <slug>] [options]
|
|
11682
12180
|
linzumi auth --linzumi-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
11683
12181
|
|
|
11684
|
-
|
|
12182
|
+
Connection:
|
|
11685
12183
|
--linzumi-url <ws-url> Linzumi backend URL, default ${defaultLinzumiWebSocketUrl}
|
|
11686
12184
|
(deprecated alias: --kandan-url)
|
|
11687
12185
|
--token <jwt> Optional override token. Otherwise ~/.linzumi/auth.json is validated or OAuth opens.
|
|
11688
12186
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
11689
12187
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
11690
12188
|
|
|
11691
|
-
|
|
12189
|
+
Workspace and optional channel binding:
|
|
11692
12190
|
--workspace <slug> Workspace slug
|
|
11693
|
-
--channel <slug|w/c>
|
|
12191
|
+
--channel <slug|w/c> Optional channel slug, or workspace/channel
|
|
11694
12192
|
--linzumi-thread-id <uuid> Resume an existing Linzumi thread instead of announcing a new root
|
|
11695
12193
|
(deprecated alias: --kandan-thread-id)
|
|
11696
12194
|
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
@@ -11723,8 +12221,9 @@ Examples:
|
|
|
11723
12221
|
linzumi commander daemon --runner-id launch-commander
|
|
11724
12222
|
linzumi start ~/
|
|
11725
12223
|
linzumi start ~/code/my-app
|
|
11726
|
-
linzumi connect --workspace <your-workspace> --
|
|
11727
|
-
linzumi connect --workspace <your-workspace> --
|
|
12224
|
+
linzumi connect --workspace <your-workspace> --launch-tui
|
|
12225
|
+
linzumi connect --workspace <your-workspace> --model gpt-5 --reasoning-effort low --fast --launch-tui
|
|
12226
|
+
linzumi connect --workspace <your-workspace> --channel <your-channel>
|
|
11728
12227
|
linzumi auth --workspace <your-workspace> --channel <your-channel>
|
|
11729
12228
|
linzumi paths add ~/code/my-app
|
|
11730
12229
|
linzumi paths list
|
|
@@ -11734,9 +12233,9 @@ Examples:
|
|
|
11734
12233
|
Missing --listen-user and authenticated user is unavailable.
|
|
11735
12234
|
linzumi connect --token "$TOKEN" --listen-users sean
|
|
11736
12235
|
Invalid flag: use --listen-user.
|
|
11737
|
-
linzumi connect --workspace <your-workspace> --
|
|
12236
|
+
linzumi connect --workspace <your-workspace> --allowed-cwd /does/not/exist
|
|
11738
12237
|
Invalid --allowed-cwd: allowed cwd roots must exist locally.
|
|
11739
|
-
linzumi connect --workspace <your-workspace> --
|
|
12238
|
+
linzumi connect --workspace <your-workspace> --forward-port vite
|
|
11740
12239
|
Invalid --forward-port: value must be a TCP port from 1 to 65535.
|
|
11741
12240
|
`;
|
|
11742
12241
|
}
|
|
@@ -11894,7 +12393,7 @@ space, persists this folder to your trusted-paths list, and starts this
|
|
|
11894
12393
|
computer as a local Codex runner.
|
|
11895
12394
|
|
|
11896
12395
|
Advanced (when you already know your workspace and channel):
|
|
11897
|
-
linzumi connect --workspace <your-workspace>
|
|
12396
|
+
linzumi connect --workspace <your-workspace>
|
|
11898
12397
|
|
|
11899
12398
|
For help:
|
|
11900
12399
|
linzumi connect --help
|