@linzumi/cli 0.0.87-beta → 0.0.88-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 +357 -33
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -11541,7 +11541,7 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11541
11541
|
const toolName2 = pending?.toolName ?? event.toolName ?? "tool";
|
|
11542
11542
|
const input = pending?.input ?? {};
|
|
11543
11543
|
const itemId = `tool:${event.itemKey}`;
|
|
11544
|
-
if (
|
|
11544
|
+
if (isFileChangeTool(toolName2)) {
|
|
11545
11545
|
const patchText = claudeFileChangePatchText(toolName2, input, {
|
|
11546
11546
|
targetExistedAtCall: pending?.targetExistedAtCall
|
|
11547
11547
|
});
|
|
@@ -11549,7 +11549,7 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11549
11549
|
const item2 = {
|
|
11550
11550
|
type: "fileChange",
|
|
11551
11551
|
id: itemId,
|
|
11552
|
-
status: "completed",
|
|
11552
|
+
status: event.isError ? "failed" : "completed",
|
|
11553
11553
|
patchText
|
|
11554
11554
|
};
|
|
11555
11555
|
recordItem(turn, item2);
|
|
@@ -11569,12 +11569,13 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11569
11569
|
submit("item/completed", { turnId: turn.turnId, item: item2 });
|
|
11570
11570
|
return;
|
|
11571
11571
|
}
|
|
11572
|
-
if (!event.isError && toolName2
|
|
11572
|
+
if (!event.isError && isClaudePlanTool(toolName2)) {
|
|
11573
11573
|
host.onTodoWriteCompleted?.(input);
|
|
11574
11574
|
const plan = claudePlanItem(itemId, input);
|
|
11575
11575
|
if (plan !== void 0) {
|
|
11576
|
-
|
|
11577
|
-
|
|
11576
|
+
const planRow = { ...plan, id: itemId };
|
|
11577
|
+
recordItem(turn, planRow);
|
|
11578
|
+
submit("item/completed", { turnId: turn.turnId, item: planRow });
|
|
11578
11579
|
return;
|
|
11579
11580
|
}
|
|
11580
11581
|
}
|
|
@@ -11605,7 +11606,7 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11605
11606
|
}
|
|
11606
11607
|
case "reasoning_delta": {
|
|
11607
11608
|
const turn = ensureTurn();
|
|
11608
|
-
const itemId =
|
|
11609
|
+
const itemId = claudeReasoningPipelineItemId(event.itemKey);
|
|
11609
11610
|
appendRawText(turn, itemId, "reasoning", event.delta);
|
|
11610
11611
|
submit("item/reasoning/textDelta", {
|
|
11611
11612
|
turnId: turn.turnId,
|
|
@@ -11626,7 +11627,7 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11626
11627
|
}
|
|
11627
11628
|
case "reasoning_message": {
|
|
11628
11629
|
const turn = ensureTurn();
|
|
11629
|
-
const itemId =
|
|
11630
|
+
const itemId = claudeReasoningPipelineItemId(event.itemKey);
|
|
11630
11631
|
setRawText(turn, itemId, "reasoning", event.content);
|
|
11631
11632
|
submit("item/completed", {
|
|
11632
11633
|
turnId: turn.turnId,
|
|
@@ -11636,14 +11637,15 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11636
11637
|
}
|
|
11637
11638
|
case "tool_call": {
|
|
11638
11639
|
const turn = ensureTurn();
|
|
11640
|
+
const targetExistedAtCall = fileChangeTargetExists(
|
|
11641
|
+
event.toolName,
|
|
11642
|
+
event.input,
|
|
11643
|
+
host.cwd
|
|
11644
|
+
);
|
|
11639
11645
|
turn.pendingTools.set(event.itemKey, {
|
|
11640
11646
|
toolName: event.toolName,
|
|
11641
11647
|
input: event.input,
|
|
11642
|
-
targetExistedAtCall
|
|
11643
|
-
event.toolName,
|
|
11644
|
-
event.input,
|
|
11645
|
-
host.cwd
|
|
11646
|
-
)
|
|
11648
|
+
targetExistedAtCall
|
|
11647
11649
|
});
|
|
11648
11650
|
if (isWebSearchTool(event.toolName)) {
|
|
11649
11651
|
const query = stringValue(event.input.query) ?? stringValue(event.input.url) ?? event.toolName;
|
|
@@ -11655,7 +11657,34 @@ function createClaudeCodeSessionPipeline(host) {
|
|
|
11655
11657
|
query
|
|
11656
11658
|
}
|
|
11657
11659
|
});
|
|
11660
|
+
return;
|
|
11661
|
+
}
|
|
11662
|
+
if (isClaudePlanTool(event.toolName)) {
|
|
11663
|
+
return;
|
|
11664
|
+
}
|
|
11665
|
+
const itemId = `tool:${event.itemKey}`;
|
|
11666
|
+
if (isFileChangeTool(event.toolName)) {
|
|
11667
|
+
const patchText = claudeFileChangePatchText(
|
|
11668
|
+
event.toolName,
|
|
11669
|
+
event.input,
|
|
11670
|
+
{ targetExistedAtCall }
|
|
11671
|
+
);
|
|
11672
|
+
if (patchText !== void 0 && patchText.trim() !== "") {
|
|
11673
|
+
submit("item/fileChange/outputDelta", {
|
|
11674
|
+
turnId: turn.turnId,
|
|
11675
|
+
itemId,
|
|
11676
|
+
delta: patchText
|
|
11677
|
+
});
|
|
11678
|
+
}
|
|
11679
|
+
return;
|
|
11658
11680
|
}
|
|
11681
|
+
submit("item/commandExecution/outputDelta", {
|
|
11682
|
+
turnId: turn.turnId,
|
|
11683
|
+
itemId,
|
|
11684
|
+
command: claudeToolCommandLabel(event.toolName, event.input),
|
|
11685
|
+
stream: "stdout",
|
|
11686
|
+
delta: "\n"
|
|
11687
|
+
});
|
|
11659
11688
|
return;
|
|
11660
11689
|
}
|
|
11661
11690
|
case "tool_result": {
|
|
@@ -11809,6 +11838,9 @@ function claudeAssistantPipelineItemId(itemKey) {
|
|
|
11809
11838
|
const streamIndex = itemKey.match(/^assistant-stream-(\d+)$/)?.[1];
|
|
11810
11839
|
return streamIndex === void 0 ? itemKey : `content-block-${streamIndex}`;
|
|
11811
11840
|
}
|
|
11841
|
+
function claudeReasoningPipelineItemId(itemKey) {
|
|
11842
|
+
return `reasoning-${claudeAssistantPipelineItemId(itemKey)}`;
|
|
11843
|
+
}
|
|
11812
11844
|
function isFileChangeTool(toolName2) {
|
|
11813
11845
|
switch (toolName2) {
|
|
11814
11846
|
case "Edit":
|
|
@@ -11823,6 +11855,9 @@ function isFileChangeTool(toolName2) {
|
|
|
11823
11855
|
function isWebSearchTool(toolName2) {
|
|
11824
11856
|
return toolName2 === "WebSearch" || toolName2 === "WebFetch";
|
|
11825
11857
|
}
|
|
11858
|
+
function isClaudePlanTool(toolName2) {
|
|
11859
|
+
return toolName2 === "TodoWrite" || toolName2 === "TaskCreate" || toolName2 === "TaskUpdate";
|
|
11860
|
+
}
|
|
11826
11861
|
function fileChangeTargetExists(toolName2, input, sessionCwd) {
|
|
11827
11862
|
if (!isFileChangeTool(toolName2)) {
|
|
11828
11863
|
return void 0;
|
|
@@ -11895,9 +11930,18 @@ ${body}
|
|
|
11895
11930
|
*** End Patch
|
|
11896
11931
|
`;
|
|
11897
11932
|
}
|
|
11933
|
+
function claudePlanInputItems(input) {
|
|
11934
|
+
if (Array.isArray(input.todos)) {
|
|
11935
|
+
return input.todos;
|
|
11936
|
+
}
|
|
11937
|
+
if (Array.isArray(input.tasks)) {
|
|
11938
|
+
return input.tasks;
|
|
11939
|
+
}
|
|
11940
|
+
return [];
|
|
11941
|
+
}
|
|
11898
11942
|
function claudePlanItem(itemId, input) {
|
|
11899
|
-
const
|
|
11900
|
-
const lines =
|
|
11943
|
+
const items = claudePlanInputItems(input);
|
|
11944
|
+
const lines = items.flatMap((todo) => {
|
|
11901
11945
|
const value = objectValue(todo);
|
|
11902
11946
|
if (value === void 0) {
|
|
11903
11947
|
return [];
|
|
@@ -11971,7 +12015,7 @@ function objectValue2(value) {
|
|
|
11971
12015
|
return isJsonObject(value) ? value : void 0;
|
|
11972
12016
|
}
|
|
11973
12017
|
function claudeTodoWritePlanSteps(input) {
|
|
11974
|
-
const todos = Array.isArray(input.todos) ? input.todos : [];
|
|
12018
|
+
const todos = Array.isArray(input.todos) ? input.todos : Array.isArray(input.tasks) ? input.tasks : [];
|
|
11975
12019
|
const steps = todos.flatMap((todo) => {
|
|
11976
12020
|
const value = objectValue2(todo);
|
|
11977
12021
|
if (value === void 0) {
|
|
@@ -12042,6 +12086,29 @@ function createClaudeCodePlanMirror(args) {
|
|
|
12042
12086
|
settle: () => flight
|
|
12043
12087
|
};
|
|
12044
12088
|
}
|
|
12089
|
+
async function seedClaudeCodingJobGoal(args) {
|
|
12090
|
+
const goal = args.goal.trim().slice(0, 2e3);
|
|
12091
|
+
if (goal === "") {
|
|
12092
|
+
return false;
|
|
12093
|
+
}
|
|
12094
|
+
try {
|
|
12095
|
+
await args.upsertCodingJobPlan(
|
|
12096
|
+
{ thread_id: args.threadId, goal },
|
|
12097
|
+
{ signal: args.signal }
|
|
12098
|
+
);
|
|
12099
|
+
args.log("claude_code.goal_seeded", {
|
|
12100
|
+
thread_id: args.threadId,
|
|
12101
|
+
goal_length: goal.length
|
|
12102
|
+
});
|
|
12103
|
+
return true;
|
|
12104
|
+
} catch (error) {
|
|
12105
|
+
args.log("claude_code.goal_seed_failed", {
|
|
12106
|
+
thread_id: args.threadId,
|
|
12107
|
+
message: error instanceof Error ? error.message : String(error)
|
|
12108
|
+
});
|
|
12109
|
+
return false;
|
|
12110
|
+
}
|
|
12111
|
+
}
|
|
12045
12112
|
var todoStatusToPlanStepStatus, planStepTitleMaxLength, planStepDescriptionMaxLength, planStepsMaxCount;
|
|
12046
12113
|
var init_claudeCodePlanMirror = __esm({
|
|
12047
12114
|
"src/claudeCodePlanMirror.ts"() {
|
|
@@ -12597,6 +12664,87 @@ var init_claudeCodeLiveBashOutput = __esm({
|
|
|
12597
12664
|
}
|
|
12598
12665
|
});
|
|
12599
12666
|
|
|
12667
|
+
// src/claudeCodeTurnStallWatchdog.ts
|
|
12668
|
+
function createClaudeCodeTurnStallWatchdog(options) {
|
|
12669
|
+
if (!(options.thresholdMs > 0)) {
|
|
12670
|
+
throw new Error("Claude turn-stall threshold must be > 0");
|
|
12671
|
+
}
|
|
12672
|
+
const now = options.now ?? (() => Date.now());
|
|
12673
|
+
const setTimer = options.setTimer ?? ((callback, delayMs) => {
|
|
12674
|
+
const handle2 = setTimeout(callback, delayMs);
|
|
12675
|
+
handle2.unref?.();
|
|
12676
|
+
return handle2;
|
|
12677
|
+
});
|
|
12678
|
+
const clearTimer = options.clearTimer ?? ((handle2) => clearTimeout(handle2));
|
|
12679
|
+
const isTurnActive = options.isTurnActive ?? (() => true);
|
|
12680
|
+
let handle;
|
|
12681
|
+
let lastKickMs = now();
|
|
12682
|
+
let stopped = false;
|
|
12683
|
+
let fired = false;
|
|
12684
|
+
let wasActive = isTurnActive();
|
|
12685
|
+
const disarm = () => {
|
|
12686
|
+
if (handle !== void 0) {
|
|
12687
|
+
clearTimer(handle);
|
|
12688
|
+
handle = void 0;
|
|
12689
|
+
}
|
|
12690
|
+
};
|
|
12691
|
+
const onTimeout = () => {
|
|
12692
|
+
handle = void 0;
|
|
12693
|
+
if (stopped || fired) {
|
|
12694
|
+
return;
|
|
12695
|
+
}
|
|
12696
|
+
const active = isTurnActive();
|
|
12697
|
+
if (!active) {
|
|
12698
|
+
wasActive = false;
|
|
12699
|
+
arm();
|
|
12700
|
+
return;
|
|
12701
|
+
}
|
|
12702
|
+
if (!wasActive) {
|
|
12703
|
+
wasActive = true;
|
|
12704
|
+
lastKickMs = now();
|
|
12705
|
+
arm();
|
|
12706
|
+
return;
|
|
12707
|
+
}
|
|
12708
|
+
const silentMs = now() - lastKickMs;
|
|
12709
|
+
if (silentMs < options.thresholdMs) {
|
|
12710
|
+
arm();
|
|
12711
|
+
return;
|
|
12712
|
+
}
|
|
12713
|
+
fired = true;
|
|
12714
|
+
options.onStall({ silentMs });
|
|
12715
|
+
};
|
|
12716
|
+
function arm() {
|
|
12717
|
+
if (stopped || fired) {
|
|
12718
|
+
return;
|
|
12719
|
+
}
|
|
12720
|
+
disarm();
|
|
12721
|
+
const delay = isTurnActive() ? Math.max(0, options.thresholdMs - (now() - lastKickMs)) : options.thresholdMs;
|
|
12722
|
+
handle = setTimer(onTimeout, delay);
|
|
12723
|
+
}
|
|
12724
|
+
arm();
|
|
12725
|
+
return {
|
|
12726
|
+
kick: () => {
|
|
12727
|
+
if (stopped || fired) {
|
|
12728
|
+
return;
|
|
12729
|
+
}
|
|
12730
|
+
wasActive = true;
|
|
12731
|
+
lastKickMs = now();
|
|
12732
|
+
arm();
|
|
12733
|
+
},
|
|
12734
|
+
stop: () => {
|
|
12735
|
+
stopped = true;
|
|
12736
|
+
disarm();
|
|
12737
|
+
}
|
|
12738
|
+
};
|
|
12739
|
+
}
|
|
12740
|
+
var defaultClaudeCodeTurnStallThresholdMs;
|
|
12741
|
+
var init_claudeCodeTurnStallWatchdog = __esm({
|
|
12742
|
+
"src/claudeCodeTurnStallWatchdog.ts"() {
|
|
12743
|
+
"use strict";
|
|
12744
|
+
defaultClaudeCodeTurnStallThresholdMs = 10 * 6e4;
|
|
12745
|
+
}
|
|
12746
|
+
});
|
|
12747
|
+
|
|
12600
12748
|
// src/claudeCodeSession.ts
|
|
12601
12749
|
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
12602
12750
|
import { homedir as homedir6 } from "node:os";
|
|
@@ -12864,8 +13012,49 @@ async function startClaudeCodeSession(options) {
|
|
|
12864
13012
|
lastCompletedTurnBody: void 0
|
|
12865
13013
|
};
|
|
12866
13014
|
const streamUsageTracker = createClaudeCodeStreamUsageTracker();
|
|
13015
|
+
let stallReason;
|
|
13016
|
+
let signalStall;
|
|
13017
|
+
const stallSignal = new Promise((resolve12) => {
|
|
13018
|
+
signalStall = resolve12;
|
|
13019
|
+
});
|
|
13020
|
+
const stallWatchdog = options.turnStallThresholdMs !== void 0 && options.turnStallThresholdMs > 0 ? createClaudeCodeTurnStallWatchdog({
|
|
13021
|
+
thresholdMs: options.turnStallThresholdMs,
|
|
13022
|
+
isTurnActive: options.turnStallIsActive,
|
|
13023
|
+
onStall: ({ silentMs }) => {
|
|
13024
|
+
stallReason = `Claude Code turn stalled: no SDK output for ${Math.round(
|
|
13025
|
+
silentMs / 1e3
|
|
13026
|
+
)}s (threshold ${Math.round(
|
|
13027
|
+
(options.turnStallThresholdMs ?? 0) / 1e3
|
|
13028
|
+
)}s)`;
|
|
13029
|
+
options.abortController?.abort();
|
|
13030
|
+
signalStall?.();
|
|
13031
|
+
}
|
|
13032
|
+
}) : void 0;
|
|
12867
13033
|
try {
|
|
12868
|
-
|
|
13034
|
+
const iterator = runner(options)[Symbol.asyncIterator]();
|
|
13035
|
+
for (; ; ) {
|
|
13036
|
+
let nextStep;
|
|
13037
|
+
if (stallWatchdog) {
|
|
13038
|
+
const nextPromise = iterator.next().then((result) => ({ kind: "next", result }));
|
|
13039
|
+
nextPromise.catch(() => void 0);
|
|
13040
|
+
const raced = await Promise.race([
|
|
13041
|
+
nextPromise,
|
|
13042
|
+
stallSignal.then(() => ({ kind: "stall" }))
|
|
13043
|
+
]);
|
|
13044
|
+
if (raced.kind === "stall") {
|
|
13045
|
+
void Promise.resolve(iterator.return?.()).catch(() => void 0);
|
|
13046
|
+
break;
|
|
13047
|
+
}
|
|
13048
|
+
nextStep = raced;
|
|
13049
|
+
} else {
|
|
13050
|
+
nextStep = { kind: "next", result: await iterator.next() };
|
|
13051
|
+
}
|
|
13052
|
+
const step = nextStep;
|
|
13053
|
+
if (step.result.done === true) {
|
|
13054
|
+
break;
|
|
13055
|
+
}
|
|
13056
|
+
const message = step.result.value;
|
|
13057
|
+
stallWatchdog?.kick();
|
|
12869
13058
|
state.sessionId = state.sessionId ?? extractClaudeSessionId(message);
|
|
12870
13059
|
const sessionId = extractClaudeSessionId(message) ?? state.sessionId;
|
|
12871
13060
|
if (sessionId !== void 0 && !state.startedSessionIds.has(sessionId)) {
|
|
@@ -12931,8 +13120,12 @@ async function startClaudeCodeSession(options) {
|
|
|
12931
13120
|
}
|
|
12932
13121
|
}
|
|
12933
13122
|
} finally {
|
|
13123
|
+
stallWatchdog?.stop();
|
|
12934
13124
|
options.streamingInput?.close();
|
|
12935
13125
|
}
|
|
13126
|
+
if (stallReason !== void 0) {
|
|
13127
|
+
return await failClaudeCodeSession(options, state.sessionId, stallReason);
|
|
13128
|
+
}
|
|
12936
13129
|
return completeClaudeCodeSession(options, state);
|
|
12937
13130
|
}
|
|
12938
13131
|
async function emitClaudeCodeTurnCompleted(options, state) {
|
|
@@ -13325,13 +13518,16 @@ function assistantTranscriptEvents(message, sessionId) {
|
|
|
13325
13518
|
contentBlockItemKey(block, index)
|
|
13326
13519
|
)
|
|
13327
13520
|
);
|
|
13328
|
-
const
|
|
13521
|
+
const hasTextContentBlock = content.some(
|
|
13522
|
+
(block) => stringValue(objectValue(block)?.type) === "text"
|
|
13523
|
+
);
|
|
13524
|
+
const directText = hasTextContentBlock ? void 0 : nonEmptyText(stringValue(message?.text));
|
|
13329
13525
|
return directText === void 0 ? contentEvents : [
|
|
13330
13526
|
...contentEvents,
|
|
13331
13527
|
{
|
|
13332
13528
|
type: "assistant_message",
|
|
13333
13529
|
sessionId,
|
|
13334
|
-
itemKey:
|
|
13530
|
+
itemKey: `content-block-${content.length}`,
|
|
13335
13531
|
content: directText,
|
|
13336
13532
|
streamState: "completed"
|
|
13337
13533
|
}
|
|
@@ -13352,7 +13548,7 @@ function userTranscriptEvents(message, sessionId) {
|
|
|
13352
13548
|
{
|
|
13353
13549
|
type: "tool_result",
|
|
13354
13550
|
sessionId,
|
|
13355
|
-
itemKey:
|
|
13551
|
+
itemKey: stringValue(value?.tool_use_id) ?? stringValue(value?.id) ?? `content-block-${index}`,
|
|
13356
13552
|
toolName: void 0,
|
|
13357
13553
|
content: visibleToolResultContent(value),
|
|
13358
13554
|
isError: value?.is_error === true
|
|
@@ -13438,12 +13634,14 @@ function unknownTranscriptEvent(message, sessionId) {
|
|
|
13438
13634
|
}
|
|
13439
13635
|
function streamItemKey(event, _message) {
|
|
13440
13636
|
const index = integerValue(event?.index);
|
|
13441
|
-
|
|
13442
|
-
return stringValue(event?.content_block_id) ?? stringValue(contentBlock?.id) ?? stringValue(contentBlock?.tool_use_id) ?? stringValue(contentBlock?.uuid) ?? (index === void 0 ? "assistant-stream" : `assistant-stream-${index}`);
|
|
13637
|
+
return index === void 0 ? "assistant-stream" : `content-block-${index}`;
|
|
13443
13638
|
}
|
|
13444
13639
|
function contentBlockItemKey(block, index) {
|
|
13445
13640
|
const value = objectValue(block);
|
|
13446
|
-
|
|
13641
|
+
if (stringValue(value?.type) === "tool_use") {
|
|
13642
|
+
return stringValue(value?.id) ?? stringValue(value?.tool_use_id) ?? `content-block-${index}`;
|
|
13643
|
+
}
|
|
13644
|
+
return `content-block-${index}`;
|
|
13447
13645
|
}
|
|
13448
13646
|
function visibleToolResultContent(block) {
|
|
13449
13647
|
const content = block?.content;
|
|
@@ -13491,6 +13689,7 @@ var init_claudeCodeSession = __esm({
|
|
|
13491
13689
|
"use strict";
|
|
13492
13690
|
init_json();
|
|
13493
13691
|
init_claudeCodeLiveBashOutput();
|
|
13692
|
+
init_claudeCodeTurnStallWatchdog();
|
|
13494
13693
|
claudeRateLimitWindowLabels = {
|
|
13495
13694
|
five_hour: "5h window",
|
|
13496
13695
|
seven_day: "7d window",
|
|
@@ -18520,7 +18719,7 @@ var linzumiCliVersion, linzumiCliVersionText;
|
|
|
18520
18719
|
var init_version = __esm({
|
|
18521
18720
|
"src/version.ts"() {
|
|
18522
18721
|
"use strict";
|
|
18523
|
-
linzumiCliVersion = "0.0.
|
|
18722
|
+
linzumiCliVersion = "0.0.88-beta";
|
|
18524
18723
|
linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
18525
18724
|
}
|
|
18526
18725
|
});
|
|
@@ -20909,7 +21108,7 @@ function createLinzumiMcpApiClient(options) {
|
|
|
20909
21108
|
const baseUrl = kandanHttpBaseUrl(options.kandanUrl);
|
|
20910
21109
|
const apiPrefix = options.authMode === "personal-agent-delegation" ? "/api/v2/personal-agent-mcp" : "/api/v2/local-runner-mcp";
|
|
20911
21110
|
const operatingMode = options.operatingMode ?? "text";
|
|
20912
|
-
const request = async (method, path2, params) => {
|
|
21111
|
+
const request = async (method, path2, params, requestOptions = {}) => {
|
|
20913
21112
|
const url = new URL(path2, baseUrl);
|
|
20914
21113
|
const paramsWithMode = {
|
|
20915
21114
|
...params,
|
|
@@ -20917,7 +21116,11 @@ function createLinzumiMcpApiClient(options) {
|
|
|
20917
21116
|
};
|
|
20918
21117
|
const requestInit = {
|
|
20919
21118
|
method,
|
|
20920
|
-
headers: { authorization: `Bearer ${options.accessToken}` }
|
|
21119
|
+
headers: { authorization: `Bearer ${options.accessToken}` },
|
|
21120
|
+
// An aborted signal cancels the in-flight fetch so a caller that has
|
|
21121
|
+
// already given up (e.g. a timed-out goal seed) cannot land a LATE POST
|
|
21122
|
+
// and clobber state written after it gave up (#1864 finding 2).
|
|
21123
|
+
...requestOptions.signal === void 0 ? {} : { signal: requestOptions.signal }
|
|
20921
21124
|
};
|
|
20922
21125
|
if (method === "GET") {
|
|
20923
21126
|
for (const [key, value] of Object.entries(paramsWithMode)) {
|
|
@@ -20950,9 +21153,11 @@ function createLinzumiMcpApiClient(options) {
|
|
|
20950
21153
|
getMessage: (params) => request("GET", `${apiPrefix}/message`, params),
|
|
20951
21154
|
getThread: (params) => request("GET", `${apiPrefix}/thread`, params),
|
|
20952
21155
|
getChannel: (params) => request("GET", `${apiPrefix}/channel`, params),
|
|
21156
|
+
searchWorkspace: (params) => request("GET", `${apiPrefix}/search`, params),
|
|
21157
|
+
askWorkspace: (params) => request("POST", `${apiPrefix}/ask`, params),
|
|
20953
21158
|
getCodingJobMetadata: (params) => request("GET", `${apiPrefix}/coding-job-metadata`, params),
|
|
20954
21159
|
renameCodingJob: (params) => request("POST", `${apiPrefix}/coding-job-title`, params),
|
|
20955
|
-
upsertCodingJobPlan: (params) => request("POST", `${apiPrefix}/coding-job-plan`, params),
|
|
21160
|
+
upsertCodingJobPlan: (params, requestOptions) => request("POST", `${apiPrefix}/coding-job-plan`, params, requestOptions),
|
|
20956
21161
|
replaceCodingJobPlanSteps: (params) => request("POST", `${apiPrefix}/coding-job-plan/steps`, params),
|
|
20957
21162
|
updateCodingJobPlanStep: (params) => request("POST", `${apiPrefix}/coding-job-plan/step`, params),
|
|
20958
21163
|
linkCodingJobPullRequest: (params) => request("POST", `${apiPrefix}/coding-job-primary-pr`, params),
|
|
@@ -25879,12 +26084,18 @@ function truncateFailureDetail(message) {
|
|
|
25879
26084
|
}
|
|
25880
26085
|
function commanderDeveloperInstructions(args) {
|
|
25881
26086
|
const agentLabel = args.agentLabel ?? "Codex";
|
|
25882
|
-
const
|
|
25883
|
-
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
|
|
25887
|
-
|
|
26087
|
+
const planOpener = `Before you begin ANY work - and again whenever the job
|
|
26088
|
+
changes - call linzumi_upsert_coding_job_plan to set or refresh the current
|
|
26089
|
+
goal and the high-level plan. Always record the plan up front, before starting
|
|
26090
|
+
work, so the user can see a high-level view of what you are doing at all times
|
|
26091
|
+
and trust your work. Keep it accurate as you go: whenever you discover steps to
|
|
26092
|
+
add, steps that are no longer needed, or the nature of the job changes, update
|
|
26093
|
+
it with linzumi_upsert_coding_job_plan.`;
|
|
26094
|
+
const planStepsInstruction = args.planTool === "todo-write" ? `${planOpener}
|
|
26095
|
+
Record and maintain the plan STEPS with the TodoWrite tool. Linzumi mirrors
|
|
26096
|
+
your TodoWrite list into the coding-job plan steps automatically, one TodoWrite
|
|
26097
|
+
item per plan step.` : `${planOpener}
|
|
26098
|
+
Replace the plan STEPS with linzumi_replace_coding_job_plan_steps.`;
|
|
25888
26099
|
const planUpdateInstruction = args.planTool === "todo-write" ? `As work proceeds, update each step's status with TodoWrite. Do not call
|
|
25889
26100
|
linzumi_replace_coding_job_plan_steps or linzumi_update_coding_job_plan_step
|
|
25890
26101
|
directly; TodoWrite is the source of truth for the plan steps.` : `As work proceeds, update each step with linzumi_update_coding_job_plan_step.`;
|
|
@@ -26273,6 +26484,14 @@ async function startCodexProviderInstance(args) {
|
|
|
26273
26484
|
}
|
|
26274
26485
|
};
|
|
26275
26486
|
}
|
|
26487
|
+
function claudeCodeTurnStallThresholdMs(env) {
|
|
26488
|
+
const raw = env.LINZUMI_CLAUDE_TURN_STALL_MS?.trim();
|
|
26489
|
+
if (raw === void 0 || raw === "") {
|
|
26490
|
+
return defaultClaudeCodeTurnStallThresholdMs;
|
|
26491
|
+
}
|
|
26492
|
+
const parsed = Number.parseInt(raw, 10);
|
|
26493
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultClaudeCodeTurnStallThresholdMs;
|
|
26494
|
+
}
|
|
26276
26495
|
function createClaudeCodeInputQueue(input) {
|
|
26277
26496
|
const pendingMessages = [
|
|
26278
26497
|
claudeCodeUserInputMessage(input.content)
|
|
@@ -26644,12 +26863,47 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
26644
26863
|
fetchImpl: args.options.fetch,
|
|
26645
26864
|
operatingMode: "text"
|
|
26646
26865
|
});
|
|
26866
|
+
const seededGoal = workDescription.trim().slice(0, 2e3);
|
|
26647
26867
|
const planMirror = createClaudeCodePlanMirror({
|
|
26648
26868
|
replacePlanSteps: planMirrorClient.replaceCodingJobPlanSteps,
|
|
26649
26869
|
threadId,
|
|
26650
|
-
goal:
|
|
26870
|
+
goal: seededGoal,
|
|
26651
26871
|
log: args.log
|
|
26652
26872
|
});
|
|
26873
|
+
const seedCodingJobGoalBeforeTurn = async () => {
|
|
26874
|
+
if (args.resumeSessionId !== void 0) {
|
|
26875
|
+
return;
|
|
26876
|
+
}
|
|
26877
|
+
const seedTimeoutMs = 5e3;
|
|
26878
|
+
const seedAbort = new AbortController();
|
|
26879
|
+
let timeoutHandle;
|
|
26880
|
+
const timedOut = new Promise((resolve12) => {
|
|
26881
|
+
timeoutHandle = setTimeout(() => {
|
|
26882
|
+
seedAbort.abort(new Error("claude goal seed timed out"));
|
|
26883
|
+
args.log("claude_code.goal_seed_timeout", {
|
|
26884
|
+
thread_id: threadId,
|
|
26885
|
+
timeout_ms: seedTimeoutMs
|
|
26886
|
+
});
|
|
26887
|
+
resolve12();
|
|
26888
|
+
}, seedTimeoutMs);
|
|
26889
|
+
});
|
|
26890
|
+
try {
|
|
26891
|
+
await Promise.race([
|
|
26892
|
+
seedClaudeCodingJobGoal({
|
|
26893
|
+
upsertCodingJobPlan: planMirrorClient.upsertCodingJobPlan,
|
|
26894
|
+
threadId,
|
|
26895
|
+
goal: seededGoal,
|
|
26896
|
+
log: args.log,
|
|
26897
|
+
signal: seedAbort.signal
|
|
26898
|
+
}),
|
|
26899
|
+
timedOut
|
|
26900
|
+
]);
|
|
26901
|
+
} finally {
|
|
26902
|
+
if (timeoutHandle !== void 0) {
|
|
26903
|
+
clearTimeout(timeoutHandle);
|
|
26904
|
+
}
|
|
26905
|
+
}
|
|
26906
|
+
};
|
|
26653
26907
|
const adapter = createClaudeCodeSessionPipeline({
|
|
26654
26908
|
instanceId: args.instanceId,
|
|
26655
26909
|
cwd: args.cwd,
|
|
@@ -26955,6 +27209,7 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
26955
27209
|
});
|
|
26956
27210
|
const runSession = async () => {
|
|
26957
27211
|
let result2;
|
|
27212
|
+
await seedCodingJobGoalBeforeTurn();
|
|
26958
27213
|
try {
|
|
26959
27214
|
try {
|
|
26960
27215
|
result2 = await startClaudeCodeSession({
|
|
@@ -26983,6 +27238,20 @@ async function startClaudeCodeProviderInstance(args) {
|
|
|
26983
27238
|
resumeSessionId: args.resumeSessionId,
|
|
26984
27239
|
abortController,
|
|
26985
27240
|
streamingInput: inputQueue,
|
|
27241
|
+
// PROD-RESILIENCE turn-stall watchdog (real-SDK sessions only; mock
|
|
27242
|
+
// runners drive their own timing). A turn that stays in flight with
|
|
27243
|
+
// no SDK output past the threshold is a wedged SILENT subprocess: the
|
|
27244
|
+
// watchdog aborts it and fails the turn so the thread reconnects,
|
|
27245
|
+
// instead of sitting "processing" forever (the ~28-min-hang incident).
|
|
27246
|
+
// The in-flight predicate (currentSourceSeq() is defined only while a
|
|
27247
|
+
// turn is queued/active) keeps a keep-alive session idling between
|
|
27248
|
+
// turns from being a false positive.
|
|
27249
|
+
...args.options.claudeCodeRunner === void 0 ? {
|
|
27250
|
+
turnStallThresholdMs: claudeCodeTurnStallThresholdMs(
|
|
27251
|
+
process.env
|
|
27252
|
+
),
|
|
27253
|
+
turnStallIsActive: () => inputQueue.currentSourceSeq() !== void 0
|
|
27254
|
+
} : {},
|
|
26986
27255
|
runner: args.options.claudeCodeRunner,
|
|
26987
27256
|
canUseTool,
|
|
26988
27257
|
...mcpServers === void 0 ? {} : { mcpServers },
|
|
@@ -29343,6 +29612,7 @@ var init_runner = __esm({
|
|
|
29343
29612
|
init_engineChildReaper();
|
|
29344
29613
|
init_claudeCodeLiveBashOutput();
|
|
29345
29614
|
init_claudeCodeSession();
|
|
29615
|
+
init_claudeCodeTurnStallWatchdog();
|
|
29346
29616
|
init_codexAppServer();
|
|
29347
29617
|
init_codexProjectTrust();
|
|
29348
29618
|
init_codexRuntimeOptions();
|
|
@@ -66096,6 +66366,60 @@ async function runMcpServer(args) {
|
|
|
66096
66366
|
},
|
|
66097
66367
|
async (params) => mcpJsonResult(await client.getChannel(params))
|
|
66098
66368
|
);
|
|
66369
|
+
server.tool(
|
|
66370
|
+
"linzumi_search_workspace",
|
|
66371
|
+
"Search the Linzumi workspace with hybrid lexical+semantic retrieval, scoped to what the authorizing user can access. Returns scored content chunks with provenance (channel, thread, authored time) and a permalink to cite.",
|
|
66372
|
+
{
|
|
66373
|
+
workspace: external_exports2.string().optional().describe(
|
|
66374
|
+
"Workspace slug. Omit to use the authenticated token scope."
|
|
66375
|
+
),
|
|
66376
|
+
query: external_exports2.string().min(1).describe("Natural-language or keyword query to retrieve against."),
|
|
66377
|
+
limit: external_exports2.number().int().min(1).max(25).optional().describe("Maximum results to return (default 10, server-capped)."),
|
|
66378
|
+
source_types: external_exports2.array(
|
|
66379
|
+
external_exports2.enum([
|
|
66380
|
+
"message",
|
|
66381
|
+
"coding_job",
|
|
66382
|
+
"github_pr",
|
|
66383
|
+
"github_pr_review",
|
|
66384
|
+
"github_check_run",
|
|
66385
|
+
"github_workflow_run",
|
|
66386
|
+
"github_diff",
|
|
66387
|
+
"dictation",
|
|
66388
|
+
"file"
|
|
66389
|
+
])
|
|
66390
|
+
).optional().describe("Restrict results to these corpora; omit for all."),
|
|
66391
|
+
authored_after: external_exports2.string().optional().describe(
|
|
66392
|
+
"ISO8601 instant; only content authored at or after this time."
|
|
66393
|
+
),
|
|
66394
|
+
authored_before: external_exports2.string().optional().describe(
|
|
66395
|
+
"ISO8601 instant; only content authored at or before this time."
|
|
66396
|
+
),
|
|
66397
|
+
repo: external_exports2.string().optional().describe(
|
|
66398
|
+
"Restrict GitHub-sourced results to one repository by full name (owner/name)."
|
|
66399
|
+
),
|
|
66400
|
+
check_status: external_exports2.enum(["pending", "success", "failed"]).optional().describe(
|
|
66401
|
+
"Restrict check and workflow results to one conclusion bucket."
|
|
66402
|
+
)
|
|
66403
|
+
},
|
|
66404
|
+
async (params) => mcpJsonResult(await client.searchWorkspace(params))
|
|
66405
|
+
);
|
|
66406
|
+
server.tool(
|
|
66407
|
+
"linzumi_ask_workspace",
|
|
66408
|
+
'Ask the Linzumi workspace a natural-language question and get a synthesized, citation-backed answer over what the authorizing user can access. Costs an LLM call on cache misses, so prefer linzumi_search_workspace for simple lookups. A "refused" status means there was not enough accessible evidence - treat it as "the workspace does not know", never retry verbatim.',
|
|
66409
|
+
{
|
|
66410
|
+
workspace: external_exports2.string().optional().describe(
|
|
66411
|
+
"Workspace slug. Omit to use the authenticated token scope."
|
|
66412
|
+
),
|
|
66413
|
+
question: external_exports2.string().min(1).max(2e3).describe("Natural-language question to answer with citations."),
|
|
66414
|
+
channel: external_exports2.string().optional().describe(
|
|
66415
|
+
"Optional channel slug providing conversational context; never widens access."
|
|
66416
|
+
),
|
|
66417
|
+
allow_cached: external_exports2.boolean().optional().describe(
|
|
66418
|
+
"Allow serving a cached synthesis when the evidence set is unchanged (default true). Set false to force fresh synthesis."
|
|
66419
|
+
)
|
|
66420
|
+
},
|
|
66421
|
+
async (params) => mcpJsonResult(await client.askWorkspace(params))
|
|
66422
|
+
);
|
|
66099
66423
|
server.tool(
|
|
66100
66424
|
"linzumi_get_coding_job_metadata",
|
|
66101
66425
|
"Read the active coding job workflow metadata for a thread, including the agent-owned goal, ordered plan steps, step lock versions, and workflow status.",
|
package/package.json
CHANGED