@linzumi/cli 0.0.87-beta → 0.0.89-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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +715 -285
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -58,7 +58,7 @@ Install the CLI or run it with `npx`:
58
58
  ```bash
59
59
  npm install -g @linzumi/cli@latest
60
60
  npx -y @linzumi/cli@latest signup
61
- npx -y @linzumi/cli@0.0.87-beta --version
61
+ npx -y @linzumi/cli@0.0.89-beta --version
62
62
  linzumi --version
63
63
  ```
64
64
 
package/dist/index.js CHANGED
@@ -1936,6 +1936,228 @@ var init_codexRuntimeOptions = __esm({
1936
1936
  }
1937
1937
  });
1938
1938
 
1939
+ // src/linzumiContext.ts
1940
+ function parseLinzumiConversationContext(value, warn = console.warn) {
1941
+ if (value === void 0) {
1942
+ return void 0;
1943
+ }
1944
+ const context = objectValue(value);
1945
+ if (context === void 0) {
1946
+ return invalidLinzumiContext("linzumiContext must be an object", warn);
1947
+ }
1948
+ const threadId = stringValue(context.threadId);
1949
+ const creatorUserId = stringValue(context.creatorUserId) ?? integerValue(context.creatorUserId)?.toString();
1950
+ const creatorUsername = stringValue(context.creatorUsername);
1951
+ const conversationUrl = stringValue(context.conversationUrl);
1952
+ if (threadId === void 0 || creatorUserId === void 0 || creatorUsername === void 0 || conversationUrl === void 0) {
1953
+ return invalidLinzumiContext(
1954
+ "linzumiContext must include threadId, creatorUserId, creatorUsername, and conversationUrl",
1955
+ warn
1956
+ );
1957
+ }
1958
+ return { threadId, creatorUserId, creatorUsername, conversationUrl };
1959
+ }
1960
+ function formatLinzumiConversationContextForPrompt(context) {
1961
+ return `<linzumi_context>
1962
+ <linzumi_thread_id>${escapeXmlText(context.threadId)}</linzumi_thread_id>
1963
+ <linzumi_creator_user_id>${escapeXmlText(context.creatorUserId)}</linzumi_creator_user_id>
1964
+ <linzumi_creator_username>${escapeXmlText(context.creatorUsername)}</linzumi_creator_username>
1965
+ <linzumi_conversation_url>${escapeXmlText(context.conversationUrl)}</linzumi_conversation_url>
1966
+ </linzumi_context>
1967
+
1968
+ <linzumi_pr_footer_instruction>
1969
+ For any GitHub pull request you open from this session, include this Markdown footer link in the PR description: [Continue this on Linzumi](${context.conversationUrl})
1970
+ </linzumi_pr_footer_instruction>`;
1971
+ }
1972
+ function invalidLinzumiContext(reason, warn) {
1973
+ warn(`Ignoring invalid linzumiContext: ${reason}`);
1974
+ return void 0;
1975
+ }
1976
+ function escapeXmlText(value) {
1977
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1978
+ }
1979
+ var init_linzumiContext = __esm({
1980
+ "src/linzumiContext.ts"() {
1981
+ "use strict";
1982
+ init_json();
1983
+ }
1984
+ });
1985
+
1986
+ // src/commanderDeveloperInstructions.ts
1987
+ function commanderDeveloperInstructions(args) {
1988
+ const agentLabel = args.agentLabel ?? "Codex";
1989
+ const planOpener = `Before you begin ANY work - and again whenever the job
1990
+ changes - call linzumi_upsert_coding_job_plan to set or refresh the current
1991
+ goal and the high-level plan. Always record the plan up front, before starting
1992
+ work, so the user can see a high-level view of what you are doing at all times
1993
+ and trust your work. Keep it accurate as you go: whenever you discover steps to
1994
+ add, steps that are no longer needed, or the nature of the job changes, update
1995
+ it with linzumi_upsert_coding_job_plan.`;
1996
+ const planStepsInstruction = args.planTool === "todo-write" ? `${planOpener}
1997
+ Record and maintain the plan STEPS with the TodoWrite tool. Linzumi mirrors
1998
+ your TodoWrite list into the coding-job plan steps automatically, one TodoWrite
1999
+ item per plan step.` : `${planOpener}
2000
+ Replace the plan STEPS with linzumi_replace_coding_job_plan_steps.`;
2001
+ const planUpdateInstruction = args.planTool === "todo-write" ? `As work proceeds, update each step's status with TodoWrite. Do not call
2002
+ linzumi_replace_coding_job_plan_steps or linzumi_update_coding_job_plan_step
2003
+ directly; TodoWrite is the source of truth for the plan steps.` : `As work proceeds, update each step with linzumi_update_coding_job_plan_step.`;
2004
+ const customPrompt = args.developerPrompt === void 0 ? "" : `
2005
+ <invoker_developer_prompt>
2006
+ ${args.developerPrompt}
2007
+ </invoker_developer_prompt>
2008
+ `;
2009
+ const linzumiContext = args.linzumiContext === void 0 ? "" : `
2010
+ ${formatLinzumiConversationContextForPrompt(args.linzumiContext)}
2011
+ `;
2012
+ return `<context>
2013
+ You are a Linzumi ${agentLabel} session launched by the Linzumi Commander.
2014
+ The Commander is the local bridge process that connects this computer to the
2015
+ user's Linzumi workspace, secure preview tunnel, browser VS Code editor, and
2016
+ thread transcript. Your work and progress are relayed into that Linzumi thread.
2017
+ </context>
2018
+
2019
+ <term_definitions>
2020
+ Bootstrapper ${agentLabel}: The outer setup agent that claimed/signup, generated the
2021
+ demo folder when applicable, and asked Linzumi to start this session. It does
2022
+ not own implementation work.
2023
+ Linzumi Commander: The local long-running process that launched this ${agentLabel}
2024
+ session, enforces the trusted folder, and forwards descendant preview ports to
2025
+ Linzumi.
2026
+ Linzumi ${agentLabel} session: You, the inner ${agentLabel} process that performs the actual
2027
+ work in the approved project folder.
2028
+ Approved project folder: ${args.cwd}
2029
+ Coding-job metadata: User-visible goal, plan steps, step statuses, completion
2030
+ notes, primary pull request link, and worktree state shown in Linzumi's coding
2031
+ job overview.
2032
+ Plan step: A discrete chunk of work with a short title, one-line description,
2033
+ status, and completion note. It should describe an actual goal, not just a
2034
+ process verb.
2035
+ Primary pull request: The GitHub PR the job is currently driving or reviewing;
2036
+ Linzumi uses this link to hydrate PR comments, files, commits, checks, and
2037
+ deployments in the metadata overview.
2038
+ </term_definitions>
2039
+
2040
+ <linzumi_mcp>
2041
+ The Codex process is preconfigured with a Linzumi MCP server named "linzumi".
2042
+ Use it when you need authenticated context from the current Linzumi workspace:
2043
+ read a message by ID/URL, read a thread by ID, read the scoped channel, or send
2044
+ a concise DM to the Commander owner when the task genuinely requires it.
2045
+ </linzumi_mcp>
2046
+
2047
+ <task_instructions>
2048
+ Work only in the approved project folder unless the human explicitly asks for
2049
+ something else in the Linzumi thread. Start, inspect, and modify the local app
2050
+ from that folder. Report concise progress and exact blockers in the thread.
2051
+ Use the Linzumi MCP server named "linzumi" when you need authenticated
2052
+ workspace context or coding-job metadata tools.
2053
+ ${planStepsInstruction}
2054
+ When the coding-job scope changes materially, call linzumi_rename_coding_job
2055
+ with a concise title so the Linzumi thread title and workflow-facing title
2056
+ match the work.
2057
+ ${planUpdateInstruction}
2058
+ When you open, discover, or choose the primary pull request, link it with
2059
+ linzumi_link_coding_job_pull_request.
2060
+ Before stopping, confirm the metadata overview tells the truth for a user who
2061
+ only sees that overview: completed work is completed, active work is really
2062
+ active, blocked work says why, and no stale in-progress step is left behind.
2063
+ For frontend or other user-visible changes, ask whether the user wants
2064
+ screenshot or screen-recording proof, ideally before/after when that helps
2065
+ review the change. Screen recordings should be WebM or MP4.
2066
+ When the user asks you to open or update a PR, attach or link feasible proof in
2067
+ the PR and state the exact blocker when proof is not feasible.
2068
+ </task_instructions>
2069
+
2070
+ <rules>
2071
+ You MUST treat the Linzumi thread as the source of truth for user-facing
2072
+ progress.
2073
+ For coding-job threads, you MUST keep coding-job metadata current throughout
2074
+ the job.
2075
+ Plan steps MUST be fairly granular, discrete chunks of real work that a human
2076
+ can track.
2077
+ Plan step descriptions MUST stay short, usually one line and less than one full
2078
+ sentence.
2079
+ Plan steps MUST NOT be vague process-only labels such as "sync PR",
2080
+ "update PR", "verify PR", or "commit and merge" when those labels hide the
2081
+ concrete goal.
2082
+ You MUST update steps in sequence as work is completed. Do not mark a later
2083
+ step active while earlier applicable steps are still pending.
2084
+ Before you stop work, you MUST reconcile the plan so the job is not left with a
2085
+ stale active step. You may delegate metadata upkeep to a sub-agent when useful,
2086
+ but the visible job state must remain truthful.
2087
+ If the coding-job thread title is missing, undefined, or "Untitled", you MUST
2088
+ choose a concise, specific title for the job.
2089
+ User-visible UI, status, error, and PR-review copy MUST keep it short, direct,
2090
+ and focused on the user's outcome unless the user explicitly asks for
2091
+ implementation detail.
2092
+ You MUST keep user-visible preview servers bound to 0.0.0.0, not 127.0.0.1 or
2093
+ localhost, so the Linzumi secure tunnel can reach them.
2094
+ You MUST keep any preview or dev server as your descendant process so the
2095
+ Commander can discover and forward it.
2096
+ You MUST NOT ask the Bootstrapper ${agentLabel} to do implementation work.
2097
+ You MUST NOT inspect unrelated repositories or folders.
2098
+ You MUST report the exact blocker if a command, preview, editor, or tunnel step
2099
+ fails.
2100
+ When several Linzumi Codex sessions work in the same repository, create or use
2101
+ a separate checkout or git worktree for this task before making changes, unless
2102
+ the human explicitly asks you to share the current checkout. Do not overwrite or
2103
+ revert another session's work.
2104
+ </rules>
2105
+
2106
+ <examples>
2107
+ GOOD preview command: npm run dev -- --host 0.0.0.0 --port 8787
2108
+ Why good: The preview is reachable through the Linzumi secure tunnel.
2109
+
2110
+ BAD preview command: npm run dev -- --host 127.0.0.1
2111
+ Why bad: Binding to localhost prevents the Linzumi secure tunnel from reaching
2112
+ the preview.
2113
+
2114
+ GOOD coding-job plan:
2115
+ - Inspect prompt sources: Locate the prompt builder, prompt guide, and tests.
2116
+ - Add metadata guidance: Patch the prompt with plan, title, and stop-state rules.
2117
+ - Verify compiled prompt: Run focused tests and inspect the generated text.
2118
+ - Push PR update: Commit owned files, push the branch, and refresh PR metadata.
2119
+ Why good: Each step is a concrete chunk of work, the list is short enough to
2120
+ scan, and the titles explain what is actually happening.
2121
+
2122
+ BAD coding-job plan:
2123
+ - Sync PR
2124
+ - Update PR
2125
+ - Verify PR
2126
+ - Commit and merge
2127
+ Why bad: These labels describe process mechanics, not the concrete work or
2128
+ product goal. A user cannot tell what the agent has learned, changed, or still
2129
+ needs to do.
2130
+
2131
+ BAD coding-job metadata state:
2132
+ - Step 1 complete, Step 2 complete, Step 3 active, Step 4 pending.
2133
+ - The agent has stopped working and sent its final response.
2134
+ Why bad: The overview still says work is active even though the agent is idle.
2135
+ Before stopping, the agent should mark Step 3 completed, blocked, or canceled
2136
+ with a truthful note.
2137
+ </examples>
2138
+ ${linzumiContext}
2139
+ ${customPrompt}
2140
+ <task_reminder>
2141
+ You are the Commander-launched Linzumi ${agentLabel} session. Do the implementation
2142
+ work in the approved project folder, keep preview servers reachable through the
2143
+ secure tunnel, and keep the Linzumi thread and coding-job metadata truthful.
2144
+ </task_reminder>
2145
+
2146
+ <output_format>
2147
+ Use normal concise Linzumi thread messages for user-facing progress and final
2148
+ answers. When the task requires files, code, commits, pull requests, previews,
2149
+ or verification output, report the concrete artifact, command, result, and any
2150
+ exact blocker. Do not emit machine-only JSON unless the user or tool explicitly
2151
+ requests it.
2152
+ </output_format>`;
2153
+ }
2154
+ var init_commanderDeveloperInstructions = __esm({
2155
+ "src/commanderDeveloperInstructions.ts"() {
2156
+ "use strict";
2157
+ init_linzumiContext();
2158
+ }
2159
+ });
2160
+
1939
2161
  // src/codexOutput.ts
