@p4code/cli 0.1.5 → 0.1.7

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 (86) hide show
  1. package/dist/bin.mjs +400 -30
  2. package/dist/client/assets/{DiffPanel-ML_kqHo4.js → DiffPanel-B_RUbYdJ.js} +2 -2
  3. package/dist/client/assets/{FilePreviewPanel-DqEQ4wir.js → FilePreviewPanel-D_TE6anL.js} +2 -2
  4. package/dist/client/assets/{PreviewPanel-BwHQqsFC.js → PreviewPanel-Dx5mB3w6.js} +2 -2
  5. package/dist/client/assets/{angular-html-B70hXg9o.js → angular-html-Z6GyEAg_.js} +2 -2
  6. package/dist/client/assets/{angular-ts-B2MRnnc1.js → angular-ts-BdawRcND.js} +2 -2
  7. package/dist/client/assets/{apl-DY9W2lgD.js → apl-0Nzwg3c9.js} +2 -2
  8. package/dist/client/assets/arrow-right-BVDRuCFE.js +2 -0
  9. package/dist/client/assets/{astro-CKmYmuzu.js → astro-iJpYXO_m.js} +2 -2
  10. package/dist/client/assets/{blade-mlm-ZlOd.js → blade-BeMtIYzp.js} +2 -2
  11. package/dist/client/assets/{c-9y7zDOj9.js → c-n5Rj77uj.js} +2 -2
  12. package/dist/client/assets/{cobol-CD87wiGp.js → cobol-CfsSyjzP.js} +2 -2
  13. package/dist/client/assets/{coffee-kgGv4P2i.js → coffee-LZB1mvnz.js} +2 -2
  14. package/dist/client/assets/{cpp-DpZlUxYu.js → cpp-C689OQY_.js} +2 -2
  15. package/dist/client/assets/{crystal-B0OLLlY8.js → crystal-CC7u-hEs.js} +2 -2
  16. package/dist/client/assets/{css--MUQJ4o7.js → css-eVqGM3Yx.js} +2 -2
  17. package/dist/client/assets/{edge-B9eY7KZn.js → edge-CvU3NO69.js} +2 -2
  18. package/dist/client/assets/{elixir-BZ2mTmkv.js → elixir-gblyDHJ2.js} +2 -2
  19. package/dist/client/assets/{elm-Bu95APK0.js → elm-BEDREZ-r.js} +2 -2
  20. package/dist/client/assets/{erb-CpKKo-fS.js → erb-C_pVTICR.js} +2 -2
  21. package/dist/client/assets/{fileCommentAnnotations-DeY-XSzV.js → fileCommentAnnotations-Z0wcn9Z2.js} +2 -2
  22. package/dist/client/assets/{git-rebase-BN70gFDl.js → git-rebase-BvBzTqE6.js} +2 -2
  23. package/dist/client/assets/{glimmer-js-DM-4mIJW.js → glimmer-js-y6xvI0dn.js} +2 -2
  24. package/dist/client/assets/{glimmer-ts-DFHG1P9q.js → glimmer-ts-BrWleVTO.js} +2 -2
  25. package/dist/client/assets/{glsl-B4TfqZ4_.js → glsl-Bkrdoinn.js} +2 -2
  26. package/dist/client/assets/{graphql-DLTs8DHV.js → graphql-CSx1KLSt.js} +2 -2
  27. package/dist/client/assets/{hack-DRRemOcq.js → hack-CEPENt6B.js} +2 -2
  28. package/dist/client/assets/{haml-4MHJURGQ.js → haml-CR4MMWD-.js} +2 -2
  29. package/dist/client/assets/{handlebars-D7k0HQlo.js → handlebars-BvZ81eM2.js} +2 -2
  30. package/dist/client/assets/{html-DovLLqdO.js → html-CJJ_Yk-p.js} +2 -2
  31. package/dist/client/assets/{html-derivative-Dz9-s5om.js → html-derivative-DyGTUpT_.js} +2 -2
  32. package/dist/client/assets/{http-cxnvN4kV.js → http-CthnayZT.js} +2 -2
  33. package/dist/client/assets/{hurl-CSBBQOts.js → hurl-CySpRHwn.js} +2 -2
  34. package/dist/client/assets/index-CTDRPB7h.js +664 -0
  35. package/dist/client/assets/index-CstNfQU9.css +1 -0
  36. package/dist/client/assets/{java-DIz7GrNg.js → java-eLetcmnB.js} +2 -2
  37. package/dist/client/assets/{javascript-MSaRoKWU.js → javascript-bze-DcnN.js} +2 -2
  38. package/dist/client/assets/{jinja-DI8yd0V9.js → jinja-BwBfXVzv.js} +2 -2
  39. package/dist/client/assets/{jison-sGqXSFug.js → jison-BG-6HBeN.js} +2 -2
  40. package/dist/client/assets/{json-C_w_LNS4.js → json-D04kql_u.js} +2 -2
  41. package/dist/client/assets/{jsx-V5NNKrjP.js → jsx-B3RhaC78.js} +2 -2
  42. package/dist/client/assets/{julia-BGVYDOsa.js → julia-CLYU2NBT.js} +2 -2
  43. package/dist/client/assets/{just-D-04ksOT.js → just-CQGSktLF.js} +2 -2
  44. package/dist/client/assets/{latex-BEsliWgq.js → latex-B2r18a0i.js} +2 -2
  45. package/dist/client/assets/{liquid-DNUeMt2o.js → liquid-DSPVRc0S.js} +2 -2
  46. package/dist/client/assets/{lua--TgilFGl.js → lua-CBxs-my9.js} +2 -2
  47. package/dist/client/assets/{marko-DxDDGF4_.js → marko-FkXUABHT.js} +2 -2
  48. package/dist/client/assets/{mdc-DzjvIH05.js → mdc-BW-qJ07Y.js} +2 -2
  49. package/dist/client/assets/{nginx-BqVTMZgm.js → nginx-r-F4ISTc.js} +2 -2
  50. package/dist/client/assets/{nim-Co2BGNef.js → nim-CqGONDki.js} +2 -2
  51. package/dist/client/assets/{perl-Ba8Q7XkF.js → perl-YqxSDkbB.js} +2 -2
  52. package/dist/client/assets/{php-Bfbmd1-m.js → php-o53m7ISl.js} +2 -2
  53. package/dist/client/assets/{pug-be-1Xvfl.js → pug-DvC1IxmZ.js} +2 -2
  54. package/dist/client/assets/{qml-D7icULiU.js → qml-B_UHgwrf.js} +2 -2
  55. package/dist/client/assets/{r-Cph7TJQ-.js → r-BslRtXqA.js} +2 -2
  56. package/dist/client/assets/{razor-CjLyUAnD.js → razor-DbFRY8EJ.js} +2 -2
  57. package/dist/client/assets/{regexp-BdlWyepz.js → regexp-LUjvkiV5.js} +2 -2
  58. package/dist/client/assets/{rst-DEpJb2t7.js → rst-DjMRC4dl.js} +2 -2
  59. package/dist/client/assets/{ruby-C_x4mzoE.js → ruby-D1SSXqCW.js} +2 -2
  60. package/dist/client/assets/{sas-Bz4iLdau.js → sas-8pH9ZpYC.js} +2 -2
  61. package/dist/client/assets/{scss-CwgGUhGr.js → scss-CLXZYmiL.js} +2 -2
  62. package/dist/client/assets/{shellscript-CHr9xiaE.js → shellscript-C3x4iGUN.js} +2 -2
  63. package/dist/client/assets/{shellsession-11TeKCPG.js → shellsession-4iw-70AB.js} +2 -2
  64. package/dist/client/assets/{soy-BLILGuwU.js → soy-BmuiXqhy.js} +2 -2
  65. package/dist/client/assets/{sql-KbVEV8JC.js → sql-CYtr1JGt.js} +2 -2
  66. package/dist/client/assets/{stata-XDGn9IyV.js → stata-D-p-ifu4.js} +2 -2
  67. package/dist/client/assets/{surrealql-BkijSB1h.js → surrealql-BQtaSVD9.js} +2 -2
  68. package/dist/client/assets/{svelte-S0qXpcmK.js → svelte-BZ0IuCtg.js} +2 -2
  69. package/dist/client/assets/{templ-Bpai32u4.js → templ-D_0C9uOG.js} +2 -2
  70. package/dist/client/assets/{tex-BqM5PBWs.js → tex-Da564oMl.js} +2 -2
  71. package/dist/client/assets/{textarea-CUI7U2B2.js → textarea-CAyleL6s.js} +2 -2
  72. package/dist/client/assets/{ts-tags-ChsaUtvn.js → ts-tags-CCvt3g0S.js} +2 -2
  73. package/dist/client/assets/{tsx-Bw54IsTH.js → tsx-C1pKkLrg.js} +2 -2
  74. package/dist/client/assets/{twig-BOAKWhe6.js → twig-ulf4RPD9.js} +2 -2
  75. package/dist/client/assets/{typescript-vJRDqHfC.js → typescript-Cm2BDYNJ.js} +2 -2
  76. package/dist/client/assets/{vue-CtHSR0DM.js → vue-D38YVq_o.js} +2 -2
  77. package/dist/client/assets/{vue-html-KPDmQoqc.js → vue-html-Dw9Iwm-j.js} +2 -2
  78. package/dist/client/assets/{vue-vine-BSN6OPd2.js → vue-vine-DGhLcVZx.js} +2 -2
  79. package/dist/client/assets/{xml-8bohM9Or.js → xml-Bm9IDqQ-.js} +2 -2
  80. package/dist/client/assets/{xsl-D-jqs0Dv.js → xsl-CLGFKBCX.js} +2 -2
  81. package/dist/client/assets/{yaml-CzI494Jo.js → yaml-C5Olrwb1.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
  86. package/dist/client/assets/index-oFUAtVw2.css +0 -1
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.7";
237
237
  //#endregion
238
238
  //#region src/config.ts
239
239
  /**
@@ -5724,6 +5724,13 @@ const TaskBoardSource = Schema$1.Literals(["local", "hub"]);
5724
5724
  const ServerSettings = Schema$1.Struct({
5725
5725
  enableAssistantStreaming: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(false))),
5726
5726
  enableProviderUpdateChecks: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5727
+ /**
5728
+ * The Claude SDK preset system prompt omits the interactive CLI's
5729
+ * terminal-tone sections, so the model tends to open a turn with a tool call
5730
+ * instead of saying what it is about to do. This appends that instruction
5731
+ * back for sessions started by the Claude provider.
5732
+ */
5733
+ enableToolCallNarration: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
5727
5734
  automaticGitFetchInterval: Schema$1.DurationFromMillis.pipe(Schema$1.withDecodingDefault(Effect.succeed(Duration.toMillis(DEFAULT_AUTOMATIC_GIT_FETCH_INTERVAL)))),
5728
5735
  defaultThreadEnvMode: ThreadEnvMode.pipe(Schema$1.withDecodingDefault(Effect.succeed("local"))),
5729
5736
  newWorktreesStartFromOrigin: Schema$1.Boolean.pipe(Schema$1.withDecodingDefault(Effect.succeed(true))),
@@ -5850,6 +5857,7 @@ const OpenCodeSettingsPatch = Schema$1.Struct({
5850
5857
  const ServerSettingsPatch = Schema$1.Struct({
5851
5858
  enableAssistantStreaming: Schema$1.optionalKey(Schema$1.Boolean),
5852
5859
  enableProviderUpdateChecks: Schema$1.optionalKey(Schema$1.Boolean),
5860
+ enableToolCallNarration: Schema$1.optionalKey(Schema$1.Boolean),
5853
5861
  automaticGitFetchInterval: Schema$1.optionalKey(Schema$1.DurationFromMillis),
5854
5862
  defaultThreadEnvMode: Schema$1.optionalKey(ThreadEnvMode),
5855
5863
  newWorktreesStartFromOrigin: Schema$1.optionalKey(Schema$1.Boolean),
@@ -31470,6 +31478,287 @@ const make$39 = Effect.gen(function* () {
31470
31478
  });
31471
31479
  const layer$34 = Layer.effect(CheckpointDiffQuery, make$39);
31472
31480
  //#endregion
31481
+ //#region ../../packages/shared/src/toolCategory.ts
31482
+ const TOOL_CATEGORY_TITLES = {
31483
+ file_read: "File read",
31484
+ file_search: "File search",
31485
+ file_change: "File change",
31486
+ command: "Command run",
31487
+ version_control: "Version control",
31488
+ build_test: "Build or test",
31489
+ mcp_tool: "MCP tool call",
31490
+ subagent: "Subagent task",
31491
+ web_search: "Web search",
31492
+ web_fetch: "Web fetch",
31493
+ task_plan: "Task or plan",
31494
+ image_view: "Image view",
31495
+ tool: "Tool call"
31496
+ };
31497
+ new Set(Object.keys(TOOL_CATEGORY_TITLES));
31498
+ /** Exact tool names, lowercased, matched before the substring heuristics below. */
31499
+ const FILE_READ_TOOL_NAMES = /* @__PURE__ */ new Set([
31500
+ "read",
31501
+ "readfile",
31502
+ "read_file",
31503
+ "view",
31504
+ "viewfile",
31505
+ "view_file",
31506
+ "notebookread",
31507
+ "notebook_read",
31508
+ "openfile",
31509
+ "open_file"
31510
+ ]);
31511
+ const FILE_SEARCH_TOOL_NAMES = /* @__PURE__ */ new Set([
31512
+ "grep",
31513
+ "glob",
31514
+ "search",
31515
+ "filesearch",
31516
+ "file_search",
31517
+ "codebasesearch",
31518
+ "codebase_search",
31519
+ "listdir",
31520
+ "list_dir",
31521
+ "list_directory",
31522
+ "ls"
31523
+ ]);
31524
+ const FILE_CHANGE_TOOL_NAMES = /* @__PURE__ */ new Set([
31525
+ "edit",
31526
+ "write",
31527
+ "multiedit",
31528
+ "multi_edit",
31529
+ "notebookedit",
31530
+ "notebook_edit",
31531
+ "applypatch",
31532
+ "apply_patch",
31533
+ "patch",
31534
+ "createfile",
31535
+ "create_file",
31536
+ "strreplace",
31537
+ "str_replace",
31538
+ "deletefile",
31539
+ "delete_file"
31540
+ ]);
31541
+ const COMMAND_TOOL_NAMES = /* @__PURE__ */ new Set([
31542
+ "bash",
31543
+ "shell",
31544
+ "terminal",
31545
+ "exec",
31546
+ "execute",
31547
+ "execcommand",
31548
+ "exec_command",
31549
+ "runcommand",
31550
+ "run_command",
31551
+ "local_shell"
31552
+ ]);
31553
+ const SUBAGENT_TOOL_NAMES = /* @__PURE__ */ new Set([
31554
+ "task",
31555
+ "agent",
31556
+ "subagent",
31557
+ "sub_agent"
31558
+ ]);
31559
+ const WEB_FETCH_TOOL_NAMES = /* @__PURE__ */ new Set([
31560
+ "webfetch",
31561
+ "web_fetch",
31562
+ "fetch",
31563
+ "fetchurl",
31564
+ "fetch_url",
31565
+ "browse",
31566
+ "openurl",
31567
+ "open_url"
31568
+ ]);
31569
+ /**
31570
+ * Matched before the subagent rules, so `task_create` is a board write while the
31571
+ * bare `Task` tool stays a subagent.
31572
+ */
31573
+ const TASK_PLAN_TOOL_NAMES = /* @__PURE__ */ new Set([
31574
+ "todowrite",
31575
+ "todo_write",
31576
+ "todoread",
31577
+ "todo_read",
31578
+ "exitplanmode",
31579
+ "exit_plan_mode",
31580
+ "updateplan",
31581
+ "update_plan",
31582
+ "task_create",
31583
+ "task_update",
31584
+ "task_list",
31585
+ "task_get",
31586
+ "task_current",
31587
+ "task_propose",
31588
+ "taskcreate",
31589
+ "taskupdate",
31590
+ "tasklist",
31591
+ "taskget"
31592
+ ]);
31593
+ /** Shell builtins that say nothing about what the command as a whole does. */
31594
+ const NEUTRAL_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31595
+ "cd",
31596
+ "echo",
31597
+ "printf",
31598
+ "pwd",
31599
+ "true",
31600
+ "false",
31601
+ "time"
31602
+ ]);
31603
+ /** `sed`/`awk` are stream editors (`sed -i` rewrites files), so they stay commands. */
31604
+ const FILE_READ_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31605
+ "cat",
31606
+ "head",
31607
+ "tail",
31608
+ "less",
31609
+ "more",
31610
+ "bat",
31611
+ "nl",
31612
+ "jq",
31613
+ "wc"
31614
+ ]);
31615
+ const FILE_SEARCH_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31616
+ "grep",
31617
+ "egrep",
31618
+ "fgrep",
31619
+ "rg",
31620
+ "ag",
31621
+ "ack",
31622
+ "find",
31623
+ "fd",
31624
+ "ls",
31625
+ "tree",
31626
+ "which"
31627
+ ]);
31628
+ /** `git status` and `git diff` sort here too: the verb is what a reader scans for. */
31629
+ const VERSION_CONTROL_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31630
+ "git",
31631
+ "gh",
31632
+ "jj",
31633
+ "hg",
31634
+ "svn",
31635
+ "glab"
31636
+ ]);
31637
+ /**
31638
+ * Builds, tests, type checks, lint and install share one category: a test run is
31639
+ * what a person scans for when a turn goes long, and the rest is the same noise.
31640
+ */
31641
+ const BUILD_TEST_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31642
+ "pnpm",
31643
+ "npm",
31644
+ "npx",
31645
+ "yarn",
31646
+ "bun",
31647
+ "bunx",
31648
+ "vitest",
31649
+ "jest",
31650
+ "vp",
31651
+ "tsc",
31652
+ "tsgo",
31653
+ "eslint",
31654
+ "oxlint",
31655
+ "biome",
31656
+ "prettier",
31657
+ "ruff",
31658
+ "pytest",
31659
+ "make",
31660
+ "cargo",
31661
+ "gradle",
31662
+ "mvn",
31663
+ "xcodebuild",
31664
+ "swift",
31665
+ "pip",
31666
+ "uv",
31667
+ "poetry"
31668
+ ]);
31669
+ const WEB_FETCH_SHELL_COMMANDS = /* @__PURE__ */ new Set([
31670
+ "curl",
31671
+ "wget",
31672
+ "http",
31673
+ "httpie"
31674
+ ]);
31675
+ const SHELL_SEGMENT_SEPARATOR = /\|\||&&|;|\||\n/u;
31676
+ const LEADING_ENV_ASSIGNMENT = /^[A-Za-z_][A-Za-z0-9_]*=\S*\s+/u;
31677
+ const REDIRECT_TARGET = /\d?>>?\s*(?<target>[^\s|;&]+)/gu;
31678
+ /** Redirect targets that discard output instead of writing a file. */
31679
+ const DISCARDED_REDIRECT_TARGETS = /* @__PURE__ */ new Set([
31680
+ "/dev/null",
31681
+ "&1",
31682
+ "&2"
31683
+ ]);
31684
+ function toolCategoryTitle(category) {
31685
+ return TOOL_CATEGORY_TITLES[category];
31686
+ }
31687
+ function readCommandInput(toolInput) {
31688
+ const raw = toolInput?.command ?? toolInput?.cmd;
31689
+ if (typeof raw !== "string") return;
31690
+ const trimmed = raw.trim();
31691
+ return trimmed.length > 0 ? trimmed : void 0;
31692
+ }
31693
+ /** `cat x > y` reads and writes; only the write matters for the row heading. */
31694
+ function redirectsToFile(segment) {
31695
+ for (const match of segment.matchAll(REDIRECT_TARGET)) {
31696
+ const target = match.groups?.target;
31697
+ if (target && !DISCARDED_REDIRECT_TARGETS.has(target)) return true;
31698
+ }
31699
+ return false;
31700
+ }
31701
+ function classifyShellSegment(segment) {
31702
+ if (redirectsToFile(segment)) return "command";
31703
+ const head = segment.trim().replace(LEADING_ENV_ASSIGNMENT, "").split(/\s+/u)[0]?.replace(/^.*\//u, "").toLowerCase();
31704
+ if (!head || NEUTRAL_SHELL_COMMANDS.has(head)) return;
31705
+ if (FILE_READ_SHELL_COMMANDS.has(head)) return "file_read";
31706
+ if (FILE_SEARCH_SHELL_COMMANDS.has(head)) return "file_search";
31707
+ if (VERSION_CONTROL_SHELL_COMMANDS.has(head)) return "version_control";
31708
+ if (BUILD_TEST_SHELL_COMMANDS.has(head)) return "build_test";
31709
+ if (WEB_FETCH_SHELL_COMMANDS.has(head)) return "web_fetch";
31710
+ return "command";
31711
+ }
31712
+ /**
31713
+ * A shell call only counts as anything narrower than a command when every
31714
+ * non-neutral segment agrees; anything unrecognized (or write-ish) keeps the
31715
+ * whole call a command, and so does a pipeline that mixes two categories — the
31716
+ * one exception being a read piped into a search, which is still a search.
31717
+ * Quoted separators split segments too, which can only downgrade to "command".
31718
+ */
31719
+ function classifyShellCommand(command) {
31720
+ const categories = new Set(command.split(SHELL_SEGMENT_SEPARATOR).map(classifyShellSegment).filter((category) => category !== void 0));
31721
+ if (categories.size === 0 || categories.has("command")) return "command";
31722
+ const [only] = categories;
31723
+ if (categories.size === 1 && only) return only;
31724
+ return [...categories].every((category) => category === "file_read" || category === "file_search") ? "file_search" : "command";
31725
+ }
31726
+ function classifyToolCategory(input) {
31727
+ const normalized = input.toolName.trim().toLowerCase();
31728
+ if (normalized.startsWith("mcp__") || normalized.includes("mcp")) return "mcp_tool";
31729
+ if (TASK_PLAN_TOOL_NAMES.has(normalized)) return "task_plan";
31730
+ if (WEB_FETCH_TOOL_NAMES.has(normalized)) return "web_fetch";
31731
+ if (SUBAGENT_TOOL_NAMES.has(normalized) || normalized.includes("agent")) return "subagent";
31732
+ if (FILE_READ_TOOL_NAMES.has(normalized)) return "file_read";
31733
+ if (FILE_SEARCH_TOOL_NAMES.has(normalized)) return "file_search";
31734
+ if (FILE_CHANGE_TOOL_NAMES.has(normalized)) return "file_change";
31735
+ if (COMMAND_TOOL_NAMES.has(normalized) || normalized.includes("bash") || normalized.includes("shell") || normalized.includes("terminal") || normalized.includes("command")) {
31736
+ const command = readCommandInput(input.toolInput);
31737
+ return command ? classifyShellCommand(command) : "command";
31738
+ }
31739
+ if (normalized.includes("websearch") || normalized.includes("web_search")) return "web_search";
31740
+ if (normalized.includes("webfetch") || normalized.includes("web_fetch")) return "web_fetch";
31741
+ if (normalized.includes("todo") || normalized.includes("plan")) return "task_plan";
31742
+ if (normalized.includes("edit") || normalized.includes("write") || normalized.includes("patch") || normalized.includes("replace") || normalized.includes("create") || normalized.includes("delete")) return "file_change";
31743
+ if (normalized.includes("read") || normalized.includes("view")) return "file_read";
31744
+ if (normalized.includes("grep") || normalized.includes("glob")) return "file_search";
31745
+ if (normalized.includes("image")) return "image_view";
31746
+ return "tool";
31747
+ }
31748
+ function asRecord$2(value) {
31749
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
31750
+ }
31751
+ /** Classify from a runtime item payload's `data` (`{ toolName, input }`). */
31752
+ function classifyToolCategoryFromToolData(data) {
31753
+ const record = asRecord$2(data);
31754
+ const toolName = record?.toolName;
31755
+ if (typeof toolName !== "string" || toolName.trim().length === 0) return;
31756
+ return classifyToolCategory({
31757
+ toolName,
31758
+ toolInput: asRecord$2(record?.input)
31759
+ });
31760
+ }
31761
+ //#endregion
31473
31762
  //#region src/orchestration/ActivityPayloadProjection.ts
31474
31763
  function asRecord$1(value) {
31475
31764
  return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -31571,9 +31860,13 @@ function projectActivityPayload(activity) {
31571
31860
  const item = projectCommandData(data);
31572
31861
  if (item) projectedData.item = item;
31573
31862
  if ("command" in data) projectedData.command = data.command;
31863
+ const input = asRecord$1(data.input);
31864
+ if (input && "command" in input) projectedData.input = { command: input.command };
31574
31865
  const changedFiles = [];
31575
31866
  collectChangedFiles(data, changedFiles, /* @__PURE__ */ new Set(), 0);
31576
31867
  if (changedFiles.length > 0) projectedData.files = changedFiles.map((path) => ({ path }));
31868
+ const toolCategory = classifyToolCategoryFromToolData(data);
31869
+ if (toolCategory) projectedData.toolCategory = toolCategory;
31577
31870
  if ("toolCallId" in data) projectedData.toolCallId = data.toolCallId;
31578
31871
  if ("kind" in data) projectedData.kind = data.kind;
31579
31872
  const rawOutput = projectRawOutput(data.rawOutput);
@@ -47043,17 +47336,17 @@ function summarizeToolRequest(toolName, input) {
47043
47336
  if (serialized.length <= TOOL_SUMMARY_MAX_LENGTH) return `${toolName}: ${serialized}`;
47044
47337
  return `${toolName}: ${serialized.slice(0, TOOL_SUMMARY_MAX_LENGTH - 3)}...`;
47045
47338
  }
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
- }
47339
+ /**
47340
+ * Heading for a tool row. Derived from the tool name plus its input so a `Read`
47341
+ * reads as "File read" instead of a bare "Tool call", and a `Bash` that only
47342
+ * inspects a file (`sed -n 1,20p src/x.ts`) reads as "File read" instead of
47343
+ * "Command run". Recomputed as streamed input arrives.
47344
+ */
47345
+ function titleForTool(toolName, toolInput) {
47346
+ return toolCategoryTitle(classifyToolCategory({
47347
+ toolName,
47348
+ toolInput
47349
+ }));
47057
47350
  }
47058
47351
  const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
47059
47352
  "image/gif",
@@ -47061,6 +47354,14 @@ const SUPPORTED_CLAUDE_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
47061
47354
  "image/png",
47062
47355
  "image/webp"
47063
47356
  ]);
