@xingular/harness 1.2.0 → 1.3.1-unstable.1

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 (186) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +26 -17
  3. package/builtin/skills/plugin-authoring/SKILL.md +34 -0
  4. package/dist/cli.js +7362 -6239
  5. package/dist/sdk/agents/builtin.js +3 -3
  6. package/dist/sdk/agents/planResearch.d.ts +23 -1
  7. package/dist/sdk/agents/planResearch.js +71 -8
  8. package/dist/sdk/agents/profiles.d.ts +16 -4
  9. package/dist/sdk/agents/profiles.js +75 -12
  10. package/dist/sdk/annotations/bridge.d.ts +2 -0
  11. package/dist/sdk/annotations/bridge.js +4 -0
  12. package/dist/sdk/annotations/capture.d.ts +44 -2
  13. package/dist/sdk/annotations/capture.js +104 -3
  14. package/dist/sdk/annotations/delivery.d.ts +24 -17
  15. package/dist/sdk/annotations/delivery.js +82 -49
  16. package/dist/sdk/annotations/inspect.js +7 -6
  17. package/dist/sdk/annotations/pageScript.js +4 -4
  18. package/dist/sdk/annotations/part.d.ts +0 -2
  19. package/dist/sdk/annotations/part.js +4 -33
  20. package/dist/sdk/annotations/queue.d.ts +2 -2
  21. package/dist/sdk/annotations/queue.js +12 -7
  22. package/dist/sdk/annotations/serverMethods.js +11 -7
  23. package/dist/sdk/annotations/store.d.ts +13 -21
  24. package/dist/sdk/annotations/store.js +47 -37
  25. package/dist/sdk/capabilities/runSnapshot.js +10 -4
  26. package/dist/sdk/commands/list.js +2 -0
  27. package/dist/sdk/commands/registry.d.ts +11 -3
  28. package/dist/sdk/commands/registry.js +3 -2
  29. package/dist/sdk/core/links.d.ts +51 -0
  30. package/dist/sdk/core/links.js +155 -0
  31. package/dist/sdk/core/shell.d.ts +20 -0
  32. package/dist/sdk/core/shell.js +73 -0
  33. package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
  34. package/dist/sdk/core/workspaceBashTool.js +16 -2
  35. package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
  36. package/dist/sdk/core/workspaceSandbox.js +52 -12
  37. package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
  38. package/dist/sdk/core/workspaceWriteOperations.js +26 -8
  39. package/dist/sdk/doctor/checks.js +71 -12
  40. package/dist/sdk/harness.d.ts +5 -0
  41. package/dist/sdk/harness.js +11 -6
  42. package/dist/sdk/hub/hub.js +5 -3
  43. package/dist/sdk/index.d.ts +3 -2
  44. package/dist/sdk/index.js +2 -1
  45. package/dist/sdk/plugins/builtin.js +14 -9
  46. package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
  47. package/dist/sdk/plugins/builtinRegistry.js +9 -3
  48. package/dist/sdk/plugins/discover.d.ts +7 -1
  49. package/dist/sdk/plugins/discover.js +41 -2
  50. package/dist/sdk/plugins/identity.js +1 -1
  51. package/dist/sdk/plugins/install.d.ts +6 -2
  52. package/dist/sdk/plugins/install.js +29 -5
  53. package/dist/sdk/plugins/inventory.js +3 -3
  54. package/dist/sdk/plugins/manifest.d.ts +2 -0
  55. package/dist/sdk/plugins/manifest.js +21 -4
  56. package/dist/sdk/plugins/plugins.d.ts +2 -0
  57. package/dist/sdk/plugins/plugins.js +1 -0
  58. package/dist/sdk/plugins/proposals.d.ts +17 -0
  59. package/dist/sdk/plugins/proposals.js +147 -0
  60. package/dist/sdk/plugins/types.d.ts +1 -1
  61. package/dist/sdk/plugins/types.js +1 -1
  62. package/dist/sdk/prompt/compile.d.ts +13 -4
  63. package/dist/sdk/prompt/compile.js +46 -20
  64. package/dist/sdk/prompt/standard.d.ts +1 -1
  65. package/dist/sdk/prompt/standard.js +1 -1
  66. package/dist/sdk/server/methods.js +14 -2
  67. package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
  68. package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
  69. package/dist/sdk/sessions/continuation.d.ts +4 -2
  70. package/dist/sdk/sessions/continuation.js +7 -6
  71. package/dist/sdk/sessions/contracts.d.ts +22 -4
  72. package/dist/sdk/sessions/contracts.js +12 -7
  73. package/dist/sdk/sessions/planMode.js +2 -2
  74. package/dist/sdk/sessions/runStore.js +3 -1
  75. package/dist/sdk/sessions/runner.js +2 -2
  76. package/dist/sdk/sessions/sessionState.d.ts +13 -0
  77. package/dist/sdk/sessions/sessionState.js +31 -0
  78. package/dist/sdk/sessions/sessions.d.ts +7 -0
  79. package/dist/sdk/sessions/sessions.js +73 -15
  80. package/dist/sdk/sessions/subagentService.d.ts +15 -0
  81. package/dist/sdk/sessions/subagentService.js +195 -55
  82. package/dist/sdk/sessions/subagents.d.ts +10 -1
  83. package/dist/sdk/sessions/subagents.js +4 -0
  84. package/dist/sdk/sessions/workSlice.d.ts +8 -5
  85. package/dist/sdk/sessions/workSlice.js +77 -45
  86. package/dist/sdk/terminal/controller.d.ts +20 -0
  87. package/dist/sdk/terminal/controller.js +43 -18
  88. package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
  89. package/dist/sdk/terminal/opentui/ansi.js +192 -0
  90. package/dist/sdk/terminal/opentui/model.d.ts +163 -0
  91. package/dist/sdk/terminal/opentui/model.js +482 -0
  92. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
  93. package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
  94. package/dist/sdk/terminal/opentui/theme.d.ts +2 -0
  95. package/dist/sdk/terminal/opentui/theme.js +2 -0
  96. package/dist/sdk/terminal/piExtension.js +98 -15
  97. package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
  98. package/dist/sdk/terminal/subagentMonitor.js +29 -8
  99. package/dist/sdk/terminal/terminals.d.ts +16 -0
  100. package/dist/sdk/terminal/terminals.js +130 -26
  101. package/dist/sdk/terminal/trajectory.d.ts +6 -1
  102. package/dist/sdk/terminal/trajectory.js +8 -0
  103. package/dist/sdk/terminal/tui/annotateCommand.d.ts +5 -1
  104. package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
  105. package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
  106. package/dist/sdk/terminal/tui/annotateSend.js +40 -0
  107. package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
  108. package/dist/sdk/terminal/tui/chrome.js +13 -0
  109. package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
  110. package/dist/sdk/terminal/tui/commandPages.js +16 -11
  111. package/dist/sdk/terminal/tui/commands.d.ts +4 -0
  112. package/dist/sdk/terminal/tui/commands.js +12 -2
  113. package/dist/sdk/terminal/tui/dialogs.js +1 -0
  114. package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
  115. package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
  116. package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
  117. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
  118. package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
  119. package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
  120. package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
  121. package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
  122. package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +14 -1
  123. package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
  124. package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
  125. package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
  126. package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
  127. package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
  128. package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
  129. package/dist/sdk/tools/defaultSkills.d.ts +4 -0
  130. package/dist/sdk/tools/defaultSkills.js +20 -0
  131. package/dist/sdk/tools/definitions.d.ts +3 -1
  132. package/dist/sdk/tools/definitions.js +17 -1
  133. package/dist/sdk/tools/dispatch.js +12 -0
  134. package/dist/sdk/tools/harnessTools.js +1 -1
  135. package/dist/sdk/tools/sessionName.d.ts +9 -0
  136. package/dist/sdk/tools/sessionName.js +58 -11
  137. package/dist/sdk/tools/spawnSubagent.js +9 -7
  138. package/dist/sdk/tools/todo.d.ts +15 -0
  139. package/dist/sdk/tools/todo.js +64 -0
  140. package/dist/sdk/world/world.d.ts +0 -2
  141. package/dist/terminal-opentui.js +3331 -1584
  142. package/dist/terminal-pi.js +421 -133
  143. package/docs/README.md +3 -0
  144. package/docs/architecture/00-overview.md +1 -1
  145. package/docs/architecture/01-extension-points.md +1 -1
  146. package/docs/architecture/02-sessions-and-runs.md +73 -36
  147. package/docs/architecture/03-variants.md +1 -3
  148. package/docs/architecture/04-prompt.md +24 -13
  149. package/docs/architecture/06-plugins-and-capabilities.md +46 -10
  150. package/docs/architecture/07-tools-and-gate.md +49 -8
  151. package/docs/architecture/09-accounts-and-connectors.md +8 -2
  152. package/docs/architecture/10-server-protocol.md +68 -16
  153. package/docs/architecture/11-terminal.md +94 -45
  154. package/docs/architecture/12-storage.md +1 -0
  155. package/docs/architecture/13-world-tools.md +8 -6
  156. package/docs/architecture/14-surfaces-and-parity.md +4 -1
  157. package/docs/architecture/README.md +20 -18
  158. package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
  159. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
  160. package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
  161. package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
  162. package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
  163. package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
  164. package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
  165. package/docs/architecture/proposals/PROP-0048-simple-annotate.md +85 -0
  166. package/docs/architecture/proposals/README.md +11 -6
  167. package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
  168. package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
  169. package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
  170. package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
  171. package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
  172. package/docs/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
  173. package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
  174. package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
  175. package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
  176. package/docs/doctor.md +14 -1
  177. package/docs/parity.md +16 -13
  178. package/docs/releases/1.3.0.md +21 -0
  179. package/docs/releases/1.3.1-unstable.1.md +8 -0
  180. package/docs/releases/README.md +9 -6
  181. package/docs/terminal-tui.md +127 -65
  182. package/package.json +1 -1
  183. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
  184. package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
  185. package/dist/sdk/tools/checklist.d.ts +0 -9
  186. package/dist/sdk/tools/checklist.js +0 -55
