@linzumi/cli 0.0.88-beta → 0.0.90-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 +511 -282
  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.88-beta --version
61
+ npx -y @linzumi/cli@0.0.90-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";
@@ -5116,6 +5338,43 @@ var init_itemProjection = __esm({
5116
5338
  function rowStreamStateIsTerminal(state) {
5117
5339
  return state !== void 0 && terminalRowStreamStates.has(state);
5118
5340
  }
5341
+ function codexProtocolFailureReasonFromItems(items) {
5342
+ for (const value of items) {
5343
+ const reason = codexProtocolFailureReasonFromItem(objectValue(value));
5344
+ if (reason !== void 0) {
5345
+ return reason;
5346
+ }
5347
+ }
5348
+ return void 0;
5349
+ }
5350
+ function codexProtocolFailureReasonFromItem(item) {
5351
+ if (item === void 0) {
5352
+ return void 0;
5353
+ }
5354
+ const explicitReason = stringValue(item.codex_protocol_failure_reason) ?? stringValue(item.protocolFailureReason);
5355
+ if (explicitReason !== void 0) {
5356
+ return explicitReason;
5357
+ }
5358
+ const itemType = stringValue(item.type);
5359
+ if (itemType !== "function_call_output" && itemType !== "custom_tool_call_output") {
5360
+ return void 0;
5361
+ }
5362
+ const output = stringValue(item.output) ?? stringValue(item.result) ?? stringValue(item.text) ?? stringValue(item.content);
5363
+ const unsupportedCall = unsupportedCodexCallName(output);
5364
+ return unsupportedCall === void 0 ? void 0 : `${unsupportedCodexToolCallReasonPrefix}: ${unsupportedCall}`;
5365
+ }
5366
+ function unsupportedCodexCallName(output) {
5367
+ const prefix = "unsupported call:";
5368
+ if (output === void 0) {
5369
+ return void 0;
5370
+ }
5371
+ const normalized = output.trim();
5372
+ if (!normalized.toLowerCase().startsWith(prefix)) {
5373
+ return void 0;
5374
+ }
5375
+ const callName = normalized.slice(prefix.length).trim();
5376
+ return callName === "" ? "unknown" : callName;
5377
+ }
5119
5378
  function createInitialLoopState() {
5120
5379
  return {
5121
5380
  phase: "running",
@@ -5260,6 +5519,7 @@ function createTurn(state, turnId, sourceSeq) {
5260
5519
  turnId,
5261
5520
  sourceSeq,
5262
5521
  phase: "active",
5522
+ protocolFailureReason: void 0,
5263
5523
  items: []
5264
5524
  };
5265
5525
  state.turns.set(turnId, turn);
@@ -5366,6 +5626,10 @@ function handleDelta(turn, signal, deps, effects) {
5366
5626
  emitRowUpdate(turn, item, deltaIndex, streamState, deps, effects);
5367
5627
  }
5368
5628
  function handleItemCompleted(turn, params, deps, effects) {
5629
+ const completedItem = objectValue(params.item);
5630
+ if (completedItem !== void 0) {
5631
+ turn.protocolFailureReason ??= codexProtocolFailureReasonFromItem(completedItem);
5632
+ }
5369
5633
  const completed = completedItemForNotification(params);
5370
5634
  if (completed === void 0) {
5371
5635
  return;
@@ -5535,6 +5799,7 @@ function handleSnapshot(state, turnId, snapshot, deps, effects) {
5535
5799
  return;
5536
5800
  }
5537
5801
  const rawItems = Array.isArray(snapshot.items) ? snapshot.items : [];
5802
+ turn.protocolFailureReason ??= codexProtocolFailureReasonFromItems(rawItems);
5538
5803
  const projected = projectSnapshotItems(rawItems);
5539
5804
  const consumedItemKeys = /* @__PURE__ */ new Set();
5540
5805
  const matches = /* @__PURE__ */ new Map();
@@ -5640,6 +5905,17 @@ function handleSnapshot(state, turnId, snapshot, deps, effects) {
5640
5905
  const assistantResponseSeen = turn.items.some(
5641
5906
  (item) => item.identity.streamKind === "assistant" && item.posted
5642
5907
  ) || projected.some((proj) => proj.streamKind === "assistant");
5908
+ const terminalFailureReason = turn.protocolFailureReason ?? (assistantResponseSeen ? void 0 : completedWithoutAssistantOutputReason);
5909
+ if (turn.protocolFailureReason !== void 0) {
5910
+ effects.push(
5911
+ log("codex.turn_completed_with_protocol_failure", {
5912
+ thread_key: deps.threadKey,
5913
+ turn_id: turn.turnId,
5914
+ source_seq: turn.sourceSeq,
5915
+ reason: turn.protocolFailureReason
5916
+ })
5917
+ );
5918
+ }
5643
5919
  if (!assistantResponseSeen) {
5644
5920
  effects.push(
5645
5921
  log("codex.turn_completed_without_assistant_output", {
@@ -5658,19 +5934,19 @@ function handleSnapshot(state, turnId, snapshot, deps, effects) {
5658
5934
  thread_key: deps.threadKey,
5659
5935
  turn_id: turn.turnId,
5660
5936
  seq: turn.sourceSeq,
5661
- ...assistantResponseSeen ? { status: "processed" } : {
5937
+ ...terminalFailureReason === void 0 ? { status: "processed" } : {
5662
5938
  status: "failed",
5663
- reason: completedWithoutAssistantOutputReason
5939
+ reason: terminalFailureReason
5664
5940
  }
5665
5941
  },
5666
5942
  resolution: "must_ack"
5667
5943
  });
5668
5944
  effects.push(
5669
- assistantResponseSeen ? { type: "turnTerminal", turnId: turn.turnId, outcome: "completed" } : {
5945
+ terminalFailureReason === void 0 ? { type: "turnTerminal", turnId: turn.turnId, outcome: "completed" } : {
5670
5946
  type: "turnTerminal",
5671
5947
  turnId: turn.turnId,
5672
5948
  outcome: "failed",
5673
- reason: completedWithoutAssistantOutputReason
5949
+ reason: terminalFailureReason
5674
5950
  }
5675
5951
  );
5676
5952
  state.turns.delete(turn.turnId);
@@ -5751,7 +6027,7 @@ function failureReason(params) {
5751
6027
  function log(event, fields) {
5752
6028
  return { type: "log", event, fields };
5753
6029
  }
5754
- var terminalRowStreamStates, turnMappingReadyMethod, turnMappingFailedMethod, maxTerminalTurnIds, completedWithoutAssistantOutputReason, turnTerminalFailureMethods, handledNotificationMethods;
6030
+ var terminalRowStreamStates, turnMappingReadyMethod, turnMappingFailedMethod, maxTerminalTurnIds, completedWithoutAssistantOutputReason, unsupportedCodexToolCallReasonPrefix, turnTerminalFailureMethods, handledNotificationMethods;
5755
6031
  var init_transition = __esm({
5756
6032
  "src/pipeline/transition.ts"() {
5757
6033
  "use strict";
@@ -5767,6 +6043,7 @@ var init_transition = __esm({
5767
6043
  turnMappingFailedMethod = "pipeline/turnMappingFailed";
5768
6044
  maxTerminalTurnIds = 512;
5769
6045
  completedWithoutAssistantOutputReason = "Codex completed without producing a response.";
6046
+ unsupportedCodexToolCallReasonPrefix = "Codex emitted unsupported tool call";
5770
6047
  turnTerminalFailureMethods = /* @__PURE__ */ new Map([
5771
6048
  ["turn/failed", "failed"],
5772
6049
  ["turn/aborted", "interrupted"],
@@ -6321,17 +6598,22 @@ function rawItemFromToolCallMessage(message) {
6321
6598
  const structured = message.structured;
6322
6599
  const itemId = /^item-\d+$/.test(message.itemKey) ? void 0 : message.itemKey;
6323
6600
  const idField = itemId === void 0 ? {} : { id: itemId };
6601
+ const output = typeof structured.output === "string" ? structured.output : "";
6324
6602
  switch (stringValue(structured.kind)) {
6325
- case "codex_command_execution":
6603
+ case "codex_command_execution": {
6604
+ const protocolFailureReason = unsupportedToolCallReason(output);
6605
+ const protocolFailureField = protocolFailureReason === void 0 ? {} : { codex_protocol_failure_reason: protocolFailureReason };
6326
6606
  return {
6327
6607
  type: "commandExecution",
6328
6608
  ...idField,
6609
+ ...protocolFailureField,
6329
6610
  command: stringValue(structured.command) ?? "command",
6330
6611
  cwd: stringValue(structured.cwd) ?? "",
6331
6612
  status: stringValue(structured.status) ?? "",
6332
6613
  ...stringValue(structured.process_id) === void 0 || stringValue(structured.process_id) === "" ? {} : { processId: stringValue(structured.process_id) },
6333
- aggregatedOutput: typeof structured.output === "string" ? structured.output : ""
6614
+ aggregatedOutput: output
6334
6615
  };
6616
+ }
6335
6617
  case "codex_file_change":
6336
6618
  return {
6337
6619
  type: "fileChange",
@@ -6344,6 +6626,15 @@ function rawItemFromToolCallMessage(message) {
6344
6626
  return void 0;
6345
6627
  }
6346
6628
  }
6629
+ function unsupportedToolCallReason(output) {
6630
+ const prefix = "unsupported call:";
6631
+ const normalized = output.trim();
6632
+ if (!normalized.toLowerCase().startsWith(prefix)) {
6633
+ return void 0;
6634
+ }
6635
+ const callName = normalized.slice(prefix.length).trim();
6636
+ return `Codex emitted unsupported tool call: ${callName === "" ? "unknown" : callName}`;
6637
+ }
6347
6638
  function mergeSnapshotItems(readItems, sessionItems) {
6348
6639
  if (sessionItems.length === 0) {
6349
6640
  return [...readItems];
@@ -6552,53 +6843,6 @@ var init_kandanQueue = __esm({
6552
6843
  }
6553
6844
  });
6554
6845
 
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
6846
  // src/reconnectContext.ts
6603
6847
  function parseReconnectContextMessages(value) {
6604
6848
  if (!Array.isArray(value)) {
@@ -11336,10 +11580,27 @@ async function startCodexThread(codex, options) {
11336
11580
  cwd: options.cwd,
11337
11581
  serviceName: "kandan-local-runner",
11338
11582
  personality: "pragmatic",
11583
+ ...developerInstructionsForThreadStart(options),
11339
11584
  ...codexThreadRuntimeOverrides(options)
11340
11585
  });
11341
11586
  return extractThreadIdFromResponse(start);
11342
11587
  }
11588
+ function developerInstructionsForThreadStart(options) {
11589
+ const developerPrompt = options.channelSession.developerPrompt;
11590
+ if (developerPrompt === void 0) {
11591
+ return {};
11592
+ }
11593
+ return {
11594
+ developerInstructions: commanderDeveloperInstructions({
11595
+ cwd: options.cwd,
11596
+ planTool: "linzumi-mcp",
11597
+ developerPrompt,
11598
+ linzumiContext: parseLinzumiConversationContext(
11599
+ options.channelSession.linzumiContext
11600
+ )
11601
+ })
11602
+ };
11603
+ }
11343
11604
  async function pushOk2(kandan, topic, event, payload) {
11344
11605
  const reply = await kandan.push(topic, event, payload);
11345
11606
  if (isJsonObject(reply) && reply.status === "ok" && isJsonObject(reply.response)) {
@@ -11366,6 +11627,7 @@ var init_channelSession = __esm({
11366
11627
  init_channelSessionSupport();
11367
11628
  init_commanderAttachments();
11368
11629
  init_codexRuntimeOptions();
11630
+ init_commanderDeveloperInstructions();
11369
11631
  init_codexOutput();
11370
11632
  init_codexSessionLog();
11371
11633
  init_integration();
@@ -11406,6 +11668,23 @@ var init_channelSession = __esm({
11406
11668
  // src/claudeCodePipeline.ts
11407
11669
  import { existsSync as existsSync3 } from "node:fs";
11408
11670
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
11671
+ function claudeIncompleteProgressCompletionReason(body, bodySource) {
11672
+ if (bodySource !== "assistant_aggregate") {
11673
+ return void 0;
11674
+ }
11675
+ const normalized = body.trim().replace(/\s+/g, " ").toLowerCase();
11676
+ if (normalized === "") {
11677
+ return void 0;
11678
+ }
11679
+ const continuationPatterns = [
11680
+ /\blet me\b.*\bbefore\b.*\b(writing|implementing|editing|patching|making|changing|coding|updating)\b/u,
11681
+ /\blet me\b.*\b(check|inspect|confirm|look|verify|investigate|find|review)\b/u,
11682
+ /\bi(?:'|\u2019)ll\b.*\b(check|inspect|confirm|look|verify|investigate|find|review|write|implement|update|edit|patch|run)\b/u,
11683
+ /\bi will\b.*\b(check|inspect|confirm|look|verify|investigate|find|review|write|implement|update|edit|patch|run)\b/u,
11684
+ /\bnext\b.*\b(i(?:'|\u2019)ll|i will|let me)\b/u
11685
+ ];
11686
+ return continuationPatterns.some((pattern) => pattern.test(normalized)) ? incompleteProgressCompletionReason : void 0;
11687
+ }
11409
11688
  function createClaudeCodeSessionPipeline(host) {
11410
11689
  const sourceSeqByTurn = /* @__PURE__ */ new Map();
11411
11690
  const snapshotsByTurn = /* @__PURE__ */ new Map();
@@ -11535,6 +11814,10 @@ function createClaudeCodeSessionPipeline(host) {
11535
11814
  submit("turn/completed", { turnId: turn.turnId });
11536
11815
  activeTurn = void 0;
11537
11816
  };
11817
+ const failTurn2 = (turn, reason) => {
11818
+ submit("turn/failed", { turnId: turn.turnId, reason });
11819
+ activeTurn = void 0;
11820
+ };
11538
11821
  const handleToolResult = (turn, event) => {
11539
11822
  const pending = turn.pendingTools.get(event.itemKey);
11540
11823
  turn.pendingTools.delete(event.itemKey);
@@ -11752,6 +12035,19 @@ function createClaudeCodeSessionPipeline(host) {
11752
12035
  }
11753
12036
  const turn = ensureTurn();
11754
12037
  lastUsage = event.usage ?? lastUsage;
12038
+ const incompleteProgressReason = claudeIncompleteProgressCompletionReason(
12039
+ event.body,
12040
+ event.bodySource
12041
+ );
12042
+ if (incompleteProgressReason !== void 0) {
12043
+ host.log("claude_pipeline.incomplete_progress_completion", {
12044
+ thread_key: host.threadKey,
12045
+ turn_id: turn.turnId,
12046
+ body_source: event.bodySource ?? "unspecified"
12047
+ });
12048
+ failTurn2(turn, incompleteProgressReason);
12049
+ return;
12050
+ }
11755
12051
  finishTurn(turn, event.body);
11756
12052
  return;
11757
12053
  }
@@ -11761,6 +12057,19 @@ function createClaudeCodeSessionPipeline(host) {
11761
12057
  return;
11762
12058
  }
11763
12059
  lastUsage = event.usage ?? lastUsage;
12060
+ const incompleteProgressReason = claudeIncompleteProgressCompletionReason(
12061
+ event.body,
12062
+ event.bodySource
12063
+ );
12064
+ if (incompleteProgressReason !== void 0) {
12065
+ host.log("claude_pipeline.incomplete_progress_completion", {
12066
+ thread_key: host.threadKey,
12067
+ turn_id: activeTurn.turnId,
12068
+ body_source: event.bodySource ?? "unspecified"
12069
+ });
12070
+ failTurn2(activeTurn, incompleteProgressReason);
12071
+ return;
12072
+ }
11764
12073
  finishTurn(activeTurn, event.body);
11765
12074
  return;
11766
12075
  }
@@ -11773,11 +12082,7 @@ function createClaudeCodeSessionPipeline(host) {
11773
12082
  });
11774
12083
  return;
11775
12084
  }
11776
- submit("turn/failed", {
11777
- turnId: activeTurn.turnId,
11778
- reason: event.reason
11779
- });
11780
- activeTurn = void 0;
12085
+ failTurn2(activeTurn, event.reason);
11781
12086
  return;
11782
12087
  }
11783
12088
  }
@@ -11989,12 +12294,13 @@ function claudeToolInputSummary(input) {
11989
12294
  return void 0;
11990
12295
  }
11991
12296
  }
11992
- var maxRetainedSnapshots, todoStatusGlyphs;
12297
+ var incompleteProgressCompletionReason, maxRetainedSnapshots, todoStatusGlyphs;
11993
12298
  var init_claudeCodePipeline = __esm({
11994
12299
  "src/claudeCodePipeline.ts"() {
11995
12300
  "use strict";
11996
12301
  init_json();
11997
12302
  init_integration();
12303
+ incompleteProgressCompletionReason = "Claude Code ended after an incomplete progress response instead of a final answer";
11998
12304
  maxRetainedSnapshots = 8;
11999
12305
  todoStatusGlyphs = {
12000
12306
  completed: "[x]",
@@ -13129,7 +13435,9 @@ async function startClaudeCodeSession(options) {
13129
13435
  return completeClaudeCodeSession(options, state);
13130
13436
  }
13131
13437
  async function emitClaudeCodeTurnCompleted(options, state) {
13132
- const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state)) ?? "";
13438
+ const aggregateText = nonEmptyText(claudeAssistantAggregateText(state));
13439
+ const body = state.resultText ?? aggregateText ?? "";
13440
+ const bodySource = state.resultText !== void 0 ? "result" : aggregateText !== void 0 ? "assistant_aggregate" : "empty";
13133
13441
  if (state.sessionId === void 0) {
13134
13442
  return;
13135
13443
  }
@@ -13137,12 +13445,17 @@ async function emitClaudeCodeTurnCompleted(options, state) {
13137
13445
  type: "turn_completed",
13138
13446
  sessionId: state.sessionId,
13139
13447
  body,
13448
+ bodySource,
13140
13449
  usage: state.usage
13141
13450
  });
13142
13451
  }
13143
13452
  async function completeClaudeCodeSession(options, state) {
13144
- const body = state.resultText ?? nonEmptyText(claudeAssistantAggregateText(state)) ?? state.lastCompletedTurnBody;
13145
- if (body === void 0) {
13453
+ const aggregateText = nonEmptyText(claudeAssistantAggregateText(state));
13454
+ const completion = state.resultText !== void 0 ? { body: state.resultText, bodySource: "result" } : aggregateText !== void 0 ? { body: aggregateText, bodySource: "assistant_aggregate" } : state.lastCompletedTurnBody !== void 0 ? {
13455
+ body: state.lastCompletedTurnBody,
13456
+ bodySource: "last_completed_turn"
13457
+ } : void 0;
13458
+ if (completion === void 0) {
13146
13459
  return await failClaudeCodeSession(
13147
13460
  options,
13148
13461
  state.sessionId,
@@ -13158,13 +13471,14 @@ async function completeClaudeCodeSession(options, state) {
13158
13471
  }
13159
13472
  const result = {
13160
13473
  sessionId: state.sessionId,
13161
- body,
13474
+ body: completion.body,
13162
13475
  usage: state.usage
13163
13476
  };
13164
13477
  await options.onTranscriptEvent?.({
13165
13478
  type: "session_completed",
13166
13479
  sessionId: result.sessionId,
13167
13480
  body: result.body,
13481
+ bodySource: completion.bodySource,
13168
13482
  usage: result.usage
13169
13483
  });
13170
13484
  return result;
@@ -14961,11 +15275,34 @@ var init_defaultUrls = __esm({
14961
15275
  import { spawn as spawn3 } from "node:child_process";
14962
15276
  import { randomBytes } from "node:crypto";
14963
15277
  import { createServer as createServer2 } from "node:http";
15278
+ function oauthCallbackTimeoutMs(override, env = process.env) {
15279
+ if (typeof override === "number" && Number.isFinite(override) && override > 0) {
15280
+ return override;
15281
+ }
15282
+ const raw = env.LINZUMI_OAUTH_CALLBACK_TIMEOUT_MS?.trim();
15283
+ if (raw !== void 0 && raw !== "") {
15284
+ const parsed = Number.parseInt(raw, 10);
15285
+ if (Number.isFinite(parsed) && parsed > 0) {
15286
+ return parsed;
15287
+ }
15288
+ }
15289
+ return DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS;
15290
+ }
14964
15291
  async function acquireLocalRunnerTokenDetails(options) {
14965
15292
  const httpBaseUrl = kandanHttpBaseUrl(options.kandanUrl);
14966
15293
  const state = randomBytes(18).toString("base64url");
14967
15294
  const callback = await startCallbackServer({
14968
- host: oauthCallbackHost(options.kandanUrl, options.callbackHost)
15295
+ host: oauthCallbackHost(options.kandanUrl, options.callbackHost),
15296
+ exchange: async (result) => {
15297
+ if (result.state !== state) {
15298
+ throw new Error("local runner OAuth state mismatch");
15299
+ }
15300
+ return await exchangeCodeForToken({
15301
+ httpBaseUrl,
15302
+ code: result.code,
15303
+ redirectUri: result.redirectUri
15304
+ });
15305
+ }
14969
15306
  });
14970
15307
  const authorizeUrl = authorizationUrl({
14971
15308
  httpBaseUrl,
@@ -14981,15 +15318,9 @@ ${authorizeUrl}
14981
15318
  `);
14982
15319
  try {
14983
15320
  await (options.openAuthorizationUrl ?? openBrowser)(authorizeUrl);
14984
- const result = await callback.waitForCallback();
14985
- if (result.state !== state) {
14986
- throw new Error("local runner OAuth state mismatch");
14987
- }
14988
- return await exchangeCodeForToken({
14989
- httpBaseUrl,
14990
- code: result.code,
14991
- redirectUri: callback.redirectUri
14992
- });
15321
+ return await callback.waitForToken(
15322
+ oauthCallbackTimeoutMs(options.waitForTokenTimeoutMs)
15323
+ );
14993
15324
  } finally {
14994
15325
  callback.close();
14995
15326
  }
@@ -15169,49 +15500,83 @@ function stringBodyField(body, key) {
15169
15500
  }
15170
15501
  function startCallbackServer(args) {
15171
15502
  return new Promise((resolve12, reject) => {
15172
- let resolveCallback;
15173
- let rejectCallback;
15174
- const callbackPromise = new Promise((callbackResolve, callbackReject) => {
15175
- resolveCallback = callbackResolve;
15176
- rejectCallback = callbackReject;
15503
+ let resolveToken;
15504
+ let rejectToken;
15505
+ let settled = false;
15506
+ const tokenPromise = new Promise(
15507
+ (tokenResolve, tokenReject) => {
15508
+ resolveToken = tokenResolve;
15509
+ rejectToken = tokenReject;
15510
+ }
15511
+ );
15512
+ tokenPromise.catch(() => {
15177
15513
  });
15514
+ const settle = (apply) => {
15515
+ if (settled) {
15516
+ return false;
15517
+ }
15518
+ settled = true;
15519
+ apply();
15520
+ return true;
15521
+ };
15522
+ let boundRedirectUri = `http://${args.host}/callback`;
15178
15523
  const server = createServer2((request, response) => {
15179
- try {
15180
- const url = new URL(request.url ?? "/", `http://${args.host}`);
15181
- const code = url.searchParams.get("code");
15182
- const state = url.searchParams.get("state");
15183
- const error = url.searchParams.get("error");
15184
- if (error !== null && error.trim() !== "") {
15185
- rejectCallback?.(new Error(`local runner OAuth failed: ${error}`));
15524
+ void (async () => {
15525
+ try {
15526
+ const url = new URL(request.url ?? "/", `http://${args.host}`);
15527
+ const code = url.searchParams.get("code");
15528
+ const state = url.searchParams.get("state");
15529
+ const error = url.searchParams.get("error");
15530
+ if (error !== null && error.trim() !== "") {
15531
+ settle(
15532
+ () => rejectToken?.(new Error(`local runner OAuth failed: ${error}`))
15533
+ );
15534
+ writeOauthResult(response, {
15535
+ title: "Not authorized",
15536
+ body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
15537
+ status: 403
15538
+ });
15539
+ return;
15540
+ }
15541
+ if (code === null || state === null || code.trim() === "" || state.trim() === "") {
15542
+ writeOauthResult(response, {
15543
+ title: "Authorization callback was incomplete",
15544
+ body: "We didn't receive the authorization code. Return to your terminal and try again.",
15545
+ status: 400
15546
+ });
15547
+ return;
15548
+ }
15549
+ let token;
15550
+ try {
15551
+ token = await args.exchange({
15552
+ code,
15553
+ state,
15554
+ redirectUri: boundRedirectUri
15555
+ });
15556
+ } catch (exchangeError) {
15557
+ settle(() => rejectToken?.(exchangeError));
15558
+ writeOauthResult(response, {
15559
+ title: "Authorization could not be completed",
15560
+ body: "We received the authorization but could not finish setup. Return to your terminal for details and rerun the bootstrap command.",
15561
+ status: 400
15562
+ });
15563
+ return;
15564
+ }
15565
+ const accepted = settle(() => resolveToken?.(token));
15186
15566
  writeOauthResult(response, {
15187
- title: "Not authorized",
15188
- body: "You denied the request. Close this tab and rerun the bootstrap command when you're ready.",
15189
- status: 403
15567
+ title: accepted ? "Computer authorized" : "Already authorized",
15568
+ body: "This computer has been authorized for agentic use in Linzumi. Feel free to close this tab and let the terminal finish setup.",
15569
+ status: 200
15190
15570
  });
15191
- return;
15192
- }
15193
- if (code === null || state === null || code.trim() === "" || state.trim() === "") {
15571
+ } catch (handlerError) {
15572
+ settle(() => rejectToken?.(handlerError));
15194
15573
  writeOauthResult(response, {
15195
- title: "Authorization callback was incomplete",
15196
- body: "We didn't receive the authorization code. Return to your terminal and try again.",
15197
- status: 400
15574
+ title: "Authorization callback failed",
15575
+ body: "Return to your terminal and rerun the bootstrap command.",
15576
+ status: 500
15198
15577
  });
15199
- return;
15200
15578
  }
15201
- resolveCallback?.({ code, state });
15202
- writeOauthResult(response, {
15203
- title: "Computer authorized",
15204
- body: "This computer has been authorized for agentic use in Linzumi. Feel free to close this tab and let the terminal finish setup.",
15205
- status: 200
15206
- });
15207
- } catch (error) {
15208
- rejectCallback?.(error);
15209
- writeOauthResult(response, {
15210
- title: "Authorization callback failed",
15211
- body: "Return to your terminal and rerun the bootstrap command.",
15212
- status: 500
15213
- });
15214
- }
15579
+ })();
15215
15580
  });
15216
15581
  server.once("error", reject);
15217
15582
  server.listen(0, args.host, () => {
@@ -15224,9 +15589,34 @@ function startCallbackServer(args) {
15224
15589
  );
15225
15590
  return;
15226
15591
  }
15592
+ boundRedirectUri = `http://${args.host}:${address.port}/callback`;
15227
15593
  resolve12({
15228
- redirectUri: `http://${args.host}:${address.port}/callback`,
15229
- waitForCallback: () => callbackPromise,
15594
+ redirectUri: boundRedirectUri,
15595
+ // Bound the wait so that "only spurious/incomplete requests, no real
15596
+ // callback" can't hang forever. Incomplete requests deliberately never
15597
+ // settle the token promise (they only render the incomplete page), so
15598
+ // the overall timeout is what guarantees forward progress. The timer is
15599
+ // unref'd so it can't keep the process alive on its own, and it's
15600
+ // cleared once the token promise settles for any reason.
15601
+ waitForToken: (timeoutMs) => {
15602
+ const effectiveTimeoutMs = oauthCallbackTimeoutMs(timeoutMs);
15603
+ const timer = setTimeout(() => {
15604
+ settle(
15605
+ () => rejectToken?.(
15606
+ new Error(
15607
+ "Authorization timed out before the browser callback completed. Rerun the connect command and finish authorizing in the browser."
15608
+ )
15609
+ )
15610
+ );
15611
+ server.close();
15612
+ }, effectiveTimeoutMs);
15613
+ timer.unref?.();
15614
+ tokenPromise.finally(() => {
15615
+ clearTimeout(timer);
15616
+ }).catch(() => {
15617
+ });
15618
+ return tokenPromise;
15619
+ },
15230
15620
  close: () => {
15231
15621
  server.close();
15232
15622
  }
@@ -15291,7 +15681,7 @@ function oauthResultHtml(args) {
15291
15681
  place-items: center;
15292
15682
  padding: 24px;
15293
15683
  background:
15294
- radial-gradient(1200px 600px at 50% -10%, var(--notice-soft), transparent 60%),
15684
+ radial-gradient(1200px 600px at 50% -10%, var(${success ? "--success-soft" : "--notice-soft"}), transparent 60%),
15295
15685
  var(--page-bg);
15296
15686
  color: var(--foreground);
15297
15687
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
@@ -15376,11 +15766,12 @@ function openBrowser(url) {
15376
15766
  });
15377
15767
  });
15378
15768
  }
15379
- var LINZUMI_LOGO_SVG;
15769
+ var DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS, LINZUMI_LOGO_SVG;
15380
15770
  var init_oauth = __esm({
15381
15771
  "src/oauth.ts"() {
15382
15772
  "use strict";
15383
15773
  init_runnerLogger();
15774
+ DEFAULT_OAUTH_CALLBACK_TIMEOUT_MS = 5 * 60 * 1e3;
15384
15775
  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>';
15385
15776
  }
15386
15777
  });
@@ -18719,7 +19110,7 @@ var linzumiCliVersion, linzumiCliVersionText;
18719
19110
  var init_version = __esm({
18720
19111
  "src/version.ts"() {
18721
19112
  "use strict";
18722
- linzumiCliVersion = "0.0.88-beta";
19113
+ linzumiCliVersion = "0.0.90-beta";
18723
19114
  linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
18724
19115
  }
18725
19116
  });
@@ -26082,173 +26473,6 @@ function truncateFailureDetail(message) {
26082
26473
  }
26083
26474
  return `${trimmed.slice(0, 237)}...`;
26084
26475
  }
26085
- function commanderDeveloperInstructions(args) {
26086
- const agentLabel = args.agentLabel ?? "Codex";
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.`;
26099
- const planUpdateInstruction = args.planTool === "todo-write" ? `As work proceeds, update each step's status with TodoWrite. Do not call
26100
- linzumi_replace_coding_job_plan_steps or linzumi_update_coding_job_plan_step
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.`;
26102
- const customPrompt = args.developerPrompt === void 0 ? "" : `
26103
- <invoker_developer_prompt>
26104
- ${args.developerPrompt}
26105
- </invoker_developer_prompt>
26106
- `;
26107
- const linzumiContext = args.linzumiContext === void 0 ? "" : `
26108
- ${formatLinzumiConversationContextForPrompt(args.linzumiContext)}
26109
- `;
26110
- return `<context>
26111
- You are a Linzumi ${agentLabel} session launched by the Linzumi Commander.
26112
- The Commander is the local bridge process that connects this computer to the
26113
- user's Linzumi workspace, secure preview tunnel, browser VS Code editor, and
26114
- thread transcript. Your work and progress are relayed into that Linzumi thread.
26115
- </context>
26116
-
26117
- <term_definitions>
26118
- Bootstrapper ${agentLabel}: The outer setup agent that claimed/signup, generated the
26119
- demo folder when applicable, and asked Linzumi to start this session. It does
26120
- not own implementation work.
26121
- Linzumi Commander: The local long-running process that launched this ${agentLabel}
26122
- session, enforces the trusted folder, and forwards descendant preview ports to
26123
- Linzumi.
26124
- Linzumi ${agentLabel} session: You, the inner ${agentLabel} process that performs the actual
26125
- work in the approved project folder.
26126
- Approved project folder: ${args.cwd}
26127
- Coding-job metadata: User-visible goal, plan steps, step statuses, completion
26128
- notes, primary pull request link, and worktree state shown in Linzumi's coding
26129
- job overview.
26130
- Plan step: A discrete chunk of work with a short title, one-line description,
26131
- status, and completion note. It should describe an actual goal, not just a
26132
- process verb.
26133
- Primary pull request: The GitHub PR the job is currently driving or reviewing;
26134
- Linzumi uses this link to hydrate PR comments, files, commits, checks, and
26135
- deployments in the metadata overview.
26136
- </term_definitions>
26137
-
26138
- <linzumi_mcp>
26139
- The Codex process is preconfigured with a Linzumi MCP server named "linzumi".
26140
- Use it when you need authenticated context from the current Linzumi workspace:
26141
- read a message by ID/URL, read a thread by ID, read the scoped channel, or send
26142
- a concise DM to the Commander owner when the task genuinely requires it.
26143
- </linzumi_mcp>
26144
-
26145
- <task_instructions>
26146
- Work only in the approved project folder unless the human explicitly asks for
26147
- something else in the Linzumi thread. Start, inspect, and modify the local app
26148
- from that folder. Report concise progress and exact blockers in the thread.
26149
- Use the Linzumi MCP server named "linzumi" when you need authenticated
26150
- workspace context or coding-job metadata tools.
26151
- ${planStepsInstruction}
26152
- When the coding-job scope changes materially, call linzumi_rename_coding_job
26153
- with a concise title so the Linzumi thread title and workflow-facing title
26154
- match the work.
26155
- ${planUpdateInstruction}
26156
- When you open, discover, or choose the primary pull request, link it with
26157
- linzumi_link_coding_job_pull_request.
26158
- Before stopping, confirm the metadata overview tells the truth for a user who
26159
- only sees that overview: completed work is completed, active work is really
26160
- active, blocked work says why, and no stale in-progress step is left behind.
26161
- For frontend or other user-visible changes, ask whether the user wants
26162
- screenshot or screen-recording proof, ideally before/after when that helps
26163
- review the change. Screen recordings should be WebM or MP4.
26164
- When the user asks you to open or update a PR, attach or link feasible proof in
26165
- the PR and state the exact blocker when proof is not feasible.
26166
- </task_instructions>
26167
-
26168
- <rules>
26169
- You MUST treat the Linzumi thread as the source of truth for user-facing
26170
- progress.
26171
- For coding-job threads, you MUST keep coding-job metadata current throughout
26172
- the job.
26173
- Plan steps MUST be fairly granular, discrete chunks of real work that a human
26174
- can track.
26175
- Plan step descriptions MUST stay short, usually one line and less than one full
26176
- sentence.
26177
- Plan steps MUST NOT be vague process-only labels such as "sync PR",
26178
- "update PR", "verify PR", or "commit and merge" when those labels hide the
26179
- concrete goal.
26180
- You MUST update steps in sequence as work is completed. Do not mark a later
26181
- step active while earlier applicable steps are still pending.
26182
- Before you stop work, you MUST reconcile the plan so the job is not left with a
26183
- stale active step. You may delegate metadata upkeep to a sub-agent when useful,
26184
- but the visible job state must remain truthful.
26185
- If the coding-job thread title is missing, undefined, or "Untitled", you MUST
26186
- choose a concise, specific title for the job.
26187
- User-visible UI, status, error, and PR-review copy MUST keep it short, direct,
26188
- and focused on the user's outcome unless the user explicitly asks for
26189
- implementation detail.
26190
- You MUST keep user-visible preview servers bound to 0.0.0.0, not 127.0.0.1 or
26191
- localhost, so the Linzumi secure tunnel can reach them.
26192
- You MUST keep any preview or dev server as your descendant process so the
26193
- Commander can discover and forward it.
26194
- You MUST NOT ask the Bootstrapper ${agentLabel} to do implementation work.
26195
- You MUST NOT inspect unrelated repositories or folders.
26196
- You MUST report the exact blocker if a command, preview, editor, or tunnel step
26197
- fails.
26198
- When several Linzumi Codex sessions work in the same repository, create or use
26199
- a separate checkout or git worktree for this task before making changes, unless
26200
- the human explicitly asks you to share the current checkout. Do not overwrite or
26201
- revert another session's work.
26202
- </rules>
26203
-
26204
- <examples>
26205
- GOOD preview command: npm run dev -- --host 0.0.0.0 --port 8787
26206
- Why good: The preview is reachable through the Linzumi secure tunnel.
26207
-
26208
- BAD preview command: npm run dev -- --host 127.0.0.1
26209
- Why bad: Binding to localhost prevents the Linzumi secure tunnel from reaching
26210
- the preview.
26211
-
26212
- GOOD coding-job plan:
26213
- - Inspect prompt sources: Locate the prompt builder, prompt guide, and tests.
26214
- - Add metadata guidance: Patch the prompt with plan, title, and stop-state rules.
26215
- - Verify compiled prompt: Run focused tests and inspect the generated text.
26216
- - Push PR update: Commit owned files, push the branch, and refresh PR metadata.
26217
- Why good: Each step is a concrete chunk of work, the list is short enough to
26218
- scan, and the titles explain what is actually happening.
26219
-
26220
- BAD coding-job plan:
26221
- - Sync PR
26222
- - Update PR
26223
- - Verify PR
26224
- - Commit and merge
26225
- Why bad: These labels describe process mechanics, not the concrete work or
26226
- product goal. A user cannot tell what the agent has learned, changed, or still
26227
- needs to do.
26228
-
26229
- BAD coding-job metadata state:
26230
- - Step 1 complete, Step 2 complete, Step 3 active, Step 4 pending.
26231
- - The agent has stopped working and sent its final response.
26232
- Why bad: The overview still says work is active even though the agent is idle.
26233
- Before stopping, the agent should mark Step 3 completed, blocked, or canceled
26234
- with a truthful note.
26235
- </examples>
26236
- ${linzumiContext}
26237
- ${customPrompt}
26238
- <task_reminder>
26239
- You are the Commander-launched Linzumi ${agentLabel} session. Do the implementation
26240
- work in the approved project folder, keep preview servers reachable through the
26241
- secure tunnel, and keep the Linzumi thread and coding-job metadata truthful.
26242
- </task_reminder>
26243
-
26244
- <output_format>
26245
- Use normal concise Linzumi thread messages for user-facing progress and final
26246
- answers. When the task requires files, code, commits, pull requests, previews,
26247
- or verification output, report the concrete artifact, command, result, and any
26248
- exact blocker. Do not emit machine-only JSON unless the user or tool explicitly
26249
- requests it.
26250
- </output_format>`;
26251
- }
26252
26476
  function availableRunnerAgentProviders(options) {
26253
26477
  switch (options.claudeCodeRunner !== void 0 || options.claudeCodeAvailable === true) {
26254
26478
  case true:
@@ -27155,6 +27379,7 @@ async function startClaudeCodeProviderInstance(args) {
27155
27379
  args.log("claude_code.turn_completed", {
27156
27380
  linzumi_thread_id: threadId,
27157
27381
  claude_session_id: event.sessionId,
27382
+ body_source: event.bodySource ?? null,
27158
27383
  body_preview: claudeConsoleBodyPreview(event.body)
27159
27384
  });
27160
27385
  settleFirstTurn();
@@ -27556,6 +27781,7 @@ function claudeSessionStoreEntryPayload(args, event) {
27556
27781
  case "session_completed":
27557
27782
  return {
27558
27783
  body: event.body,
27784
+ bodySource: event.bodySource ?? null,
27559
27785
  usage: event.usage ?? null
27560
27786
  };
27561
27787
  case "turn_interrupted":
@@ -29618,6 +29844,7 @@ var init_runner = __esm({
29618
29844
  init_codexRuntimeOptions();
29619
29845
  init_codexNotificationConsoleStats();
29620
29846
  init_json();
29847
+ init_commanderDeveloperInstructions();
29621
29848
  init_linzumiContext();
29622
29849
  init_localCapabilities();
29623
29850
  init_localConfig();
@@ -67237,6 +67464,7 @@ async function runRemoteCodexHarnessWorker(config, deps = {}) {
67237
67464
  rootSeq: config.rootSeq,
67238
67465
  startCodexThread: true,
67239
67466
  linzumiContext: config.linzumiContext,
67467
+ developerPrompt: config.developerPrompt,
67240
67468
  listenUser: config.listenUser,
67241
67469
  model: config.model,
67242
67470
  reasoningEffort: config.reasoningEffort,
@@ -67326,6 +67554,7 @@ function remoteCodexHarnessWorkerConfigFromJson(value) {
67326
67554
  cwd: requiredString2(input, "cwd"),
67327
67555
  workerCwd: requiredString2(input, "workerCwd"),
67328
67556
  workDescription: requiredString2(input, "workDescription"),
67557
+ ...optionalStringField(input, "developerPrompt"),
67329
67558
  readyToken: requiredString2(input, "readyToken"),
67330
67559
  codexBin: requiredString2(input, "codexBin"),
67331
67560
  execServerUrl: requiredString2(input, "execServerUrl"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linzumi/cli",
3
- "version": "0.0.88-beta",
3
+ "version": "0.0.90-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": {