@p4code/cli 0.1.5 → 0.1.6

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 (85) hide show
  1. package/dist/bin.mjs +256 -32
  2. package/dist/client/assets/{DiffPanel-ML_kqHo4.js → DiffPanel-CcuhH24b.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-DqEQ4wir.js → FilePreviewPanel-IupIzTGt.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-BwHQqsFC.js → PreviewPanel-CMRKi8Jr.js} +2 -2
  5. package/dist/client/assets/{angular-html-B70hXg9o.js → angular-html-D4VEGoU_.js} +2 -2
  6. package/dist/client/assets/{angular-ts-B2MRnnc1.js → angular-ts-DDbN_Pcg.js} +2 -2
  7. package/dist/client/assets/{apl-DY9W2lgD.js → apl-BH7XGSiX.js} +2 -2
  8. package/dist/client/assets/arrow-right-B-_6vDIF.js +2 -0
  9. package/dist/client/assets/{astro-CKmYmuzu.js → astro-DKjLEfvq.js} +2 -2
  10. package/dist/client/assets/{blade-mlm-ZlOd.js → blade-mO6atsi4.js} +2 -2
  11. package/dist/client/assets/{c-9y7zDOj9.js → c-drphy09y.js} +2 -2
  12. package/dist/client/assets/{cobol-CD87wiGp.js → cobol-Do365H-h.js} +2 -2
  13. package/dist/client/assets/{coffee-kgGv4P2i.js → coffee-CJ9y15YY.js} +2 -2
  14. package/dist/client/assets/{cpp-DpZlUxYu.js → cpp-C1FVZ39w.js} +2 -2
  15. package/dist/client/assets/{crystal-B0OLLlY8.js → crystal-D1Y9mCOy.js} +2 -2
  16. package/dist/client/assets/{css--MUQJ4o7.js → css-C1chQpfJ.js} +2 -2
  17. package/dist/client/assets/{edge-B9eY7KZn.js → edge-Cir90YUi.js} +2 -2
  18. package/dist/client/assets/{elixir-BZ2mTmkv.js → elixir-D8U_xdUO.js} +2 -2
  19. package/dist/client/assets/{elm-Bu95APK0.js → elm-DFyeiMH7.js} +2 -2
  20. package/dist/client/assets/{erb-CpKKo-fS.js → erb-C6TvSrSi.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-DeY-XSzV.js → fileCommentAnnotations-BotL9n2H.js} +2 -2
  22. package/dist/client/assets/{git-rebase-BN70gFDl.js → git-rebase-C7PDtezn.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-DM-4mIJW.js → glimmer-js-CXugsloP.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-DFHG1P9q.js → glimmer-ts-IiHVp7il.js} +2 -2
  25. package/dist/client/assets/{glsl-B4TfqZ4_.js → glsl-eBdXjOgx.js} +2 -2
  26. package/dist/client/assets/{graphql-DLTs8DHV.js → graphql-CBwWowq8.js} +2 -2
  27. package/dist/client/assets/{hack-DRRemOcq.js → hack-DoKoRNA-.js} +2 -2
  28. package/dist/client/assets/{haml-4MHJURGQ.js → haml-B2cA0ELm.js} +2 -2
  29. package/dist/client/assets/{handlebars-D7k0HQlo.js → handlebars-OQNujFnM.js} +2 -2
  30. package/dist/client/assets/{html-DovLLqdO.js → html-B9l55IY7.js} +2 -2
  31. package/dist/client/assets/{html-derivative-Dz9-s5om.js → html-derivative-C24d_8BY.js} +2 -2
  32. package/dist/client/assets/{http-cxnvN4kV.js → http-Ca2KxOLT.js} +2 -2
  33. package/dist/client/assets/{hurl-CSBBQOts.js → hurl-BuLZo6wH.js} +2 -2
  34. package/dist/client/assets/index-B-nUm8w7.js +664 -0
  35. package/dist/client/assets/{index-oFUAtVw2.css → index-DfKhU-QO.css} +1 -1
  36. package/dist/client/assets/{java-DIz7GrNg.js → java-BEVFbCvA.js} +2 -2
  37. package/dist/client/assets/{javascript-MSaRoKWU.js → javascript-AWDSrR1H.js} +2 -2
  38. package/dist/client/assets/{jinja-DI8yd0V9.js → jinja-CFK-VDc5.js} +2 -2
  39. package/dist/client/assets/{jison-sGqXSFug.js → jison-CSmyyQ0V.js} +2 -2
  40. package/dist/client/assets/{json-C_w_LNS4.js → json-D0qpEJUc.js} +2 -2
  41. package/dist/client/assets/{jsx-V5NNKrjP.js → jsx-smdyqdvA.js} +2 -2
  42. package/dist/client/assets/{julia-BGVYDOsa.js → julia-DWo_5QH9.js} +2 -2
  43. package/dist/client/assets/{just-D-04ksOT.js → just-74GGYpCk.js} +2 -2
  44. package/dist/client/assets/{latex-BEsliWgq.js → latex-d8vfKbSv.js} +2 -2
  45. package/dist/client/assets/{liquid-DNUeMt2o.js → liquid-CsWNZJPs.js} +2 -2
  46. package/dist/client/assets/{lua--TgilFGl.js → lua-x3z7TbKl.js} +2 -2
  47. package/dist/client/assets/{marko-DxDDGF4_.js → marko-CJyhwQKw.js} +2 -2
  48. package/dist/client/assets/{mdc-DzjvIH05.js → mdc-CtbrrOYL.js} +2 -2
  49. package/dist/client/assets/{nginx-BqVTMZgm.js → nginx-CY-uw8dq.js} +2 -2
  50. package/dist/client/assets/{nim-Co2BGNef.js → nim-DqT4w7rK.js} +2 -2
  51. package/dist/client/assets/{perl-Ba8Q7XkF.js → perl-CE3_DH1L.js} +2 -2
  52. package/dist/client/assets/{php-Bfbmd1-m.js → php-Zxsq1XMP.js} +2 -2
  53. package/dist/client/assets/{pug-be-1Xvfl.js → pug-DnCImvlN.js} +2 -2
  54. package/dist/client/assets/{qml-D7icULiU.js → qml-DLfoubjW.js} +2 -2
  55. package/dist/client/assets/{r-Cph7TJQ-.js → r-BtfLsgk8.js} +2 -2
  56. package/dist/client/assets/{razor-CjLyUAnD.js → razor-CzAGh9fe.js} +2 -2
  57. package/dist/client/assets/{regexp-BdlWyepz.js → regexp-Bw17HBbP.js} +2 -2
  58. package/dist/client/assets/{rst-DEpJb2t7.js → rst-DhRr0i5h.js} +2 -2
  59. package/dist/client/assets/{ruby-C_x4mzoE.js → ruby-BnQcHU-H.js} +2 -2
  60. package/dist/client/assets/{sas-Bz4iLdau.js → sas-mMUu1htl.js} +2 -2
  61. package/dist/client/assets/{scss-CwgGUhGr.js → scss-0_lOQ1qp.js} +2 -2
  62. package/dist/client/assets/{shellscript-CHr9xiaE.js → shellscript-BAdG8zJQ.js} +2 -2
  63. package/dist/client/assets/{shellsession-11TeKCPG.js → shellsession-upfFHc_2.js} +2 -2
  64. package/dist/client/assets/{soy-BLILGuwU.js → soy-Dzncl5z6.js} +2 -2
  65. package/dist/client/assets/{sql-KbVEV8JC.js → sql-Ca2FgbAh.js} +2 -2
  66. package/dist/client/assets/{stata-XDGn9IyV.js → stata-D8IymSlL.js} +2 -2
  67. package/dist/client/assets/{surrealql-BkijSB1h.js → surrealql-CZsXvHAi.js} +2 -2
  68. package/dist/client/assets/{svelte-S0qXpcmK.js → svelte-nM61Il-m.js} +2 -2
  69. package/dist/client/assets/{templ-Bpai32u4.js → templ-DzjlBYVo.js} +2 -2
  70. package/dist/client/assets/{tex-BqM5PBWs.js → tex-BPwIdn9G.js} +2 -2
  71. package/dist/client/assets/{textarea-CUI7U2B2.js → textarea-DhTMBBxg.js} +2 -2
  72. package/dist/client/assets/{ts-tags-ChsaUtvn.js → ts-tags-BL9D6Ojm.js} +2 -2
  73. package/dist/client/assets/{tsx-Bw54IsTH.js → tsx-AKDVGVPx.js} +2 -2
  74. package/dist/client/assets/{twig-BOAKWhe6.js → twig-B79f_Jz8.js} +2 -2
  75. package/dist/client/assets/{typescript-vJRDqHfC.js → typescript-BsyojNKj.js} +2 -2
  76. package/dist/client/assets/{vue-CtHSR0DM.js → vue-BTqtDJT-.js} +2 -2
  77. package/dist/client/assets/{vue-html-KPDmQoqc.js → vue-html-B_UfyILk.js} +2 -2
  78. package/dist/client/assets/{vue-vine-BSN6OPd2.js → vue-vine-B8EK-Iln.js} +2 -2
  79. package/dist/client/assets/{xml-8bohM9Or.js → xml-Do-3lCfL.js} +2 -2
  80. package/dist/client/assets/{xsl-D-jqs0Dv.js → xsl-CwgwYGXC.js} +2 -2
  81. package/dist/client/assets/{yaml-CzI494Jo.js → yaml-C4XlY-p5.js} +2 -2
  82. package/dist/client/index.html +3 -3
  83. package/package.json +1 -1
  84. package/dist/client/assets/arrow-right-Bdq-W9Bm.js +0 -2
  85. package/dist/client/assets/index-BXFHdiV7.js +0 -664
