@linzumi/cli 0.0.39-beta → 0.0.41-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 +566 -260
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
3
|
-
import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as
|
|
3
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, realpathSync as realpathSync6 } from "node:fs";
|
|
4
4
|
import { homedir as homedir9 } from "node:os";
|
|
5
|
-
import { resolve as
|
|
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
10
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
11
|
+
import { realpathSync as realpathSync4 } from "node:fs";
|
|
11
12
|
import { hostname as hostname2 } from "node:os";
|
|
12
|
-
import { join as join6 } from "node:path";
|
|
13
|
+
import { join as join6, resolve as resolve5 } from "node:path";
|
|
13
14
|
|
|
14
15
|
// src/channelSessionSupport.ts
|
|
15
16
|
import { spawnSync } from "node:child_process";
|
|
@@ -289,6 +290,9 @@ function codexSandboxPolicy(sandbox, cwd) {
|
|
|
289
290
|
// src/codexOutput.ts
|
|
290
291
|
import { Buffer as Buffer2 } from "node:buffer";
|
|
291
292
|
var maxVisibleWebSearchQueries = 6;
|
|
293
|
+
var maxPersistedCommandOutputChars = 14000;
|
|
294
|
+
var maxPersistedCommandLabelChars = 512;
|
|
295
|
+
var commandOutputTruncationReason = "phoenix_insert_body_limit";
|
|
292
296
|
function codexOutputMessagesForTurn(response, turnId) {
|
|
293
297
|
if ("error" in response) {
|
|
294
298
|
return [];
|
|
@@ -354,21 +358,20 @@ function codexOutputMessagesForItem(item, index) {
|
|
|
354
358
|
case "commandExecution": {
|
|
355
359
|
const command = stringValue(item.command) ?? "command";
|
|
356
360
|
const output = nonBlankStringValue(item.aggregatedOutput) ?? "";
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
`);
|
|
361
|
+
const projection = codexCommandOutputProjection(command, output);
|
|
360
362
|
return [
|
|
361
363
|
{
|
|
362
364
|
itemKey,
|
|
363
|
-
body,
|
|
365
|
+
body: projection.body,
|
|
364
366
|
structured: baseStructured("codex_command_execution", {
|
|
365
|
-
command,
|
|
367
|
+
command: projection.command,
|
|
366
368
|
cwd: stringValue(item.cwd) ?? "",
|
|
367
369
|
status: stringValue(item.status) ?? "",
|
|
368
370
|
process_id: stringValue(item.processId) ?? "",
|
|
369
371
|
duration_ms: integerValue(item.durationMs) ?? null,
|
|
370
372
|
exit_code: integerValue(item.exitCode) ?? null,
|
|
371
|
-
output
|
|
373
|
+
output: projection.output,
|
|
374
|
+
...commandOutputTruncationMetadata(projection)
|
|
372
375
|
})
|
|
373
376
|
}
|
|
374
377
|
];
|
|
@@ -500,21 +503,29 @@ function codexCommandOutputDeltaFromNotification(params) {
|
|
|
500
503
|
};
|
|
501
504
|
}
|
|
502
505
|
function codexCommandExecutionStructuredMessage(itemKey, fields, streamState) {
|
|
506
|
+
const projection = codexCommandOutputProjection(fields.command, fields.output);
|
|
507
|
+
return codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection);
|
|
508
|
+
}
|
|
509
|
+
function codexCommandExecutionStructuredMessageFromProjection(itemKey, fields, streamState, projection) {
|
|
503
510
|
return {
|
|
504
511
|
kind: "codex_command_execution",
|
|
505
512
|
item_id: itemKey,
|
|
506
513
|
transcript_unit_id: `codex_command_execution:${itemKey}`,
|
|
507
514
|
stream_state: streamState,
|
|
508
|
-
command:
|
|
515
|
+
command: projection.command,
|
|
509
516
|
cwd: fields.cwd ?? "",
|
|
510
517
|
status: fields.status ?? streamState,
|
|
511
518
|
process_id: fields.processId ?? "",
|
|
512
519
|
duration_ms: fields.durationMs ?? null,
|
|
513
520
|
exit_code: fields.exitCode ?? null,
|
|
514
521
|
stream: fields.stream ?? "",
|
|
515
|
-
output:
|
|
522
|
+
output: projection.output,
|
|
523
|
+
...commandOutputTruncationMetadata(projection)
|
|
516
524
|
};
|
|
517
525
|
}
|
|
526
|
+
function codexCommandOutputBody(command, output) {
|
|
527
|
+
return codexCommandOutputProjection(command, output).body;
|
|
528
|
+
}
|
|
518
529
|
function codexFileChangeDeltaFromNotification(params) {
|
|
519
530
|
const patchText = textDeltaFromParams(params);
|
|
520
531
|
if (patchText === undefined || patchText === "") {
|
|
@@ -617,6 +628,65 @@ function assistantVisibleText(item) {
|
|
|
617
628
|
function assistantDeltaText(params) {
|
|
618
629
|
return textDeltaFromParams(params);
|
|
619
630
|
}
|
|
631
|
+
function codexCommandOutputProjection(command, output) {
|
|
632
|
+
const normalizedCommand = truncatePersistedText(sanitizePersistedText(command), maxPersistedCommandLabelChars, "command label");
|
|
633
|
+
const normalizedOutput = truncateCommandOutput(sanitizePersistedText(output));
|
|
634
|
+
const body = commandOutputBodyForProjection(normalizedCommand.text, normalizedOutput.text);
|
|
635
|
+
return {
|
|
636
|
+
command: normalizedCommand.text,
|
|
637
|
+
output: normalizedOutput.text,
|
|
638
|
+
body,
|
|
639
|
+
truncated: normalizedCommand.truncated || normalizedOutput.truncated,
|
|
640
|
+
originalLength: output.length,
|
|
641
|
+
maxOutputChars: maxPersistedCommandOutputChars,
|
|
642
|
+
truncationReason: commandOutputTruncationReason
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
function commandOutputTruncationMetadata(projection) {
|
|
646
|
+
return projection.truncated ? {
|
|
647
|
+
output_truncated: true,
|
|
648
|
+
output_original_length: projection.originalLength,
|
|
649
|
+
output_max_chars: projection.maxOutputChars,
|
|
650
|
+
output_truncation_reason: projection.truncationReason
|
|
651
|
+
} : {};
|
|
652
|
+
}
|
|
653
|
+
function commandOutputBodyForProjection(command, output) {
|
|
654
|
+
return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
|
|
655
|
+
|
|
656
|
+
`);
|
|
657
|
+
}
|
|
658
|
+
function truncateCommandOutput(output) {
|
|
659
|
+
if (output.length <= maxPersistedCommandOutputChars) {
|
|
660
|
+
return { text: output, truncated: false };
|
|
661
|
+
}
|
|
662
|
+
const marker = `
|
|
663
|
+
|
|
664
|
+
[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.]
|
|
665
|
+
|
|
666
|
+
`;
|
|
667
|
+
const availableChars = Math.max(0, maxPersistedCommandOutputChars - marker.length);
|
|
668
|
+
const headChars = Math.min(2000, Math.floor(availableChars / 3));
|
|
669
|
+
const tailChars = availableChars - headChars;
|
|
670
|
+
const text = `${output.slice(0, headChars)}${marker}${output.slice(-tailChars)}`;
|
|
671
|
+
return {
|
|
672
|
+
text: text.slice(0, maxPersistedCommandOutputChars),
|
|
673
|
+
truncated: true
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
function truncatePersistedText(text, maxChars, label) {
|
|
677
|
+
if (text.length <= maxChars) {
|
|
678
|
+
return { text, truncated: false };
|
|
679
|
+
}
|
|
680
|
+
const marker = `[Linzumi truncated ${label}]`;
|
|
681
|
+
const availableChars = Math.max(0, maxChars - marker.length - 1);
|
|
682
|
+
return {
|
|
683
|
+
text: `${text.slice(0, availableChars)} ${marker}`.slice(0, maxChars),
|
|
684
|
+
truncated: true
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function sanitizePersistedText(text) {
|
|
688
|
+
return text.replaceAll("\x00", "�");
|
|
689
|
+
}
|
|
620
690
|
function textDeltaFromParams(params) {
|
|
621
691
|
const delta = params.delta;
|
|
622
692
|
if (typeof delta === "string") {
|
|
@@ -640,57 +710,67 @@ function commandMessageForFunctionCall(item, output, index) {
|
|
|
640
710
|
const command = name === "exec_command" ? nonBlankStringValue(args?.cmd) ?? name : `${name}${toolArgumentsSummary(item.arguments)}`;
|
|
641
711
|
const cwd = nonBlankStringValue(args?.cwd) ?? "";
|
|
642
712
|
const outputText = toolOutputText(output);
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
713
|
+
const projection = codexCommandOutputProjection(command, outputText);
|
|
714
|
+
return [
|
|
715
|
+
{
|
|
716
|
+
itemKey,
|
|
717
|
+
body: projection.body,
|
|
718
|
+
structured: codexCommandExecutionStructuredMessage(itemKey, {
|
|
719
|
+
command,
|
|
720
|
+
cwd,
|
|
721
|
+
output: outputText,
|
|
722
|
+
status: output === undefined ? "started" : "completed"
|
|
723
|
+
}, "completed")
|
|
724
|
+
}
|
|
725
|
+
];
|
|
656
726
|
}
|
|
657
727
|
function messageForCustomToolCall(item, output, index) {
|
|
658
728
|
const name = stringValue(item.name) ?? "custom_tool_call";
|
|
659
729
|
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
660
730
|
const input = nonBlankStringValue(item.input) ?? nonBlankStringValue(item.arguments) ?? "";
|
|
661
731
|
if (name === "apply_patch") {
|
|
662
|
-
return [
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
732
|
+
return [
|
|
733
|
+
{
|
|
734
|
+
itemKey,
|
|
735
|
+
body: input,
|
|
736
|
+
structured: {
|
|
737
|
+
kind: "codex_file_change",
|
|
738
|
+
item_id: itemKey,
|
|
739
|
+
transcript_unit_id: `codex_file_change:${itemKey}`,
|
|
740
|
+
stream_state: "completed",
|
|
741
|
+
status: output === undefined ? "started" : "completed",
|
|
742
|
+
patch_text: input,
|
|
743
|
+
changes: fileChangesFromPatch(input)
|
|
744
|
+
}
|
|
673
745
|
}
|
|
674
|
-
|
|
746
|
+
];
|
|
675
747
|
}
|
|
676
748
|
const outputText = toolOutputText(output);
|
|
677
749
|
const command = `${name}${toolArgumentsSummary(item.input)}`;
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
750
|
+
const projection = codexCommandOutputProjection(command, outputText);
|
|
751
|
+
return [
|
|
752
|
+
{
|
|
753
|
+
itemKey,
|
|
754
|
+
body: projection.body,
|
|
755
|
+
structured: codexCommandExecutionStructuredMessage(itemKey, {
|
|
756
|
+
command,
|
|
757
|
+
output: outputText,
|
|
758
|
+
status: output === undefined ? "started" : "completed"
|
|
759
|
+
}, "completed")
|
|
760
|
+
}
|
|
761
|
+
];
|
|
685
762
|
}
|
|
686
763
|
function commandMessageForUnpairedToolOutput(item, index) {
|
|
687
764
|
const itemKey = stringValue(item.call_id) ?? stringValue(item.id) ?? `item-${index}`;
|
|
688
765
|
const output = toolOutputText(item);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
766
|
+
const projection = codexCommandOutputProjection("tool output", output);
|
|
767
|
+
return [
|
|
768
|
+
{
|
|
769
|
+
itemKey,
|
|
770
|
+
body: projection.output,
|
|
771
|
+
structured: codexCommandExecutionStructuredMessageFromProjection(itemKey, { status: "completed" }, "completed", projection)
|
|
772
|
+
}
|
|
773
|
+
];
|
|
694
774
|
}
|
|
695
775
|
function toolArgumentsSummary(value) {
|
|
696
776
|
const text = nonBlankStringValue(value);
|
|
@@ -709,13 +789,34 @@ function fileChangesFromPatch(patchText) {
|
|
|
709
789
|
const addPrefix = "*** Add File: ";
|
|
710
790
|
const deletePrefix = "*** Delete File: ";
|
|
711
791
|
if (line.startsWith(updatePrefix)) {
|
|
712
|
-
return [
|
|
792
|
+
return [
|
|
793
|
+
{
|
|
794
|
+
path: line.slice(updatePrefix.length).trim(),
|
|
795
|
+
diff: "",
|
|
796
|
+
kind: "update",
|
|
797
|
+
move_path: ""
|
|
798
|
+
}
|
|
799
|
+
];
|
|
713
800
|
}
|
|
714
801
|
if (line.startsWith(addPrefix)) {
|
|
715
|
-
return [
|
|
802
|
+
return [
|
|
803
|
+
{
|
|
804
|
+
path: line.slice(addPrefix.length).trim(),
|
|
805
|
+
diff: "",
|
|
806
|
+
kind: "add",
|
|
807
|
+
move_path: ""
|
|
808
|
+
}
|
|
809
|
+
];
|
|
716
810
|
}
|
|
717
811
|
if (line.startsWith(deletePrefix)) {
|
|
718
|
-
return [
|
|
812
|
+
return [
|
|
813
|
+
{
|
|
814
|
+
path: line.slice(deletePrefix.length).trim(),
|
|
815
|
+
diff: "",
|
|
816
|
+
kind: "delete",
|
|
817
|
+
move_path: ""
|
|
818
|
+
}
|
|
819
|
+
];
|
|
719
820
|
}
|
|
720
821
|
return [];
|
|
721
822
|
});
|
|
@@ -2344,8 +2445,8 @@ async function attachChannelSession(args) {
|
|
|
2344
2445
|
const session = args.options.channelSession;
|
|
2345
2446
|
const chatTopic = `chat:${session.workspaceSlug}:${session.channelSlug}`;
|
|
2346
2447
|
const state = initialChannelSessionState(0, session.rootSeq, session.kandanThreadId, session.codexThreadId, args.options);
|
|
2347
|
-
const joined = await args.kandan.join(chatTopic,
|
|
2348
|
-
rejoinPayload: () => (
|
|
2448
|
+
const joined = await args.kandan.join(chatTopic, chatJoinPayload(args.options.runnerId, state, 0), {
|
|
2449
|
+
rejoinPayload: () => chatJoinPayload(args.options.runnerId, state, state.minSeq)
|
|
2349
2450
|
});
|
|
2350
2451
|
const cursor = integerValue(joined.cursor) ?? 0;
|
|
2351
2452
|
const runnerIdentity = identityFromAccessToken(args.options.token);
|
|
@@ -2491,6 +2592,19 @@ async function attachChannelSession(args) {
|
|
|
2491
2592
|
}
|
|
2492
2593
|
};
|
|
2493
2594
|
}
|
|
2595
|
+
function chatJoinPayload(runnerId, state, lastSeq) {
|
|
2596
|
+
const base = { last_seq: lastSeq };
|
|
2597
|
+
switch (state.kandanThreadId) {
|
|
2598
|
+
case undefined:
|
|
2599
|
+
return base;
|
|
2600
|
+
default:
|
|
2601
|
+
return {
|
|
2602
|
+
...base,
|
|
2603
|
+
runner_id: runnerId,
|
|
2604
|
+
thread_id: state.kandanThreadId
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
}
|
|
2494
2608
|
async function bindCurrentCodexThread(args, state) {
|
|
2495
2609
|
if (state.codexThreadId === undefined) {
|
|
2496
2610
|
return;
|
|
@@ -2544,7 +2658,10 @@ function initialChannelSessionState(cursor, rootSeq, kandanThreadId, codexThread
|
|
|
2544
2658
|
webSearchProgressForwardChain: Promise.resolve(),
|
|
2545
2659
|
typingHeartbeat: undefined,
|
|
2546
2660
|
typingHeartbeatInFlight: false,
|
|
2547
|
-
runtimeSettings: runtimeSettingsFromOptions(options)
|
|
2661
|
+
runtimeSettings: runtimeSettingsFromOptions(options),
|
|
2662
|
+
pendingRuntimeSettingsResume: false,
|
|
2663
|
+
runtimeSettingsGeneration: 0,
|
|
2664
|
+
runtimeSettingsResumeChain: Promise.resolve()
|
|
2548
2665
|
};
|
|
2549
2666
|
}
|
|
2550
2667
|
async function handleLostPortForwardCandidate(args, state, payloadContext, candidate) {
|
|
@@ -2682,7 +2799,7 @@ async function handleChannelSessionControl(args, state, payloadContext, control)
|
|
|
2682
2799
|
interruptedQueuedMessages: interrupted.ok ? interrupted.selectedCount : 0
|
|
2683
2800
|
};
|
|
2684
2801
|
}
|
|
2685
|
-
function updateSessionSettings(args, state, control) {
|
|
2802
|
+
async function updateSessionSettings(args, state, control) {
|
|
2686
2803
|
if (state.codexThreadId !== control.threadId) {
|
|
2687
2804
|
return;
|
|
2688
2805
|
}
|
|
@@ -2694,6 +2811,8 @@ function updateSessionSettings(args, state, control) {
|
|
|
2694
2811
|
};
|
|
2695
2812
|
}
|
|
2696
2813
|
state.runtimeSettings = mergeRuntimeSettings(state.runtimeSettings, control);
|
|
2814
|
+
state.pendingRuntimeSettingsResume = true;
|
|
2815
|
+
state.runtimeSettingsGeneration += 1;
|
|
2697
2816
|
publishRuntimeSettings(args, state).catch((error) => {
|
|
2698
2817
|
args.log("kandan.session_settings_publish_failed", {
|
|
2699
2818
|
message: error instanceof Error ? error.message : String(error)
|
|
@@ -2706,6 +2825,7 @@ function updateSessionSettings(args, state, control) {
|
|
|
2706
2825
|
sandbox: state.runtimeSettings.sandbox ?? null,
|
|
2707
2826
|
fast: state.runtimeSettings.fast ?? null
|
|
2708
2827
|
});
|
|
2828
|
+
await tryResumeCodexThreadForPendingRuntimeSettings(args, state);
|
|
2709
2829
|
return {
|
|
2710
2830
|
instanceId: args.instanceId,
|
|
2711
2831
|
ok: true,
|
|
@@ -2716,6 +2836,71 @@ function updateSessionSettings(args, state, control) {
|
|
|
2716
2836
|
fast: state.runtimeSettings.fast ?? null
|
|
2717
2837
|
};
|
|
2718
2838
|
}
|
|
2839
|
+
async function resumeCodexThreadForPendingRuntimeSettings(args, state) {
|
|
2840
|
+
const requestedGeneration = state.runtimeSettingsGeneration;
|
|
2841
|
+
const previousResume = state.runtimeSettingsResumeChain.catch(() => {
|
|
2842
|
+
return;
|
|
2843
|
+
});
|
|
2844
|
+
const resume = previousResume.then(() => performCodexThreadResumeForPendingRuntimeSettings(args, state, requestedGeneration));
|
|
2845
|
+
state.runtimeSettingsResumeChain = resume.then(() => {
|
|
2846
|
+
return;
|
|
2847
|
+
}, () => {
|
|
2848
|
+
return;
|
|
2849
|
+
});
|
|
2850
|
+
return await resume;
|
|
2851
|
+
}
|
|
2852
|
+
async function performCodexThreadResumeForPendingRuntimeSettings(args, state, requestedGeneration) {
|
|
2853
|
+
if (state.pendingRuntimeSettingsResume !== true || state.closed || state.turn.status !== "idle" || localTuiTurnIsActive(state)) {
|
|
2854
|
+
return true;
|
|
2855
|
+
}
|
|
2856
|
+
const codexThreadId = state.codexThreadId;
|
|
2857
|
+
if (codexThreadId === undefined) {
|
|
2858
|
+
return true;
|
|
2859
|
+
}
|
|
2860
|
+
if (state.runtimeSettingsGeneration !== requestedGeneration) {
|
|
2861
|
+
return false;
|
|
2862
|
+
}
|
|
2863
|
+
const resumeGeneration = requestedGeneration;
|
|
2864
|
+
const resumeSettings = state.runtimeSettings;
|
|
2865
|
+
const runtimeOptions = runtimeOptionsForSettings(args.options, resumeSettings);
|
|
2866
|
+
const resumeParams = {
|
|
2867
|
+
threadId: codexThreadId,
|
|
2868
|
+
...codexThreadRuntimeOverrides(runtimeOptions)
|
|
2869
|
+
};
|
|
2870
|
+
args.log("codex.session_settings_resume_requested", {
|
|
2871
|
+
codex_thread_id: codexThreadId,
|
|
2872
|
+
model: resumeSettings.model ?? null,
|
|
2873
|
+
reasoning_effort: resumeSettings.reasoningEffort ?? null,
|
|
2874
|
+
approval_policy: resumeSettings.approvalPolicy ?? null,
|
|
2875
|
+
sandbox: resumeSettings.sandbox ?? null,
|
|
2876
|
+
fast: resumeSettings.fast ?? null,
|
|
2877
|
+
generation: resumeGeneration
|
|
2878
|
+
});
|
|
2879
|
+
const resumed = await args.codex.request("thread/resume", resumeParams);
|
|
2880
|
+
if ("error" in resumed) {
|
|
2881
|
+
throw new Error(`failed to resume Codex thread with updated settings: ${resumed.error.message}`);
|
|
2882
|
+
}
|
|
2883
|
+
if (state.pendingRuntimeSettingsResume === true && state.runtimeSettingsGeneration === resumeGeneration) {
|
|
2884
|
+
state.pendingRuntimeSettingsResume = false;
|
|
2885
|
+
}
|
|
2886
|
+
args.log("codex.session_settings_resume_completed", {
|
|
2887
|
+
codex_thread_id: codexThreadId,
|
|
2888
|
+
generation: resumeGeneration,
|
|
2889
|
+
current_generation: state.runtimeSettingsGeneration,
|
|
2890
|
+
pending: state.pendingRuntimeSettingsResume
|
|
2891
|
+
});
|
|
2892
|
+
return state.pendingRuntimeSettingsResume !== true;
|
|
2893
|
+
}
|
|
2894
|
+
async function tryResumeCodexThreadForPendingRuntimeSettings(args, state) {
|
|
2895
|
+
try {
|
|
2896
|
+
return await resumeCodexThreadForPendingRuntimeSettings(args, state);
|
|
2897
|
+
} catch (error) {
|
|
2898
|
+
args.log("codex.session_settings_resume_failed", {
|
|
2899
|
+
message: error instanceof Error ? error.message : String(error)
|
|
2900
|
+
});
|
|
2901
|
+
return false;
|
|
2902
|
+
}
|
|
2903
|
+
}
|
|
2719
2904
|
async function resolvePendingCodexApprovalRequest(args, state, control) {
|
|
2720
2905
|
if (state.codexThreadId !== control.threadId) {
|
|
2721
2906
|
return;
|
|
@@ -3277,6 +3462,10 @@ async function drainKandanMessageQueue(args, state, payloadContext) {
|
|
|
3277
3462
|
if (state.closed || state.turn.status !== "idle" || localTuiTurnIsActive(state)) {
|
|
3278
3463
|
return;
|
|
3279
3464
|
}
|
|
3465
|
+
const resumed = await tryResumeCodexThreadForPendingRuntimeSettings(args, state);
|
|
3466
|
+
if (!resumed) {
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3280
3469
|
const next = dequeuePendingKandanMessage(state.queue);
|
|
3281
3470
|
if (next === undefined) {
|
|
3282
3471
|
return;
|
|
@@ -3602,11 +3791,17 @@ async function forwardCompletedCodexTurn(args, state, turnId, payloadContext) {
|
|
|
3602
3791
|
if (completingActiveTurn && state.turn.status === "completing" && state.turn.turnId === turnId) {
|
|
3603
3792
|
state.turn = { status: "idle" };
|
|
3604
3793
|
await stopCodexTyping(args, state);
|
|
3605
|
-
await
|
|
3794
|
+
const resumed = await tryResumeCodexThreadForPendingRuntimeSettings(args, state);
|
|
3795
|
+
if (resumed) {
|
|
3796
|
+
await drainKandanMessageQueue(args, state, payloadContext);
|
|
3797
|
+
}
|
|
3606
3798
|
}
|
|
3607
3799
|
if (completingLocalTuiTurn && !completingActiveTurn) {
|
|
3608
3800
|
await stopCodexTyping(args, state);
|
|
3609
|
-
await
|
|
3801
|
+
const resumed = await tryResumeCodexThreadForPendingRuntimeSettings(args, state);
|
|
3802
|
+
if (resumed) {
|
|
3803
|
+
await drainKandanMessageQueue(args, state, payloadContext);
|
|
3804
|
+
}
|
|
3610
3805
|
}
|
|
3611
3806
|
}
|
|
3612
3807
|
}
|
|
@@ -3890,13 +4085,25 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
3890
4085
|
}
|
|
3891
4086
|
const existing = findStreamingCommandOutput(state, delta.itemKey);
|
|
3892
4087
|
const output = `${existing?.output ?? ""}${delta.delta}`;
|
|
3893
|
-
const body = commandOutputBody("command output", output);
|
|
3894
4088
|
const structured = codexCommandExecutionStructuredMessage(delta.itemKey, {
|
|
3895
4089
|
command: "command output",
|
|
3896
4090
|
output,
|
|
3897
4091
|
processId: delta.processId,
|
|
3898
4092
|
stream: delta.stream
|
|
3899
4093
|
}, "streaming");
|
|
4094
|
+
const persistedOutput = typeof structured.output === "string" ? structured.output : output;
|
|
4095
|
+
const body = codexCommandOutputBody("command output", output);
|
|
4096
|
+
if (structured.output_truncated === true) {
|
|
4097
|
+
args.log("codex.command_output_truncated", {
|
|
4098
|
+
item_key: delta.itemKey,
|
|
4099
|
+
turn_id: turnId,
|
|
4100
|
+
process_id: delta.processId ?? null,
|
|
4101
|
+
stream: delta.stream,
|
|
4102
|
+
failure_class: stringValue(structured.output_truncation_reason) ?? null,
|
|
4103
|
+
original_length: integerValue(structured.output_original_length) ?? null,
|
|
4104
|
+
persisted_length: persistedOutput.length
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
3900
4107
|
if (existing === undefined) {
|
|
3901
4108
|
const session = args.options.channelSession;
|
|
3902
4109
|
const reply = await pushOk(args.kandan, args.topic, "session:post_thread_message", {
|
|
@@ -3920,7 +4127,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
3920
4127
|
itemKey: delta.itemKey,
|
|
3921
4128
|
turnId,
|
|
3922
4129
|
seq,
|
|
3923
|
-
output,
|
|
4130
|
+
output: persistedOutput,
|
|
3924
4131
|
processId: delta.processId,
|
|
3925
4132
|
stream: delta.stream
|
|
3926
4133
|
});
|
|
@@ -3929,7 +4136,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
3929
4136
|
await editCodexStructuredOutput(args, state, existing.seq, body, structured);
|
|
3930
4137
|
rememberStreamingCommandOutput(state, {
|
|
3931
4138
|
...existing,
|
|
3932
|
-
output,
|
|
4139
|
+
output: persistedOutput,
|
|
3933
4140
|
processId: delta.processId ?? existing.processId,
|
|
3934
4141
|
stream: delta.stream
|
|
3935
4142
|
});
|
|
@@ -3939,7 +4146,7 @@ async function forwardCommandOutputDeltaPayload(args, state, delta, payloadConte
|
|
|
3939
4146
|
turn_id: turnId,
|
|
3940
4147
|
process_id: delta.processId ?? null,
|
|
3941
4148
|
stream: delta.stream,
|
|
3942
|
-
output_length:
|
|
4149
|
+
output_length: persistedOutput.length
|
|
3943
4150
|
});
|
|
3944
4151
|
}
|
|
3945
4152
|
async function forwardFileChangeDeltaPayload(args, state, delta, payloadContext) {
|
|
@@ -4134,11 +4341,6 @@ function webSearchProgressClientMessageId(instanceId, turnId) {
|
|
|
4134
4341
|
function webSearchProgressItemKey(turnId) {
|
|
4135
4342
|
return `web-search:${turnId}`;
|
|
4136
4343
|
}
|
|
4137
|
-
function commandOutputBody(command, output) {
|
|
4138
|
-
return [`$ ${command}`, output].filter((part) => part.trim() !== "").join(`
|
|
4139
|
-
|
|
4140
|
-
`);
|
|
4141
|
-
}
|
|
4142
4344
|
async function streamCompletedCodexOutput(args, state, payloadContext, params) {
|
|
4143
4345
|
if (state.kandanThreadId === undefined || state.codexThreadId === undefined) {
|
|
4144
4346
|
return;
|
|
@@ -6167,30 +6369,14 @@ function prepareCodeServerLaunch(options) {
|
|
|
6167
6369
|
if (platform !== "darwin") {
|
|
6168
6370
|
return filesystemSandboxUnavailable();
|
|
6169
6371
|
}
|
|
6170
|
-
const sandboxExecBin = options.sandboxExecBin ?? "/usr/bin/sandbox-exec";
|
|
6171
|
-
if (!existsSync2(sandboxExecBin)) {
|
|
6172
|
-
return filesystemSandboxUnavailable();
|
|
6173
|
-
}
|
|
6174
6372
|
const codeServerExecutable = resolveCodeServerExecutable(options.codeServerBin, options.envPath ?? process.env.PATH ?? "");
|
|
6175
6373
|
if (!codeServerExecutable.ok) {
|
|
6176
6374
|
return filesystemSandboxUnavailable();
|
|
6177
6375
|
}
|
|
6178
6376
|
return {
|
|
6179
6377
|
ok: true,
|
|
6180
|
-
command:
|
|
6181
|
-
args:
|
|
6182
|
-
"-p",
|
|
6183
|
-
codeServerSandboxProfile(options, codeServerExecutable.directory),
|
|
6184
|
-
"--",
|
|
6185
|
-
"/bin/sh",
|
|
6186
|
-
"-c",
|
|
6187
|
-
'export HOME="$1"; export PWD="$1"; export TMPDIR="$2"; export TMP="$2"; export TEMP="$2"; shift 2; exec "$@"',
|
|
6188
|
-
"kandan-code-server-env",
|
|
6189
|
-
options.cwd,
|
|
6190
|
-
join4(options.userDataDir, "tmp"),
|
|
6191
|
-
codeServerExecutable.command,
|
|
6192
|
-
...codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
|
|
6193
|
-
]
|
|
6378
|
+
command: codeServerExecutable.command,
|
|
6379
|
+
args: codeServerArgs(options.port, options.cwd, options.userDataDir, options.extensionsDir)
|
|
6194
6380
|
};
|
|
6195
6381
|
}
|
|
6196
6382
|
function prepareLinuxCodeServerLaunch(options) {
|
|
@@ -6261,43 +6447,6 @@ function filesystemSandboxUnavailable() {
|
|
|
6261
6447
|
reason: "local_editor_filesystem_sandbox_unavailable"
|
|
6262
6448
|
};
|
|
6263
6449
|
}
|
|
6264
|
-
function codeServerSandboxProfile(options, codeServerBinDir) {
|
|
6265
|
-
const readOnlyRoots = uniquePaths([
|
|
6266
|
-
"/System",
|
|
6267
|
-
"/Library",
|
|
6268
|
-
"/usr",
|
|
6269
|
-
"/bin",
|
|
6270
|
-
"/sbin",
|
|
6271
|
-
"/etc",
|
|
6272
|
-
"/private/etc",
|
|
6273
|
-
"/opt/homebrew",
|
|
6274
|
-
"/dev",
|
|
6275
|
-
...options.codeServerRuntimeRoot === undefined ? [] : sandboxPathAliases(options.codeServerRuntimeRoot),
|
|
6276
|
-
codeServerBinDir
|
|
6277
|
-
]);
|
|
6278
|
-
const readWriteRoots = uniquePaths([
|
|
6279
|
-
...sandboxPathAliases(options.cwd),
|
|
6280
|
-
...sandboxPathAliases(options.userDataDir),
|
|
6281
|
-
...options.extensionsDir === undefined ? [] : sandboxPathAliases(options.extensionsDir)
|
|
6282
|
-
]);
|
|
6283
|
-
return [
|
|
6284
|
-
"(version 1)",
|
|
6285
|
-
"(deny default)",
|
|
6286
|
-
"(allow process*)",
|
|
6287
|
-
"(allow signal (target self))",
|
|
6288
|
-
"(allow signal (target same-sandbox))",
|
|
6289
|
-
"(allow sysctl*)",
|
|
6290
|
-
"(allow mach*)",
|
|
6291
|
-
"(allow ipc*)",
|
|
6292
|
-
"(allow network*)",
|
|
6293
|
-
"(allow file-read-metadata)",
|
|
6294
|
-
"(allow file-map-executable)",
|
|
6295
|
-
'(allow file-read* (literal "/") (literal "/private") (literal "/private/var"))',
|
|
6296
|
-
`(allow file-read* ${readOnlyRoots.map(sandboxSubpath).join(" ")})`,
|
|
6297
|
-
`(allow file-read* file-write* ${readWriteRoots.map(sandboxSubpath).join(" ")})`
|
|
6298
|
-
].join(`
|
|
6299
|
-
`);
|
|
6300
|
-
}
|
|
6301
6450
|
function resolveCodeServerExecutable(command, envPath) {
|
|
6302
6451
|
if (hasPathSeparator(command)) {
|
|
6303
6452
|
const directory = safeRealpathDir(command);
|
|
@@ -6338,9 +6487,6 @@ function sandboxPathAliases(path) {
|
|
|
6338
6487
|
function uniquePaths(paths) {
|
|
6339
6488
|
return Array.from(new Set(paths.filter((path) => path.length > 0)));
|
|
6340
6489
|
}
|
|
6341
|
-
function sandboxSubpath(path) {
|
|
6342
|
-
return `(subpath "${path.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}")`;
|
|
6343
|
-
}
|
|
6344
6490
|
function prepareLocalEditorCollaboration(collaboration, runnerId, serverPort, browserBaseUrl) {
|
|
6345
6491
|
if (collaboration === undefined || serverPort === undefined) {
|
|
6346
6492
|
return;
|
|
@@ -6466,19 +6612,11 @@ function installDirectory(sourceDir, destinationDir) {
|
|
|
6466
6612
|
mkdirSync3(dirname2(destinationDir), { recursive: true });
|
|
6467
6613
|
cpSync(sourceDir, destinationDir, { recursive: true });
|
|
6468
6614
|
}
|
|
6469
|
-
function codeServerEnv(env, cwd,
|
|
6615
|
+
function codeServerEnv(env, cwd, _userDataDir, collaboration) {
|
|
6470
6616
|
const { PORT: _port, ...hostEnv } = env;
|
|
6471
|
-
const tempDir = join4(userDataDir, "tmp");
|
|
6472
6617
|
const base = {
|
|
6473
6618
|
...hostEnv,
|
|
6474
|
-
|
|
6475
|
-
PWD: cwd,
|
|
6476
|
-
TMPDIR: tempDir,
|
|
6477
|
-
TMP: tempDir,
|
|
6478
|
-
TEMP: tempDir,
|
|
6479
|
-
XDG_CACHE_HOME: join4(userDataDir, "xdg-cache"),
|
|
6480
|
-
XDG_CONFIG_HOME: join4(userDataDir, "xdg-config"),
|
|
6481
|
-
XDG_DATA_HOME: join4(userDataDir, "xdg-data")
|
|
6619
|
+
PWD: cwd
|
|
6482
6620
|
};
|
|
6483
6621
|
if (collaboration === undefined) {
|
|
6484
6622
|
return base;
|
|
@@ -7955,6 +8093,9 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
7955
8093
|
};
|
|
7956
8094
|
});
|
|
7957
8095
|
const allowedCwds = { value: [...options.allowedCwds] };
|
|
8096
|
+
const missingAllowedCwds = {
|
|
8097
|
+
value: normalizeAllowedCwds(options.missingAllowedCwds ?? [])
|
|
8098
|
+
};
|
|
7958
8099
|
const localEditorState = {
|
|
7959
8100
|
value: { status: "disabled" }
|
|
7960
8101
|
};
|
|
@@ -7970,7 +8111,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
7970
8111
|
codexRemoteTui: true,
|
|
7971
8112
|
startInstance: allowedCwds.value.length > 0,
|
|
7972
8113
|
allowedCwds: allowedCwds.value,
|
|
7973
|
-
|
|
8114
|
+
missingAllowedCwds: missingAllowedCwds.value,
|
|
7974
8115
|
allowedCwdSuggestions: allowedCwdSuggestions(options.cwd, allowedCwds.value),
|
|
7975
8116
|
portForwarding: liveForwardPorts.size > 0,
|
|
7976
8117
|
allowedPorts: Array.from(liveForwardPorts).sort((left, right) => left - right),
|
|
@@ -7985,7 +8126,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
7985
8126
|
const joinPayload = () => ({
|
|
7986
8127
|
clientName: "kandan-local-codex-runner",
|
|
7987
8128
|
version: "0.0.1",
|
|
7988
|
-
workspace: options
|
|
8129
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
7989
8130
|
channel: options.channelSession?.channelSlug ?? null,
|
|
7990
8131
|
capabilities: capabilitiesPayload()
|
|
7991
8132
|
});
|
|
@@ -8055,6 +8196,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8055
8196
|
const codexThreads = options.channelSession === undefined ? await discoverCodexThreads(codex, options.cwd) : [];
|
|
8056
8197
|
const discoveredCodexThreads = { value: codexThreads };
|
|
8057
8198
|
const runnerHost = hostname2();
|
|
8199
|
+
const runtimeDefaults = runnerRuntimeDefaults(options);
|
|
8058
8200
|
const instancePayload = {
|
|
8059
8201
|
instanceId,
|
|
8060
8202
|
codexUrl,
|
|
@@ -8062,8 +8204,10 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8062
8204
|
cwd: options.cwd,
|
|
8063
8205
|
hostname: runnerHost,
|
|
8064
8206
|
codexThreads,
|
|
8065
|
-
|
|
8066
|
-
|
|
8207
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8208
|
+
channel: options.channelSession?.channelSlug ?? null,
|
|
8209
|
+
model: runtimeDefaults.model ?? null,
|
|
8210
|
+
reasoningEffort: runtimeDefaults.reasoningEffort ?? null,
|
|
8067
8211
|
fast: options.fast ?? false
|
|
8068
8212
|
};
|
|
8069
8213
|
await kandan.push(topic, "instance_started", instancePayload);
|
|
@@ -8108,9 +8252,9 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8108
8252
|
dynamicChannelSessions.clear();
|
|
8109
8253
|
});
|
|
8110
8254
|
const attachThreadSession = async (control, cwd, codexThreadId) => {
|
|
8111
|
-
const workspaceSlug =
|
|
8112
|
-
const channelSlug =
|
|
8113
|
-
const kandanThreadId =
|
|
8255
|
+
const workspaceSlug = optionalThreadControlField(control, "workspace");
|
|
8256
|
+
const channelSlug = optionalThreadControlField(control, "channel");
|
|
8257
|
+
const kandanThreadId = optionalThreadControlField(control, "threadId");
|
|
8114
8258
|
if (workspaceSlug === undefined || channelSlug === undefined || kandanThreadId === undefined) {
|
|
8115
8259
|
return;
|
|
8116
8260
|
}
|
|
@@ -8171,13 +8315,13 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8171
8315
|
codexUrl,
|
|
8172
8316
|
cwd: options.cwd,
|
|
8173
8317
|
hostname: runnerHost,
|
|
8174
|
-
workspace: options
|
|
8318
|
+
workspace: runnerWorkspaceSlug(options) ?? null,
|
|
8175
8319
|
channel: options.channelSession?.channelSlug ?? null,
|
|
8176
8320
|
threadId: channelSession?.currentKandanThreadId() ?? null,
|
|
8177
8321
|
codexThreadId: channelSession?.currentCodexThreadId() ?? null,
|
|
8178
8322
|
codexThreads: discoveredCodexThreads.value,
|
|
8179
|
-
model:
|
|
8180
|
-
reasoningEffort:
|
|
8323
|
+
model: runtimeDefaults.model ?? null,
|
|
8324
|
+
reasoningEffort: runtimeDefaults.reasoningEffort ?? null,
|
|
8181
8325
|
fast: options.fast ?? false,
|
|
8182
8326
|
capabilities: capabilitiesPayload()
|
|
8183
8327
|
});
|
|
@@ -8342,7 +8486,9 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8342
8486
|
return;
|
|
8343
8487
|
}
|
|
8344
8488
|
if (isUpdateRunnerConfigControl(control)) {
|
|
8345
|
-
|
|
8489
|
+
const updatedAllowedCwds = configuredAllowedCwds(control.allowedCwds);
|
|
8490
|
+
allowedCwds.value = updatedAllowedCwds.allowedCwds;
|
|
8491
|
+
missingAllowedCwds.value = updatedAllowedCwds.missingAllowedCwds;
|
|
8346
8492
|
pushHeartbeat();
|
|
8347
8493
|
return;
|
|
8348
8494
|
}
|
|
@@ -8374,7 +8520,7 @@ async function openLocalCodexRunner(options, log, cleanup, close) {
|
|
|
8374
8520
|
if (handled !== undefined) {
|
|
8375
8521
|
return handled;
|
|
8376
8522
|
}
|
|
8377
|
-
return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control, attachThreadSession);
|
|
8523
|
+
return applyControl(codex, kandan, topic, instanceId, options, allowedCwds.value, control, log, attachThreadSession);
|
|
8378
8524
|
}).then((response) => {
|
|
8379
8525
|
return kandan.push(topic, "codex_response", response);
|
|
8380
8526
|
}).catch((error) => {
|
|
@@ -8450,7 +8596,7 @@ async function discoverCodexThreads(codex, cwd) {
|
|
|
8450
8596
|
}
|
|
8451
8597
|
function extractStartedThreadId(response) {
|
|
8452
8598
|
if ("error" in response) {
|
|
8453
|
-
|
|
8599
|
+
throw new Error(`thread/start failed: ${response.error.message}`);
|
|
8454
8600
|
}
|
|
8455
8601
|
return stringValue(objectValue(objectValue(response.result)?.thread)?.id);
|
|
8456
8602
|
}
|
|
@@ -8560,10 +8706,12 @@ async function prepareCodexThreadForTuiResume(codex, codexThreadId) {
|
|
|
8560
8706
|
throw new Error(`failed to verify Codex TUI resume: ${verified.error.message}`);
|
|
8561
8707
|
}
|
|
8562
8708
|
}
|
|
8563
|
-
async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control, onStartedThread) {
|
|
8709
|
+
async function applyControl(codex, kandan, topic, instanceId, options, allowedCwds, control, log, onStartedThread) {
|
|
8564
8710
|
switch (control.type) {
|
|
8565
8711
|
case "start_instance": {
|
|
8566
8712
|
const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
|
|
8713
|
+
let startupStage = "starting_codex_session";
|
|
8714
|
+
let startedCodexThreadId;
|
|
8567
8715
|
if (!cwd.ok) {
|
|
8568
8716
|
return {
|
|
8569
8717
|
instanceId,
|
|
@@ -8572,56 +8720,87 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8572
8720
|
error: cwd.reason
|
|
8573
8721
|
};
|
|
8574
8722
|
}
|
|
8575
|
-
|
|
8576
|
-
|
|
8723
|
+
const processingStatePayload = startInstanceMessageStatePayload(control, "processing", "starting Codex session");
|
|
8724
|
+
if (processingStatePayload !== undefined) {
|
|
8725
|
+
kandan.push(topic, "message_state", processingStatePayload).catch((error) => {
|
|
8726
|
+
log("kandan.start_instance_processing_state_push_failed", {
|
|
8727
|
+
message: error instanceof Error ? error.message : String(error)
|
|
8728
|
+
});
|
|
8729
|
+
});
|
|
8577
8730
|
}
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
|
|
8731
|
+
try {
|
|
8732
|
+
if (options.codexUrl === undefined) {
|
|
8733
|
+
startupStage = "checking_project_trust";
|
|
8734
|
+
ensureCodexProjectTrusted(cwd.cwd);
|
|
8735
|
+
}
|
|
8736
|
+
startupStage = "starting_codex_session";
|
|
8737
|
+
const developerPrompt = normalizedWorkDescription(control.developerPrompt);
|
|
8738
|
+
const runtimeSettings = startInstanceRuntimeSettings(options, control);
|
|
8739
|
+
const response = await codex.request("thread/start", {
|
|
8585
8740
|
cwd: cwd.cwd,
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
|
|
8591
|
-
|
|
8592
|
-
|
|
8593
|
-
|
|
8594
|
-
|
|
8595
|
-
|
|
8596
|
-
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8741
|
+
serviceName: "kandan-local-runner",
|
|
8742
|
+
personality: "pragmatic",
|
|
8743
|
+
developerInstructions: commanderDeveloperInstructions({
|
|
8744
|
+
cwd: cwd.cwd,
|
|
8745
|
+
developerPrompt
|
|
8746
|
+
}),
|
|
8747
|
+
...runtimeSettings.model === undefined ? {} : { model: runtimeSettings.model },
|
|
8748
|
+
...runtimeSettings.reasoningEffort === undefined ? {} : { reasoningEffort: runtimeSettings.reasoningEffort },
|
|
8749
|
+
...runtimeSettings.approvalPolicy === undefined ? {} : { approvalPolicy: runtimeSettings.approvalPolicy },
|
|
8750
|
+
...runtimeSettings.sandbox === undefined ? {} : { sandbox: runtimeSettings.sandbox },
|
|
8751
|
+
...runtimeSettings.fast === true ? { serviceTier: "fast" } : {}
|
|
8752
|
+
});
|
|
8753
|
+
const codexThreadId = extractStartedThreadId(response);
|
|
8754
|
+
startedCodexThreadId = codexThreadId;
|
|
8755
|
+
const workDescription = normalizedWorkDescription(control.workDescription);
|
|
8756
|
+
if (codexThreadId !== undefined && developerPrompt !== undefined) {
|
|
8757
|
+
await postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId);
|
|
8758
|
+
}
|
|
8759
|
+
let startedThreadSession;
|
|
8760
|
+
if (codexThreadId !== undefined && onStartedThread !== undefined) {
|
|
8761
|
+
startupStage = "binding_kandan_thread";
|
|
8762
|
+
startedThreadSession = await onStartedThread(control, cwd.cwd, codexThreadId);
|
|
8763
|
+
}
|
|
8764
|
+
if (codexThreadId !== undefined && workDescription !== undefined) {
|
|
8765
|
+
startupStage = "starting_first_turn";
|
|
8766
|
+
const rootSeq = integerValue(control.rootSeq);
|
|
8767
|
+
const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
|
|
8768
|
+
if (startedThreadSession !== undefined && sourceSeq !== undefined) {
|
|
8769
|
+
const identity = identityFromAccessToken(options.token);
|
|
8770
|
+
await startedThreadSession.startThreadMessageTurn({
|
|
8771
|
+
seq: sourceSeq,
|
|
8772
|
+
body: workDescription,
|
|
8773
|
+
actorSlug: identity.actorUsername,
|
|
8774
|
+
actorUserId: identity.actorUserId
|
|
8775
|
+
});
|
|
8776
|
+
} else {
|
|
8777
|
+
await codex.request("turn/start", {
|
|
8778
|
+
threadId: codexThreadId,
|
|
8779
|
+
input: [{ type: "text", text: workDescription }]
|
|
8780
|
+
});
|
|
8781
|
+
}
|
|
8616
8782
|
}
|
|
8783
|
+
return {
|
|
8784
|
+
instanceId,
|
|
8785
|
+
controlType: control.type,
|
|
8786
|
+
cwd: cwd.cwd,
|
|
8787
|
+
matchedRoot: cwd.matchedRoot,
|
|
8788
|
+
response
|
|
8789
|
+
};
|
|
8790
|
+
} catch (error) {
|
|
8791
|
+
const failureReason = startInstanceFailureReason(startupStage, error);
|
|
8792
|
+
log("kandan.start_instance_failed", {
|
|
8793
|
+
stage: startupStage,
|
|
8794
|
+
message: error instanceof Error ? error.message : String(error),
|
|
8795
|
+
thread_id: optionalThreadControlField(control, "threadId") ?? null,
|
|
8796
|
+
source_seq: integerValue(control.sourceSeq) ?? integerValue(control.rootSeq) ?? null,
|
|
8797
|
+
codex_thread_id: startedCodexThreadId ?? null
|
|
8798
|
+
});
|
|
8799
|
+
try {
|
|
8800
|
+
await publishStartInstanceMessageState(kandan, topic, control, "failed", failureReason, { codexThreadId: startedCodexThreadId });
|
|
8801
|
+
} catch (_publishError) {}
|
|
8802
|
+
throw error;
|
|
8617
8803
|
}
|
|
8618
|
-
return {
|
|
8619
|
-
instanceId,
|
|
8620
|
-
controlType: control.type,
|
|
8621
|
-
cwd: cwd.cwd,
|
|
8622
|
-
matchedRoot: cwd.matchedRoot,
|
|
8623
|
-
response
|
|
8624
|
-
};
|
|
8625
8804
|
}
|
|
8626
8805
|
case "reconnect_thread": {
|
|
8627
8806
|
const cwd = resolveAllowedCwd(control.cwd, allowedCwds);
|
|
@@ -8730,6 +8909,26 @@ async function applyControl(codex, kandan, topic, instanceId, options, allowedCw
|
|
|
8730
8909
|
return { instanceId, controlType: control.type, skipped: true };
|
|
8731
8910
|
}
|
|
8732
8911
|
}
|
|
8912
|
+
function startInstanceFailureReason(stage, error) {
|
|
8913
|
+
const detail = truncateFailureDetail(error instanceof Error ? error.message : String(error));
|
|
8914
|
+
switch (stage) {
|
|
8915
|
+
case "checking_project_trust":
|
|
8916
|
+
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}`;
|
|
8917
|
+
case "starting_codex_session":
|
|
8918
|
+
return `Codex did not create a session. Restart the local runner and try again. Detail: ${detail}`;
|
|
8919
|
+
case "binding_kandan_thread":
|
|
8920
|
+
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}`;
|
|
8921
|
+
case "starting_first_turn":
|
|
8922
|
+
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}`;
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8925
|
+
function truncateFailureDetail(message) {
|
|
8926
|
+
const trimmed = message.trim();
|
|
8927
|
+
if (trimmed.length <= 240) {
|
|
8928
|
+
return trimmed;
|
|
8929
|
+
}
|
|
8930
|
+
return `${trimmed.slice(0, 237)}...`;
|
|
8931
|
+
}
|
|
8733
8932
|
function commanderDeveloperInstructions(args) {
|
|
8734
8933
|
const customPrompt = args.developerPrompt === undefined ? "" : `
|
|
8735
8934
|
<invoker_developer_prompt>
|
|
@@ -8786,9 +8985,9 @@ secure tunnel, and keep the Linzumi thread truthful.
|
|
|
8786
8985
|
</task_reminder>`;
|
|
8787
8986
|
}
|
|
8788
8987
|
async function postVisibleDeveloperPrompt(kandan, topic, control, developerPrompt, codexThreadId) {
|
|
8789
|
-
const workspace =
|
|
8790
|
-
const channel =
|
|
8791
|
-
const threadId =
|
|
8988
|
+
const workspace = optionalThreadControlField(control, "workspace");
|
|
8989
|
+
const channel = optionalThreadControlField(control, "channel");
|
|
8990
|
+
const threadId = optionalThreadControlField(control, "threadId");
|
|
8792
8991
|
if (workspace === undefined || channel === undefined || threadId === undefined) {
|
|
8793
8992
|
return;
|
|
8794
8993
|
}
|
|
@@ -8810,24 +9009,81 @@ ${developerPrompt}`,
|
|
|
8810
9009
|
client_message_id: `codex-start-instructions-${threadId}`
|
|
8811
9010
|
});
|
|
8812
9011
|
}
|
|
9012
|
+
async function publishStartInstanceMessageState(kandan, topic, control, status, reason, diagnostics = {}) {
|
|
9013
|
+
const payload = startInstanceMessageStatePayload(control, status, reason, diagnostics);
|
|
9014
|
+
if (payload === undefined) {
|
|
9015
|
+
return;
|
|
9016
|
+
}
|
|
9017
|
+
await kandan.push(topic, "message_state", payload);
|
|
9018
|
+
}
|
|
9019
|
+
function startInstanceMessageStatePayload(control, status, reason, diagnostics = {}) {
|
|
9020
|
+
const rootSeq = integerValue(control.rootSeq);
|
|
9021
|
+
const sourceSeq = integerValue(control.sourceSeq) ?? rootSeq;
|
|
9022
|
+
if (sourceSeq === undefined) {
|
|
9023
|
+
return;
|
|
9024
|
+
}
|
|
9025
|
+
const workspace = requiredStartInstanceControlField(control, "workspace");
|
|
9026
|
+
const channel = requiredStartInstanceControlField(control, "channel");
|
|
9027
|
+
const threadId = requiredStartInstanceControlField(control, "threadId");
|
|
9028
|
+
return {
|
|
9029
|
+
workspace,
|
|
9030
|
+
channel,
|
|
9031
|
+
thread_id: threadId,
|
|
9032
|
+
seq: sourceSeq,
|
|
9033
|
+
status,
|
|
9034
|
+
reason,
|
|
9035
|
+
...diagnostics.codexThreadId === undefined ? {} : { codex_thread_id: diagnostics.codexThreadId }
|
|
9036
|
+
};
|
|
9037
|
+
}
|
|
9038
|
+
function requiredStartInstanceControlField(control, field) {
|
|
9039
|
+
return requiredThreadControlField(control, field);
|
|
9040
|
+
}
|
|
9041
|
+
function requiredThreadControlField(control, field) {
|
|
9042
|
+
const value = optionalThreadControlField(control, field);
|
|
9043
|
+
if (value === undefined) {
|
|
9044
|
+
throw new Error(`${control.type} control missing ${field}`);
|
|
9045
|
+
}
|
|
9046
|
+
return value;
|
|
9047
|
+
}
|
|
9048
|
+
function optionalThreadControlField(control, field) {
|
|
9049
|
+
const value = stringValue(control[field])?.trim();
|
|
9050
|
+
if (value === undefined || value === "") {
|
|
9051
|
+
return;
|
|
9052
|
+
}
|
|
9053
|
+
return value;
|
|
9054
|
+
}
|
|
8813
9055
|
function startInstanceRuntimeSettings(options, control) {
|
|
8814
|
-
const
|
|
9056
|
+
const defaults = runnerRuntimeDefaults(options);
|
|
8815
9057
|
return {
|
|
8816
|
-
model: control.model ??
|
|
8817
|
-
reasoningEffort: control.reasoningEffort ??
|
|
8818
|
-
approvalPolicy: control.approvalPolicy ??
|
|
8819
|
-
sandbox: control.sandbox ??
|
|
9058
|
+
model: control.model ?? defaults.model,
|
|
9059
|
+
reasoningEffort: control.reasoningEffort ?? defaults.reasoningEffort,
|
|
9060
|
+
approvalPolicy: control.approvalPolicy ?? defaults.approvalPolicy,
|
|
9061
|
+
sandbox: control.sandbox ?? defaults.sandbox,
|
|
8820
9062
|
fast: control.fast ?? options.fast
|
|
8821
9063
|
};
|
|
8822
9064
|
}
|
|
8823
9065
|
async function startOwnedCodexAppServer(options) {
|
|
8824
9066
|
ensureCodexProjectTrusted(options.cwd);
|
|
9067
|
+
const defaults = runnerRuntimeDefaults(options);
|
|
8825
9068
|
return await startCodexAppServer(options.codexBin, options.cwd, {
|
|
8826
|
-
model:
|
|
8827
|
-
reasoningEffort:
|
|
9069
|
+
model: defaults.model,
|
|
9070
|
+
reasoningEffort: defaults.reasoningEffort,
|
|
8828
9071
|
fast: options.fast
|
|
8829
9072
|
});
|
|
8830
9073
|
}
|
|
9074
|
+
function runnerWorkspaceSlug(options) {
|
|
9075
|
+
return options.channelSession?.workspaceSlug ?? options.workspaceSlug;
|
|
9076
|
+
}
|
|
9077
|
+
function runnerRuntimeDefaults(options) {
|
|
9078
|
+
const session = options.channelSession;
|
|
9079
|
+
const defaults = options.runtimeDefaults;
|
|
9080
|
+
return {
|
|
9081
|
+
model: defaults?.model ?? session?.model,
|
|
9082
|
+
reasoningEffort: defaults?.reasoningEffort ?? session?.reasoningEffort,
|
|
9083
|
+
approvalPolicy: defaults?.approvalPolicy ?? session?.approvalPolicy,
|
|
9084
|
+
sandbox: defaults?.sandbox ?? session?.sandbox
|
|
9085
|
+
};
|
|
9086
|
+
}
|
|
8831
9087
|
function isUpdateRunnerConfigControl(control) {
|
|
8832
9088
|
return control.type === "update_runner_config";
|
|
8833
9089
|
}
|
|
@@ -8840,6 +9096,30 @@ function normalizeAllowedCwds(values) {
|
|
|
8840
9096
|
return normalized === "" ? [] : [normalized];
|
|
8841
9097
|
})));
|
|
8842
9098
|
}
|
|
9099
|
+
function configuredAllowedCwds(values) {
|
|
9100
|
+
const allowedCwds = [];
|
|
9101
|
+
const missingAllowedCwds = [];
|
|
9102
|
+
for (const value of normalizeAllowedCwds(values)) {
|
|
9103
|
+
const absolutePath = resolve5(expandUserPath(value));
|
|
9104
|
+
try {
|
|
9105
|
+
const realPath = realpathSync4(absolutePath);
|
|
9106
|
+
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9107
|
+
} catch (error) {
|
|
9108
|
+
if (isMissingAllowedCwdError(error)) {
|
|
9109
|
+
missingAllowedCwds.push(absolutePath);
|
|
9110
|
+
continue;
|
|
9111
|
+
}
|
|
9112
|
+
throw error;
|
|
9113
|
+
}
|
|
9114
|
+
}
|
|
9115
|
+
return {
|
|
9116
|
+
allowedCwds: normalizeAllowedCwds(allowedCwds),
|
|
9117
|
+
missingAllowedCwds: normalizeAllowedCwds(missingAllowedCwds)
|
|
9118
|
+
};
|
|
9119
|
+
}
|
|
9120
|
+
function isMissingAllowedCwdError(error) {
|
|
9121
|
+
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
9122
|
+
}
|
|
8843
9123
|
function allowedCwdSuggestions(cwd, allowedCwds) {
|
|
8844
9124
|
return normalizeAllowedCwds([cwd, ...allowedCwds]);
|
|
8845
9125
|
}
|
|
@@ -8988,14 +9268,14 @@ import {
|
|
|
8988
9268
|
existsSync as existsSync5,
|
|
8989
9269
|
mkdirSync as mkdirSync6,
|
|
8990
9270
|
readFileSync as readFileSync4,
|
|
8991
|
-
realpathSync as
|
|
9271
|
+
realpathSync as realpathSync5,
|
|
8992
9272
|
writeFileSync as writeFileSync5
|
|
8993
9273
|
} from "node:fs";
|
|
8994
9274
|
import { homedir as homedir6 } from "node:os";
|
|
8995
|
-
import { dirname as dirname5, resolve as
|
|
9275
|
+
import { dirname as dirname5, resolve as resolve6 } from "node:path";
|
|
8996
9276
|
function localConfigPath(env = process.env) {
|
|
8997
9277
|
const override = env.LINZUMI_CONFIG_FILE;
|
|
8998
|
-
return override !== undefined && override.trim() !== "" ?
|
|
9278
|
+
return override !== undefined && override.trim() !== "" ? resolve6(expandUserPath(override)) : resolve6(homedir6(), ".linzumi", "config.json");
|
|
8999
9279
|
}
|
|
9000
9280
|
function readLocalConfig(path = localConfigPath()) {
|
|
9001
9281
|
if (!existsSync5(path)) {
|
|
@@ -9010,32 +9290,36 @@ function readLocalConfig(path = localConfigPath()) {
|
|
|
9010
9290
|
allowedCwds: uniqueStrings(parsed.allowedCwds)
|
|
9011
9291
|
};
|
|
9012
9292
|
}
|
|
9013
|
-
function
|
|
9293
|
+
function readConfiguredAllowedCwdDetails(path = localConfigPath()) {
|
|
9014
9294
|
const allowedCwds = [];
|
|
9015
|
-
const
|
|
9295
|
+
const missingAllowedCwds = [];
|
|
9016
9296
|
for (const cwd of readLocalConfig(path).allowedCwds) {
|
|
9297
|
+
const absolutePath = resolve6(expandUserPath(cwd));
|
|
9017
9298
|
try {
|
|
9018
|
-
const
|
|
9019
|
-
const realPath = realpathSync4(absolutePath);
|
|
9299
|
+
const realPath = realpathSync5(absolutePath);
|
|
9020
9300
|
allowedCwds.push(...realPath === absolutePath ? [realPath] : [realPath, absolutePath]);
|
|
9021
|
-
} catch (
|
|
9022
|
-
|
|
9301
|
+
} catch (error) {
|
|
9302
|
+
if (isMissingPathError(error)) {
|
|
9303
|
+
missingAllowedCwds.push(absolutePath);
|
|
9304
|
+
continue;
|
|
9305
|
+
}
|
|
9306
|
+
throw error;
|
|
9023
9307
|
}
|
|
9024
9308
|
}
|
|
9025
9309
|
return {
|
|
9026
9310
|
allowedCwds: uniqueStrings(allowedCwds),
|
|
9027
|
-
|
|
9311
|
+
missingAllowedCwds: uniqueStrings(missingAllowedCwds)
|
|
9028
9312
|
};
|
|
9029
9313
|
}
|
|
9030
9314
|
function addAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9031
|
-
const normalizedPath =
|
|
9315
|
+
const normalizedPath = realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9032
9316
|
const config = readLocalConfig(path);
|
|
9033
9317
|
const allowedCwds = uniqueStrings([...config.allowedCwds, normalizedPath]);
|
|
9034
9318
|
writeLocalConfig({ version: 1, allowedCwds }, path);
|
|
9035
9319
|
return allowedCwds;
|
|
9036
9320
|
}
|
|
9037
9321
|
function removeAllowedCwd(pathValue, path = localConfigPath()) {
|
|
9038
|
-
const requestedPath =
|
|
9322
|
+
const requestedPath = resolve6(expandUserPath(pathValue));
|
|
9039
9323
|
const normalizedRequest = realpathOrResolved(requestedPath);
|
|
9040
9324
|
const config = readLocalConfig(path);
|
|
9041
9325
|
const allowedCwds = config.allowedCwds.filter((cwd) => {
|
|
@@ -9058,11 +9342,14 @@ function uniqueStrings(values) {
|
|
|
9058
9342
|
...new Set(values.map((value) => value.trim()).filter((value) => value !== ""))
|
|
9059
9343
|
];
|
|
9060
9344
|
}
|
|
9345
|
+
function isMissingPathError(error) {
|
|
9346
|
+
return typeof error === "object" && error !== null && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR" || error.code === "EACCES" || error.code === "ELOOP" || error.code === "EIO");
|
|
9347
|
+
}
|
|
9061
9348
|
function realpathOrResolved(pathValue) {
|
|
9062
9349
|
try {
|
|
9063
|
-
return
|
|
9350
|
+
return realpathSync5(resolve6(expandUserPath(pathValue)));
|
|
9064
9351
|
} catch (_error) {
|
|
9065
|
-
return
|
|
9352
|
+
return resolve6(expandUserPath(pathValue));
|
|
9066
9353
|
}
|
|
9067
9354
|
}
|
|
9068
9355
|
|
|
@@ -9460,7 +9747,7 @@ async function runClaim(command, deps) {
|
|
|
9460
9747
|
workspaceName: stringValue(response.workspace_name),
|
|
9461
9748
|
channelId: stringValue(response.channel_id),
|
|
9462
9749
|
ownerUsername: stringValue(response.owner_username),
|
|
9463
|
-
channelUrl:
|
|
9750
|
+
channelUrl: stringValue(response.channel_url),
|
|
9464
9751
|
loginUrl: requiredString(response, "login_url"),
|
|
9465
9752
|
supportChannelId: requiredString(response, "support_channel_id"),
|
|
9466
9753
|
supportChannelUrl: requiredString(response, "support_channel_url"),
|
|
@@ -9479,8 +9766,10 @@ async function runClaim(command, deps) {
|
|
|
9479
9766
|
deps.stdout.write(`login_url: ${tokenFile.loginUrl}
|
|
9480
9767
|
`);
|
|
9481
9768
|
writeHumanLoginUrlWarning(deps);
|
|
9482
|
-
|
|
9769
|
+
if (tokenFile.channelUrl !== undefined) {
|
|
9770
|
+
deps.stdout.write(`channel_url: ${tokenFile.channelUrl}
|
|
9483
9771
|
`);
|
|
9772
|
+
}
|
|
9484
9773
|
deps.stdout.write(`support_channel_id: ${tokenFile.supportChannelId}
|
|
9485
9774
|
`);
|
|
9486
9775
|
deps.stdout.write(`support_channel_url: ${tokenFile.supportChannelUrl}
|
|
@@ -9749,7 +10038,7 @@ function readStoredAgentTokenFile(path, readTextFile = readOptionalTextFile) {
|
|
|
9749
10038
|
workspaceName: stringValue(parsed.workspaceName),
|
|
9750
10039
|
channelId: stringValue(parsed.channelId),
|
|
9751
10040
|
ownerUsername: stringValue(parsed.ownerUsername),
|
|
9752
|
-
channelUrl:
|
|
10041
|
+
channelUrl: stringValue(parsed.channelUrl),
|
|
9753
10042
|
loginUrl: requiredString(parsed, "loginUrl"),
|
|
9754
10043
|
supportChannelId: requiredString(parsed, "supportChannelId"),
|
|
9755
10044
|
supportChannelUrl: stringValue(parsed.supportChannelUrl),
|
|
@@ -9803,7 +10092,7 @@ Launch target:
|
|
|
9803
10092
|
|
|
9804
10093
|
// src/helloLinzumiProject.ts
|
|
9805
10094
|
import { existsSync as existsSync8, mkdirSync as mkdirSync8, readFileSync as readFileSync7, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9806
|
-
import { dirname as dirname7, join as join9, resolve as
|
|
10095
|
+
import { dirname as dirname7, join as join9, resolve as resolve7 } from "node:path";
|
|
9807
10096
|
import { fileURLToPath } from "node:url";
|
|
9808
10097
|
var defaultHelloLinzumiProjectDir = "/tmp/hello_linzumi";
|
|
9809
10098
|
var defaultHelloLinzumiProjectName = "hello_linzumi";
|
|
@@ -9837,10 +10126,10 @@ function resolveHelloProjectRoot(options) {
|
|
|
9837
10126
|
throw new Error("linzumi init-hello-linzumi-demo-app accepts either --dir or --parent-dir/--name, not both");
|
|
9838
10127
|
}
|
|
9839
10128
|
if (options.rootPath !== undefined) {
|
|
9840
|
-
return
|
|
10129
|
+
return resolve7(options.rootPath);
|
|
9841
10130
|
}
|
|
9842
10131
|
const name = normalizeProjectName(options.name ?? defaultHelloLinzumiProjectName);
|
|
9843
|
-
return
|
|
10132
|
+
return resolve7(options.parentDir ?? defaultHelloLinzumiParentDir, name);
|
|
9844
10133
|
}
|
|
9845
10134
|
function normalizeProjectName(value) {
|
|
9846
10135
|
const name = value.trim();
|
|
@@ -10375,7 +10664,7 @@ import {
|
|
|
10375
10664
|
writeFileSync as writeFileSync8
|
|
10376
10665
|
} from "node:fs";
|
|
10377
10666
|
import { homedir as homedir8 } from "node:os";
|
|
10378
|
-
import { dirname as dirname8, join as join10, resolve as
|
|
10667
|
+
import { dirname as dirname8, join as join10, resolve as resolve8 } from "node:path";
|
|
10379
10668
|
import { execFileSync, spawn as spawn7 } from "node:child_process";
|
|
10380
10669
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10381
10670
|
var connectedMarker = "Runner connected:";
|
|
@@ -10391,7 +10680,7 @@ function defaultCommanderLogFile(runnerId, statusDir = commanderStatusDir()) {
|
|
|
10391
10680
|
function startCommanderDaemon(options) {
|
|
10392
10681
|
const statusDir = options.statusDir ?? commanderStatusDir();
|
|
10393
10682
|
const statusFile = commanderStatusFile(options.runnerId, statusDir);
|
|
10394
|
-
const logFile =
|
|
10683
|
+
const logFile = resolve8(options.logFile ?? defaultCommanderLogFile(options.runnerId, statusDir));
|
|
10395
10684
|
const entrypoint = options.entrypoint ?? currentEntrypoint();
|
|
10396
10685
|
const nodeBin = options.nodeBin ?? process.execPath;
|
|
10397
10686
|
const command = [
|
|
@@ -10588,7 +10877,7 @@ function safeRunnerId(runnerId) {
|
|
|
10588
10877
|
}
|
|
10589
10878
|
async function waitForFileChangeOrTimeout(path, deadline, now, ready2 = () => false) {
|
|
10590
10879
|
const remaining = Math.max(0, deadline - now());
|
|
10591
|
-
await new Promise((
|
|
10880
|
+
await new Promise((resolve9) => {
|
|
10592
10881
|
let resolved = false;
|
|
10593
10882
|
let watcher;
|
|
10594
10883
|
const finish = () => {
|
|
@@ -10598,7 +10887,7 @@ async function waitForFileChangeOrTimeout(path, deadline, now, ready2 = () => fa
|
|
|
10598
10887
|
resolved = true;
|
|
10599
10888
|
watcher?.close();
|
|
10600
10889
|
clearTimeout(timer);
|
|
10601
|
-
|
|
10890
|
+
resolve9();
|
|
10602
10891
|
};
|
|
10603
10892
|
const timer = setTimeout(finish, remaining);
|
|
10604
10893
|
try {
|
|
@@ -10678,7 +10967,7 @@ function isMainModule() {
|
|
|
10678
10967
|
if (scriptPath === undefined) {
|
|
10679
10968
|
return false;
|
|
10680
10969
|
}
|
|
10681
|
-
return fileURLToPath3(import.meta.url) ===
|
|
10970
|
+
return fileURLToPath3(import.meta.url) === resolve9(scriptPath);
|
|
10682
10971
|
}
|
|
10683
10972
|
async function main(args) {
|
|
10684
10973
|
const parsed = parseCommand(args);
|
|
@@ -10687,7 +10976,7 @@ async function main(args) {
|
|
|
10687
10976
|
process.stdout.write(connectGuideText());
|
|
10688
10977
|
return;
|
|
10689
10978
|
case "version":
|
|
10690
|
-
process.stdout.write(`linzumi 0.0.
|
|
10979
|
+
process.stdout.write(`linzumi 0.0.41-beta
|
|
10691
10980
|
`);
|
|
10692
10981
|
return;
|
|
10693
10982
|
case "auth":
|
|
@@ -10840,7 +11129,7 @@ function runPathsCommand(args) {
|
|
|
10840
11129
|
if (pathValue === undefined || pathValue.trim() === "") {
|
|
10841
11130
|
throw new Error("missing path for linzumi paths add");
|
|
10842
11131
|
}
|
|
10843
|
-
const trustedPath =
|
|
11132
|
+
const trustedPath = realpathSync6(resolve9(expandUserPath(pathValue)));
|
|
10844
11133
|
addAllowedCwd(pathValue);
|
|
10845
11134
|
process.stdout.write(`Trusted ${trustedPath}
|
|
10846
11135
|
`);
|
|
@@ -11030,6 +11319,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
11030
11319
|
kandanUrl,
|
|
11031
11320
|
token: targetToken,
|
|
11032
11321
|
runnerId: stringValue3(values, "runner-id") ?? `runner-${randomUUID3()}`,
|
|
11322
|
+
workspaceSlug: target.workspaceSlug,
|
|
11033
11323
|
cwd,
|
|
11034
11324
|
codexBin,
|
|
11035
11325
|
codexUrl: stringValue3(values, "codex-url"),
|
|
@@ -11042,6 +11332,7 @@ async function parseStartRunnerArgs(args, deps = {
|
|
|
11042
11332
|
editorRuntime: editorRuntime.runtime,
|
|
11043
11333
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11044
11334
|
dependencyStatus,
|
|
11335
|
+
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11045
11336
|
channelSession: {
|
|
11046
11337
|
workspaceSlug: target.workspaceSlug,
|
|
11047
11338
|
channelSlug: target.channelSlug,
|
|
@@ -11073,12 +11364,23 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11073
11364
|
const tokenFilePath = stringValue3(values, "agent-token-file") ?? defaultAgentTokenFilePath();
|
|
11074
11365
|
const tokenFile = readStoredAgentTokenFile(tokenFilePath, deps.readTextFile);
|
|
11075
11366
|
const channelSlug = tokenFile.channelId;
|
|
11076
|
-
|
|
11367
|
+
rejectWorkspaceCommanderThreadFlags(values, channelSlug);
|
|
11368
|
+
const channelSession = channelSlug === undefined ? undefined : {
|
|
11369
|
+
workspaceSlug: tokenFile.workspaceId,
|
|
11370
|
+
channelSlug,
|
|
11371
|
+
kandanThreadId: stringValue3(values, "linzumi-thread-id"),
|
|
11372
|
+
listenUser: stringValue3(values, "listen-user") ?? requiredStoredOwnerUsername(tokenFile.ownerUsername),
|
|
11373
|
+
model: stringValue3(values, "model"),
|
|
11374
|
+
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
11375
|
+
sandbox: stringValue3(values, "sandbox"),
|
|
11376
|
+
approvalPolicy: stringValue3(values, "approval-policy"),
|
|
11377
|
+
streamFlushMs: positiveIntegerValue(values, "stream-flush-ms")
|
|
11378
|
+
};
|
|
11077
11379
|
const kandanUrl = stringValue3(values, "linzumi-url") ?? agentApiUrlToKandanUrl(tokenFile.apiUrl);
|
|
11078
11380
|
const requestedCwdValue = cwdArg ?? stringValue3(values, "cwd");
|
|
11079
11381
|
const requestedCwd = resolveUserPath(requestedCwdValue ?? process.cwd());
|
|
11080
|
-
const
|
|
11081
|
-
const allowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : requestedCwdValue === undefined ?
|
|
11382
|
+
const configuredAllowedCwds2 = requestedCwdValue === undefined && !values.has("allowed-cwd") ? readConfiguredAllowedCwdDetails() : { allowedCwds: [], missingAllowedCwds: [] };
|
|
11383
|
+
const allowedCwds = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : requestedCwdValue === undefined ? configuredAllowedCwds2.allowedCwds.length > 0 ? [...configuredAllowedCwds2.allowedCwds] : assertConfiguredAllowedCwds([requestedCwd]) : assertConfiguredAllowedCwds([requestedCwd]);
|
|
11082
11384
|
const cwd = allowedCwds[0] ?? requestedCwd;
|
|
11083
11385
|
const codexBin = stringValue3(values, "codex-bin") ?? "codex";
|
|
11084
11386
|
const customCodeServerBin = stringValue3(values, "code-server-bin");
|
|
@@ -11105,6 +11407,7 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11105
11407
|
kandanUrl,
|
|
11106
11408
|
token: tokenFile.commanderToken,
|
|
11107
11409
|
runnerId: stringValue3(values, "runner-id") ?? `agent-runner-${randomUUID3()}`,
|
|
11410
|
+
workspaceSlug: tokenFile.workspaceId,
|
|
11108
11411
|
cwd,
|
|
11109
11412
|
codexBin,
|
|
11110
11413
|
codexUrl: stringValue3(values, "codex-url"),
|
|
@@ -11112,24 +11415,14 @@ async function parseAgentRunnerArgs(args, deps = {
|
|
|
11112
11415
|
fast: values.get("fast") === true,
|
|
11113
11416
|
logFile: stringValue3(values, "log-file"),
|
|
11114
11417
|
allowedCwds,
|
|
11115
|
-
|
|
11418
|
+
missingAllowedCwds: configuredAllowedCwds2.missingAllowedCwds,
|
|
11116
11419
|
allowedForwardPorts: parseAllowedPortList(stringValue3(values, "forward-port")),
|
|
11117
11420
|
codeServerBin: editorRuntime.codeServerBin,
|
|
11118
11421
|
editorRuntime: editorRuntime.runtime,
|
|
11119
11422
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11120
11423
|
dependencyStatus,
|
|
11121
|
-
|
|
11122
|
-
channelSession
|
|
11123
|
-
workspaceSlug: tokenFile.workspaceId,
|
|
11124
|
-
channelSlug,
|
|
11125
|
-
kandanThreadId: stringValue3(values, "linzumi-thread-id"),
|
|
11126
|
-
listenUser,
|
|
11127
|
-
model: stringValue3(values, "model"),
|
|
11128
|
-
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
11129
|
-
sandbox: stringValue3(values, "sandbox"),
|
|
11130
|
-
approvalPolicy: stringValue3(values, "approval-policy"),
|
|
11131
|
-
streamFlushMs: positiveIntegerValue(values, "stream-flush-ms")
|
|
11132
|
-
}
|
|
11424
|
+
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11425
|
+
channelSession
|
|
11133
11426
|
};
|
|
11134
11427
|
}
|
|
11135
11428
|
function readAgentTokenTextFile(path) {
|
|
@@ -11147,6 +11440,12 @@ function rejectAgentRunnerTargetingFlags(values) {
|
|
|
11147
11440
|
throw new Error(`linzumi commander uses the claimed human Commander token scope; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")}.`);
|
|
11148
11441
|
}
|
|
11149
11442
|
}
|
|
11443
|
+
function rejectWorkspaceCommanderThreadFlags(values, channelSlug) {
|
|
11444
|
+
if (channelSlug !== undefined || !values.has("linzumi-thread-id")) {
|
|
11445
|
+
return;
|
|
11446
|
+
}
|
|
11447
|
+
throw new Error("linzumi commander cannot bind --linzumi-thread-id because the agent token file has no channelId");
|
|
11448
|
+
}
|
|
11150
11449
|
function requiredStoredOwnerUsername(ownerUsername) {
|
|
11151
11450
|
if (ownerUsername !== undefined) {
|
|
11152
11451
|
return ownerUsername;
|
|
@@ -11203,7 +11502,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11203
11502
|
process.exit(0);
|
|
11204
11503
|
}
|
|
11205
11504
|
if (values.get("version") === true) {
|
|
11206
|
-
process.stdout.write(`linzumi 0.0.
|
|
11505
|
+
process.stdout.write(`linzumi 0.0.41-beta
|
|
11207
11506
|
`);
|
|
11208
11507
|
process.exit(0);
|
|
11209
11508
|
}
|
|
@@ -11211,10 +11510,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11211
11510
|
const kandanUrl = required(values, "linzumi-url");
|
|
11212
11511
|
const cwd = stringValue3(values, "cwd") ?? process.cwd();
|
|
11213
11512
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
11214
|
-
const
|
|
11215
|
-
|
|
11216
|
-
missingCwds: []
|
|
11217
|
-
} : readConfiguredAllowedCwdState();
|
|
11513
|
+
const localConfiguredAllowedCwds = values.has("allowed-cwd") ? { allowedCwds: [], missingAllowedCwds: [] } : readConfiguredAllowedCwdDetails();
|
|
11514
|
+
const configuredAllowedCwds2 = values.has("allowed-cwd") ? assertConfiguredAllowedCwds(parseAllowedCwdList(stringValue3(values, "allowed-cwd"))) : [...localConfiguredAllowedCwds.allowedCwds];
|
|
11218
11515
|
const codexBin = stringValue3(values, "codex-bin") ?? "codex";
|
|
11219
11516
|
const customCodeServerBin = stringValue3(values, "code-server-bin");
|
|
11220
11517
|
const explicitToken = stringValue3(values, "token");
|
|
@@ -11254,17 +11551,26 @@ async function parseRunnerArgs(args, deps = {
|
|
|
11254
11551
|
launchTui: values.get("launch-tui") === true,
|
|
11255
11552
|
fast: values.get("fast") === true,
|
|
11256
11553
|
logFile: stringValue3(values, "log-file"),
|
|
11257
|
-
allowedCwds: Array.from(new Set([...cwdAllowedCwds, ...
|
|
11258
|
-
|
|
11554
|
+
allowedCwds: Array.from(new Set([...cwdAllowedCwds, ...configuredAllowedCwds2])),
|
|
11555
|
+
missingAllowedCwds: localConfiguredAllowedCwds.missingAllowedCwds,
|
|
11259
11556
|
allowedForwardPorts: parseAllowedPortList(stringValue3(values, "forward-port")),
|
|
11260
11557
|
codeServerBin: editorRuntime.codeServerBin,
|
|
11261
11558
|
editorRuntime: editorRuntime.runtime,
|
|
11262
11559
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
11263
11560
|
dependencyStatus,
|
|
11264
11561
|
workspaceSlug: channelSession?.workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
11562
|
+
runtimeDefaults: runnerRuntimeDefaultsFromValues(values),
|
|
11265
11563
|
channelSession
|
|
11266
11564
|
};
|
|
11267
11565
|
}
|
|
11566
|
+
function runnerRuntimeDefaultsFromValues(values) {
|
|
11567
|
+
return {
|
|
11568
|
+
model: stringValue3(values, "model"),
|
|
11569
|
+
reasoningEffort: stringValue3(values, "reasoning-effort"),
|
|
11570
|
+
approvalPolicy: stringValue3(values, "approval-policy"),
|
|
11571
|
+
sandbox: stringValue3(values, "sandbox")
|
|
11572
|
+
};
|
|
11573
|
+
}
|
|
11268
11574
|
function strictFlagValues(args, definitions = flagDefinitions) {
|
|
11269
11575
|
const values = new Map;
|
|
11270
11576
|
for (let index = 0;index < args.length; index += 1) {
|
|
@@ -11359,9 +11665,9 @@ function resolveUserPath(pathValue) {
|
|
|
11359
11665
|
return homedir9();
|
|
11360
11666
|
}
|
|
11361
11667
|
if (pathValue.startsWith("~/")) {
|
|
11362
|
-
return
|
|
11668
|
+
return resolve9(homedir9(), pathValue.slice(2));
|
|
11363
11669
|
}
|
|
11364
|
-
return
|
|
11670
|
+
return resolve9(pathValue);
|
|
11365
11671
|
}
|
|
11366
11672
|
function parseChannelSession(values, token, target) {
|
|
11367
11673
|
if (target === undefined) {
|
|
@@ -11645,10 +11951,10 @@ Usage:
|
|
|
11645
11951
|
|
|
11646
11952
|
What it does:
|
|
11647
11953
|
Starts this computer as the claimed human's scoped Linzumi Commander. The command
|
|
11648
|
-
reads ~/.linzumi/agent-token.json,
|
|
11649
|
-
trusted folders from ~/.linzumi/config.json when no
|
|
11650
|
-
|
|
11651
|
-
|
|
11954
|
+
reads ~/.linzumi/agent-token.json, connects to its workspace even when no
|
|
11955
|
+
channel is assigned, reads trusted folders from ~/.linzumi/config.json when no
|
|
11956
|
+
folder is passed, and uses the token's channel scope for channel-bound startup
|
|
11957
|
+
when one is present.
|
|
11652
11958
|
|
|
11653
11959
|
Options:
|
|
11654
11960
|
--agent-token-file <path> Agent token cache, default ~/.linzumi/agent-token.json
|