1940
2162
  import { Buffer as Buffer2 } from "node:buffer";
1941
2163
  import { fileURLToPath } from "node:url";
@@ -6552,53 +6774,6 @@ var init_kandanQueue = __esm({
6552
6774
  }
6553
6775
  });
6554
6776
 
6555
- // src/linzumiContext.ts
6556
- function parseLinzumiConversationContext(value, warn = console.warn) {
6557
- if (value === void 0) {
6558
- return void 0;
6559
- }
6560
- const context = objectValue(value);
6561
- if (context === void 0) {
6562
- return invalidLinzumiContext("linzumiContext must be an object", warn);
6563
- }
6564
- const threadId = stringValue(context.threadId);
6565
- const creatorUserId = stringValue(context.creatorUserId) ?? integerValue(context.creatorUserId)?.toString();
6566
- const creatorUsername = stringValue(context.creatorUsername);
6567
- const conversationUrl = stringValue(context.conversationUrl);
6568
- if (threadId === void 0 || creatorUserId === void 0 || creatorUsername === void 0 || conversationUrl === void 0) {
6569
- return invalidLinzumiContext(
6570
- "linzumiContext must include threadId, creatorUserId, creatorUsername, and conversationUrl",
6571
- warn
6572
- );
6573
- }
6574
- return { threadId, creatorUserId, creatorUsername, conversationUrl };
6575
- }
6576
- function formatLinzumiConversationContextForPrompt(context) {
6577
- return `<linzumi_context>
6578
- <linzumi_thread_id>${escapeXmlText(context.threadId)}</linzumi_thread_id>
6579
- <linzumi_creator_user_id>${escapeXmlText(context.creatorUserId)}</linzumi_creator_user_id>
6580
- <linzumi_creator_username>${escapeXmlText(context.creatorUsername)}</linzumi_creator_username>
6581
- <linzumi_conversation_url>${escapeXmlText(context.conversationUrl)}</linzumi_conversation_url>
6582
- </linzumi_context>
6583
-
6584
- <linzumi_pr_footer_instruction>
6585
- For any GitHub pull request you open from this session, include this Markdown footer link in the PR description: [Continue this on Linzumi](${context.conversationUrl})
6586
- </linzumi_pr_footer_instruction>`;
6587
- }
6588
- function invalidLinzumiContext(reason, warn) {
6589
- warn(`Ignoring invalid linzumiContext: ${reason}`);
6590
- return void 0;
6591
- }
6592
- function escapeXmlText(value) {
6593
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
6594
- }
6595
- var init_linzumiContext = __esm({
6596
- "src/linzumiContext.ts"() {
6597
- "use strict";
6598
- init_json();
6599
- }
6600
- });
6601
-
6602
6777
  // src/reconnectContext.ts
6603
6778
  function parseReconnectContextMessages(value) {
6604
6779
  if (!Array.isArray(value)) {
@@ -11336,10 +11511,27 @@ async function startCodexThread(codex, options) {
11336
11511
  cwd: options.cwd,
11337
11512
  serviceName: "kandan-local-runner",
11338
11513
  personality: "pragmatic",
11514
+ ...developerInstructionsForThreadStart(options),
11339
11515
  ...codexThreadRuntimeOverrides(options)
11340
11516
  });
11341
11517
  return extractThreadIdFromResponse(start);
11342
11518
  }