package/dist/bin.mjs CHANGED
@@ -233,7 +233,7 @@ const make$73 = () => {
233
233
  const layer$69 = Layer.sync(NetService, make$73);
234
234
  //#endregion
235
235
  //#region package.json
236
- var version = "0.1.5";
236
+ var version = "0.1.6";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -5634,11 +5634,22 @@ const ClaudeSettings = makeProviderSettingsSchema({
5634
5634
  placeholder: "e.g. --chrome",
5635
5635
  clearWhenEmpty: "omit"
5636
5636
  }
5637
+ })),
5638
+ /**
5639
+ * The SDK preset system prompt omits the interactive CLI's terminal-tone
5640
+ * sections, so the model tends to open a turn with a tool call instead of
5641
+ * saying what it is about to do. This appends that instruction back.
5642
+ */
5643
+ narrateBeforeTools: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true)), Schema$1.annotateKey({
5644
+ title: "Explain before tool calls",
5645
+ description: "Ask Claude to write one short sentence before its first tool call in a turn.",
5646
+ providerSettingsForm: { control: "switch" }
5637
5647
  }))
5638
5648
  }, { order: [
5639
5649
  "binaryPath",
5640
5650
  "homePath",
5641
- "launchArgs"
5651
+ "launchArgs",
5652
+ "narrateBeforeTools"
5642
5653
  ] });