@@ -1,5 +1,5 @@
1
1
  // src/terminal/piExtension.ts
2
- import { createBashTool, createEditTool, createWriteTool } from "@earendil-works/pi-coding-agent";
2
+ import { createBashTool, createEditTool, createPowerShellTool, createWriteTool } from "@earendil-works/pi-coding-agent";
3
3
  import { Type as Type2 } from "typebox";
4
4
 
5
5
  // src/terminal/client.ts
@@ -199,6 +199,8 @@ var runEventTypeSchema = z.enum([
199
199
  "extension.notice",
200
200
  "reaction.added",
201
201
  "agent.message",
202
+ "todo.updated",
203
+ // Written before the rename; accepted so stored run logs keep their lists. Never written now.
202
204
  "checklist.updated",
203
205
  "checkpoint",
204
206
  "heartbeat",
@@ -336,9 +338,9 @@ var extensionNoticePayload = z.object({
336
338
  text: z.string(),
337
339
  extension: z.string().optional()
338
340
  });
339
- var checklistItemSchema = z.object({ text: z.string().min(1).max(500), status: z.enum(["pending", "in_progress", "completed"]) }).strict();
340
- var MAX_CHECKLIST_ITEMS = 50;
341
- var checklistPayload = z.object({ items: z.array(checklistItemSchema).max(MAX_CHECKLIST_ITEMS) }).strict();
341
+ var todoItemSchema = z.object({ text: z.string().min(1).max(500), status: z.enum(["pending", "in_progress", "completed"]) }).strict();
342
+ var MAX_TODO_ITEMS = 50;
343
+ var todoPayload = z.object({ items: z.array(todoItemSchema).max(MAX_TODO_ITEMS) }).strict();
342
344
  var MAX_CHECKPOINT_SUMMARY = 1200;
343
345
  var MAX_CHECKPOINT_DONE = 20;
344
346
  var MAX_CHECKPOINT_EVIDENCE = 5;
@@ -469,7 +471,8 @@ var runEventPayloadSchemas = {
469
471
  "extension.notice": extensionNoticePayload,
470
472
  "reaction.added": reactionAddedPayload,
471
473
  "agent.message": agentMessagePayload,
472
- "checklist.updated": checklistPayload,
474
+ "todo.updated": todoPayload,
475
+ "checklist.updated": todoPayload,
473
476
  checkpoint: checkpointPayload,
474
477
  heartbeat: heartbeatPayload,
475
478
  "artifact.added": artifactPayload,
@@ -526,7 +529,9 @@ var runRecordSchema = z.object({
526
529
  variantScope: text.optional(),
527
530
  variantVersion: text.optional(),
528
531
  profile: text.optional(),
529
- title: text.optional()
532
+ title: text.optional(),
533
+ model: text.optional(),
534
+ effort: text.optional()
530
535
  });
531
536
 
532
537
  // src/core/index.ts
@@ -565,7 +570,7 @@ var PLAN_RESEARCH_TOOLS = /* @__PURE__ */ new Set([
565
570
  "grep",
566
571
  "find",
567
572
  "ls",
568
- "checklist_write",
573
+ "todo_write",
569
574
  "session_name",
570
575
  "history_search",
571
576
  "history_read",
@@ -653,38 +658,39 @@ function askUserTool(run2) {
653
658
  };
654
659
  }
655
660
 
656
- // src/tools/checklist.ts
657
- var CHECKLIST_WRITE = "checklist_write";
661
+ // src/tools/todo.ts
662
+ var TODO_WRITE = "todo_write";
663
+ var TODO_GUIDANCE = "If the work has several distinct steps or will take more than one tool call, call todo_write before your first other tool call with the steps you know so far, and keep the list current as steps start and finish. Before your final reply, write one final update: mark every fully completed step completed and leave unfinished or blocked steps accurate. Never mark unfinished work completed. The person follows your work from it, and a subagent\u2019s todo list shows as its progress in the person\u2019s list of subagents.";
658
664
  var MARK = { pending: "[ ]", in_progress: "[~]", completed: "[x]" };
659
- function describeChecklist(items) {
660
- if (!items.length) return "The checklist is empty.";
665
+ function describeTodo(items) {
666
+ if (!items.length) return "The todo list is empty.";
661
667
  return items.map((item) => `${MARK[item.status]} ${item.text}`).join("\n");
662
668
  }
663
- function checklistTool(run2) {
669
+ function todoTool(run2) {
664
670
  return {
665
- name: CHECKLIST_WRITE,
666
- label: "Write the checklist",
667
- description: "Keep a checklist of the steps of the current work. The user watches this list to follow your progress, so write it early and keep it current. Send the whole list every time; it replaces the previous one. Use it whenever the work has several distinct steps or will take several tool calls, when the user gives several things to do, and when a subagent brief asks for more than one thing. Do not use it for a single, simple step or for conversation. Statuses: pending, in_progress, completed. Keep exactly one step in_progress while you work, mark a step completed as soon as it is done (not in batches), and add steps you discover. Before your final response, write one final checklist update: mark every fully completed step completed, and leave unfinished or blocked steps in their accurate pending or in_progress status. Never mark unfinished work completed. Only mark a step completed when it is fully done; if something blocks it, keep it in_progress and add a step for the blocker.",
671
+ name: TODO_WRITE,
672
+ label: "Write the todo list",
673
+ description: `Keep a todo list of the steps of the current work. The user watches this list to follow your progress. ${TODO_GUIDANCE} Send the whole list every time; it replaces the previous one. Do not use it for a single, simple step or for conversation. Statuses: pending, in_progress, completed. Keep exactly one step in_progress while you work, mark a step completed as soon as it is done (not in batches), and add steps you discover. Only mark a step completed when it is fully done; if something blocks it, keep it in_progress and add a step for the blocker.`,
668
674
  parameters: (Type3) => Type3.Object({
669
675
  items: Type3.Array(Type3.Object({
670
676
  text: Type3.String({ description: "One step, as a short imperative line" }),
671
677
  status: Type3.Union([Type3.Literal("pending"), Type3.Literal("in_progress"), Type3.Literal("completed")])
672
- }), { maxItems: MAX_CHECKLIST_ITEMS })
678
+ }), { maxItems: MAX_TODO_ITEMS })
673
679
  }),
674
680
  readOnly: true,
675
681
  planModeSafe: () => true,
676
682
  execute: async (input) => {
677
- if (!Array.isArray(input.items)) return "Send items: the whole checklist as an array.";
678
- if (input.items.length > MAX_CHECKLIST_ITEMS) return `Keep the checklist to ${MAX_CHECKLIST_ITEMS} steps or fewer.`;
683
+ if (!Array.isArray(input.items)) return "Send items: the whole todo list as an array.";
684
+ if (input.items.length > MAX_TODO_ITEMS) return `Keep the todo list to ${MAX_TODO_ITEMS} steps or fewer.`;
679
685
  const items = [];
680
686
  for (const raw of input.items) {
681
687
  const entry = raw;
682
- const parsed = checklistItemSchema.safeParse({ text: typeof entry?.text === "string" ? entry.text.trim() : entry?.text, status: entry?.status });
688
+ const parsed = todoItemSchema.safeParse({ text: typeof entry?.text === "string" ? entry.text.trim() : entry?.text, status: entry?.status });
683
689
  if (!parsed.success) return "Every step needs text of 1 to 500 characters and a status of pending, in_progress or completed.";
684
690
  items.push(parsed.data);
685
691
  }
686
- run2.emit("checklist.updated", { items });
687
- return describeChecklist(items);
692
+ run2.emit("todo.updated", { items });
693
+ return describeTodo(items);
688
694
  }
689
695
  };
690
696
  }
@@ -798,14 +804,15 @@ function checkpointTool(run2, slice) {
798
804
  import { createHash } from "node:crypto";
799
805
  var DEFAULT_SLICE_POLICY = {
800
806
  enabled: true,
801
- // Roughly 40 minutes of ordinary work (about 15 s a call), so a checkpoint is a chapter of a
802
- // task, not an interruption every few edits. A task still gets eight of them before the cap.
803
- maxToolCalls: 160,
804
- maxMinutes: 40,
805
- // A generous cost ceiling, counted on new input, cache writes and output only: heavy reading at
806
- // a large context can add tens of thousands of tokens a request, so a small cap stopped useful
807
- // work (see the "Confirm plan model support" thread). 320k is a safety net, not the bound.
808
- maxNewTokens: 32e4,
807
+ // A chapter of a task, not an interruption every few edits: roughly 80 minutes of ordinary work
808
+ // (about 15 s a call), with 60 minutes of wall clock as the ceiling, whichever comes first. A task
809
+ // still gets eight slices before it returns to the person.
810
+ maxToolCalls: 320,
811
+ maxMinutes: 60,
812
+ // No token cap: the tool calls and the clock are the bound. Counting new input, cache writes and
813
+ // output made a few large reads end a reply before useful work (see the "Confirm plan model
814
+ // support" thread), so a cap is available to whoever asks for one and off unless they do.
815
+ maxNewTokens: 0,
809
816
  maxSlices: 8,
810
817
  continue: true
811
818
  };
@@ -884,42 +891,54 @@ function canonical(value, depth = 0) {
884
891
  }
885
892
  var signature = (name, input) => `${name}\0${createHash("sha256").update(canonical(input ?? null)).digest("hex")}`;
886
893
  var clip = (value, limit) => value.length <= limit ? value : `${value.slice(0, limit - 1)}\u2026`;
887
- function handoffRequest(limit, state) {
888
- return clip(
889
- `This reply reached its work-slice limit (${limit}; step ${state.steps}). Call checkpoint_write now with what is done, the single next step, anything blocking, the files you touched and the checks that matter. Then tell the user in a few lines where you stopped. Do not call other tools.`,
890
- 600
891
- );
892
- }
893
- function stalledNote(reason) {
894
- return clip(
895
- `You are repeating work: ${reason}. Do not repeat it. Choose a different approach, read what the earlier attempt returned, or call checkpoint_write with the blocker so the next slice can decide.`,
896
- 600
897
- );
894
+ function handoffRequest(limit, state, checkpointAvailable = true) {
895
+ const handoff = checkpointAvailable ? "Call checkpoint_write now with what is done, the single next step, anything blocking, the files you touched and the checks that matter. Then tell the user in a few lines where you stopped." : "Stop using tools and return a concise partial research report with findings, blockers and the next useful research step. The Harness records an evidence-derived checkpoint.";
896
+ return clip(`This reply reached its work-slice limit (${limit}; step ${state.steps}). ${handoff} Do not call other tools.`, 600);
897
+ }
898
+ function stalledNote(reason, checkpointAvailable = true) {
899
+ const handoff = checkpointAvailable ? "Choose a different approach, read what the earlier attempt returned, or call checkpoint_write with the blocker so the next slice can decide." : "Do not use more tools. Return concise findings and blockers in your final response; the Harness records an evidence-derived checkpoint.";
900
+ return clip(`You are repeating work: ${reason}. Do not repeat it. ${handoff}`, 600);
898
901
  }
899
902
  function workSliceGuard(options) {
900
903
  const { policy, state } = options;
901
904
  const calls = /* @__PURE__ */ new Map();
905
+ const checkCalls = /* @__PURE__ */ new Set();
902
906
  const edits = /* @__PURE__ */ new Map();
903
907
  const failures = /* @__PURE__ */ new Map();
904
- let stepsSinceProgress = 0;
905
908
  let nudged = false;
909
+ const checkpointAvailable = () => typeof options.checkpointAvailable === "function" ? options.checkpointAvailable() : options.checkpointAvailable !== false;
906
910
  const resetRepetition = () => {
907
911
  calls.clear();
912
+ checkCalls.clear();
908
913
  edits.clear();
909
914
  failures.clear();
910
- stepsSinceProgress = 0;
911
915
  nudged = false;
912
916
  };
913
917
  const end = (kind, limit) => {
914
918
  state.endedBy = kind;
915
919
  state.endedByLimit = limit;
916
920
  options.onEnd?.(kind, limit);
917
- const reason = kind === "stuck" ? stalledNote(limit) : kind === "compaction-thrash" ? handoffRequest("three context compactions in one reply", state) : handoffRequest(limit, state);
921
+ const canCheckpoint = checkpointAvailable();
922
+ const reason = kind === "stuck" ? stalledNote(limit, canCheckpoint) : kind === "compaction-thrash" ? handoffRequest("three context compactions in one reply", state, canCheckpoint) : handoffRequest(limit, state, canCheckpoint);
918
923
  return { block: true, terminate: true, reason };
919
924
  };
925
+ const nudge = (reason) => {
926
+ if (nudged) return end("stuck", reason);
927
+ nudged = true;
928
+ const canCheckpoint = checkpointAvailable();
929
+ if (!canCheckpoint) {
930
+ state.endedBy = "stuck";
931
+ state.endedByLimit = reason;
932
+ }
933
+ return { block: true, reason: stalledNote(reason, canCheckpoint) };
934
+ };
920
935
  const repeated = (name, input) => {
921
936
  if (POLLING_TOOLS.has(name)) return null;
922
937
  const key = signature(name, input);
938
+ if (name === "bash") {
939
+ const command = input && typeof input === "object" ? String(input.command ?? "") : "";
940
+ if (command && looksLikeCheck(command)) checkCalls.add(key);
941
+ }
923
942
  const count = (calls.get(key) ?? 0) + 1;
924
943
  calls.set(key, count);
925
944
  if (count < 3) return null;
@@ -945,9 +964,18 @@ function workSliceGuard(options) {
945
964
  pi.on("tool_result", (event) => {
946
965
  if (event.toolName === "checkpoint_write" && !event.isError) {
947
966
  state.checkpointWritten = true;
948
- stepsSinceProgress = 0;
949
967
  nudged = false;
950
968
  }
969
+ if ((event.toolName === "edit" || event.toolName === "write") && event.isError === false) {
970
+ const path = event.input && typeof event.input === "object" ? String(event.input.path ?? "") : "";
971
+ if (path) {
972
+ edits.set(path, (edits.get(path) ?? 0) + 1);
973
+ failures.clear();
974
+ for (const key of checkCalls) calls.delete(key);
975
+ checkCalls.clear();
976
+ nudged = false;
977
+ }
978
+ }
951
979
  if (event.toolName !== "bash") return;
952
980
  const command = event.input && typeof event.input === "object" ? String(event.input.command ?? "") : "";
953
981
  if (!command || !looksLikeCheck(command)) return;
@@ -958,16 +986,18 @@ function workSliceGuard(options) {
958
986
  failures.delete(command);
959
987
  edits.clear();
960
988
  calls.clear();
961
- stepsSinceProgress = 0;
989
+ checkCalls.clear();
962
990
  nudged = false;
963
991
  });
964
992
  pi.on("tool_call", (event) => {
965
993
  const name = event.toolName;
966
994
  state.steps += 1;
967
995
  state.lastTool = name;
968
- const handoff = HANDOFF_TOOLS.has(name);
996
+ const canCheckpoint = checkpointAvailable();
997
+ const handoff = canCheckpoint && HANDOFF_TOOLS.has(name);
969
998
  if (state.endedBy) {
970
- return handoff ? void 0 : { block: true, terminate: true, reason: "This reply is over. Its checkpoint is recorded; nothing else runs." };
999
+ const reason2 = state.checkpointWritten ? "This reply is over. Its checkpoint is recorded; nothing else runs." : "This reply is over. The Harness will record an evidence-derived checkpoint; nothing else runs.";
1000
+ return handoff ? void 0 : { block: true, terminate: true, reason: reason2 };
971
1001
  }
972
1002
  if (state.compactions >= 3) {
973
1003
  if (handoff) {
@@ -982,34 +1012,24 @@ function workSliceGuard(options) {
982
1012
  state.endedBy = "budget";
983
1013
  state.endedByLimit = limit;
984
1014
  state.refusedAt ??= state.steps;
985
- stepsSinceProgress = 0;
986
- return handoff ? void 0 : { block: true, reason: handoffRequest(limit, state) };
1015
+ return handoff ? void 0 : { block: true, reason: handoffRequest(limit, state, canCheckpoint) };
987
1016
  }
988
1017
  if (handoff) return void 0;
1018
+ if (name === "todo_write") return void 0;
989
1019
  const edit = name === "edit" || name === "write";
990
1020
  const path = edit && event.input && typeof event.input === "object" ? String(event.input.path ?? "") : "";
991
1021
  if (edit && path) {
992
- const count = (edits.get(path) ?? 0) + 1;
993
- edits.set(path, count);
994
- stepsSinceProgress = 0;
1022
+ const count = edits.get(path) ?? 0;
995
1023
  if (count >= 4) {
996
1024
  const reason2 = `${path} was edited ${count} times with no passing check between`;
997
- if (nudged) return end("stuck", reason2);
998
- nudged = true;
999
- return { block: true, reason: stalledNote(reason2) };
1025
+ return nudge(reason2);
1000
1026
  }
1001
- } else {
1002
- stepsSinceProgress += 1;
1003
1027
  }
1004
1028
  const repeat = repeated(name, event.input);
1005
1029
  const failure = failing(name, event.input);
1006
- const noProgress = stepsSinceProgress >= 25 ? `${stepsSinceProgress} steps with no file change and no passing check` : null;
1007
- const reason = repeat ?? failure ?? noProgress;
1030
+ const reason = repeat ?? failure;
1008
1031
  if (!reason) return void 0;
1009
- if (nudged) return end("stuck", reason);
1010
- nudged = true;
1011
- stepsSinceProgress = 0;
1012
- return { block: true, reason: stalledNote(reason) };
1032
+ return nudge(reason);
1013
1033
  });
1014
1034
  };
1015
1035
  }
@@ -1058,20 +1078,47 @@ async function withCommandBudget(run2, abort, seconds) {
1058
1078
 
1059
1079
  // src/tools/sessionName.ts
1060
1080
  var SESSION_NAME = "session_name";
1081
+ var SESSION_GUIDANCE = "If the request needs tools or changes state \u2014 not a greeting, thanks, or a short conversational reply \u2014 call session_name once before your first other tool call, naming the work from the request. Use one short imperative sentence of at most six words. Revise the name only when the overall objective changes; keep the current name when it does not.";
1082
+ function normalizeSessionName(value) {
1083
+ if (typeof value !== "string") throw invalid("Send name as a short sentence of at most six words.");
1084
+ if (/[\x00-\x08\x0e-\x1f\x7f-\x9f]/u.test(value)) {
1085
+ throw invalid("Session names cannot contain terminal escapes or control characters.");
1086
+ }
1087
+ const name = value.replace(/\s+/gu, " ").trim();
1088
+ if (!name || name.split(" ").length > 6) throw invalid("Use a non-empty session name of at most six words.");
1089
+ return name;
1090
+ }
1091
+ var CONVERSATIONAL = /^(?:hi|hello|hey|yo|thanks|thank you|ok|okay|sure|great|nice|cool|sounds good|got it|good morning|good afternoon|good evening|how are you|what'?s up)[.!?]*$/iu;
1092
+ var LEADING = /^(?:please|can you|could you|would you|i want you to|i'd like you to|help me|let'?s|go ahead and)[,\s]+/iu;
1093
+ function deriveSessionName(text4) {
1094
+ if (typeof text4 !== "string") return null;
1095
+ const input = text4.trim();
1096
+ if (!input || input.startsWith("!") || input.startsWith("```") || /^https?:\/\//iu.test(input)) return null;
1097
+ if (CONVERSATIONAL.test(input)) return null;
1098
+ if (/^continue\b/iu.test(input)) return "Continue previous work";
1099
+ const sentence = input.split(/[.!?](?:\s|$)/u)[0] ?? input;
1100
+ const cleaned = sentence.replace(/```[\s\S]*?```/gu, " ").replace(/[`*_~#>]/gu, " ").replace(/[:;,]+/gu, " ").replace(LEADING, "").replace(/\s+/gu, " ").trim();
1101
+ if (!/[a-z]/iu.test(cleaned)) return null;
1102
+ const words = cleaned.split(" ").filter(Boolean).slice(0, 6);
1103
+ if (!words.length) return null;
1104
+ const name = words.join(" ").replace(/^[\p{P}\p{S}]+/u, "").replace(/[\p{P}\p{S}]+$/u, "").trim();
1105
+ if (!name) return null;
1106
+ const capitalized = name[0].toUpperCase() + name.slice(1);
1107
+ try {
1108
+ return normalizeSessionName(capitalized);
1109
+ } catch {
1110
+ return null;
1111
+ }
1112
+ }
1061
1113
  function sessionNameTool(current) {
1062
1114
  return {
1063
1115
  name: SESSION_NAME,
1064
1116
  label: "Name the session",
1065
- description: 'Set or revise this session\u2019s name. Name meaningful work early, then revise the name when the overall objective changes; greetings alone need no name. Use one short imperative sentence describing the objective, at most six words, such as "Add web browser integrations". You may revise a manually assigned name. Keep a useful current name when the objective is unchanged. This only names your own session.',
1117
+ description: `Set or revise this session\u2019s name. ${SESSION_GUIDANCE} For example, "Add web browser integrations". You may revise a manually assigned name. This only names your own session.`,
1066
1118
  parameters: (Type3) => Type3.Object({ name: Type3.String({ description: "One short sentence describing the objective, at most six words" }) }),
1067
1119
  planModeSafe: () => true,
1068
1120
  execute: async (input) => {
1069
- if (typeof input.name !== "string") throw invalid("Send name as a short sentence of at most six words.");
1070
- if (/[\x00-\x08\x0e-\x1f\x7f-\x9f]/u.test(input.name)) {
1071
- throw invalid("Session names cannot contain terminal escapes or control characters.");
1072
- }
1073
- const name = input.name.replace(/\s+/gu, " ").trim();
1074
- if (!name || name.split(" ").length > 6) throw invalid("Use a non-empty session name of at most six words.");
1121
+ const name = normalizeSessionName(input.name);
1075
1122
  const session = current();
1076
1123
  if (!session) throw invalid("The native session is unavailable; try naming it again once it is ready.");
1077
1124
  if (session.sessionName !== name) session.setSessionName(name);
@@ -1118,12 +1165,13 @@ var WORLD_GUIDANCE = [
1118
1165
  ].join("\n");
1119
1166
 
1120
1167
  // src/prompt/compile.ts
1121
- var SECTION_ORDER = { harnessIdentity: 5, system: 25, guardrails: 40, memory: 45, references: 50, subagents: 64, scripts: 66, wiki: 80, tools: 90, reporting: 95 };
1168
+ var SESSION_STATE_BUDGET = 1e3;
1169
+ var SECTION_ORDER = { harnessIdentity: 5, system: 25, sessionState: 30, guardrails: 40, memory: 45, references: 50, subagents: 64, scripts: 66, wiki: 80, tools: 90, reporting: 95 };
1122
1170
  function clip2(text4, limit) {
1123
1171
  return charCount(text4) <= limit ? text4 : `${takeChars(text4, limit)}\u2026`;
1124
1172
  }
1125
1173
  function renderCompactionCheckpoint(input) {
1126
- if (!input.checklist.length && !input.planInstructions) return null;
1174
+ if (!input.todo.length && !input.planInstructions) return null;
1127
1175
  const parts = [
1128
1176
  "This is current state from the active run after context compaction, not a new user request. Continue the same request. Do not infer that omitted details or decisions were settled.",
1129
1177
  input.goal.trim() ? `## Current request
@@ -1133,18 +1181,18 @@ ${clip2(input.goal.trim(), 1e3)}` : "",
1133
1181
 
1134
1182
  ${clip2(input.planInstructions, 4e3)}` : ""
1135
1183
  ].filter(Boolean);
1136
- if (input.checklist.length) {
1137
- const active = input.checklist.filter((item) => item.status !== "completed");
1138
- const completed = input.checklist.filter((item) => item.status === "completed");
1139
- const checklist = [
1184
+ if (input.todo.length) {
1185
+ const active = input.todo.filter((item) => item.status !== "completed");
1186
+ const completed = input.todo.filter((item) => item.status === "completed");
1187
+ const todo = [
1140
1188
  active.length ? `### In progress and pending
1141
- ${clip2(describeChecklist(active), 2500)}` : "",
1189
+ ${clip2(describeTodo(active), 2500)}` : "",
1142
1190
  completed.length ? `### Completed (${completed.length})
1143
- ${clip2(describeChecklist(completed), 500)}` : ""
1191
+ ${clip2(describeTodo(completed), 500)}` : ""
1144
1192
  ].filter(Boolean).join("\n\n");
1145
- parts.push(`## Current checklist
1193
+ parts.push(`## Current todo list
1146
1194
 
1147
- ${checklist}`);
1195
+ ${todo}`);
1148
1196
  }
1149
1197
  const body = clip2(parts.join("\n\n"), 8e3);
1150
1198
  return `[Harness context update \u2014 not a user message]
@@ -1153,7 +1201,19 @@ ${checklist}`);
1153
1201
 
1154
1202
  ${body}`;
1155
1203
  }
1204
+ function sessionStateText(state, options) {
1205
+ const parts = [];
1206
+ if (options.name) parts.push(`Name: ${state.name ? JSON.stringify(state.name) : "unnamed"}.`);
1207
+ if (options.todo) {
1208
+ parts.push(`Todo for this run: ${describeTodo(state.todo)}`);
1209
+ if (state.previousTodo?.length) parts.push(`Previous run's todo (context, not this run's list): ${describeTodo(state.previousTodo)}`);
1210
+ }
1211
+ if (!parts.length) return null;
1212
+ parts.push(state.firstTurn ? "This is the first turn in this native session." : "This run continues the conversation; the todo list resets for each run.");
1213
+ return clip2(parts.join("\n"), SESSION_STATE_BUDGET);
1214
+ }
1156
1215
  var BUILT_IN_DYNAMIC = [
1216
+ { id: "session-state", order: SECTION_ORDER.sessionState },
1157
1217
  { id: "references", order: SECTION_ORDER.references },
1158
1218
  { id: "wiki", order: SECTION_ORDER.wiki }
1159
1219
  ];
@@ -1165,7 +1225,7 @@ var PLAN_READ_ONLY_TOOLS = /* @__PURE__ */ new Set([
1165
1225
  "find",
1166
1226
  "ls",
1167
1227
  SESSION_NAME,
1168
- CHECKLIST_WRITE,
1228
+ TODO_WRITE,
1169
1229
  ASK_USER,
1170
1230
  "ask_user_question",
1171
1231
  GET_CAPABILITIES,
@@ -1264,12 +1324,12 @@ function planRefusal(tool, readOnly, args, planModeSafe) {
1264
1324
 
1265
1325
  // src/sessions/compactionCheckpoint.ts
1266
1326
  var COMPACTION_CHECKPOINT_EXTENSION = "compaction-checkpoint";
1267
- function validatedChecklist(value) {
1268
- if (!Array.isArray(value) || value.length > MAX_CHECKLIST_ITEMS) return null;
1327
+ function validatedTodo(value) {
1328
+ if (!Array.isArray(value) || value.length > MAX_TODO_ITEMS) return null;
1269
1329
  const items = [];
1270
1330
  for (const valueItem of value) {
1271
1331
  const item = valueItem;
1272
- const parsed = checklistItemSchema.safeParse({
1332
+ const parsed = todoItemSchema.safeParse({
1273
1333
  text: typeof item?.text === "string" ? item.text.trim() : item?.text,
1274
1334
  status: item?.status
1275
1335
  });
@@ -1281,26 +1341,26 @@ function validatedChecklist(value) {
1281
1341
  function compactionCheckpoint(options = {}) {
1282
1342
  return (pi) => {
1283
1343
  let goal = "";
1284
- let checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
1344
+ let todo = validatedTodo(options.initialTodo ?? []) ?? [];
1285
1345
  let lastCompactionKey = null;
1286
1346
  let compacted = false;
1287
1347
  const reset = () => {
1288
1348
  goal = "";
1289
- checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
1349
+ todo = validatedTodo(options.initialTodo ?? []) ?? [];
1290
1350
  lastCompactionKey = null;
1291
1351
  compacted = false;
1292
1352
  };
1293
1353
  pi.on("before_agent_start", (event) => {
1294
1354
  if (event.messageType !== "user") return;
1295
1355
  goal = event.prompt;
1296
- checklist = validatedChecklist(options.initialChecklist ?? []) ?? [];
1356
+ todo = validatedTodo(options.initialTodo ?? []) ?? [];
1297
1357
  lastCompactionKey = null;
1298
1358
  compacted = false;
1299
1359
  });
1300
1360
  pi.on("tool_result", (event) => {
1301
- if (event.toolName !== CHECKLIST_WRITE || event.isError) return;
1302
- const items = validatedChecklist(event.input.items);
1303
- if (items) checklist = items;
1361
+ if (event.toolName !== TODO_WRITE || event.isError) return;
1362
+ const items = validatedTodo(event.input.items);
1363
+ if (items) todo = items;
1304
1364
  });
1305
1365
  pi.on("context", (event) => {
1306
1366
  const summary = event.messages.findLast((message) => message.role === "compactionSummary");
@@ -1314,7 +1374,7 @@ function compactionCheckpoint(options = {}) {
1314
1374
  compacted = false;
1315
1375
  }
1316
1376
  if (!compacted) return;
1317
- const content = renderCompactionCheckpoint({ goal, checklist, planInstructions });
1377
+ const content = renderCompactionCheckpoint({ goal, todo, planInstructions });
1318
1378
  if (!content) return;
1319
1379
  return {
1320
1380
  messages: [...event.messages, {
@@ -1428,23 +1488,150 @@ function inspectWorkspaceWrite(workspaceRoot, cwd, tool, input) {
1428
1488
  // src/core/workspaceBashTool.ts
1429
1489
  import { Type } from "typebox";
1430
1490
 
1491
+ // src/core/shell.ts
1492
+ import { accessSync as accessSync2, constants as constants2, statSync as statSync3 } from "node:fs";
1493
+ import { win32 } from "node:path";
1494
+
1495
+ // src/core/path.ts
1496
+ import { execFileSync } from "node:child_process";
1497
+ import { accessSync, constants, statSync as statSync2 } from "node:fs";
1498
+ import { homedir as homedir2 } from "node:os";
1499
+ import { delimiter, extname, isAbsolute as isAbsolute2, join as join2 } from "node:path";
1500
+ var resolved = null;
1501
+ function searchPath() {
1502
+ const platform = process.platform;
1503
+ const pathKey = pathVariableName(process.env);
1504
+ const inherited = process.env[pathKey] ?? "";
1505
+ if (resolved?.platform === platform && resolved.inherited === inherited) return resolved.path;
1506
+ const separator = pathSeparator(platform);
1507
+ const entries = [];
1508
+ if (platform !== "win32") {
1509
+ try {
1510
+ const output = execFileSync(process.env.SHELL || "/bin/zsh", ["-ilc", 'printf %s "$PATH"'], { encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] });
1511
+ entries.push(...(output.split("\n").filter(Boolean).at(-1) ?? "").split(separator));
1512
+ } catch {
1513
+ }
1514
+ }
1515
+ entries.push(...inherited.split(separator));
1516
+ if (platform !== "win32") {
1517
+ const home = homedir2();
1518
+ if (home) entries.push(...[".local/bin", ".bun/bin", ".npm-global/bin", ".cargo/bin"].map((folder) => join2(home, folder)));
1519
+ entries.push("/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin");
1520
+ }
1521
+ const path = [...new Set(entries.filter(Boolean))].join(separator);
1522
+ resolved = { platform, inherited, path };
1523
+ return path;
1524
+ }
1525
+ function pathVariableName(environment) {
1526
+ return Object.keys(environment).find((name) => name.toLowerCase() === "path") ?? "PATH";
1527
+ }
1528
+ function pathSeparator(platform) {
1529
+ return platform === "win32" ? ";" : delimiter;
1530
+ }
1531
+ function isExecutable(path) {
1532
+ try {
1533
+ accessSync(path, process.platform === "win32" ? constants.F_OK : constants.X_OK);
1534
+ return statSync2(path).isFile();
1535
+ } catch {
1536
+ return false;
1537
+ }
1538
+ }
1539
+ function findBinary(name) {
1540
+ const platform = process.platform;
1541
+ const suffixes = platform === "win32" && !extname(name) ? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [];
1542
+ const names = [name, ...suffixes.map((suffix) => `${name}${suffix}`)];
1543
+ if (isAbsolute2(name)) {
1544
+ for (const candidate of names) if (isExecutable(candidate)) return candidate;
1545
+ return null;
1546
+ }
1547
+ for (const directory of searchPath().split(pathSeparator(platform))) {
1548
+ for (const candidateName of names) {
1549
+ const candidate = join2(directory, candidateName);
1550
+ if (isExecutable(candidate)) return candidate;
1551
+ }
1552
+ }
1553
+ return null;
1554
+ }
1555
+
1556
+ // src/core/shell.ts
1557
+ var POWERSHELL_ARGS = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command"];
1558
+ var POWERSHELL_UTF8 = "try { [Console]::OutputEncoding=[System.Text.Encoding]::UTF8 } catch {}\n";
1559
+ var cached;
1560
+ function hostShell() {
1561
+ if (cached === void 0) cached = resolveShell(process.platform);
1562
+ return cached;
1563
+ }
1564
+ function resolveShell(platform, lookup = {}) {
1565
+ const env = lookup.env ?? process.env;
1566
+ const exists = lookup.exists ?? isFile;
1567
+ const onPath = lookup.onPath ?? findBinary;
1568
+ if (platform !== "win32") {
1569
+ const executable = ["/bin/bash", "/usr/bin/bash"].find((candidate) => exists(candidate));
1570
+ return executable ? { kind: "bash", executable, args: ["-c"] } : null;
1571
+ }
1572
+ const gitBash = [env.ProgramFiles, env["ProgramFiles(x86)"]].filter((folder) => Boolean(folder)).map((folder) => win32.join(folder, "Git", "bin", "bash.exe")).find((candidate) => exists(candidate));
1573
+ if (gitBash) return { kind: "bash", executable: gitBash, args: ["-c"] };
1574
+ const bashOnPath = onPath("bash.exe");
1575
+ if (bashOnPath && !isWslLauncher(bashOnPath, env)) return { kind: "bash", executable: bashOnPath, args: ["-c"] };
1576
+ const existing = (folder, ...parts) => {
1577
+ if (!folder) return null;
1578
+ const candidate = win32.join(folder, ...parts);
1579
+ return exists(candidate) ? candidate : null;
1580
+ };
1581
+ const powershell = onPath("pwsh.exe") ?? existing(env.ProgramFiles, "PowerShell", "7", "pwsh.exe") ?? onPath("powershell.exe") ?? existing(env.SystemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
1582
+ return powershell ? { kind: "powershell", executable: powershell, args: POWERSHELL_ARGS } : null;
1583
+ }
1584
+ function shellArguments(shell, command) {
1585
+ return [...shell.args, shell.kind === "powershell" ? `${POWERSHELL_UTF8}${command}` : command];
1586
+ }
1587
+ function shellLabel(shell) {
1588
+ if (shell.kind === "powershell") return "PowerShell";
1589
+ return /\\git\\/iu.test(shell.executable) ? "Git Bash" : "Bash";
1590
+ }
1591
+ function isWslLauncher(path, env) {
1592
+ const normalized = win32.normalize(path).toLowerCase();
1593
+ const systemRoot = env.SystemRoot;
1594
+ if (systemRoot) {
1595
+ return ["System32", "Sysnative"].some((folder) => normalized === win32.join(systemRoot, folder, "bash.exe").toLowerCase());
1596
+ }
1597
+ return /^[a-z]:\\windows\\(?:system32|sysnative)\\bash\.exe$/u.test(normalized);
1598
+ }
1599
+ function isFile(path) {
1600
+ try {
1601
+ accessSync2(path, constants2.F_OK);
1602
+ return statSync3(path).isFile();
1603
+ } catch {
1604
+ return false;
1605
+ }
1606
+ }
1607
+
1431
1608
  // src/core/workspaceSandbox.ts
1432
1609
  import { AsyncLocalStorage } from "node:async_hooks";
1433
1610
  import { spawn } from "node:child_process";
1434
1611
  import { randomUUID } from "node:crypto";
1435
- import { accessSync, constants, existsSync, lstatSync as lstatSync2, mkdtempSync, realpathSync as realpathSync2, readdirSync, rmSync, statSync as statSync2 } from "node:fs";
1612
+ import { accessSync as accessSync3, constants as constants3, existsSync, lstatSync as lstatSync2, mkdtempSync, realpathSync as realpathSync2, readdirSync, rmSync, statSync as statSync4 } from "node:fs";
1436
1613
  import { tmpdir } from "node:os";
1437
- import { join as join2 } from "node:path";
1614
+ import { join as join3 } from "node:path";
1615
+ import { spawnSync } from "node:child_process";
1438
1616
  var MARKER_PREFIX = "__XINGULAR_WORKSPACE_SANDBOX_STARTED_";
1439
1617
  var bashWriteScope = new AsyncLocalStorage();
1440
1618
  function runWithWorkspaceWriteScope(scope, action) {
1441
1619
  return bashWriteScope.run(scope, action);
1442
1620
  }
1621
+ var WINDOWS_UNSANDBOXED_REASON = "Windows has no workspace write-boundary sandbox, so this command would run without one. It will not run until this one-call request is approved.";
1622
+ var SANDBOX_BACKENDS = {
1623
+ darwin: ["/usr/bin/sandbox-exec"],
1624
+ linux: ["/usr/bin/bwrap", "/bin/bwrap"]
1625
+ };
1626
+ function workspaceSandboxBackend(platform = process.platform) {
1627
+ const candidates = SANDBOX_BACKENDS[platform];
1628
+ return candidates ? systemExecutable(candidates) : null;
1629
+ }
1443
1630
  function systemExecutable(candidates) {
1444
1631
  for (const candidate of candidates) {
1445
1632
  try {
1446
- accessSync(candidate, constants.X_OK);
1447
- if (statSync2(candidate).isFile()) return candidate;
1633
+ accessSync3(candidate, constants3.X_OK);
1634
+ if (statSync4(candidate).isFile()) return candidate;
1448
1635
  } catch {
1449
1636
  }
1450
1637
  }
@@ -1455,6 +1642,16 @@ function shellPath() {
1455
1642
  if (!shell) throw new Error("The workspace write-boundary sandbox requires Bash, but no Bash executable was found.");
1456
1643
  return shell;
1457
1644
  }
1645
+ function requireShell(resolve3) {
1646
+ const shell = resolve3();
1647
+ if (!shell) {
1648
+ throw new Error(process.platform === "win32" ? "No shell was found: neither Git Bash nor PowerShell is available. Bash was not run." : "No Bash executable was found. Bash was not run.");
1649
+ }
1650
+ return shell;
1651
+ }
1652
+ function unconfinedCommand(shell, command) {
1653
+ return { executable: shell.executable, args: shellArguments(shell, command), marker: "" };
1654
+ }
1458
1655
  function sbplString(value) {
1459
1656
  return `"${value.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n")}"`;
1460
1657
  }
@@ -1496,12 +1693,12 @@ ${command}`;
1496
1693
  if (readOnlyTempRoot !== null) return readOnlySandboxCommand(platform, realpathSync2(readOnlyTempRoot), cwd, shell, wrappedCommand, marker);
1497
1694
  const writeRoots = [...new Set([workspaceRoot, ...additionalWriteRoots].map((root) => realpathSync2(root)))].sort((left, right) => left.length - right.length);
1498
1695
  if (platform === "darwin") {
1499
- const executable = systemExecutable(["/usr/bin/sandbox-exec"]);
1696
+ const executable = workspaceSandboxBackend(platform);
1500
1697
  if (!executable) throw new Error("Workspace write-boundary sandbox unavailable: sandbox-exec was not found. Bash was not run.");
1501
1698
  return { executable, args: ["-p", macSandboxProfile(writeRoots), shell, "-c", wrappedCommand], marker };
1502
1699
  }
1503
1700
  if (platform === "linux") {
1504
- const executable = systemExecutable(["/usr/bin/bwrap", "/bin/bwrap"]);
1701
+ const executable = workspaceSandboxBackend(platform);
1505
1702
  if (!executable) throw new Error("Workspace write-boundary sandbox unavailable: install bubblewrap (bwrap). Bash was not run.");
1506
1703
  return {
1507
1704
  executable,
@@ -1530,12 +1727,12 @@ ${command}`;
1530
1727
  }
1531
1728
  function readOnlySandboxCommand(platform, tempRoot, cwd, shell, wrappedCommand, marker) {
1532
1729
  if (platform === "darwin") {
1533
- const executable = systemExecutable(["/usr/bin/sandbox-exec"]);
1730
+ const executable = workspaceSandboxBackend(platform);
1534
1731
  if (!executable) throw new Error("Read-only Bash sandbox unavailable: sandbox-exec was not found. Bash was not run.");
1535
1732
  return { executable, args: ["-p", macReadOnlyProfile(tempRoot), shell, "-c", wrappedCommand], marker };
1536
1733
  }
1537
1734
  if (platform === "linux") {
1538
- const executable = systemExecutable(["/usr/bin/bwrap", "/bin/bwrap"]);
1735
+ const executable = workspaceSandboxBackend(platform);
1539
1736
  if (!executable) throw new Error("Read-only Bash sandbox unavailable: install bubblewrap (bwrap). Bash was not run.");
1540
1737
  return {
1541
1738
  executable,
@@ -1570,7 +1767,11 @@ function readOnlySandboxCommand(platform, tempRoot, cwd, shell, wrappedCommand,
1570
1767
  throw new Error(`Read-only Bash sandbox unavailable on ${platform}. Bash was not run.`);
1571
1768
  }
1572
1769
  function killProcess(child) {
1573
- if (child.pid && process.platform !== "win32") {
1770
+ if (child.pid && process.platform === "win32") {
1771
+ spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
1772
+ return;
1773
+ }
1774
+ if (child.pid) {
1574
1775
  try {
1575
1776
  process.kill(-child.pid, "SIGKILL");
1576
1777
  return;
@@ -1691,11 +1892,13 @@ function createWorkspaceBashOperations(workspaceRoot, options = {}) {
1691
1892
  const canonicalCwd = realpathSync2(cwd);
1692
1893
  if (!isWithinWorkspace(root, canonicalCwd)) throw new Error("The Bash working directory is outside the active project workspace.");
1693
1894
  const scope = options.workspaceWriteScope?.() ?? bashWriteScope.getStore() ?? "workspace";
1895
+ const platform = options.platform ?? process.platform;
1896
+ const shell = options.shell ?? hostShell;
1694
1897
  if (scope === "read-only") {
1695
- const platform2 = options.platform ?? process.platform;
1696
- const tempRoot = realpathSync2(mkdtempSync(join2(tmpdir(), "harness-read-only-bash-")));
1898
+ if (platform === "win32") throw new Error("Plan-mode Bash needs a read-only sandbox, which Windows does not have. Bash was not run.");
1899
+ const tempRoot = realpathSync2(mkdtempSync(join3(tmpdir(), "harness-read-only-bash-")));
1697
1900
  try {
1698
- const spec2 = workspaceSandboxCommand(platform2, root, canonicalCwd, command, [], tempRoot);
1901
+ const spec2 = workspaceSandboxCommand(platform, root, canonicalCwd, command, [], tempRoot);
1699
1902
  const env = { ...execOptions.env ?? process.env, TMPDIR: tempRoot };
1700
1903
  const result2 = await runProcess(spec2, canonicalCwd, { ...execOptions, env }, spec2.marker);
1701
1904
  return { exitCode: result2.exitCode };
@@ -1710,11 +1913,12 @@ function createWorkspaceBashOperations(workspaceRoot, options = {}) {
1710
1913
  bypass = false;
1711
1914
  }
1712
1915
  if (bypass) {
1713
- const result2 = await runProcess({ executable: shellPath(), args: ["-c", command], marker: "" }, canonicalCwd, execOptions, null);
1916
+ const result2 = await runProcess(unconfinedCommand(requireShell(shell), command), canonicalCwd, execOptions, null);
1714
1917
  return { exitCode: result2.exitCode };
1715
1918
  }
1716
- if (scope === "outside") {
1717
- const reason = "This command requests filesystem writes outside the active project workspace. It will not run until this one-call request is approved.";
1919
+ if (scope === "outside" || platform === "win32") {
1920
+ const resolved2 = requireShell(shell);
1921
+ const reason = scope === "outside" ? "This command requests filesystem writes outside the active project workspace. It will not run until this one-call request is approved." : WINDOWS_UNSANDBOXED_REASON;
1718
1922
  let approved = false;
1719
1923
  try {
1720
1924
  approved = await options.requestWorkspaceWriteScope?.({ command, cwd: canonicalCwd, workspaceRoot: root, reason }) ?? false;
@@ -1722,12 +1926,11 @@ function createWorkspaceBashOperations(workspaceRoot, options = {}) {
1722
1926
  approved = false;
1723
1927
  }
1724
1928
  if (!approved) throw new Error(`${reason} Bash was not run.`);
1725
- execOptions.onData(Buffer.from("\n[Outside-workspace writes approved for this command; running once.]\n"));
1726
- const result2 = await runProcess({ executable: shellPath(), args: ["-c", command], marker: "" }, canonicalCwd, execOptions, null);
1929
+ if (scope === "outside") execOptions.onData(Buffer.from("\n[Outside-workspace writes approved for this command; running once.]\n"));
1930
+ const result2 = await runProcess(unconfinedCommand(resolved2, command), canonicalCwd, execOptions, null);
1727
1931
  return { exitCode: result2.exitCode };
1728
1932
  }
1729
1933
  assertNoExternalHardlinks(root);
1730
- const platform = options.platform ?? process.platform;
1731
1934
  const spec = workspaceSandboxCommand(platform, root, canonicalCwd, command);
1732
1935
  const result = await runProcess(spec, canonicalCwd, execOptions, spec.marker);
1733
1936
  return { exitCode: result.exitCode };
@@ -1736,12 +1939,17 @@ function createWorkspaceBashOperations(workspaceRoot, options = {}) {
1736
1939
  }
1737
1940
 
1738
1941
  // src/core/workspaceBashTool.ts
1739
- function withWorkspaceWriteScope(tool) {
1942
+ function createWorkspaceShellTool(pi, cwd, operations, shell = hostShell(), platform = process.platform) {
1943
+ const native = shell?.kind === "powershell" ? { ...pi.createPowerShellTool(cwd, { operations }), name: "bash" } : pi.createBashTool(cwd, { operations });
1944
+ const note = platform === "win32" && shell ? ` On this machine commands run in ${shellLabel(shell)}. Windows has no workspace sandbox, so each command asks for approval unless Full Permission is on, and plan mode cannot run commands.` : "";
1945
+ return withWorkspaceWriteScope(native, note);
1946
+ }
1947
+ function withWorkspaceWriteScope(tool, note = "") {
1740
1948
  const native = tool;
1741
1949
  const execute = native.execute.bind(tool);
1742
1950
  return {
1743
1951
  ...tool,
1744
- description: `${native.description} Workspace writes are the default. Set workspaceWriteScope to "outside" to request approval before broader writes. Commands run for ${DEFAULT_COMMAND_TIMEOUT_SECONDS} seconds unless timeout says otherwise; above ${COMMAND_TIMEOUT_CEILING_SECONDS} seconds a reason is required, and ${COMMAND_TIMEOUT_MAX_SECONDS} seconds is the maximum.`,
1952
+ description: `${native.description}${note} Workspace writes are the default. Set workspaceWriteScope to "outside" to request approval before broader writes. Commands run for ${DEFAULT_COMMAND_TIMEOUT_SECONDS} seconds unless timeout says otherwise; above ${COMMAND_TIMEOUT_CEILING_SECONDS} seconds a reason is required, and ${COMMAND_TIMEOUT_MAX_SECONDS} seconds is the maximum.`,
1745
1953
  parameters: Type.Object({
1746
1954
  command: Type.String({ description: "Shell command to execute" }),
1747
1955
  timeout: Type.Optional(Type.Number({ description: `Timeout in seconds (default ${DEFAULT_COMMAND_TIMEOUT_SECONDS})` })),
@@ -1767,7 +1975,7 @@ function withWorkspaceWriteScope(tool) {
1767
1975
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
1768
1976
  import { spawn as spawn2 } from "node:child_process";
1769
1977
  import { createHash as createHash2 } from "node:crypto";
1770
- import { constants as fsConstants, realpathSync as realpathSync3, statSync as statSync3 } from "node:fs";
1978
+ import { constants as fsConstants, realpathSync as realpathSync3, statSync as statSync5 } from "node:fs";
1771
1979
  import { access, readFile } from "node:fs/promises";
1772
1980
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
1773
1981
  var writeAuthorization = new AsyncLocalStorage2();
@@ -1816,6 +2024,22 @@ try {
1816
2024
  throw error
1817
2025
  }
1818
2026
  `;
2027
+ function atomicWriteLaunch(platform, root, additionalRoots) {
2028
+ if (platform === "win32") {
2029
+ const env = {};
2030
+ for (const name of ["SystemRoot", "windir", "TEMP", "TMP", "USERPROFILE"]) {
2031
+ if (process.env[name]) env[name] = process.env[name];
2032
+ }
2033
+ return { executable: process.execPath, args: ["-e", atomicWriteScript], env };
2034
+ }
2035
+ const command = `exec ${shellQuote(process.execPath)} -e ${shellQuote(atomicWriteScript)}`;
2036
+ const spec = workspaceSandboxCommand(platform, root, root, command, additionalRoots);
2037
+ return {
2038
+ executable: spec.executable,
2039
+ args: spec.args,
2040
+ env: { PATH: "/usr/bin:/bin", ...process.env.HOME ? { HOME: process.env.HOME } : {} }
2041
+ };
2042
+ }
1819
2043
  function shellQuote(value) {
1820
2044
  return `'${value.replaceAll("'", "'\\''")}'`;
1821
2045
  }
@@ -1824,7 +2048,7 @@ function nearestExistingDirectory(path) {
1824
2048
  while (true) {
1825
2049
  try {
1826
2050
  const canonical2 = realpathSync3(current);
1827
- if (!statSync3(canonical2).isDirectory()) throw new Error(`Write scope is not a directory: ${canonical2}`);
2051
+ if (!statSync5(canonical2).isDirectory()) throw new Error(`Write scope is not a directory: ${canonical2}`);
1828
2052
  return canonical2;
1829
2053
  } catch (error) {
1830
2054
  if (error.code !== "ENOENT") throw error;
@@ -1853,17 +2077,12 @@ async function runAtomicWrite(authorization, path, content) {
1853
2077
  throw new Error("The write authorization no longer matches the active workspace target.");
1854
2078
  }
1855
2079
  const additionalRoots = outside ? [nearestExistingDirectory(canonicalPath)] : [];
1856
- const command = `exec ${shellQuote(process.execPath)} -e ${shellQuote(atomicWriteScript)}`;
1857
- const spec = workspaceSandboxCommand(process.platform, root, root, command, additionalRoots);
1858
- const env = {
1859
- PATH: "/usr/bin:/bin",
1860
- ...process.env.HOME ? { HOME: process.env.HOME } : {}
1861
- };
2080
+ const launch = atomicWriteLaunch(process.platform, root, additionalRoots);
1862
2081
  await new Promise((resolvePromise, rejectPromise) => {
1863
- const child = spawn2(spec.executable, spec.args, {
2082
+ const child = spawn2(launch.executable, launch.args, {
1864
2083
  cwd: root,
1865
2084
  detached: process.platform !== "win32",
1866
- env,
2085
+ env: launch.env,
1867
2086
  stdio: ["pipe", "pipe", "pipe"],
1868
2087
  windowsHide: true
1869
2088
  });
@@ -2051,6 +2270,7 @@ async function piExtension_default(pi, planRun, getSession) {
2051
2270
  let context = null;
2052
2271
  let nativeSubagents = false;
2053
2272
  let subagentParent = false;
2273
+ let stoppedChildren = null;
2054
2274
  let timer = null;
2055
2275
  let polling = false;
2056
2276
  let failure;
@@ -2063,6 +2283,10 @@ async function piExtension_default(pi, planRun, getSession) {
2063
2283
  const fileTools = /* @__PURE__ */ new Map();
2064
2284
  const writeAuthorizations = /* @__PURE__ */ new Map();
2065
2285
  let workspaceRoot = null;
2286
+ let sessionState = null;
2287
+ let stateDelivered = false;
2288
+ let autoNameTried = false;
2289
+ let lastTodo = [];
2066
2290
  let stage = "Tool discovery";
2067
2291
  try {
2068
2292
  await terminalCall("startup", { phase: stage });
@@ -2132,21 +2356,38 @@ async function piExtension_default(pi, planRun, getSession) {
2132
2356
  },
2133
2357
  workspaceWriteScope: () => planRun?.active ? "read-only" : void 0
2134
2358
  });
2135
- pi.registerTool(withWorkspaceWriteScope(createBashTool(process.cwd(), { operations: workspaceBash })));
2359
+ pi.registerTool(createWorkspaceShellTool({ createBashTool, createPowerShellTool }, process.cwd(), workspaceBash));
2136
2360
  pi.registerTool(withWorkspaceWriteAuthorization(createWriteTool(process.cwd(), { operations: workspaceWriteOperations }), writeAuthorizations));
2137
2361
  pi.registerTool(withWorkspaceWriteAuthorization(createEditTool(process.cwd(), { operations: workspaceEditOperations }), writeAuthorizations));
2362
+ pi.on("context", (event) => {
2363
+ if (!sessionState || stateDelivered) return;
2364
+ stateDelivered = true;
2365
+ return {
2366
+ messages: [...event.messages, {
2367
+ role: "custom",
2368
+ customType: "xingular-session-state",
2369
+ content: sessionState,
2370
+ display: false,
2371
+ timestamp: Date.now()
2372
+ }]
2373
+ };
2374
+ });
2138
2375
  compactionCheckpoint({ planInstructions: () => planRun?.active ? PLAN_INSTRUCTIONS : null })(pi);
2139
2376
  const slicePolicy = parseWorkSlice(void 0);
2140
2377
  const sliceState = createSliceState(slicePolicy);
2141
- if (slicePolicy.enabled) workSliceGuard({ policy: slicePolicy, state: sliceState })(pi);
2378
+ if (slicePolicy.enabled) workSliceGuard({ policy: slicePolicy, state: sliceState, checkpointAvailable: () => !planRun?.active || planRun.allowedTools.has(CHECKPOINT_WRITE) })(pi);
2142
2379
  pi.on("context", (event) => {
2143
2380
  const messages = pruneToolImages(event.messages);
2144
2381
  return messages === event.messages ? void 0 : { messages };
2145
2382
  });
2146
2383
  pi.on("session_start", async (_event, ctx) => {
2147
2384
  context = ctx;
2385
+ sessionState = null;
2386
+ stateDelivered = false;
2387
+ autoNameTried = false;
2388
+ lastTodo = [];
2148
2389
  if (timer) clearInterval(timer);
2149
- await terminalCall("ready", { nativeSessionId: ctx.sessionManager.getSessionFile() });
2390
+ await terminalCall("ready", { nativeSessionId: ctx.sessionManager.getSessionFile(), owners: subagentHistory(ctx.sessionManager.getBranch?.() ?? []).owners });
2150
2391
  timer = setInterval(() => {
2151
2392
  if (polling || !context) return;
2152
2393
  polling = true;
@@ -2170,13 +2411,16 @@ async function piExtension_default(pi, planRun, getSession) {
2170
2411
  usage = emptyUsage();
2171
2412
  reasoningTokens = void 0;
2172
2413
  costReported = false;
2173
- const begun = await terminalCall("begin", { text: event.messageType === "user" ? event.prompt : "" });
2414
+ const begun = await terminalCall("begin", { text: event.messageType === "user" ? event.prompt : "", model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel?.() ?? null });
2174
2415
  workspaceRoot = typeof begun.workspaceRoot === "string" ? begun.workspaceRoot : null;
2175
2416
  if (!workspaceRoot) throw new Error("The active project workspace could not be resolved; the turn was not started.");
2417
+ pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
2418
+ stoppedChildren = null;
2176
2419
  if (nativeSubagents) {
2177
- pi.appendEntry(SUBAGENT_OWNER, { runId: begun.runId });
2178
2420
  const snapshot = await controlCall("terminal.subagents.register", { tools: pi.getActiveTools(), model: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : null, effort: pi.getThinkingLevel(), plan: planRun?.active === true, history: subagentHistory(ctx.sessionManager.getBranch()) });
2179
2421
  if (planRun && Array.isArray(snapshot.connectors)) planRun.connectors = snapshot.connectors;
2422
+ if (planRun && snapshot.subagentProfiles) planRun.subagentProfiles = snapshot.subagentProfiles;
2423
+ stoppedChildren = snapshot.notice ?? null;
2180
2424
  subagentParent = true;
2181
2425
  }
2182
2426
  if (planRun && event.messageType === "user") {
@@ -2232,6 +2476,44 @@ async function piExtension_default(pi, planRun, getSession) {
2232
2476
  const instruction = refusal ? `The user's initial Bash command was not run. Reason: ${refusal}. Do not claim that it ran; explain the limitation and ask how to proceed.` : "The user\u2019s initial Bash command has already run. Read its native Bash result and explain what it shows. Do not run the same command again unless the user explicitly asks.";
2233
2477
  return { message: { customType: "xingular-initial-bash", content: instruction, display: false, details: {} } };
2234
2478
  });
2479
+ pi.on("before_agent_start", async () => {
2480
+ const notice = stoppedChildren;
2481
+ stoppedChildren = null;
2482
+ if (notice) return { message: { customType: SUBAGENT_RESULTS, content: notice.text, display: false, details: { [SUBAGENT_RECEIPT]: notice.receipt } } };
2483
+ });
2484
+ pi.on("before_agent_start", async (event, ctx) => {
2485
+ if (event.messageType !== "user") return;
2486
+ const branch = ctx.sessionManager.getBranch?.() ?? [];
2487
+ const hasUser = branch.some((entry) => entry.type === "message" && entry.message?.role === "user");
2488
+ let name = typeof pi.getSessionName === "function" ? pi.getSessionName() ?? null : null;
2489
+ if (!name && !autoNameTried && pi.setSessionName) {
2490
+ const candidate = deriveSessionName(event.prompt);
2491
+ if (candidate) {
2492
+ autoNameTried = true;
2493
+ try {
2494
+ const policy = await controlCall("terminal.policy", { tool: SESSION_NAME, args: { name: candidate } });
2495
+ if (policy.allowed && !policy.requiresApproval) {
2496
+ pi.setSessionName(candidate);
2497
+ name = candidate;
2498
+ }
2499
+ } catch {
2500
+ }
2501
+ }
2502
+ }
2503
+ const state = {
2504
+ name,
2505
+ todo: [],
2506
+ ...lastTodo.length ? { previousTodo: lastTodo } : {},
2507
+ firstTurn: !hasUser
2508
+ };
2509
+ lastTodo = [];
2510
+ const active = typeof pi.getActiveTools === "function" ? pi.getActiveTools() : null;
2511
+ sessionState = sessionStateText(state, {
2512
+ name: active ? active.includes(SESSION_NAME) : true,
2513
+ todo: active ? active.includes(TODO_WRITE) : true
2514
+ });
2515
+ stateDelivered = false;
2516
+ });
2235
2517
  const deliverChildren = async (event, ctx, settle) => {
2236
2518
  if (!nativeSubagents || settle && event.outcome !== "completed" || ctx.signal?.aborted) return;
2237
2519
  const result = await withParentCancellation(ctx.signal, () => controlCall("terminal.subagents.delivery", { history: subagentHistory([...ctx.sessionManager.getBranch(), ...event.entries]), settle }, 2147483647));
@@ -2371,6 +2653,10 @@ async function piExtension_default(pi, planRun, getSession) {
2371
2653
  if (timer) clearInterval(timer);
2372
2654
  timer = null;
2373
2655
  context = null;
2656
+ sessionState = null;
2657
+ stateDelivered = false;
2658
+ autoNameTried = false;
2659
+ lastTodo = [];
2374
2660
  await terminalCall("shutdown").catch(() => {
2375
2661
  });
2376
2662
  });
@@ -2391,18 +2677,20 @@ async function piExtension_default(pi, planRun, getSession) {
2391
2677
  }
2392
2678
  });
2393
2679
  let recorded = Promise.resolve();
2394
- const checklist = checklistTool({ emit: (_type, payload) => {
2395
- recorded = terminalCall("checklist", payload);
2680
+ const todo = todoTool({ emit: (_type, payload) => {
2681
+ recorded = terminalCall("todo", payload);
2396
2682
  } });
2397
2683
  pi.registerTool({
2398
- name: checklist.name,
2399
- label: checklist.label,
2400
- description: checklist.description,
2401
- parameters: Type2.Unsafe(checklist.parameters(Type2)),
2684
+ name: todo.name,
2685
+ label: todo.label,
2686
+ description: todo.description,
2687
+ parameters: Type2.Unsafe(todo.parameters(Type2)),
2402
2688
  async execute(_id, input) {
2403
2689
  recorded = Promise.resolve();
2404
- const result = await checklist.execute(input);
2690
+ const result = await todo.execute(input);
2405
2691
  await recorded;
2692
+ const items = input.items;
2693
+ if (Array.isArray(items)) lastTodo = items;
2406
2694
  return { content: [{ type: "text", text: typeof result === "string" ? result : result.text }], details: void 0 };
2407
2695
  }
2408
2696
  });