47357
+ /**
47358
+ * Appended to the preset system prompt when `narrateBeforeTools` is on. The SDK
47359
+ * preset drops the interactive CLI's terminal-tone sections, so without this the
47360
+ * model opens a turn with a tool call rather than a line saying what it is doing.
47361
+ */
47362
+ /** Emitted by the CLI but absent from the SDK's `SDKMessage` union. */
47363
+ const UNTYPED_COMMAND_LIFECYCLE_MESSAGE = "command_lifecycle";
47364
+ 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
47365
  const CLAUDE_SETTING_SOURCES = [
47065
47366
  "user",
47066
47367
  "project",
@@ -47786,6 +48087,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47786
48087
  });
47787
48088
  const updatedAt = yield* nowIso;
47788
48089
  context.turnState = void 0;
48090
+ context.steeredMessages.clear();
47789
48091
  context.session = {
47790
48092
  ...context.session,
47791
48093
  status: "ready",
@@ -47832,6 +48134,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47832
48134
  const updatedAt = yield* nowIso;
47833
48135
  context.turnState = turnState;
47834
48136
  context.interruptRequested = false;
48137
+ context.steeredMessages.clear();
47835
48138
  context.session = {
47836
48139
  ...context.session,
47837
48140
  status: "running",
@@ -47853,24 +48156,49 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47853
48156
  /**
47854
48157
  * Start the queued sendTurn, if the session has gone idle.
47855
48158
  *
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.
48159
+ * Runs after every turn completion. What waits here are the steers Stop took
48160
+ * back from the CLI before they ran (see `reclaimSteeredMessages`); they are
48161
+ * already merged into one request, so they start as a single turn the moment
48162
+ * the interrupted loop ends.
47861
48163
  */
47862
48164
  const drainPendingTurns = Effect.fn("drainPendingTurns")(function* (context) {
47863
48165
  if (context.stopped || context.turnState !== void 0) return;
47864
48166
  const pending = context.pendingTurnRequest;
47865
48167
  if (pending === void 0) return;
47866
48168
  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);
48169
+ yield* beginTurn(context, pending.turnId, context.session.model);
47869
48170
  yield* Queue.offer(context.promptQueue, {
47870
48171
  type: "message",
47871
48172
  message: pending.message
47872
48173
  }).pipe(Effect.ignore);
47873
48174
  });
48175
+ /**
48176
+ * Take back the steers the CLI has not started yet, so Stop still runs them.
48177
+ *
48178
+ * A steer sits in the CLI's async-message queue until the agent loop reaches
48179
+ * its next boundary. `cancelAsyncMessage` answers true only while it is still
48180
+ * waiting there: those never reached the model and become the next turn
48181
+ * together, while an already-dequeued one is part of the turn being stopped
48182
+ * and must not run a second time.
48183
+ */
48184
+ const reclaimSteeredMessages = Effect.fn("reclaimSteeredMessages")(function* (context) {
48185
+ const steered = Array.from(context.steeredMessages);
48186
+ context.steeredMessages.clear();
48187
+ const cancelAsyncMessage = context.query.cancelAsyncMessage;
48188
+ if (steered.length === 0 || cancelAsyncMessage === void 0) return;
48189
+ const reclaimed = [];
48190
+ for (const [messageUuid, message] of steered) if (yield* Effect.tryPromise({
48191
+ try: () => cancelAsyncMessage(messageUuid),
48192
+ catch: (cause) => toRequestError$1(context.session.threadId, "turn/cancelAsyncMessage", cause)
48193
+ }).pipe(Effect.catchCause((cause) => Effect.logWarning("claude steer cancel failed", { cause }).pipe(Effect.as(false))))) reclaimed.push(message);
48194
+ if (reclaimed.length === 0) return;
48195
+ const merged = reclaimed.reduce((left, right) => mergeUserMessages(left, right));
48196
+ const pending = context.pendingTurnRequest;
48197
+ context.pendingTurnRequest = {
48198
+ turnId: pending?.turnId ?? TurnId.make(yield* randomUUIDv4),
48199
+ message: pending ? mergeUserMessages(pending.message, merged) : merged
48200
+ };
48201
+ });
47874
48202
  const handleStreamEvent = Effect.fn("handleStreamEvent")(function* (context, message) {
47875
48203
  if (message.type !== "stream_event") return;
47876
48204
  const { event } = message;
@@ -47921,11 +48249,13 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47921
48249
  const partialInputJson = tool.partialInputJson + event.delta.partial_json;
47922
48250
  const parsedInput = tryParseJsonRecord(partialInputJson);
47923
48251
  const detail = parsedInput ? summarizeToolRequest(tool.toolName, parsedInput) : tool.detail;
48252
+ const title = parsedInput ? titleForTool(tool.toolName, parsedInput) : tool.title;
47924
48253
  let nextTool = {
47925
48254
  ...tool,
47926
48255
  partialInputJson,
47927
48256
  ...parsedInput ? { input: parsedInput } : {},
47928
- ...detail ? { detail } : {}
48257
+ ...detail ? { detail } : {},
48258
+ title
47929
48259
  };
47930
48260
  const nextFingerprint = parsedInput && Object.keys(parsedInput).length > 0 ? toolInputFingerprint(parsedInput) : void 0;
47931
48261
  context.inFlightTools.set(event.index, nextTool);
@@ -47998,7 +48328,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
47998
48328
  itemId,
47999
48329
  itemType,
48000
48330
  toolName,
48001
- title: titleForTool(itemType),
48331
+ title: titleForTool(toolName, toolInput),
48002
48332
  detail,
48003
48333
  input: toolInput,
48004
48334
  partialInputJson: "",
@@ -48476,6 +48806,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48476
48806
  const handleSdkMessage = Effect.fn("handleSdkMessage")(function* (context, message) {
48477
48807
  yield* logNativeSdkMessage(context, message);
48478
48808
  yield* ensureThreadId(context, message);
48809
+ if (message.type === UNTYPED_COMMAND_LIFECYCLE_MESSAGE) return;
48479
48810
  switch (message.type) {
48480
48811
  case "stream_event":
48481
48812
  yield* handleStreamEvent(context, message);
@@ -48858,13 +49189,15 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48858
49189
  };
48859
49190
  const mcpSession = readMcpProviderSession(input.threadId);
48860
49191
  const externalMcpServers = options?.resolveMcpServers === void 0 ? {} : yield* options.resolveMcpServers;
49192
+ const narrateBeforeTools = options?.resolveToolCallNarration === void 0 ? DEFAULT_SERVER_SETTINGS.enableToolCallNarration : yield* options.resolveToolCallNarration;
48861
49193
  const queryOptions = {
48862
49194
  ...input.cwd ? { cwd: input.cwd } : {},
48863
49195
  ...apiModelId ? { model: apiModelId } : {},
48864
49196
  pathToClaudeCodeExecutable: claudeBinaryPath,
48865
49197
  systemPrompt: {
48866
49198
  type: "preset",
48867
- preset: "claude_code"
49199
+ preset: "claude_code",
49200
+ ...narrateBeforeTools ? { append: NARRATE_BEFORE_TOOLS_PROMPT } : {}
48868
49201
  },
48869
49202
  settingSources: [...CLAUDE_SETTING_SOURCES],
48870
49203
  ...effectiveEffort ? { effort: effectiveEffort } : {},
@@ -48906,6 +49239,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48906
49239
  "claude.query.include_partial_messages": true,
48907
49240
  "claude.query.additional_directories": input.cwd ? [input.cwd] : [],
48908
49241
  "claude.query.setting_sources": [...CLAUDE_SETTING_SOURCES],
49242
+ "claude.query.narrate_before_tools": narrateBeforeTools,
48909
49243
  "claude.query.settings_json": encodeJsonStringForDiagnostics$2(settings) ?? "",
48910
49244
  "claude.query.extra_args_json": encodeJsonStringForDiagnostics$2(extraArgs) ?? "",
48911
49245
  "claude.query.path_to_executable": claudeBinaryPath
@@ -48956,6 +49290,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
48956
49290
  claudeTasks,
48957
49291
  turnState: void 0,
48958
49292
  pendingTurnRequest: void 0,
49293
+ steeredMessages: /* @__PURE__ */ new Map(),
48959
49294
  lastKnownContextWindow: initialContextWindow,
48960
49295
  lastKnownTokenUsage: void 0,
48961
49296
  lastKnownTotalProcessedTokens: void 0,
@@ -49024,17 +49359,19 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
49024
49359
  boundInstanceId
49025
49360
  });
49026
49361
  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
49362
+ const steerUuid = yield* randomUUIDv4;
49363
+ const steered = {
49364
+ ...message,
49365
+ uuid: steerUuid
49034
49366
  };
49367
+ context.steeredMessages.set(steerUuid, steered);
49368
+ yield* Queue.offer(context.promptQueue, {
49369
+ type: "message",
49370
+ message: steered
49371
+ }).pipe(Effect.mapError((cause) => toRequestError$1(input.threadId, "turn/start", cause)));
49035
49372
  return {
49036
49373
  threadId: context.session.threadId,
49037
- turnId,
49374
+ turnId: context.turnState.turnId,
49038
49375
  ...context.session.resumeCursor !== void 0 ? { resumeCursor: context.session.resumeCursor } : {}
49039
49376
  };
49040
49377
  }
@@ -49054,6 +49391,7 @@ const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (claudeSettin
49054
49391
  const interruptTurn = Effect.fn("interruptTurn")(function* (threadId, _turnId) {
49055
49392
  const context = yield* requireSession(threadId);
49056
49393
  context.interruptRequested = true;
49394
+ yield* reclaimSteeredMessages(context);
49057
49395
  yield* Effect.tryPromise({
49058
49396
  try: () => context.query.interrupt(),
49059
49397
  catch: (cause) => toRequestError$1(threadId, "turn/interrupt", cause)
@@ -49312,11 +49650,13 @@ const ClaudeDriver = {
49312
49650
  });
49313
49651
  const mcpRegistry = yield* McpRegistry;
49314
49652
  const resolveDisabledSkills = serverSettings.getSettings.pipe(Effect.map((settings) => settings.disabledSkills), Effect.orElseSucceed(() => []));
49653
+ const resolveToolCallNarration = serverSettings.getSettings.pipe(Effect.map((settings) => settings.enableToolCallNarration), Effect.orElseSucceed(() => DEFAULT_SERVER_SETTINGS.enableToolCallNarration));
49315
49654
  const adapter = yield* makeClaudeAdapter(effectiveConfig, {
49316
49655
  instanceId,
49317
49656
  environment: processEnv,
49318
49657
  resolveMcpServers: mcpRegistry.resolveForSession,
49319
49658
  resolveDisabledSkills,
49659
+ resolveToolCallNarration,
49320
49660
  ...eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}
49321
49661
  });
49322
49662
  const textGeneration = yield* makeClaudeTextGeneration(effectiveConfig, processEnv);
@@ -82205,6 +82545,31 @@ const make$2 = Effect.gen(function* () {
82205
82545
  },
82206
82546
  createdAt: input.createdAt
82207
82547
  })));
82548
+ /**
82549
+ * A successful interrupt otherwise leaves nothing behind: the only row the
82550
+ * timeline ever got was the failure case above, and the turn fold's "You
82551
+ * stopped after Xs" is invisible while the fold is open and says nothing in
82552
+ * the work log. The turn id comes from the request rather than the provider
82553
+ * because orchestration turn ids are not provider turn ids.
82554
+ */
82555
+ const appendTurnInterruptedActivity = (input) => Effect.all({
82556
+ commandId: serverCommandId("provider-interrupt-activity"),
82557
+ eventId: serverEventId()
82558
+ }).pipe(Effect.flatMap(({ commandId, eventId }) => orchestrationEngine.dispatch({
82559
+ type: "thread.activity.append",
82560
+ commandId,
82561
+ threadId: input.threadId,
82562
+ activity: {
82563
+ id: eventId,
82564
+ tone: "info",
82565
+ kind: "provider.turn.interrupted",
82566
+ summary: "Interrupted by user",
82567
+ payload: {},
82568
+ turnId: input.turnId,
82569
+ createdAt: input.createdAt
82570
+ },
82571
+ createdAt: input.createdAt
82572
+ })));
82208
82573
  const formatFailureDetail = (cause) => {
82209
82574
  const failReason = cause.reasons.find(Cause.isFailReason);
82210
82575
  const providerError = isProviderAdapterRequestError(failReason?.error) ? failReason.error : void 0;
@@ -82591,6 +82956,11 @@ const make$2 = Effect.gen(function* () {
82591
82956
  createdAt: event.payload.createdAt
82592
82957
  });
82593
82958
  yield* providerService.interruptTurn({ threadId: event.payload.threadId });
82959
+ yield* appendTurnInterruptedActivity({
82960
+ threadId: event.payload.threadId,
82961
+ turnId: event.payload.turnId ?? null,
82962
+ createdAt: event.payload.createdAt
82963
+ });
82594
82964
  });
82595
82965
  const processApprovalResponseRequested = Effect.fn("processApprovalResponseRequested")(function* (event) {
82596
82966
  const thread = yield* resolveThread(event.payload.threadId);