5643
5654
  const CursorSettings = makeProviderSettingsSchema({
5644
5655
  enabled: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false)), Schema$1.annotateKey({ providerSettingsForm: { hidden: true } })),
@@ -5827,7 +5838,8 @@ const ClaudeSettingsPatch = Schema$1.Struct({
5827
5838
  binaryPath: Schema$1.optionalKey(TrimmedString),
5828
5839
  homePath: Schema$1.optionalKey(TrimmedString),
5829
5840
  customModels: Schema$1.optionalKey(Schema$1.Array(Schema$1.String)),
5830
- launchArgs: Schema$1.optionalKey(TrimmedString)
5841
+ launchArgs: Schema$1.optionalKey(TrimmedString),
5842
+ narrateBeforeTools: Schema$1.optionalKey(Schema$1.Boolean)
5831
5843
  });
5832
5844
  const CursorSettingsPatch = Schema$1.Struct({
5833
5845
  enabled: Schema$1.optionalKey(Schema$1.Boolean),
@@ -46471,6 +46483,177 @@ function parseCliArgs(args, options) {
46471
46483
  };
46472
46484
  }
46473
46485
  //#endregion
46486
+ //#region ../../packages/shared/src/toolCategory.ts
46487
+ const TOOL_CATEGORY_TITLES = {
46488
+ file_read: "File read",
46489
+ file_search: "File search",
46490
+ file_change: "File change",
46491
+ command: "Command run",
46492
+ mcp_tool: "MCP tool call",
46493
+ subagent: "Subagent task",
46494
+ web_search: "Web search",
46495
+ image_view: "Image view",
46496
+ tool: "Tool call"
46497
+ };
46498
+ /** Exact tool names, lowercased, matched before the substring heuristics below. */
46499
+ const FILE_READ_TOOL_NAMES = /* @__PURE__ */ new Set([
46500
+ "read",
46501
+ "readfile",
46502
+ "read_file",
46503
+ "view",
46504
+ "viewfile",
46505
+ "view_file",
46506
+ "notebookread",
46507
+ "notebook_read",
46508
+ "openfile",
46509
+ "open_file"
46510
+ ]);
46511
+ const FILE_SEARCH_TOOL_NAMES = /* @__PURE__ */ new Set([
46512
+ "grep",
46513
+ "glob",
46514
+ "search",
46515
+ "filesearch",
46516
+ "file_search",
46517
+ "codebasesearch",
46518
+ "codebase_search",
46519
+ "listdir",
46520
+ "list_dir",
46521
+ "list_directory",
46522
+ "ls"
46523
+ ]);
46524
+ const FILE_CHANGE_TOOL_NAMES = /* @__PURE__ */ new Set([
46525
+ "edit",
46526
+ "write",
46527
+ "multiedit",
46528
+ "multi_edit",
46529
+ "notebookedit",
46530
+ "notebook_edit",
46531
+ "applypatch",
46532
+ "apply_patch",
46533
+ "patch",
46534
+ "createfile",
46535
+ "create_file",
46536
+ "strreplace",
46537
+ "str_replace",
46538
+ "deletefile",
46539
+ "delete_file"
46540
+ ]);
46541
+ const COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set([
46542
+ "bash",
46543
+ "shell",
46544
+ "terminal",
46545
+ "exec",
46546
+ "execute",
46547
+ "execcommand",
46548
+ "exec_command",
46549
+ "runcommand",
46550
+ "run_command",
46551
+ "local_shell"
46552
+ ]);
46553
+ const SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
46554
+ "task",
46555
+ "agent",
46556
+ "subagent",
46557
+ "sub_agent"
46558
+ ]);
46559
+ /** Shell builtins that say nothing about what the command as a whole does. */
46560
+ const NEUTRAL_SHELL_COMMANDS = /* @__PURE__ */ new Set([
46561
+ "cd",
46562
+ "echo",
46563
+ "printf",
46564
+ "pwd",
46565
+ "true",
46566
+ "false",
46567
+ "time"
46568
+ ]);
46569
+ /** `sed`/`awk` are stream editors (`sed -i` rewrites files), so they stay commands. */
46570
+ const FILE_READ_SHELL_COMMANDS = /* @__PURE__ */ new Set([
46571
+ "cat",
46572
+ "head",
46573
+ "tail",
46574
+ "less",
46575
+ "more",
46576
+ "bat",
46577
+ "nl",
46578
+ "jq",
46579
+ "wc"
46580
+ ]);
46581
+ const FILE_SEARCH_SHELL_COMMANDS = /* @__PURE__ */ new Set([
46582
+ "grep",
46583
+ "egrep",
46584
+ "fgrep",
46585
+ "rg",
46586
+ "ag",
46587
+ "ack",
46588
+ "find",
46589
+ "fd",
46590
+ "ls",
46591
+ "tree",
46592
+ "which"
46593
+ ]);
46594
+ const SHELL_SEGMENT_SEPARATOR = /\|\||&&|;|\||\n/u;
46595
+ const LEADING_ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=\S*\s+/u;
46596
+ const REDIRECT_TARGET = /\d?>>?\s*(?<target>[^\s|;&]+)/gu;
46597
+ /** Redirect targets that discard output instead of writing a file. */
46598
+ const DISCARDED_REDIRECT_TARGETS = /* @__PURE__ */ new Set([
46599
+ "/dev/null",
46600
+ "&1",
46601
+ "&2"
46602
+ ]);
46603
+ function toolCategoryTitle(category) {
46604
+ return TOOL_CATEGORY_TITLES[category];
46605
+ }
46606
+ function readCommandInput(toolInput) {
46607
+ const raw = toolInput?.command ?? toolInput?.cmd;
46608
+ if (typeof raw !== "string") return;
46609
+ const trimmed = raw.trim();
46610
+ return trimmed.length > 0 ? trimmed : void 0;
46611
+ }
46612
+ /** `cat x > y` reads and writes; only the write matters for the row heading. */
46613
+ function redirectsToFile(segment) {
46614
+ for (const match of segment.matchAll(REDIRECT_TARGET)) {
46615
+ const target = match.groups?.target;
46616
+ if (target && !DISCARDED_REDIRECT_TARGETS.has(target)) return true;
46617
+ }
46618
+ return false;
46619
+ }
46620
+ function classifyShellSegment(segment) {
46621
+ if (redirectsToFile(segment)) return "command";
46622
+ const head = segment.trim().replace(LEADING_ENV_ASSIGNMENT, "").split(/\s+/u)[0]?.replace(/^.*\//u, "").toLowerCase();
46623
+ if (!head || NEUTRAL_SHELL_COMMANDS.has(head)) return;
46624
+ if (FILE_READ_SHELL_COMMANDS.has(head)) return "file_read";
46625
+ if (FILE_SEARCH_SHELL_COMMANDS.has(head)) return "file_search";
46626
+ return "command";
46627
+ }
46628
+ /**
46629
+ * A shell call only counts as a read or a search when every non-neutral segment
46630
+ * agrees; anything unrecognized (or write-ish) keeps the whole call a command.
46631
+ * Quoted separators split segments too, which can only downgrade to "command".
46632
+ */
46633
+ function classifyShellCommand(command) {
46634
+ const categories = command.split(SHELL_SEGMENT_SEPARATOR).map(classifyShellSegment).filter((category) => category !== void 0);
46635
+ if (categories.length === 0 || categories.includes("command")) return "command";
46636
+ return categories.includes("file_search") ? "file_search" : "file_read";
46637
+ }
46638
+ function classifyToolCategory(input) {
46639
+ const normalized = input.toolName.trim().toLowerCase();
46640
+ if (normalized.startsWith("mcp__") || normalized.includes("mcp")) return "mcp_tool";
46641
+ if (SUBAGENT_TOOL_NAMES.has(normalized) || normalized.includes("agent")) return "subagent";
46642
+ if (FILE_READ_TOOL_NAMES.has(normalized)) return "file_read";
46643
+ if (FILE_SEARCH_TOOL_NAMES.has(normalized)) return "file_search";
46644
+ if (FILE_CHANGE_TOOL_NAMES.has(normalized)) return "file_change";
46645
+ if (COMMAND_TOOL_NAMES.has(normalized) || normalized.includes("bash") || normalized.includes("shell") || normalized.includes("terminal") || normalized.includes("command")) {
46646
+ const command = readCommandInput(input.toolInput);
46647
+ return command ? classifyShellCommand(command) : "command";
46648
+ }
46649
+ if (normalized.includes("websearch") || normalized.includes("web_search")) return "web_search";
46650
+ if (normalized.includes("edit") || normalized.includes("write") || normalized.includes("patch") || normalized.includes("replace") || normalized.includes("create") || normalized.includes("delete")) return "file_change";
46651
+ if (normalized.includes("read") || normalized.includes("view")) return "file_read";
46652
+ if (normalized.includes("grep") || normalized.includes("glob")) return "file_search";
46653
+ if (normalized.includes("image")) return "image_view";
46654
+ return "tool";
46655
+ }
46656
+ //#endregion
46474
46657
  //#region src/provider/askUserQuestionAnswers.ts
46475
46658
  /**
46476
46659
  * What separates the options of one multi-select answer.
@@ -47043,17 +47226,17 @@ function summarizeToolRequest(toolName, input) {
47043
47226
  if (serialized.length <= TOOL_SUMMARY_MAX_LENGTH) return `${toolName}: ${serialized}`;
47044
47227
  return `${toolName}: ${serialized.slice(0, TOOL_SUMMARY_MAX_LENGTH - 3)}...`;
47045
47228
  }
47046
- function titleForTool(itemType) {
47047
- switch (itemType) {
47048
- case "command_execution": return "Command run";
47049
- case "file_change": return "File change";
47050
- case "mcp_tool_call": return "MCP tool call";
47051
- case "collab_agent_tool_call": return "Subagent task";
47052
- case "web_search": return "Web search";
47053
- case "image_view": return "Image view";
47054
- case "dynamic_tool_call": return "Tool call";
47055
- default: return "Item";
47056
- }
47229
+ /**
47230
+ * Heading for a tool row. Derived from the tool name plus its input so a `Read`
47231
+ * reads as "File read" instead of a bare "Tool call", and a `Bash` that only
47232
+ * inspects a file (`sed -n 1,20p src/x.ts`) reads as "File read" instead of
47233
+ * "Command run". Recomputed as streamed input arrives.
47234
+ */
47235
+ function titleForTool(toolName, toolInput) {
47236
+ return toolCategoryTitle(classifyToolCategory({
47237
+ toolName,
47238
+ toolInput
47239
+ }));
47057
47240
  }
47058
47241
  const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
47059
47242
  "image/gif",
@@ -47061,6 +47244,12 @@ const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
47061
47244
  "image/png",
47062
47245
  "image/webp"
47063
47246
  ]);