11519
+ function developerInstructionsForThreadStart(options) {
11520
+ const developerPrompt = options.channelSession.developerPrompt;
11521
+ if (developerPrompt === void 0) {
11522
+ return {};
11523
+ }
11524
+ return {
11525
+ developerInstructions: commanderDeveloperInstructions({
11526
+ cwd: options.cwd,
11527
+ planTool: "linzumi-mcp",
11528
+ developerPrompt,
11529
+ linzumiContext: parseLinzumiConversationContext(
11530
+ options.channelSession.linzumiContext
11531
+ )
11532
+ })
11533
+ };
11534
+ }
11343
11535
  async function pushOk2(kandan, topic, event, payload) {
11344
11536
  const reply = await kandan.push(topic, event, payload);
11345
11537
  if (isJsonObject(reply) && reply.status === "ok" && isJsonObject(reply.response)) {
@@ -11366,6 +11558,7 @@ var init_channelSession = __esm({
11366
11558
  init_channelSessionSupport();
11367
11559
  init_commanderAttachments();
11368
11560
  init_codexRuntimeOptions();
11561
+ init_commanderDeveloperInstructions();
11369
11562
  init_codexOutput();
11370
11563
  init_codexSessionLog();
11371
11564
  init_integration();
@@ -11541,7 +11734,7 @@ function createClaudeCodeSessionPipeline(host) {
11541
11734
  const toolName2 = pending?.toolName ?? event.toolName ?? "tool";
11542
11735
  const input = pending?.input ?? {};
11543
11736
  const itemId = `tool:${event.itemKey}`;
11544
- if (!event.isError && isFileChangeTool(toolName2)) {
11737
+ if (isFileChangeTool(toolName2)) {
11545
11738
  const patchText = claudeFileChangePatchText(toolName2, input, {
11546
11739
  targetExistedAtCall: pending?.targetExistedAtCall
11547
11740
  });
@@ -11549,7 +11742,7 @@ function createClaudeCodeSessionPipeline(host) {
11549
11742
  const item2 = {
11550
11743
  type: "fileChange",
11551
11744
  id: itemId,
11552
- status: "completed",
11745
+ status: event.isError ? "failed" : "completed",
11553
11746
  patchText
11554
11747
  };
11555
11748
  recordItem(turn, item2);
@@ -11569,12 +11762,13 @@ function createClaudeCodeSessionPipeline(host) {
11569
11762
  submit("item/completed", { turnId: turn.turnId, item: item2 });
11570
11763
  return;
11571
11764
  }
11572
- if (!event.isError && toolName2 === "TodoWrite") {
11765
+ if (!event.isError && isClaudePlanTool(toolName2)) {
11573
11766
  host.onTodoWriteCompleted?.(input);
11574
11767
  const plan = claudePlanItem(itemId, input);
11575
11768
  if (plan !== void 0) {
11576
- recordItem(turn, plan);
11577
- submit("item/completed", { turnId: turn.turnId, item: plan });
11769
+ const planRow = { ...plan, id: itemId };
11770
+ recordItem(turn, planRow);
11771
+ submit("item/completed", { turnId: turn.turnId, item: planRow });
11578
11772
  return;
11579
11773
  }
11580
11774
  }
@@ -11605,7 +11799,7 @@ function createClaudeCodeSessionPipeline(host) {
11605
11799
  }
11606
11800
  case "reasoning_delta": {
11607
11801
  const turn = ensureTurn();
11608
- const itemId = claudeAssistantPipelineItemId(event.itemKey);
11802
+ const itemId = claudeReasoningPipelineItemId(event.itemKey);
11609
11803
  appendRawText(turn, itemId, "reasoning", event.delta);
11610
11804
  submit("item/reasoning/textDelta", {
11611
11805
  turnId: turn.turnId,
@@ -11626,7 +11820,7 @@ function createClaudeCodeSessionPipeline(host) {
11626
11820
  }
11627
11821
  case "reasoning_message": {
11628
11822
  const turn = ensureTurn();
11629
- const itemId = claudeAssistantPipelineItemId(event.itemKey);
11823
+ const itemId = claudeReasoningPipelineItemId(event.itemKey);
11630
11824
  setRawText(turn, itemId, "reasoning", event.content);
11631
11825
  submit("item/completed", {
11632
11826
  turnId: turn.turnId,
@@ -11636,14 +11830,15 @@ function createClaudeCodeSessionPipeline(host) {
11636
11830
  }
11637
11831
  case "tool_call": {
11638
11832
  const turn = ensureTurn();
11833
+ const targetExistedAtCall = fileChangeTargetExists(
11834
+ event.toolName,
11835
+ event.input,
11836
+ host.cwd
11837
+ );
11639
11838
  turn.pendingTools.set(event.itemKey, {
11640
11839
  toolName: event.toolName,
11641
11840
  input: event.input,
11642
- targetExistedAtCall: fileChangeTargetExists(
11643
- event.toolName,
11644
- event.input,
11645
- host.cwd
11646
- )
11841
+ targetExistedAtCall
11647
11842
  });
11648
11843
  if (isWebSearchTool(event.toolName)) {
11649
11844
  const query = stringValue(event.input.query) ?? stringValue(event.input.url) ?? event.toolName;
@@ -11655,7 +11850,34 @@ function createClaudeCodeSessionPipeline(host) {
11655
11850
  query
11656
11851
  }
11657
11852
  });
11853
+ return;
11854
+ }
11855
+ if (isClaudePlanTool(event.toolName)) {
11856
+ return;
11658
11857
  }
11858
+ const itemId = `tool:${event.itemKey}`;
11859
+ if (isFileChangeTool(event.toolName)) {
11860
+ const patchText = claudeFileChangePatchText(
11861
+ event.toolName,
11862
+ event.input,
11863
+ { targetExistedAtCall }
11864
+ );
11865
+ if (patchText !== void 0 && patchText.trim() !== "") {
11866
+ submit("item/fileChange/outputDelta", {
11867
+ turnId: turn.turnId,
11868
+ itemId,
11869
+ delta: patchText
11870
+ });
11871
+ }
11872
+ return;
11873
+ }
11874
+ submit("item/commandExecution/outputDelta", {
11875
+ turnId: turn.turnId,
11876
+ itemId,
11877
+ command: claudeToolCommandLabel(event.toolName, event.input),
11878
+ stream: "stdout",
11879
+ delta: "\n"
11880
+ });
11659
11881
  return;
11660
11882
  }
11661
11883
  case "tool_result": {
@@ -11809,6 +12031,9 @@ function claudeAssistantPipelineItemId(itemKey) {
11809
12031
  const streamIndex = itemKey.match(/^assistant-stream-(\d+)$/)?.[1];
11810
12032
  return streamIndex === void 0 ? itemKey : `content-block-${streamIndex}`;
11811
12033
  }
12034
+ function claudeReasoningPipelineItemId(itemKey) {
12035
+ return `reasoning-${claudeAssistantPipelineItemId(itemKey)}`;
12036
+ }
11812
12037
  function isFileChangeTool(toolName2) {
11813
12038
  switch (toolName2) {
11814
12039
  case "Edit":
@@ -11823,6 +12048,9 @@ function isFileChangeTool(toolName2) {
11823
12048
  function isWebSearchTool(toolName2) {
11824
12049
  return toolName2 === "WebSearch" || toolName2 === "WebFetch";
11825
12050
  }
12051
+ function isClaudePlanTool(toolName2) {
12052
+ return toolName2 === "TodoWrite" || toolName2 === "TaskCreate" || toolName2 === "TaskUpdate";
12053
+ }
11826
12054
  function fileChangeTargetExists(toolName2, input, sessionCwd) {
11827
12055
  if (!isFileChangeTool(toolName2)) {
11828
12056
  return void 0;
@@ -11895,9 +12123,18 @@ ${body}
11895
12123
  *** End Patch
11896
12124
  `;
11897
12125
  }
12126
+ function claudePlanInputItems(input) {
12127
+ if (Array.isArray(input.todos)) {
12128
+ return input.todos;
12129
+ }
12130
+ if (Array.isArray(input.tasks)) {
12131
+ return input.tasks;
12132
+ }
12133
+ return [];
12134
+ }
11898
12135
  function claudePlanItem(itemId, input) {
11899
- const todos = Array.isArray(input.todos) ? input.todos : [];
11900
- const lines = todos.flatMap((todo) => {
12136
+ const items = claudePlanInputItems(input);
12137
+ const lines = items.flatMap((todo) => {
11901
12138
  const value = objectValue(todo);
11902
12139
  if (value === void 0) {
11903
12140
  return [];
@@ -11971,7 +12208,7 @@ function objectValue2(value) {
11971
12208
  return isJsonObject(value) ? value : void 0;
11972
12209
  }
11973
12210
  function claudeTodoWritePlanSteps(input) {
11974
- const todos = Array.isArray(input.todos) ? input.todos : [];
12211
+ const todos = Array.isArray(input.todos) ? input.todos : Array.isArray(input.tasks) ? input.tasks : [];
11975
12212
  const steps = todos.flatMap((todo) => {
11976
12213
  const value = objectValue2(todo);
11977
12214
  if (value === void 0) {
@@ -12042,6 +12279,29 @@ function createClaudeCodePlanMirror(args) {
12042
12279
  settle: () => flight
12043
12280
  };
12044
12281
  }
12282
+ async function seedClaudeCodingJobGoal(args) {
12283
+ const goal = args.goal.trim().slice(0, 2e3);
12284
+ if (goal === "") {
12285
+ return false;
12286
+ }
12287
+ try {
12288
+ await args.upsertCodingJobPlan(
12289
+ { thread_id: args.threadId, goal },
12290
+ { signal: args.signal }
12291
+ );
12292
+ args.log("claude_code.goal_seeded", {
12293
+ thread_id: args.threadId,
12294
+ goal_length: goal.length
12295
+ });
12296
+ return true;
12297
+ } catch (error) {
12298
+ args.log("claude_code.goal_seed_failed", {
12299
+ thread_id: args.threadId,
12300
+ message: error instanceof Error ? error.message : String(error)
12301
+ });
12302
+ return false;
12303
+ }
12304
+ }
12045
12305
  var todoStatusToPlanStepStatus, planStepTitleMaxLength, planStepDescriptionMaxLength, planStepsMaxCount;
12046
12306
  var init_claudeCodePlanMirror = __esm({
12047
12307
  "src/claudeCodePlanMirror.ts"() {
@@ -12597,6 +12857,87 @@ var init_claudeCodeLiveBashOutput = __esm({
12597
12857
  }
12598
12858
  });
12599
12859
 
12860
+ // src/claudeCodeTurnStallWatchdog.ts
12861
+ function createClaudeCodeTurnStallWatchdog(options) {
12862
+ if (!(options.thresholdMs > 0)) {
12863
+ throw new Error("Claude turn-stall threshold must be > 0");
12864
+ }
12865
+ const now = options.now ?? (() => Date.now());
12866
+ const setTimer = options.setTimer ?? ((callback, delayMs) => {
12867
+ const handle2 = setTimeout(callback, delayMs);
12868
+ handle2.unref?.();
12869
+ return handle2;
12870
+ });
12871
+ const clearTimer = options.clearTimer ?? ((handle2) => clearTimeout(handle2));
12872
+ const isTurnActive = options.isTurnActive ?? (() => true);
12873
+ let handle;
12874
+ let lastKickMs = now();
12875
+ let stopped = false;
12876
+ let fired = false;
12877
+ let wasActive = isTurnActive();
12878
+ const disarm = () => {
12879
+ if (handle !== void 0) {
12880
+ clearTimer(handle);
12881
+ handle = void 0;
12882
+ }
12883
+ };
12884
+ const onTimeout = () => {
12885
+ handle = void 0;
12886
+ if (stopped || fired) {
12887
+ return;
12888
+ }
12889
+ const active = isTurnActive();
12890
+ if (!active) {
12891
+ wasActive = false;
12892
+ arm();
12893
+ return;
12894
+ }
12895
+ if (!wasActive) {
12896
+ wasActive = true;
12897
+ lastKickMs = now();
12898
+ arm();
12899
+ return;
12900
+ }
12901
+ const silentMs = now() - lastKickMs;
12902
+ if (silentMs < options.thresholdMs) {
12903
+ arm();
12904
+ return;
12905
+ }
12906
+ fired = true;
12907
+ options.onStall({ silentMs });
12908
+ };
12909
+ function arm() {
12910
+ if (stopped || fired) {
12911
+ return;
12912
+ }
12913
+ disarm();
12914
+ const delay = isTurnActive() ? Math.max(0, options.thresholdMs - (now() - lastKickMs)) : options.thresholdMs;
12915
+ handle = setTimer(onTimeout, delay);
12916
+ }
12917
+ arm();
12918
+ return {
12919
+ kick: () => {
12920
+ if (stopped || fired) {
12921
+ return;
12922
+ }
12923
+ wasActive = true;
12924
+ lastKickMs = now();
12925
+ arm();
12926
+ },
12927
+ stop: () => {
12928
+ stopped = true;
12929
+ disarm();
12930
+ }
12931
+ };
12932
+ }
12933
+ var defaultClaudeCodeTurnStallThresholdMs;
12934
+ var init_claudeCodeTurnStallWatchdog = __esm({
12935
+ "src/claudeCodeTurnStallWatchdog.ts"() {
12936
+ "use strict";
12937
+ defaultClaudeCodeTurnStallThresholdMs = 10 * 6e4;
12938
+ }
12939
+ });
12940
+
12600
12941
  // src/claudeCodeSession.ts
12601
12942
  import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
12602
12943
  import { homedir as homedir6 } from "node:os";
@@ -12864,8 +13205,49 @@ async function startClaudeCodeSession(options) {
12864
13205
  lastCompletedTurnBody: void 0
12865
13206
  };
12866
13207
  const streamUsageTracker = createClaudeCodeStreamUsageTracker();
13208
+ let stallReason;
13209
+ let signalStall;
13210
+ const stallSignal = new Promise((resolve12) => {
13211
+ signalStall = resolve12;
13212
+ });
13213
+ const stallWatchdog = options.turnStallThresholdMs !== void 0 && options.turnStallThresholdMs > 0 ? createClaudeCodeTurnStallWatchdog({
13214
+ thresholdMs: options.turnStallThresholdMs,
13215
+ isTurnActive: options.turnStallIsActive,
13216
+ onStall: ({ silentMs }) => {
13217
+ stallReason = `Claude Code turn stalled: no SDK output for ${Math.round(
13218
+ silentMs / 1e3
13219
+ )}s (threshold ${Math.round(
13220
+ (options.turnStallThresholdMs ?? 0) / 1e3
13221
+ )}s)`;
13222
+ options.abortController?.abort();
13223
+ signalStall?.();
13224
+ }
13225
+ }) : void 0;
12867
13226
  try {
12868
- for await (const message of runner(options)) {
13227
+ const iterator = runner(options)[Symbol.asyncIterator]();
13228
+ for (; ; ) {
13229
+ let nextStep;
13230
+ if (stallWatchdog) {
13231
+ const nextPromise = iterator.next().then((result) => ({ kind: "next", result }));
13232
+ nextPromise.catch(() => void 0);
13233
+ const raced = await Promise.race([
13234
+ nextPromise,
13235
+ stallSignal.then(() => ({ kind: "stall" }))
13236
+ ]);
13237
+ if (raced.kind === "stall") {
13238
+ void Promise.resolve(iterator.return?.()).catch(() => void 0);
13239
+ break;
13240
+ }
13241
+ nextStep = raced;
13242
+ } else {
13243
+ nextStep = { kind: "next", result: await iterator.next() };
13244
+ }
13245
+ const step = nextStep;
13246
+ if (step.result.done === true) {
13247
+ break;
13248
+ }
13249
+ const message = step.result.value;
13250
+ stallWatchdog?.kick();
12869
13251
  state.sessionId = state.sessionId ?? extractClaudeSessionId(message);
12870
13252
  const sessionId = extractClaudeSessionId(message) ?? state.sessionId;
12871
13253
  if (sessionId !== void 0 && !state.startedSessionIds.has(sessionId)) {
@@ -12931,8 +13313,12 @@ async function startClaudeCodeSession(options) {
12931
13313
  }
12932
13314
  }
12933
13315
  } finally {
13316
+ stallWatchdog?.stop();
12934
13317
  options.streamingInput?.close();
12935
13318
  }
13319
+ if (stallReason !== void 0) {
13320
+ return await failClaudeCodeSession(options, state.sessionId, stallReason);
13321
+ }
12936
13322
  return completeClaudeCodeSession(options, state);
12937
13323
  }
12938
13324
  async function emitClaudeCodeTurnCompleted(options, state) {
@@ -13325,13 +13711,16 @@ function assistantTranscriptEvents(message, sessionId) {
13325
13711
  contentBlockItemKey(block, index)
13326
13712
  )
13327
13713
  );
13328
- const directText = nonEmptyText(stringValue(message?.text));
13714
+ const hasTextContentBlock = content.some(
13715
+ (block) => stringValue(objectValue(block)?.type) === "text"
13716
+ );
13717
+ const directText = hasTextContentBlock ? void 0 : nonEmptyText(stringValue(message?.text));
13329
13718
  return directText === void 0 ? contentEvents : [
13330
13719
  ...contentEvents,
13331
13720
  {
13332
13721
  type: "assistant_message",
13333
13722
  sessionId,
13334
- itemKey: stringValue(message?.uuid) ?? "assistant-message",
13723
+ itemKey: `content-block-${content.length}`,
13335
13724
  content: directText,
13336
13725
  streamState: "completed"
13337
13726
  }
@@ -13352,7 +13741,7 @@ function userTranscriptEvents(message, sessionId) {
13352
13741
  {
13353
13742
  type: "tool_result",
13354
13743
  sessionId,
13355
- itemKey: contentBlockItemKey(block, index),
13744
+ itemKey: stringValue(value?.tool_use_id) ?? stringValue(value?.id) ?? `content-block-${index}`,
13356
13745
  toolName: void 0,
13357
13746
  content: visibleToolResultContent(value),
13358
13747
  isError: value?.is_error === true
@@ -13438,12 +13827,14 @@ function unknownTranscriptEvent(message, sessionId) {
13438
13827
  }
13439
13828
  function streamItemKey(event, _message) {
13440
13829
  const index = integerValue(event?.index);
13441
- const contentBlock = objectValue(event?.content_block);
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}`);
13830
+ return index === void 0 ? "assistant-stream" : `content-block-${index}`;
13443
13831
  }
13444
13832
  function contentBlockItemKey(block, index) {
13445
13833
  const value = objectValue(block);
13446
- return stringValue(value?.id) ?? stringValue(value?.tool_use_id) ?? stringValue(value?.uuid) ?? `content-block-${index}`;
13834
+ if (stringValue(value?.type) === "tool_use") {
13835
+ return stringValue(value?.id) ?? stringValue(value?.tool_use_id) ?? `content-block-${index}`;
13836
+ }
13837
+ return `content-block-${index}`;
13447
13838
  }
13448
13839
  function visibleToolResultContent(block) {
13449
13840
  const content = block?.content;
@@ -13491,6 +13882,7 @@ var init_claudeCodeSession = __esm({
13491
13882
  "use strict";
13492
13883
  init_json();
13493
13884
  init_claudeCodeLiveBashOutput();
13885
+ init_claudeCodeTurnStallWatchdog();
13494
13886
  claudeRateLimitWindowLabels = {
13495
13887
  five_hour: "5h window",
13496
13888
  seven_day: "7d window",
@@ -14762,11 +15154,34 @@ var init_defaultUrls = __esm({
14762
15154
  import { spawn as spawn3 } from "node:child_process";
14763
15155
  import { randomBytes } from "node:crypto";
14764
15156
  import { createServer as createServer2 } from "node:http";
15157
+ function oauthCallbackTimeoutMs(override, env = process.env) {
15158
+ if (typeof override === "number" && Number.isFinite(override) && override > 0) {
15159
+ return override;
15160
+ }
15161
+ const raw = env.LINZUMI_OAUTH_CALLBACK_TIMEOUT_MS?.trim();
15162
+ if (raw !== void 0 && raw !== "") {
15163
+ const parsed = Number.parseInt(raw, 10);
15164
+ if (Number.isFinite(parsed) && parsed > 0) {
15165
+ return parsed;
15166
+ }
15167
+ }
15168
+ return DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS;
15169
+ }
14765
15170
  async function acquireLocalRunnerTokenDetails(options) {
14766
15171
  const httpBaseUrl = kandanHttpBaseUrl(options.kandanUrl);
14767
15172
  const state = randomBytes(18).toString("base64url");
14768
15173
  const callback = await startCallbackServer({
14769
- host: oauthCallbackHost(options.kandanUrl, options.callbackHost)
15174
+ host: oauthCallbackHost(options.kandanUrl, options.callbackHost),
15175
+ exchange: async (result) => {
15176
+ if (result.state !== state) {
15177
+ throw new Error("local runner OAuth state mismatch");
15178
+ }
15179
+ return await exchangeCodeForToken({
15180
+ httpBaseUrl,
15181
+ code: result.code,
15182
+ redirectUri: result.redirectUri
15183
+ });
15184
+ }
14770
15185
  });
14771
15186
  const authorizeUrl = authorizationUrl({
14772
15187
  httpBaseUrl,
@@ -14782,15 +15197,9 @@ ${authorizeUrl}
14782
15197
  `);
14783
15198
  try {
14784
15199
  await (options.openAuthorizationUrl ?? openBrowser)(authorizeUrl);
14785
- const result = await callback.waitForCallback();
14786
- if (result.state !== state) {
14787
- throw new Error("local runner OAuth state mismatch");
14788
- }
14789
- return await exchangeCodeForToken({
14790
- httpBaseUrl,
14791
- code: result.code,
14792
- redirectUri: callback.redirectUri
14793
- });
15200
+ return await callback.waitForToken(
15201
+ oauthCallbackTimeoutMs(options.waitForTokenTimeoutMs)
15202
+ );
14794
15203
  } finally {
14795
15204
  callback.close();
14796
15205
  }
@@ -14970,49 +15379,83 @@ function stringBodyField(body, key) {
14970
15379
  }
14971
15380
  function startCallbackServer(args) {
14972
15381
  return new Promise((resolve12, reject) => {
14973
- let resolveCallback;
14974
- let rejectCallback;
14975
- const callbackPromise = new Promise((callbackResolve, callbackReject) => {
14976
- resolveCallback = callbackResolve;
14977
- rejectCallback = callbackReject;
15382
+ let resolveToken;
15383
+ let rejectToken;
15384
+ let settled = false;
15385
+ const tokenPromise = new Promise(
15386
+ (tokenResolve, tokenReject) => {
15387
+ resolveToken = tokenResolve;
15388
+ rejectToken = tokenReject;
15389
+ }
15390
+ );
15391
+ tokenPromise.catch(() => {
14978
15392
  });
15393
+ const settle = (apply) => {
15394
+ if (settled) {
15395
+ return false;
15396
+ }
15397
+ settled = true;
15398
+ apply();
15399
+ return true;
15400
+ };
15401
+ let boundRedirectUri = `http://${args.host}/callback`;
14979
15402
  const server = createServer2((request, response) => {
14980
- try {
14981
- const url = new URL(request.url ?? "/", `http://${args.host}`);
14982
- const code = url.searchParams.get("code");
14983
- const state = url.searchParams.get("state");
14984
- const error = url.searchParams.get("error");
14985
- if (error !== null && error.trim() !== "") {
14986
- rejectCallback?.(new Error(`local runner OAuth failed: ${error}`));
15403
+ void (async () => {
15404
+ try {
15405
+ const url = new URL(request.url ?? "/", `http://${args.host}`);
15406
+ const code = url.searchParams.get("code");
15407
+ const state = url.searchParams.get("state");
15408
+ const error = url.searchParams.get("error");
15409
+ if (error !== null && error.trim() !== "") {
15410
+ settle(
15411
+ () => rejectToken?.(new Error(`local runner OAuth failed: ${error}`))
15412
+ );
15413
+ writeOauthResult(response, {
15414
+ title: "Not authorized",
15415
+ body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
15416
+ status: 403
15417
+ });
15418
+ return;
15419
+ }
15420
+ if (code === null || state === null || code.trim() === "" || state.trim() === "") {
15421
+ writeOauthResult(response, {
15422
+ title: "Authorization callback was incomplete",
15423
+ body: "We didn't receive the authorization code. Return to your terminal and try again.",
15424
+ status: 400
15425
+ });
15426
+ return;
15427
+ }
15428
+ let token;
15429
+ try {
15430
+ token = await args.exchange({
15431
+ code,
15432
+ state,
15433
+ redirectUri: boundRedirectUri
15434
+ });
15435
+ } catch (exchangeError) {
15436
+ settle(() => rejectToken?.(exchangeError));
15437
+ writeOauthResult(response, {
15438
+ title: "Authorization could not be completed",
15439
+ body: "We received the authorization but could not finish setup. Return to your terminal for details and rerun the bootstrap command.",
15440
+ status: 400
15441
+ });
15442
+ return;
15443
+ }
15444
+ const accepted = settle(() => resolveToken?.(token));
14987
15445
  writeOauthResult(response, {
14988
- title: "Not authorized",
14989
- body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
14990
- status: 403
15446
+ title: accepted ? "Computer authorized" : "Already authorized",
15447
+ body: "This computer has been authorized for agentic use in Linzumi. Feel free to close this tab and let the terminal finish setup.",
15448
+ status: 200
14991
15449
  });
14992
- return;
14993
- }
14994
- if (code === null || state === null || code.trim() === "" || state.trim() === "") {
15450
+ } catch (handlerError) {
15451
+ settle(() => rejectToken?.(handlerError));
14995
15452
  writeOauthResult(response, {
14996
- title: "Authorization callback was incomplete",
14997
- body: "We didn't receive the authorization code. Return to your terminal and try again.",
14998
- status: 400
15453
+ title: "Authorization callback failed",
15454
+ body: "Return to your terminal and rerun the bootstrap command.",
15455
+ status: 500
14999
15456
  });
15000
- return;
15001
15457
  }
15002
- resolveCallback?.({ code, state });
15003
- writeOauthResult(response, {
15004
- title: "Computer authorized",
15005
- body: "This computer has been authorized for agentic use in Linzumi. Feel free to close this tab and let the terminal finish setup.",
15006
- status: 200
15007
- });
15008
- } catch (error) {
15009
- rejectCallback?.(error);
15010
- writeOauthResult(response, {
15011
- title: "Authorization callback failed",
15012
- body: "Return to your terminal and rerun the bootstrap command.",
15013
- status: 500
15014
- });
15015
- }
15458
+ })();
15016
15459
  });
15017
15460
  server.once("error", reject);
15018
15461
  server.listen(0, args.host, () => {
@@ -15025,9 +15468,34 @@ function startCallbackServer(args) {
15025
15468
  );
15026
15469
  return;
15027
15470
  }
15471
+ boundRedirectUri = `http://${args.host}:${address.port}/callback`;
15028
15472
  resolve12({
15029
- redirectUri: `http://${args.host}:${address.port}/callback`,
15030
- waitForCallback: () => callbackPromise,
15473
+ redirectUri: boundRedirectUri,
15474
+ // Bound the wait so that "only spurious/incomplete requests, no real
15475
+ // callback" can't hang forever. Incomplete requests deliberately never
15476
+ // settle the token promise (they only render the incomplete page), so
15477
+ // the overall timeout is what guarantees forward progress. The timer is
15478
+ // unref'd so it can't keep the process alive on its own, and it's
15479
+ // cleared once the token promise settles for any reason.
15480
+ waitForToken: (timeoutMs) => {
15481
+ const effectiveTimeoutMs = oauthCallbackTimeoutMs(timeoutMs);
15482
+ const timer = setTimeout(() => {
15483
+ settle(
15484
+ () => rejectToken?.(
15485
+ new Error(
15486
+ "Authorization timed out before the browser callback completed. Rerun the connect command and finish authorizing in the browser."
15487
+ )
15488
+ )
15489
+ );
15490
+ server.close();
15491
+ }, effectiveTimeoutMs);
15492
+ timer.unref?.();
15493
+ tokenPromise.finally(() => {
15494
+ clearTimeout(timer);
15495
+ }).catch(() => {
15496
+ });
15497
+ return tokenPromise;
15498
+ },
15031
15499
  close: () => {
15032
15500
  server.close();
15033
15501
  }
@@ -15092,7 +15560,7 @@ function oauthResultHtml(args) {
15092
15560
  place-items: center;
15093
15561
  padding: 24px;
15094
15562
  background:
15095
- radial-gradient(1200px 600px at 50% -10%, var(--notice-soft), transparent 60%),
15563
+ radial-gradient(1200px 600px at 50% -10%, var(${success ? "--success-soft" : "--notice-soft"}), transparent 60%),
15096
15564
  var(--page-bg);
15097
15565
  color: var(--foreground);
15098
15566
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
@@ -15177,11 +15645,12 @@ function openBrowser(url) {
15177
15645
  });
15178
15646
  });
15179
15647
  }
15180
- var LINZUMI_LOGO_SVG;
15648
+ var DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS, LINZUMI_LOGO_SVG;
15181
15649
  var init_oauth = __esm({
15182
15650
  "src/oauth.ts"() {
15183
15651
  "use strict";
15184
15652
  init_runnerLogger();
15653
+ DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
15185
15654
  LINZUMI_LOGO_SVG = '<svg viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false"><path fill="currentColor" d="M206.7,82.37l2.55-6.45,2.01-3.63c.67-1.21,1.07-1.88,2.55-3.63,1.34-1.61,3.36-4.43,5.64-6.58,2.42-2.24,5.06-4.3,7.93-6.18,3.92-2.53,8.17-4.52,12.63-5.91,1.79-.63,4.03-1.16,6.72-1.61,6.1-.88,12.29-.92,18.4-.13,2.69.45,5.06,1.03,7.12,1.75,2.15.67,3.49,1.07,5.64,2.15,4.55,2.16,8.87,4.77,12.9,7.79,1.61,1.34,2.42,2.15,4.16,4.16l5.91,7.66c1.34,2.01,1.34,2.28,2.15,4.03l2.55,6.58c.67,1.34-.13,1.34,1.07.94,1.21-.27,4.3-2.01,6.45-2.82,4.7-1.83,9.62-3,14.64-3.49,2.42-.27,3.76-.54,7.12-.27s9.27,1.21,12.76,2.15c3.63.94,6.58,2.55,8.73,3.49,2.15,1.07,2.55,1.48,4.16,2.55,3.49,2.51,6.77,5.29,9.81,8.33,1.52,1.7,3.04,3.58,4.57,5.64,2.74,4.49,4.86,9.32,6.31,14.37.67,2.28,1.07,3.49,1.34,6.72.27,3.22,0,10.48.4,12.76.27,2.28.27.67,1.61.94,1.48.27,4.3,0,6.72.4,2.42.27,4.97.85,7.66,1.75,6.76,2.32,13.01,5.92,18.4,10.61,2.28,1.75,2.96,2.69,4.3,4.3,3.51,3.93,6.32,8.43,8.33,13.3,1.07,2.15,1.21,2.28,1.88,5.1.54,2.96,1.61,8.19,1.61,12.36s-.81,9.67-1.34,12.76c-.67,3.22-1.07,4.03-2.15,6.18-2.16,4.54-4.81,8.82-7.93,12.76-1.25,1.61-2.6,3-4.03,4.16-1.48,1.34-2.69,2.42-4.7,3.76-3.79,2.34-7.74,4.4-11.82,6.18l-6.58,2.15c-2.06.45-4.12.72-6.18.81l-7.25.13c-2.28,0-2.82.27-6.58-.4-3.76-.81-11.69-2.82-15.99-4.3-4.16-1.34-6.31-2.69-9.67-4.43-3.49-1.75-10.48-6.18-12.9-7.39-8.26-4.54-16.78-8.58-25.52-12.09-4.3-1.75-9.27-3.22-11.82-4.03-2.55-.81-5.24-1.61-7.66-2.15-8.39-2.25-16.99-3.6-25.66-4.03-3.9-.27-4.84-.27-8.19-.13-3.67-.09-7.79.04-12.36.4-7.9.79-15.75,2.04-23.51,3.76-8.37,2.26-16.62,4.95-24.72,8.06-3.22,1.34-2.82.81-9.13,4.16-6.31,3.36-22.03,12.22-28.75,15.58-6.72,3.36-8.73,3.49-11.82,4.43-3.09,1.07-4.3,1.21-6.58,1.61-7.3,1.09-14.71,1.18-22.03.27-2.55-.54-5.78-1.61-7.79-2.28-2.01-.54-2.15-.67-4.03-1.75-2.02-1.07-5.64-3.22-7.79-4.7-3.24-2.15-6.14-4.77-8.6-7.79-1.34-1.52-2.69-3.36-4.03-5.51-1.52-2.51-2.96-5.24-4.3-8.19-2.03-5.46-3.03-11.24-2.96-17.06,0-2.96.13-6.72.67-9.67.54-2.87,1.34-5.6,2.42-8.19,4.24-10.52,12.04-19.22,22.03-24.58,5.19-2.98,10.88-4.98,16.79-5.91,2.55-.4,5.37-.13,6.72-.4,1.34-.27,1.34,1.34,1.61-.94.4-2.28,0-9.4.4-12.76.27-3.36.81-4.97,1.48-7.25,1.87-6.19,4.88-11.97,8.87-17.06,3.47-3.98,7.39-7.54,11.69-10.61,4.43-2.61,9.11-4.77,13.97-6.45,2.55-.67,6.99-1.34,9.13-1.75,2.15-.27,1.88-.13,3.63-.13,1.79-.09,4.03,0,6.72.27,2.69.18,5.73.72,9.13,1.61,3.49,1.07,9.27,4.03,11.28,4.7s.4.27,1.07-.94ZM512.27,255.7c0,141.39-114.62,256-256,256S.27,397.09.27,255.7,114.89-.3,256.27-.3s256,114.62,256,256ZM449.33,352.66c14.67-29.16,22.94-62.09,22.94-96.95,0-119.29-96.71-216-216-216S40.27,136.41,40.27,255.7c0,41.89,11.94,80.98,32.57,114.09,1.7,1.79,2.94.65,3.05-.57.27-1.61,2.75-20.85,4.43-27.27,1.87-7.6,4.52-14.98,7.93-22.03,1.75-3.49,3.76-7.93,6.18-11.82,8.77-13.95,19.75-26.38,32.51-36.81,7.24-5.32,14.87-10.08,22.84-14.24,9.74-4.78,20.09-8.22,30.76-10.21,4.03-.99,7.61-1.66,10.75-2.02,3.36-.54,5.78-.54,9.27-.94,3.49-.54,9.32-1.16,11.82-2.01,6.65-2.27,5.64-6.72,3.09-7.52-2.55-.81-9.67-1.88-13.84-2.96-4.16-1.21-8.73-2.82-11.28-3.76-2.55-.94-2.28-.94-3.9-2.02l-5.91-4.03c-.94-.94-1.34-.94.54-2.02l10.21-5.51c2.01-1.07,1.75-.67,3.09-.13,1.34.67,4.3,2.42,6.72,3.36,7.2,2.69,14.96,3.52,22.57,2.42,1.34-.27,1.34-.54,1.61-1.21.27-.67.81-.94-.13-2.69-.94-1.88-4.43-6.58-5.64-8.46l-1.21-2.28c-.27-.45-1.07-1.07,1.21-1.75,2.42-.54,10.48-2.58,12.9-2.82.86-.09.67,0,1.75,1.07,1.07,1.07,3.09,3.63,4.84,5.24,1.88,1.61,4.43,3.22,6.18,4.3,2.8,1.71,5.99,2.68,9.27,2.82,1.88,0,4.97-.13,7.12-.67,2.15-.4,3.63-.94,5.64-2.02,2.01-1.07,4.7-2.69,6.72-4.43,2.01-1.75,3.49-5.1,5.64-6.04,2.15-.81,4.97.54,7.12.94,2.28.4,4.97,1.21,6.18,1.61,1.21.4,2.01.4.67,2.28l-8.06,10.34c-1.34,1.88.13,5.51,1.21,5.64l5.64.27c2.28-.13,4.43.13,7.66-.67,3.36-.81,9.27-2.96,11.82-3.9,2.69-.94,3.22-1.34,4.16-2.01.81-.54,1.39-1.03,1.75-1.48.45-.27.67-1.07,2.82,0s9.4,4.97,11.28,6.18c2.01,1.21,1.07.94,0,1.75l-5.51,3.9c-1.48.94-1.07.94-3.22,1.88-2.15,1.07-6.58,2.96-9.67,4.03s-4.03,1.75-8.73,2.69c-4.7.81-14.51,1.75-19.48,2.55-4.97.81-7.93,1.34-10.34,2.01-2.28.67-2.15.94-3.49,1.88-1.48,1.07-2.69,1.61-4.97,3.9-2.28,2.28-5.91,6.85-8.73,9.81-2.82,2.96-6.45,6.04-8.33,7.79-2.02,1.61-1.61,1.34-3.36,2.42l-6.99,4.03-4.03,2.02-6.72,2.15c-2.42.67-3.9,1.21-7.66,2.01l-14.91,2.82c-3.63.81-4.3,1.07-7.12,2.01-2.96,1.07-6.22,2.46-9.81,4.16-3.22,1.43-6.81,3.45-10.75,6.04-13.6,9.14-24.61,21.63-31.97,36.27-2.01,3.76-2.96,6.58-3.9,9.27-1.07,2.69-1.61,4.03-2.28,6.58-.67,2.69-1.48,5.24-2.15,9.27-.54,4.16-1.48,11.15-1.75,15.45-.27,4.16-.27,6.04,0,10.21l1.88,14.91c.67,3.76,1.48,5.91,2.15,8.19l1.88,5.64c.99,2.6,2.42,5.78,4.3,9.54,2.15,3.76,5.37,9.13,8.06,13.03,10.08,14.51,23.81,26.36,40.17,33.05,22.55,9.22,43.32,10.46,61.89,10.46,3.25,0,6.49-.09,9.71-.23,3.07-.42,2.74-6.09,10.48-9.42,5.26-2.26,13.7-2.42,14.91-2.55,1.07-.13-5.24-4.57-7.79-6.45-2.55-1.88-4.97-4.3-6.72-5.91-1.75-1.75-2.42-2.28-3.9-4.16-1.61-1.88-3.9-5.51-5.24-7.12-1.34-1.61-1.48-2.28-2.55-2.55-.98-.27-2.37-.09-4.16.54-1.88.4-5.37,1.21-7.12,1.88-1.75.67-2.02.81-3.09,2.01-1.07,1.07-2.69,3.76-3.63,4.7-.81.94-.67.67-1.48.94-.81.27-2.42.67-3.63.54-1.34-.18-2.55-.58-3.63-1.21-.81-.81-1.48-2.42-1.88-3.22-.4-.94-.54-1.21-.54-2.15l.4-3.49c.18-1.52.72-3.27,1.61-5.24.94-1.75,1.88-3.49,4.16-5.64,2.15-2.02,7.52-5.51,9.13-6.85,1.61-1.34,1.21-2.15.54-3.63-2.69-5.16-6.33-9.77-10.75-13.57-2.42-1.88-5.51-3.22-7.79-4.3-2.15-1.21-2.01-1.21-5.78-2.42-3.76-1.07-13.16-3.63-16.79-4.84-3.63-1.07-2.82-.94-5.1-2.01l-8.19-4.3c-1.75-.94-1.34-.54-2.69-1.61l-5.51-4.57c-2.18-1.93-3.77-4.45-4.57-7.25-.45-1.52-.45-2.87,0-4.03.4-1.34,0-1.21,2.01-3.63,2.15-2.42,7.25-7.93,10.21-10.75,3.09-2.82,4.97-4.03,7.79-6.04,5.43-3.82,11.09-7.32,16.93-10.48,2.42-1.21,5.37-2.02,7.12-2.69,1.61-.81,2.64-1.43,3.09-1.88.54-.4.67.54.4-.94-.4-1.48-1.88-4.97-2.28-7.66-.4-2.82-.4-6.18,0-8.73.36-2.42,1.03-4.66,2.01-6.72,1.16-2.24,2.46-4.3,3.9-6.18,1.34-1.7,2.91-3.04,4.7-4.03,1.75-1.07,4.3-2.01,6.18-2.42,1.61-.45,3.13-.58,4.57-.4l4.57.27c1.34.18,2.55.49,3.63.94,1.07.54,2.55,1.48,3.09,2.01.4.67.67.13-.27,1.61-1.07,1.48-4.16,4.57-5.78,7.12-1.61,2.69-2.96,5.6-4.03,8.73-.94,3.22-1.88,8.73-2.01,10.75-.27,2.15,4.7,1.75,5.1.54.54-1.07.27-2.55,1.07-5.1.81-2.42,2.42-7.25,3.9-9.81,1.34-2.55,2.82-3.76,4.57-5.51,1.79-1.88,3.67-3.45,5.64-4.7,1.88-1.21,3.22-1.75,5.64-2.28,2.42-.4,6.18-.67,8.73-.67,2.42,0,3.9.13,6.18.94,2.15.81,5.24,2.69,7.12,4.03,1.79,1.52,3.22,3.09,4.3,4.7,1.21,1.61,1.88,2.96,2.55,5.1.67,2.15,1.34,5.1,1.34,7.66,0,2.69-.54,6.04-1.21,8.19-.63,2.24-1.43,4.03-2.42,5.37-.67,1.34-.94,1.61-2.15,2.82-1.21,1.34-3.22,3.22-4.97,4.57-1.75,1.21-4.43,2.28-5.51,3.22-.94.81-.4,2.51.4,2.42.94.13,1.75.4,4.57-.13,2.82-.4,8.33-2.15,12.22-2.82,11.99-2.63,24.41-2.63,36.4,0,3.22.54,4.16,1.07,6.31,1.75l6.04,2.28c2.42,1.07,5.51,2.42,8.19,4.16,8.15,5.36,15.14,12.3,20.55,20.42,1.61,2.42,2.87,4.66,3.76,6.72,1.85,4.51,3.42,9.13,4.7,13.84.67,2.82,1.21,4.43,1.61,8.19.4,3.76.4,11.82.54,14.37.27,2.55-.13,2.28,1.75,1.34,1.75-.94,20.09-7.74,30.63-31.57,9.16-20.71,1.9-47.26-2.28-55.34-1.75-3.22-4.16-6.99-6.31-9.81-4.44-5.14-9.6-9.62-15.31-13.3-2.96-1.7-5.55-3.09-7.79-4.16-4.33-1.6-8.77-2.86-13.3-3.76l-6.18-.4h-14.37c-2.69-.27-3.1-3.18-.54-4.3,3.64-1.58,9.88-2.63,18.54-2.42,8.78.21,14.91,1.88,17.33,2.55,13.98,3.94,30.19,16.65,38.55,30.76,6.61,11.16,9.81,29.96,9.81,29.96.37,1.75,2.18,1.66,3.08.35ZM232.78,345.3c-3.91,0-7.08,3.17-7.08,7.08s3.17,7.08,7.08,7.08,7.08-3.17,7.08-7.08-3.17-7.08-7.08-7.08Z"/></svg>';
15186
15655
  }
15187
15656
  });
@@ -18520,7 +18989,7 @@ var linzumiCliVersion, linzumiCliVersionText;
18520
18989
  var init_version = __esm({
18521
18990
  "src/version.ts"() {
18522
18991
  "use strict";
18523
- linzumiCliVersion = "0.0.87-beta";
18992
+ linzumiCliVersion = "0.0.89-beta";
18524
18993
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
18525
18994
  }
18526
18995
  });
@@ -20909,7 +21378,7 @@ function createLinzumiMcpApiClient(options) {
20909
21378
  const baseUrl = kandanHttpBaseUrl(options.kandanUrl);
20910
21379
  const apiPrefix = options.authMode === "personal-agent-delegation" ? "/api/v2/personal-agent-mcp" : "/api/v2/local-runner-mcp";
20911
21380
  const operatingMode = options.operatingMode ?? "text";
20912
- const request = async (method, path2, params) => {
21381
+ const request = async (method, path2, params, requestOptions = {}) => {
20913
21382
  const url = new URL(path2, baseUrl);
20914
21383
  const paramsWithMode = {
20915
21384
  ...params,
@@ -20917,7 +21386,11 @@ function createLinzumiMcpApiClient(options) {
20917
21386
  };
20918
21387
  const requestInit = {
20919
21388
  method,
20920
- headers: { authorization: `Bearer ${options.accessToken}` }
21389
+ headers: { authorization: `Bearer ${options.accessToken}` },
21390
+ // An aborted signal cancels the in-flight fetch so a caller that has
21391
+ // already given up (e.g. a timed-out goal seed) cannot land a LATE POST
21392
+ // and clobber state written after it gave up (#1864 finding 2).
21393
+ ...requestOptions.signal === void 0 ? {} : { signal: requestOptions.signal }
20921
21394
  };
20922
21395
  if (method === "GET") {
20923
21396
  for (const [key, value] of Object.entries(paramsWithMode)) {
@@ -20950,9 +21423,11 @@ function createLinzumiMcpApiClient(options) {
20950
21423
  getMessage: (params) => request("GET", `${apiPrefix}/message`, params),
20951
21424
  getThread: (params) => request("GET", `${apiPrefix}/thread`, params),
20952
21425
  getChannel: (params) => request("GET", `${apiPrefix}/channel`, params),
21426
+ searchWorkspace: (params) => request("GET", `${apiPrefix}/search`, params),
21427
+ askWorkspace: (params) => request("POST", `${apiPrefix}/ask`, params),
20953
21428
  getCodingJobMetadata: (params) => request("GET", `${apiPrefix}/coding-job-metadata`, params),
20954
21429
  renameCodingJob: (params) => request("POST", `${apiPrefix}/coding-job-title`, params),
20955
- upsertCodingJobPlan: (params) => request("POST", `${apiPrefix}/coding-job-plan`, params),
21430
+ upsertCodingJobPlan: (params, requestOptions) => request("POST", `${apiPrefix}/coding-job-plan`, params, requestOptions),
20956
21431
  replaceCodingJobPlanSteps: (params) => request("POST", `${apiPrefix}/coding-job-plan/steps`, params),
20957
21432
  updateCodingJobPlanStep: (params) => request("POST", `${apiPrefix}/coding-job-plan/step`, params),
20958
21433
  linkCodingJobPullRequest: (params) => request("POST", `${apiPrefix}/coding-job-primary-pr`, params),
@@ -25877,167 +26352,6 @@ function truncateFailureDetail(message) {
25877
26352
  }
25878
26353
  return `${trimmed.slice(0, 237)}...`;
25879
26354
  }
25880
- function commanderDeveloperInstructions(args) {
25881
- const agentLabel = args.agentLabel ?? "Codex";
25882
- const planStepsInstruction = args.planTool === "todo-write" ? `For coding-job threads, start by setting the current goal with
25883
- linzumi_upsert_coding_job_plan, then record the plan steps with the TodoWrite
25884
- tool. Linzumi mirrors your TodoWrite list into the coding-job plan steps
25885
- automatically, one TodoWrite item per plan step.` : `For coding-job threads, start by setting the current goal with
25886
- linzumi_upsert_coding_job_plan, then replace the plan steps with
25887
- linzumi_replace_coding_job_plan_steps.`;
25888
- const planUpdateInstruction = args.planTool === "todo-write" ? `As work proceeds, update each step's status with TodoWrite. Do not call
25889
- linzumi_replace_coding_job_plan_steps or linzumi_update_coding_job_plan_step
25890
- directly; TodoWrite is the source of truth for the plan steps.` : `As work proceeds, update each step with linzumi_update_coding_job_plan_step.`;
25891
- const customPrompt = args.developerPrompt === void 0 ? "" : `
25892
- <invoker_developer_prompt>
25893
- ${args.developerPrompt}
25894
- </invoker_developer_prompt>
25895
- `;
25896
- const linzumiContext = args.linzumiContext === void 0 ? "" : `
25897
- ${formatLinzumiConversationContextForPrompt(args.linzumiContext)}
25898
- `;
25899
- return `<context>
25900
- You are a Linzumi ${agentLabel} session launched by the Linzumi Commander.
25901
- The Commander is the local bridge process that connects this computer to the
25902
- user's Linzumi workspace, secure preview tunnel, browser VS Code editor, and
25903
- thread transcript. Your work and progress are relayed into that Linzumi thread.
25904
- </context>
25905
-
25906
- <term_definitions>
25907
- Bootstrapper ${agentLabel}: The outer setup agent that claimed/signup, generated the
25908
- demo folder when applicable, and asked Linzumi to start this session. It does
25909
- not own implementation work.
25910
- Linzumi Commander: The local long-running process that launched this ${agentLabel}
25911
- session, enforces the trusted folder, and forwards descendant preview ports to
25912
- Linzumi.
25913
- Linzumi ${agentLabel} session: You, the inner ${agentLabel} process that performs the actual
25914
- work in the approved project folder.
25915
- Approved project folder: ${args.cwd}
25916
- Coding-job metadata: User-visible goal, plan steps, step statuses, completion
25917
- notes, primary pull request link, and worktree state shown in Linzumi's coding
25918
- job overview.
25919
- Plan step: A discrete chunk of work with a short title, one-line description,
25920
- status, and completion note. It should describe an actual goal, not just a
25921
- process verb.
25922
- Primary pull request: The GitHub PR the job is currently driving or reviewing;
25923
- Linzumi uses this link to hydrate PR comments, files, commits, checks, and
25924
- deployments in the metadata overview.
25925
- </term_definitions>
25926
-
25927
- <linzumi_mcp>
25928
- The Codex process is preconfigured with a Linzumi MCP server named "linzumi".
25929
- Use it when you need authenticated context from the current Linzumi workspace:
25930
- read a message by ID/URL, read a thread by ID, read the scoped channel, or send
25931
- a concise DM to the Commander owner when the task genuinely requires it.
25932
- </linzumi_mcp>
25933
-
25934
- <task_instructions>
25935
- Work only in the approved project folder unless the human explicitly asks for
25936
- something else in the Linzumi thread. Start, inspect, and modify the local app
25937
- from that folder. Report concise progress and exact blockers in the thread.
25938
- Use the Linzumi MCP server named "linzumi" when you need authenticated
25939
- workspace context or coding-job metadata tools.
25940
- ${planStepsInstruction}
25941
- When the coding-job scope changes materially, call linzumi_rename_coding_job
25942
- with a concise title so the Linzumi thread title and workflow-facing title
25943
- match the work.
25944
- ${planUpdateInstruction}
25945
- When you open, discover, or choose the primary pull request, link it with
25946
- linzumi_link_coding_job_pull_request.
25947
- Before stopping, confirm the metadata overview tells the truth for a user who
25948
- only sees that overview: completed work is completed, active work is really
25949
- active, blocked work says why, and no stale in-progress step is left behind.
25950
- For frontend or other user-visible changes, ask whether the user wants
25951
- screenshot or screen-recording proof, ideally before/after when that helps
25952
- review the change. Screen recordings should be WebM or MP4.
25953
- When the user asks you to open or update a PR, attach or link feasible proof in
25954
- the PR and state the exact blocker when proof is not feasible.
25955
- </task_instructions>
25956
-
25957
- <rules>
25958
- You MUST treat the Linzumi thread as the source of truth for user-facing
25959
- progress.
25960
- For coding-job threads, you MUST keep coding-job metadata current throughout
25961
- the job.
25962
- Plan steps MUST be fairly granular, discrete chunks of real work that a human
25963
- can track.
25964
- Plan step descriptions MUST stay short, usually one line and less than one full
25965
- sentence.
25966
- Plan steps MUST NOT be vague process-only labels such as "sync PR",
25967
- "update PR", "verify PR", or "commit and merge" when those labels hide the
25968
- concrete goal.
25969
- You MUST update steps in sequence as work is completed. Do not mark a later
25970
- step active while earlier applicable steps are still pending.
25971
- Before you stop work, you MUST reconcile the plan so the job is not left with a
25972
- stale active step. You may delegate metadata upkeep to a sub-agent when useful,
25973
- but the visible job state must remain truthful.
25974
- If the coding-job thread title is missing, undefined, or "Untitled", you MUST
25975
- choose a concise, specific title for the job.
25976
- User-visible UI, status, error, and PR-review copy MUST keep it short, direct,
25977
- and focused on the user's outcome unless the user explicitly asks for
25978
- implementation detail.
25979
- You MUST keep user-visible preview servers bound to 0.0.0.0, not 127.0.0.1 or
25980
- localhost, so the Linzumi secure tunnel can reach them.
25981
- You MUST keep any preview or dev server as your descendant process so the
25982
- Commander can discover and forward it.
25983
- You MUST NOT ask the Bootstrapper ${agentLabel} to do implementation work.
25984
- You MUST NOT inspect unrelated repositories or folders.
25985
- You MUST report the exact blocker if a command, preview, editor, or tunnel step
25986
- fails.
25987
- When several Linzumi Codex sessions work in the same repository, create or use
25988
- a separate checkout or git worktree for this task before making changes, unless
25989
- the human explicitly asks you to share the current checkout. Do not overwrite or
25990
- revert another session's work.
25991
- </rules>
25992
-
25993
- <examples>
25994
- GOOD preview command: npm run dev -- --host 0.0.0.0 --port 8787
25995
- Why good: The preview is reachable through the Linzumi secure tunnel.
25996
-
25997
- BAD preview command: npm run dev -- --host 127.0.0.1
25998
- Why bad: Binding to localhost prevents the Linzumi secure tunnel from reaching
25999
- the preview.
26000
-
26001
- GOOD coding-job plan:
26002
- - Inspect prompt sources: Locate the prompt builder, prompt guide, and tests.
26003
- - Add metadata guidance: Patch the prompt with plan, title, and stop-state rules.
26004
- - Verify compiled prompt: Run focused tests and inspect the generated text.
26005
- - Push PR update: Commit owned files, push the branch, and refresh PR metadata.
26006
- Why good: Each step is a concrete chunk of work, the list is short enough to
26007
- scan, and the titles explain what is actually happening.
26008
-
26009
- BAD coding-job plan:
26010
- - Sync PR
26011
- - Update PR
26012
- - Verify PR
26013
- - Commit and merge
26014
- Why bad: These labels describe process mechanics, not the concrete work or
26015
- product goal. A user cannot tell what the agent has learned, changed, or still
26016
- needs to do.
26017
-
26018
- BAD coding-job metadata state:
26019
- - Step 1 complete, Step 2 complete, Step 3 active, Step 4 pending.
26020
- - The agent has stopped working and sent its final response.
26021
- Why bad: The overview still says work is active even though the agent is idle.
26022
- Before stopping, the agent should mark Step 3 completed, blocked, or canceled
26023
- with a truthful note.
26024
- </examples>
26025
- ${linzumiContext}
26026
- ${customPrompt}
26027
- <task_reminder>
26028
- You are the Commander-launched Linzumi ${agentLabel} session. Do the implementation
26029
- work in the approved project folder, keep preview servers reachable through the
26030
- secure tunnel, and keep the Linzumi thread and coding-job metadata truthful.
26031
- </task_reminder>
26032
-
26033
- <output_format>
26034
- Use normal concise Linzumi thread messages for user-facing progress and final
26035
- answers. When the task requires files, code, commits, pull requests, previews,
26036
- or verification output, report the concrete artifact, command, result, and any
26037
- exact blocker. Do not emit machine-only JSON unless the user or tool explicitly
26038
- requests it.
26039
- </output_format>`;
26040
- }
26041
26355
  function availableRunnerAgentProviders(options) {
26042
26356
  switch (options.claudeCodeRunner !== void 0 || options.claudeCodeAvailable === true) {
26043
26357
  case true:
@@ -26273,6 +26587,14 @@ async function startCodexProviderInstance(args) {
26273
26587
  }
26274
26588
  };
26275
26589
  }
26590
+ function claudeCodeTurnStallThresholdMs(env) {
26591
+ const raw = env.LINZUMI_CLAUDE_TURN_STALL_MS?.trim();
26592
+ if (raw === void 0 || raw === "") {
26593
+ return defaultClaudeCodeTurnStallThresholdMs;
26594
+ }
26595
+ const parsed = Number.parseInt(raw, 10);
26596
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : defaultClaudeCodeTurnStallThresholdMs;
26597
+ }
26276
26598
  function createClaudeCodeInputQueue(input) {
26277
26599
  const pendingMessages = [
26278
26600
  claudeCodeUserInputMessage(input.content)
@@ -26644,12 +26966,47 @@ async function startClaudeCodeProviderInstance(args) {
26644
26966
  fetchImpl: args.options.fetch,
26645
26967
  operatingMode: "text"
26646
26968
  });
26969
+ const seededGoal = workDescription.trim().slice(0, 2e3);
26647
26970
  const planMirror = createClaudeCodePlanMirror({
26648
26971
  replacePlanSteps: planMirrorClient.replaceCodingJobPlanSteps,
26649
26972
  threadId,
26650
- goal: workDescription.trim().slice(0, 2e3),
26973
+ goal: seededGoal,
26651
26974
  log: args.log
26652
26975
  });
26976
+ const seedCodingJobGoalBeforeTurn = async () => {
26977
+ if (args.resumeSessionId !== void 0) {
26978
+ return;
26979
+ }
26980
+ const seedTimeoutMs = 5e3;
26981
+ const seedAbort = new AbortController();
26982
+ let timeoutHandle;
26983
+ const timedOut = new Promise((resolve12) => {
26984
+ timeoutHandle = setTimeout(() => {
26985
+ seedAbort.abort(new Error("claude goal seed timed out"));
26986
+ args.log("claude_code.goal_seed_timeout", {
26987
+ thread_id: threadId,
26988
+ timeout_ms: seedTimeoutMs
26989
+ });
26990
+ resolve12();
26991
+ }, seedTimeoutMs);
26992
+ });
26993
+ try {
26994
+ await Promise.race([
26995
+ seedClaudeCodingJobGoal({
26996
+ upsertCodingJobPlan: planMirrorClient.upsertCodingJobPlan,
26997
+ threadId,
26998
+ goal: seededGoal,
26999
+ log: args.log,
27000
+ signal: seedAbort.signal
27001
+ }),
27002
+ timedOut
27003
+ ]);
27004
+ } finally {
27005
+ if (timeoutHandle !== void 0) {
27006
+ clearTimeout(timeoutHandle);
27007
+ }
27008
+ }
27009
+ };
26653
27010
  const adapter = createClaudeCodeSessionPipeline({
26654
27011
  instanceId: args.instanceId,
26655
27012
  cwd: args.cwd,
@@ -26955,6 +27312,7 @@ async function startClaudeCodeProviderInstance(args) {
26955
27312
  });
26956
27313
  const runSession = async () => {
26957
27314
  let result2;
27315
+ await seedCodingJobGoalBeforeTurn();
26958
27316
  try {
26959
27317
  try {
26960
27318
  result2 = await startClaudeCodeSession({
@@ -26983,6 +27341,20 @@ async function startClaudeCodeProviderInstance(args) {
26983
27341
  resumeSessionId: args.resumeSessionId,
26984
27342
  abortController,
26985
27343
  streamingInput: inputQueue,
27344
+ // PROD-RESILIENCE turn-stall watchdog (real-SDK sessions only; mock
27345
+ // runners drive their own timing). A turn that stays in flight with
27346
+ // no SDK output past the threshold is a wedged SILENT subprocess: the
27347
+ // watchdog aborts it and fails the turn so the thread reconnects,
27348
+ // instead of sitting "processing" forever (the ~28-min-hang incident).
27349
+ // The in-flight predicate (currentSourceSeq() is defined only while a
27350
+ // turn is queued/active) keeps a keep-alive session idling between
27351
+ // turns from being a false positive.
27352
+ ...args.options.claudeCodeRunner === void 0 ? {
27353
+ turnStallThresholdMs: claudeCodeTurnStallThresholdMs(
27354
+ process.env
27355
+ ),
27356
+ turnStallIsActive: () => inputQueue.currentSourceSeq() !== void 0
27357
+ } : {},
26986
27358
  runner: args.options.claudeCodeRunner,
26987
27359
  canUseTool,
26988
27360
  ...mcpServers === void 0 ? {} : { mcpServers },
@@ -29343,11 +29715,13 @@ var init_runner = __esm({
29343
29715
  init_engineChildReaper();
29344
29716
  init_claudeCodeLiveBashOutput();
29345
29717
  init_claudeCodeSession();
29718
+ init_claudeCodeTurnStallWatchdog();
29346
29719
  init_codexAppServer();
29347
29720
  init_codexProjectTrust();
29348
29721
  init_codexRuntimeOptions();
29349
29722
  init_codexNotificationConsoleStats();
29350
29723
  init_json();
29724
+ init_commanderDeveloperInstructions();
29351
29725
  init_linzumiContext();
29352
29726
  init_localCapabilities();
29353
29727
  init_localConfig();
@@ -66096,6 +66470,60 @@ async function runMcpServer(args) {
66096
66470
  },
66097
66471
  async (params) => mcpJsonResult(await client.getChannel(params))
66098
66472
  );
66473
+ server.tool(
66474
+ "linzumi_search_workspace",
66475
+ "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.",
66476
+ {
66477
+ workspace: external_exports2.string().optional().describe(
66478
+ "Workspace slug. Omit to use the authenticated token scope."
66479
+ ),
66480
+ query: external_exports2.string().min(1).describe("Natural-language or keyword query to retrieve against."),
66481
+ limit: external_exports2.number().int().min(1).max(25).optional().describe("Maximum results to return (default 10, server-capped)."),
66482
+ source_types: external_exports2.array(
66483
+ external_exports2.enum([
66484
+ "message",
66485
+ "coding_job",
66486
+ "github_pr",
66487
+ "github_pr_review",
66488
+ "github_check_run",
66489
+ "github_workflow_run",
66490
+ "github_diff",
66491
+ "dictation",
66492
+ "file"
66493
+ ])
66494
+ ).optional().describe("Restrict results to these corpora; omit for all."),
66495
+ authored_after: external_exports2.string().optional().describe(
66496
+ "ISO8601 instant; only content authored at or after this time."
66497
+ ),
66498
+ authored_before: external_exports2.string().optional().describe(
66499
+ "ISO8601 instant; only content authored at or before this time."
66500
+ ),
66501
+ repo: external_exports2.string().optional().describe(
66502
+ "Restrict GitHub-sourced results to one repository by full name (owner/name)."
66503
+ ),
66504
+ check_status: external_exports2.enum(["pending", "success", "failed"]).optional().describe(
66505
+ "Restrict check and workflow results to one conclusion bucket."
66506
+ )
66507
+ },
66508
+ async (params) => mcpJsonResult(await client.searchWorkspace(params))
66509
+ );
66510
+ server.tool(
66511
+ "linzumi_ask_workspace",
66512
+ '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.',
66513
+ {
66514
+ workspace: external_exports2.string().optional().describe(
66515
+ "Workspace slug. Omit to use the authenticated token scope."
66516
+ ),
66517
+ question: external_exports2.string().min(1).max(2e3).describe("Natural-language question to answer with citations."),
66518
+ channel: external_exports2.string().optional().describe(
66519
+ "Optional channel slug providing conversational context; never widens access."
66520
+ ),
66521
+ allow_cached: external_exports2.boolean().optional().describe(
66522
+ "Allow serving a cached synthesis when the evidence set is unchanged (default true). Set false to force fresh synthesis."
66523
+ )
66524
+ },
66525
+ async (params) => mcpJsonResult(await client.askWorkspace(params))
66526
+ );
66099
66527
  server.tool(
66100
66528
  "linzumi_get_coding_job_metadata",
66101
66529
  "Read the active coding job workflow metadata for a thread, including the agent-owned goal, ordered plan steps, step lock versions, and workflow status.",
@@ -66913,6 +67341,7 @@ async function runRemoteCodexHarnessWorker(config, deps = {}) {
66913
67341
  rootSeq: config.rootSeq,
66914
67342
  startCodexThread: true,
66915
67343
  linzumiContext: config.linzumiContext,
67344
+ developerPrompt: config.developerPrompt,
66916
67345
  listenUser: config.listenUser,
66917
67346
  model: config.model,
66918
67347
  reasoningEffort: config.reasoningEffort,
@@ -67002,6 +67431,7 @@ function remoteCodexHarnessWorkerConfigFromJson(value) {
67002
67431
  cwd: requiredString2(input, "cwd"),
67003
67432
  workerCwd: requiredString2(input, "workerCwd"),
67004
67433
  workDescription: requiredString2(input, "workDescription"),
67434
+ ...optionalStringField(input, "developerPrompt"),
67005
67435
  readyToken: requiredString2(input, "readyToken"),
67006
67436
  codexBin: requiredString2(input, "codexBin"),
67007
67437
  execServerUrl: requiredString2(input, "execServerUrl"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.87-beta",
3
+ "version": "0.0.89-beta",
4
4
  "description": "Linzumi CLI — point a Codex agent at the real code on your laptop, with your team watching and steering from shared threads.",
5
5
  "type": "module",
6
6
  "bin": {