47247
+ /**
47248
+ * Appended to the preset system prompt when `narrateBeforeTools` is on. The SDK
47249
+ * preset drops the interactive CLI's terminal-tone sections, so without this the
47250
+ * model opens a turn with a tool call rather than a line saying what it is doing.
47251
+ */
47252
+ const NARRATE_BEFORE_TOOLS_PROMPT = "Before your first tool call in a turn, write one short sentence saying what you are about to do. Keep it to a single line and skip it when you are answering without tools.";
47064
47253
  const CLAUDE_SETTING_SOURCES = [
47065
47254
  "user",
47066
47255
  "project",
@@ -47786,6 +47975,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47786
47975
  });
47787
47976
  const updatedAt = yield* nowIso;
47788
47977
  context.turnState = void 0;
47978
+ context.steeredMessages.clear();
47789
47979
  context.session = {
47790
47980
  ...context.session,
47791
47981
  status: "ready",
@@ -47832,6 +48022,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47832
48022
  const updatedAt = yield* nowIso;
47833
48023
  context.turnState = turnState;
47834
48024
  context.interruptRequested = false;
48025
+ context.steeredMessages.clear();
47835
48026
  context.session = {
47836
48027
  ...context.session,
47837
48028
  status: "running",
@@ -47853,24 +48044,49 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47853
48044
  /**
47854
48045
  * Start the queued sendTurn, if the session has gone idle.
47855
48046
  *
47856
- * Runs after every turn completion. Messages sent during a running turn wait
47857
- * here for the agent loop to end; pressing Stop ends the loop early, and the
47858
- * same drain is what makes Stop mean "and run my messages now". Everything
47859
- * queued during that turn is already merged into one request by `sendTurn`,
47860
- * so it starts as a single turn.
48047
+ * Runs after every turn completion. What waits here are the steers Stop took
48048
+ * back from the CLI before they ran (see `reclaimSteeredMessages`); they are
48049
+ * already merged into one request, so they start as a single turn the moment
48050
+ * the interrupted loop ends.
47861
48051
  */
47862
48052
  const drainPendingTurns = Effect.fn("drainPendingTurns")(function* (context) {
47863
48053
  if (context.stopped || context.turnState !== void 0) return;
47864
48054
  const pending = context.pendingTurnRequest;
47865
48055
  if (pending === void 0) return;
47866
48056
  context.pendingTurnRequest = void 0;
47867
- yield* applyTurnSessionOptions(context, pending.modelSelection, pending.interactionMode).pipe(Effect.catchCause((cause) => Effect.logWarning("claude pending turn session options failed", { cause })));
47868
- yield* beginTurn(context, pending.turnId, pending.modelSelection?.model);
48057
+ yield* beginTurn(context, pending.turnId, context.session.model);
47869
48058
  yield* Queue.offer(context.promptQueue, {
47870
48059
  type: "message",
47871
48060
  message: pending.message
47872
48061
  }).pipe(Effect.ignore);
47873
48062
  });
48063
+ /**
48064
+ * Take back the steers the CLI has not started yet, so Stop still runs them.
48065
+ *
48066
+ * A steer sits in the CLI's async-message queue until the agent loop reaches
48067
+ * its next boundary. `cancelAsyncMessage` answers true only while it is still
48068
+ * waiting there: those never reached the model and become the next turn
48069
+ * together, while an already-dequeued one is part of the turn being stopped
48070
+ * and must not run a second time.
48071
+ */
48072
+ const reclaimSteeredMessages = Effect.fn("reclaimSteeredMessages")(function* (context) {
48073
+ const steered = Array.from(context.steeredMessages);
48074
+ context.steeredMessages.clear();
48075
+ const cancelAsyncMessage = context.query.cancelAsyncMessage;
48076
+ if (steered.length === 0 || cancelAsyncMessage === void 0) return;
48077
+ const reclaimed = [];
48078
+ for (const [messageUuid, message] of steered) if (yield* Effect.tryPromise({
48079
+ try: () => cancelAsyncMessage(messageUuid),
48080
+ catch: (cause) => toRequestError$1(context.session.threadId, "turn/cancelAsyncMessage", cause)
48081
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("claude steer cancel failed", { cause }).pipe(Effect.as(false))))) reclaimed.push(message);
48082
+ if (reclaimed.length === 0) return;
48083
+ const merged = reclaimed.reduce((left, right) => mergeUserMessages(left, right));
48084
+ const pending = context.pendingTurnRequest;
48085
+ context.pendingTurnRequest = {
48086
+ turnId: pending?.turnId ?? TurnId.make(yield* randomUUIDv4),
48087
+ message: pending ? mergeUserMessages(pending.message, merged) : merged
48088
+ };
48089
+ });
47874
48090
  const handleStreamEvent = Effect.fn("handleStreamEvent")(function* (context, message) {
47875
48091
  if (message.type !== "stream_event") return;
47876
48092
  const { event } = message;
@@ -47921,11 +48137,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47921
48137
  const partialInputJson = tool.partialInputJson + event.delta.partial_json;
47922
48138
  const parsedInput = tryParseJsonRecord(partialInputJson);
47923
48139
  const detail = parsedInput ? summarizeToolRequest(tool.toolName, parsedInput) : tool.detail;
48140
+ const title = parsedInput ? titleForTool(tool.toolName, parsedInput) : tool.title;
47924
48141
  let nextTool = {
47925
48142
  ...tool,
47926
48143
  partialInputJson,
47927
48144
  ...parsedInput ? { input: parsedInput } : {},
47928
- ...detail ? { detail } : {}
48145
+ ...detail ? { detail } : {},
48146
+ title
47929
48147
  };
47930
48148
  const nextFingerprint = parsedInput && Object.keys(parsedInput).length > 0 ? toolInputFingerprint(parsedInput) : void 0;
47931
48149
  context.inFlightTools.set(event.index, nextTool);
@@ -47998,7 +48216,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47998
48216
  itemId,
47999
48217
  itemType,
48000
48218
  toolName,
48001
- title: titleForTool(itemType),
48219
+ title: titleForTool(toolName, toolInput),
48002
48220
  detail,
48003
48221
  input: toolInput,
48004
48222
  partialInputJson: "",
@@ -48864,7 +49082,8 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48864
49082
  pathToClaudeCodeExecutable: claudeBinaryPath,
48865
49083
  systemPrompt: {
48866
49084
  type: "preset",
48867
- preset: "claude_code"
49085
+ preset: "claude_code",
49086
+ ...claudeSettings.narrateBeforeTools ? { append: NARRATE_BEFORE_TOOLS_PROMPT } : {}
48868
49087
  },
48869
49088
  settingSources: [...CLAUDE_SETTING_SOURCES],
48870
49089
  ...effectiveEffort ? { effort: effectiveEffort } : {},
@@ -48906,6 +49125,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48906
49125
  "claude.query.include_partial_messages": true,
48907
49126
  "claude.query.additional_directories": input.cwd ? [input.cwd] : [],
48908
49127
  "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES],
49128
+ "claude.query.narrate_before_tools": claudeSettings.narrateBeforeTools,
48909
49129
  "claude.query.settings_json": encodeJsonStringForDiagnostics$2(settings) ?? "",
48910
49130
  "claude.query.extra_args_json": encodeJsonStringForDiagnostics$2(extraArgs) ?? "",
48911
49131
  "claude.query.path_to_executable": claudeBinaryPath
@@ -48956,6 +49176,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48956
49176
  claudeTasks,
48957
49177
  turnState: void 0,
48958
49178
  pendingTurnRequest: void 0,
49179
+ steeredMessages: /* @__PURE__ */ new Map(),
48959
49180
  lastKnownContextWindow: initialContextWindow,
48960
49181
  lastKnownTokenUsage: void 0,
48961
49182
  lastKnownTotalProcessedTokens: void 0,
@@ -49024,17 +49245,19 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
49024
49245
  boundInstanceId
49025
49246
  });
49026
49247
  if (context.turnState) {
49027
- const pending = context.pendingTurnRequest;
49028
- const turnId = pending?.turnId ?? TurnId.make(yield* randomUUIDv4);
49029
- context.pendingTurnRequest = {
49030
- turnId,
49031
- message: pending ? mergeUserMessages(pending.message, message) : message,
49032
- modelSelection,
49033
- interactionMode: input.interactionMode
49248
+ const steerUuid = yield* randomUUIDv4;
49249
+ const steered = {
49250
+ ...message,
49251
+ uuid: steerUuid
49034
49252
  };
49253
+ context.steeredMessages.set(steerUuid, steered);
49254
+ yield* Queue.offer(context.promptQueue, {
49255
+ type: "message",
49256
+ message: steered
49257
+ }).pipe(Effect.mapError((cause) => toRequestError$1(input.threadId, "turn/start", cause)));
49035
49258
  return {
49036
49259
  threadId: context.session.threadId,
49037
- turnId,
49260
+ turnId: context.turnState.turnId,
49038
49261
  ...context.session.resumeCursor !== void 0 ? { resumeCursor: context.session.resumeCursor } : {}
49039
49262
  };
49040
49263
  }
@@ -49054,6 +49277,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
49054
49277
  const interruptTurn = Effect.fn("interruptTurn")(function* (threadId, _turnId) {
49055
49278
  const context = yield* requireSession(threadId);
49056
49279
  context.interruptRequested = true;
49280
+ yield* reclaimSteeredMessages(context);
49057
49281
  yield* Effect.tryPromise({
49058
49282
  try: () => context.query.interrupt(),
49059
49283
  catch: (cause) => toRequestError$1(threadId, "turn/interrupt", cause)