@xingular/harness 1.0.0 → 1.1.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 (257) hide show
  1. package/CHANGELOG.md +33 -5
  2. package/README.md +70 -19
  3. package/dist/cli.js +10321 -5785
  4. package/dist/sdk/agents/builtin.d.ts +2 -0
  5. package/dist/sdk/agents/builtin.js +34 -0
  6. package/dist/sdk/agents/narrow.d.ts +13 -0
  7. package/dist/sdk/agents/narrow.js +29 -0
  8. package/dist/sdk/agents/profiles.d.ts +80 -0
  9. package/dist/sdk/agents/profiles.js +221 -0
  10. package/dist/sdk/annotations/bridge.d.ts +42 -0
  11. package/dist/sdk/annotations/bridge.js +264 -0
  12. package/dist/sdk/annotations/capture.d.ts +116 -0
  13. package/dist/sdk/annotations/capture.js +308 -0
  14. package/dist/sdk/annotations/delivery.d.ts +72 -0
  15. package/dist/sdk/annotations/delivery.js +130 -0
  16. package/dist/sdk/annotations/inspect.d.ts +10 -0
  17. package/dist/sdk/annotations/inspect.js +33 -0
  18. package/dist/sdk/annotations/isolated.d.ts +6 -0
  19. package/dist/sdk/annotations/isolated.js +51 -0
  20. package/dist/sdk/annotations/pageScript.d.ts +28 -0
  21. package/dist/sdk/annotations/pageScript.js +570 -0
  22. package/dist/sdk/annotations/part.d.ts +10 -0
  23. package/dist/sdk/annotations/part.js +144 -0
  24. package/dist/sdk/annotations/queue.d.ts +4 -0
  25. package/dist/sdk/annotations/queue.js +40 -0
  26. package/dist/sdk/annotations/serverMethods.d.ts +3 -0
  27. package/dist/sdk/annotations/serverMethods.js +62 -0
  28. package/dist/sdk/annotations/store.d.ts +160 -0
  29. package/dist/sdk/annotations/store.js +386 -0
  30. package/dist/sdk/browser/index.d.ts +14 -3
  31. package/dist/sdk/browser/index.js +11 -4
  32. package/dist/sdk/browser/serverMethods.d.ts +3 -0
  33. package/dist/sdk/browser/serverMethods.js +7 -0
  34. package/dist/sdk/browser/session.d.ts +1 -0
  35. package/dist/sdk/browser/session.js +45 -9
  36. package/dist/sdk/cli.js +19 -11
  37. package/dist/sdk/core/bus.js +1 -1
  38. package/dist/sdk/core/env.d.ts +1 -1
  39. package/dist/sdk/core/env.js +1 -1
  40. package/dist/sdk/doctor/serverMethods.d.ts +3 -0
  41. package/dist/sdk/doctor/serverMethods.js +8 -0
  42. package/dist/sdk/harness.d.ts +15 -0
  43. package/dist/sdk/harness.js +44 -5
  44. package/dist/sdk/history/recall.js +8 -8
  45. package/dist/sdk/history/serverMethods.d.ts +3 -0
  46. package/dist/sdk/history/serverMethods.js +8 -0
  47. package/dist/sdk/hub/cli.d.ts +1 -1
  48. package/dist/sdk/hub/cli.js +1 -1
  49. package/dist/sdk/hub/schema.d.ts +1 -1
  50. package/dist/sdk/hub/serverMethods.d.ts +3 -0
  51. package/dist/sdk/hub/serverMethods.js +11 -0
  52. package/dist/sdk/index.d.ts +10 -0
  53. package/dist/sdk/index.js +7 -0
  54. package/dist/sdk/mcp/connections.js +14 -7
  55. package/dist/sdk/mcp/serverMethods.d.ts +3 -0
  56. package/dist/sdk/mcp/serverMethods.js +22 -0
  57. package/dist/sdk/plugins/builtin.d.ts +1 -0
  58. package/dist/sdk/plugins/builtin.js +128 -33
  59. package/dist/sdk/plugins/builtinRegistry.d.ts +53 -0
  60. package/dist/sdk/plugins/builtinRegistry.js +49 -0
  61. package/dist/sdk/plugins/cli.d.ts +1 -1
  62. package/dist/sdk/plugins/cli.js +4 -3
  63. package/dist/sdk/prompt/compile.d.ts +5 -2
  64. package/dist/sdk/prompt/compile.js +25 -8
  65. package/dist/sdk/refine/cli.d.ts +1 -1
  66. package/dist/sdk/refine/cli.js +2 -2
  67. package/dist/sdk/refine/serverMethods.d.ts +3 -0
  68. package/dist/sdk/refine/serverMethods.js +20 -0
  69. package/dist/sdk/server/methods.js +9 -86
  70. package/dist/sdk/server/params.d.ts +4 -0
  71. package/dist/sdk/server/params.js +11 -0
  72. package/dist/sdk/sessions/contracts.d.ts +17 -0
  73. package/dist/sdk/sessions/contracts.js +42 -3
  74. package/dist/sdk/sessions/planMode.d.ts +5 -2
  75. package/dist/sdk/sessions/planMode.js +12 -3
  76. package/dist/sdk/sessions/reasoningSummaries.d.ts +2 -2
  77. package/dist/sdk/sessions/reasoningSummaries.js +3 -3
  78. package/dist/sdk/sessions/registry.d.ts +1 -0
  79. package/dist/sdk/sessions/registry.js +4 -1
  80. package/dist/sdk/sessions/runIndex.js +7 -7
  81. package/dist/sdk/sessions/runStore.js +3 -1
  82. package/dist/sdk/sessions/runner.js +2 -2
  83. package/dist/sdk/sessions/sessions.d.ts +17 -5
  84. package/dist/sdk/sessions/sessions.js +79 -12
  85. package/dist/sdk/sessions/sideQuestionServerMethods.d.ts +3 -0
  86. package/dist/sdk/sessions/sideQuestionServerMethods.js +19 -0
  87. package/dist/sdk/sessions/sideQuestions.d.ts +66 -0
  88. package/dist/sdk/sessions/sideQuestions.js +290 -0
  89. package/dist/sdk/sessions/subagentServerMethods.d.ts +3 -0
  90. package/dist/sdk/sessions/subagentServerMethods.js +41 -0
  91. package/dist/sdk/sessions/subagentService.d.ts +13 -2
  92. package/dist/sdk/sessions/subagentService.js +34 -16
  93. package/dist/sdk/sessions/subagents.d.ts +9 -1
  94. package/dist/sdk/sessions/subagents.js +19 -0
  95. package/dist/sdk/terminal/browser.d.ts +62 -0
  96. package/dist/sdk/terminal/browser.js +41 -0
  97. package/dist/sdk/terminal/browserControl.d.ts +29 -0
  98. package/dist/sdk/terminal/browserControl.js +186 -0
  99. package/dist/sdk/terminal/commandHelp.d.ts +17 -0
  100. package/dist/sdk/terminal/commandHelp.js +20 -0
  101. package/dist/sdk/terminal/controller.d.ts +7 -1
  102. package/dist/sdk/terminal/controller.js +34 -2
  103. package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +28 -0
  104. package/dist/sdk/terminal/opentui/subagentsWidget.js +52 -0
  105. package/dist/sdk/terminal/piExtension.js +33 -48
  106. package/dist/sdk/terminal/socket.d.ts +6 -3
  107. package/dist/sdk/terminal/socket.js +40 -9
  108. package/dist/sdk/terminal/standalone.d.ts +2 -0
  109. package/dist/sdk/terminal/standalone.js +50 -10
  110. package/dist/sdk/terminal/subagentMonitor.d.ts +38 -0
  111. package/dist/sdk/terminal/subagentMonitor.js +121 -0
  112. package/dist/sdk/terminal/terminals.d.ts +42 -6
  113. package/dist/sdk/terminal/terminals.js +359 -18
  114. package/dist/sdk/terminal/trajectory.js +2 -2
  115. package/dist/sdk/terminal/tui/annotateCommand.d.ts +3 -0
  116. package/dist/sdk/terminal/tui/annotateCommand.js +22 -0
  117. package/dist/sdk/terminal/tui/commandPages.d.ts +5 -1
  118. package/dist/sdk/terminal/tui/commandPages.js +54 -6
  119. package/dist/sdk/terminal/tui/commands.d.ts +8 -4
  120. package/dist/sdk/terminal/tui/commands.js +181 -58
  121. package/dist/sdk/terminal/tui/dialogGeometry.d.ts +2 -2
  122. package/dist/sdk/terminal/tui/dialogGeometry.js +4 -2
  123. package/dist/sdk/terminal/tui/dialogs.d.ts +6 -1
  124. package/dist/sdk/terminal/tui/dialogs.js +9 -2
  125. package/dist/sdk/terminal/tui/modelPicker.js +1 -6
  126. package/dist/sdk/terminal/tui/page.d.ts +3 -0
  127. package/dist/sdk/terminal/tui/pageOverlay.js +17 -6
  128. package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +26 -4
  129. package/dist/sdk/terminal/tui/pages/accountsPage.js +264 -36
  130. package/dist/sdk/terminal/tui/pages/agentsPage.d.ts +74 -0
  131. package/dist/sdk/terminal/tui/pages/agentsPage.js +190 -0
  132. package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +94 -0
  133. package/dist/sdk/terminal/tui/pages/annotatePage.js +341 -0
  134. package/dist/sdk/terminal/tui/pages/browserPage.d.ts +87 -0
  135. package/dist/sdk/terminal/tui/pages/browserPage.js +738 -0
  136. package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +2 -0
  137. package/dist/sdk/terminal/tui/pages/doctorPage.js +103 -15
  138. package/dist/sdk/terminal/tui/pages/notesPage.d.ts +75 -0
  139. package/dist/sdk/terminal/tui/pages/notesPage.js +201 -0
  140. package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +8 -0
  141. package/dist/sdk/terminal/tui/pages/settingsPage.js +16 -1
  142. package/dist/sdk/terminal/tui/pages/subagentsPage.d.ts +23 -0
  143. package/dist/sdk/terminal/tui/pages/subagentsPage.js +70 -0
  144. package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +12 -5
  145. package/dist/sdk/terminal/tui/pages/trajectoryPage.js +156 -83
  146. package/dist/sdk/terminal/tui/pages/worktreesPage.d.ts +72 -0
  147. package/dist/sdk/terminal/tui/pages/worktreesPage.js +203 -0
  148. package/dist/sdk/terminal/tui/planMode.d.ts +1 -1
  149. package/dist/sdk/terminal/tui/planMode.js +1 -1
  150. package/dist/sdk/terminal/tui/pluginCommands.d.ts +14 -0
  151. package/dist/sdk/terminal/tui/pluginCommands.js +27 -0
  152. package/dist/sdk/terminal/tui/questions.d.ts +2 -0
  153. package/dist/sdk/terminal/tui/runtime.js +4 -2
  154. package/dist/sdk/terminal/tui/settingsModel.d.ts +2 -0
  155. package/dist/sdk/terminal/tui/settingsModel.js +32 -2
  156. package/dist/sdk/terminal/tui/sideQuestion.d.ts +26 -26
  157. package/dist/sdk/terminal/tui/sideQuestion.js +131 -98
  158. package/dist/sdk/terminal/tui/ui.d.ts +2 -0
  159. package/dist/sdk/terminal/tui/ui.js +1 -0
  160. package/dist/sdk/tools/askUser.js +26 -8
  161. package/dist/sdk/tools/checklist.js +2 -2
  162. package/dist/sdk/tools/harnessTools.d.ts +2 -0
  163. package/dist/sdk/tools/harnessTools.js +1 -1
  164. package/dist/sdk/tools/spawnSubagent.js +13 -4
  165. package/dist/sdk/web/part.d.ts +10 -0
  166. package/dist/sdk/web/part.js +6 -0
  167. package/dist/sdk/web/serverMethods.d.ts +3 -0
  168. package/dist/sdk/web/serverMethods.js +7 -0
  169. package/dist/sdk/wiki/serverMethods.d.ts +3 -0
  170. package/dist/sdk/wiki/serverMethods.js +26 -0
  171. package/dist/sdk/worktrees/cli.d.ts +11 -0
  172. package/dist/sdk/worktrees/cli.js +146 -0
  173. package/dist/sdk/worktrees/copy.d.ts +9 -0
  174. package/dist/sdk/worktrees/copy.js +111 -0
  175. package/dist/sdk/worktrees/git.d.ts +16 -0
  176. package/dist/sdk/worktrees/git.js +81 -0
  177. package/dist/sdk/worktrees/serverMethods.d.ts +3 -0
  178. package/dist/sdk/worktrees/serverMethods.js +30 -0
  179. package/dist/sdk/worktrees/service.d.ts +73 -0
  180. package/dist/sdk/worktrees/service.js +483 -0
  181. package/dist/sdk/worktrees/setup.d.ts +23 -0
  182. package/dist/sdk/worktrees/setup.js +65 -0
  183. package/dist/sdk/worktrees/types.d.ts +91 -0
  184. package/dist/sdk/worktrees/types.js +1 -0
  185. package/dist/sdk/world/cli.d.ts +2 -2
  186. package/dist/sdk/world/cli.js +6 -6
  187. package/dist/sdk/world/world.d.ts +3 -1
  188. package/dist/sdk/world/world.js +8 -9
  189. package/dist/terminal-opentui.js +14540 -7374
  190. package/dist/terminal-pi.js +285 -666
  191. package/docs/README.md +22 -2
  192. package/docs/architecture/00-overview.md +28 -16
  193. package/docs/architecture/02-sessions-and-runs.md +72 -15
  194. package/docs/architecture/03-variants.md +10 -2
  195. package/docs/architecture/04-prompt.md +26 -6
  196. package/docs/architecture/06-plugins-and-capabilities.md +39 -3
  197. package/docs/architecture/07-tools-and-gate.md +21 -6
  198. package/docs/architecture/09-accounts-and-connectors.md +8 -0
  199. package/docs/architecture/10-server-protocol.md +111 -2
  200. package/docs/architecture/11-terminal.md +185 -5
  201. package/docs/architecture/12-storage.md +15 -1
  202. package/docs/architecture/13-world-tools.md +28 -6
  203. package/docs/architecture/14-surfaces-and-parity.md +89 -0
  204. package/docs/architecture/README.md +15 -4
  205. package/docs/architecture/proposals/PROP-0006-run-checklist.md +4 -2
  206. package/docs/architecture/proposals/PROP-0013-resource-browser.md +58 -0
  207. package/docs/architecture/proposals/PROP-0014-fullscreen-browser-tabs.md +58 -0
  208. package/docs/architecture/proposals/PROP-0015-one-capability-surface.md +88 -0
  209. package/docs/architecture/proposals/PROP-0016-managed-worktrees.md +156 -0
  210. package/docs/architecture/proposals/PROP-0017-page-annotations.md +127 -0
  211. package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +179 -0
  212. package/docs/architecture/proposals/PROP-0019-contextual-side-questions.md +122 -0
  213. package/docs/architecture/proposals/PROP-0020-builtin-feature-plugins.md +105 -0
  214. package/docs/architecture/proposals/PROP-0021-command-namespaces-and-duplicates.md +99 -0
  215. package/docs/architecture/proposals/PROP-0022-plan-only-questions.md +102 -0
  216. package/docs/architecture/proposals/PROP-0023-xagent-cli-name.md +61 -0
  217. package/docs/architecture/proposals/PROP-0024-question-sketch-previews.md +130 -0
  218. package/docs/architecture/proposals/PROP-0025-contextual-question-wireframes.md +49 -0
  219. package/docs/architecture/proposals/PROP-0026-connected-sketch-display.md +94 -0
  220. package/docs/architecture/proposals/PROP-0027-xingular-agent-product-identity.md +60 -0
  221. package/docs/architecture/proposals/PROP-0028-normalize-question-wireframes.md +55 -0
  222. package/docs/architecture/proposals/PROP-0029-agent-profiles.md +124 -0
  223. package/docs/architecture/proposals/PROP-0030-subagent-title.md +86 -0
  224. package/docs/architecture/proposals/README.md +25 -0
  225. package/docs/decisions/ADR-0009-xharness-command.md +6 -5
  226. package/docs/decisions/ADR-0014-public-release-baseline.md +3 -2
  227. package/docs/decisions/ADR-0015-resource-browser.md +28 -0
  228. package/docs/decisions/ADR-0016-fullscreen-browser-tabs.md +31 -0
  229. package/docs/decisions/ADR-0017-one-capability-surface.md +34 -0
  230. package/docs/decisions/ADR-0018-builtin-feature-plugins.md +35 -0
  231. package/docs/decisions/ADR-0019-managed-git-worktrees.md +31 -0
  232. package/docs/decisions/ADR-0020-page-annotations.md +37 -0
  233. package/docs/decisions/ADR-0021-required-command-namespaces.md +40 -0
  234. package/docs/decisions/ADR-0022-plan-only-question-tools.md +34 -0
  235. package/docs/decisions/ADR-0023-contextual-side-question-runs.md +35 -0
  236. package/docs/decisions/ADR-0024-xagent-command.md +40 -0
  237. package/docs/decisions/ADR-0025-question-choice-sketches.md +49 -0
  238. package/docs/decisions/ADR-0026-contextual-question-wireframes.md +58 -0
  239. package/docs/decisions/ADR-0027-connected-sketch-display.md +60 -0
  240. package/docs/decisions/ADR-0028-xingular-agent-product-identity.md +56 -0
  241. package/docs/decisions/ADR-0029-normalized-question-wireframe-borders.md +33 -0
  242. package/docs/decisions/ADR-0030-agent-profiles.md +43 -0
  243. package/docs/doctor.md +10 -4
  244. package/docs/features/README.md +26 -0
  245. package/docs/features/interfaces/README.md +1 -1
  246. package/docs/future-work.md +48 -0
  247. package/docs/parity.md +78 -0
  248. package/docs/releases/1.0.0.md +2 -2
  249. package/docs/releases/4.0.0.md +46 -0
  250. package/docs/releases/README.md +20 -15
  251. package/docs/study/00-index.md +5 -0
  252. package/docs/study/autonomous-engineering-sources.md +277 -0
  253. package/docs/study/autonomy-sources.md +147 -0
  254. package/docs/study/proactive-autonomy.md +408 -0
  255. package/docs/study/tui-resource-browser.md +78 -0
  256. package/docs/terminal-tui.md +325 -93
  257. package/package.json +3 -3
@@ -22,8 +22,8 @@ export function checklistTool(run) {
22
22
  return {
23
23
  name: CHECKLIST_WRITE,
24
24
  label: 'Write the checklist',
25
- description: 'Keep a checklist of the steps of the current work, so you and the user can see progress. Send the whole list every time; it replaces the previous one. ' +
26
- 'Use it when the work has three or more distinct steps, or when the user gives several things to do. Do not use it for a single, simple step or for conversation. ' +
25
+ 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. ' +
26
+ '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. ' +
27
27
  '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. ' +
28
28
  '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.',
29
29
  parameters: (Type) => Type.Object({
@@ -1,6 +1,8 @@
1
1
  import type { CallerContext } from "../core/caller.js";
2
+ import type { HarnessToolDefinition } from "./definitions.js";
2
3
  import { type DispatchEnv } from "./dispatch.js";
3
4
  import type { ToolSpec } from "./types.js";
4
5
  export declare function harnessTools(env: DispatchEnv, context: CallerContext, options?: {
5
6
  variantDir?: string | null;
7
+ definitions?: readonly HarnessToolDefinition[];
6
8
  }): ToolSpec[];
@@ -1,7 +1,7 @@
1
1
  import { describeTool, HARNESS_TOOLS } from "./definitions.js";
2
2
  import { dispatch } from "./dispatch.js";
3
3
  export function harnessTools(env, context, options = {}) {
4
- return HARNESS_TOOLS.map((tool) => ({
4
+ return (options.definitions ?? HARNESS_TOOLS).map((tool) => ({
5
5
  name: tool.name,
6
6
  label: tool.label,
7
7
  description: describeTool(tool, env.productName),
@@ -1,5 +1,5 @@
1
1
  import { HarnessError, invalid } from "../core/errors.js";
2
- import { SPAWN_SUBAGENT, WAIT_SUBAGENTS, SUBAGENT_RECEIPT } from "../sessions/subagents.js";
2
+ import { SPAWN_SUBAGENT, WAIT_SUBAGENTS, SUBAGENT_RECEIPT, MAX_SUBAGENT_TITLE_WORDS, subagentTitle } from "../sessions/subagents.js";
3
3
  export { SPAWN_SUBAGENT, SUBAGENT_RELATION, DEFAULT_SUBAGENT_DEPTH, MAX_SUBAGENT_DEPTH, subagentDepth, subagentReport } from "../sessions/subagents.js";
4
4
  function reported(parentRunId, results, spawned) {
5
5
  const included = [];
@@ -27,10 +27,19 @@ export function subagentTools(service, parentRunId) {
27
27
  return [{
28
28
  name: SPAWN_SUBAGENT,
29
29
  label: 'Start a subagent',
30
- description: 'Start a fresh child run for independent work: yourself, or one of your subagents when you name it in agent. Give a complete brief: the child has no conversation history and no more capabilities than you. Returns its run ID immediately by default so you can continue working. Results arrive automatically before you finish. Use wait_subagents to join selected children, or wait: true for an inline result. Stopping you stops your children.',
31
- parameters: (Type) => Type.Object({ brief: Type.String(), agent: Type.Optional(Type.String({ description: 'The name of one of your subagents; omit for a copy of yourself' })), tools: Type.Optional(Type.Array(Type.String())), wait: Type.Optional(Type.Boolean({ description: 'Wait for the child and return its result; default false' })) }),
30
+ description: 'Start a fresh child run for independent work: a copy of yourself, or an agent profile from your Subagents list when you name it in agent (for example worker to make changes, explorer to find and explain). Give a short title, which the user sees in the roster, and a complete brief: the child has no conversation history and no more capabilities than you. Returns its run ID immediately by default so you can continue working. Results arrive automatically before you finish. Use wait_subagents to join selected children, or wait: true for an inline result. Stopping you stops your children.',
31
+ parameters: (Type) => Type.Object({
32
+ title: Type.String({ description: `A short imperative title for the roster, at most ${MAX_SUBAGENT_TITLE_WORDS} words, such as "Map the auth flow"` }),
33
+ brief: Type.String(),
34
+ agent: Type.Optional(Type.String({ description: 'The name of an agent profile from your Subagents list; omit for a copy of yourself' })),
35
+ tools: Type.Optional(Type.Array(Type.String())),
36
+ wait: Type.Optional(Type.Boolean({ description: 'Wait for the child and return its result; default false' }))
37
+ }),
32
38
  execute: execute(async (input) => {
33
- const child = await service.spawn({ parentRunId, brief: input.brief, agent: input.agent, tools: input.tools, wait: input.wait });
39
+ const title = subagentTitle(input.title);
40
+ if (!title)
41
+ throw invalid(`Give the subagent a title of at most ${MAX_SUBAGENT_TITLE_WORDS} words, with no control characters.`);
42
+ const child = await service.spawn({ parentRunId, brief: input.brief, title, agent: input.agent, tools: input.tools, wait: input.wait });
34
43
  if (input.wait === true)
35
44
  return reported(parentRunId, service.results({ parentRunId, runIds: [child.runId] }), [child.runId]);
36
45
  return { text: JSON.stringify(child), details: { [SUBAGENT_RECEIPT]: { version: 1, ownerRunId: parentRunId, spawned: [child.runId] } } };
@@ -0,0 +1,10 @@
1
+ import type { SecretStore } from "../accounts/secrets.js";
2
+ import type { WorldPart } from "../world/world.js";
3
+ import type { WebRequest } from "./safeRequest.js";
4
+ import { KEYLESS_SEARCH } from "./tools.js";
5
+ export declare function webPart(options: {
6
+ secrets: SecretStore;
7
+ request?: WebRequest;
8
+ keyless: () => boolean;
9
+ }): WorldPart;
10
+ export { KEYLESS_SEARCH };
@@ -0,0 +1,6 @@
1
+ import { KEYLESS_SEARCH, WEB_PLUGIN, webTools } from "./tools.js";
2
+ export function webPart(options) {
3
+ const provider = webTools({ secrets: options.secrets, request: options.request, keyless: options.keyless });
4
+ return { plugin: WEB_PLUGIN, tools: () => provider({}) };
5
+ }
6
+ export { KEYLESS_SEARCH };
@@ -0,0 +1,3 @@
1
+ import type { Harness } from "../harness.js";
2
+ import type { Handler } from "../server/rpc.js";
3
+ export declare function webServerMethods(harness: Harness): Record<string, Handler>;
@@ -0,0 +1,7 @@
1
+ import { text } from "../server/params.js";
2
+ export function webServerMethods(harness) {
3
+ return {
4
+ 'web.status': () => harness.world.searchStatus(),
5
+ 'web.setKey': (params) => harness.world.setSearchKey(text(params, 'provider'), typeof params.key === 'string' ? params.key : null)
6
+ };
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { Harness } from "../harness.js";
2
+ import type { Handler } from "../server/rpc.js";
3
+ export declare function wikiServerMethods(harness: Harness): Record<string, Handler>;
@@ -0,0 +1,26 @@
1
+ import { optional, text } from "../server/params.js";
2
+ import * as notes from "./notes.js";
3
+ export function wikiServerMethods(harness) {
4
+ const root = () => harness.env.root;
5
+ return {
6
+ 'wiki.list': (params) => notes.list(root(), params.project === undefined ? {} : { project: optional(params, 'project') }),
7
+ 'wiki.read': (params) => notes.read(root(), text(params, 'path')),
8
+ 'wiki.write': (params) => {
9
+ const note = notes.write(root(), text(params, 'path'), typeof params.text === 'string' ? params.text : '', { expectedUpdatedAt: optional(params, 'expectedUpdatedAt') });
10
+ harness.bus.emit({ type: 'wiki.changed' });
11
+ return note;
12
+ },
13
+ 'wiki.remove': (params) => {
14
+ notes.remove(root(), text(params, 'path'));
15
+ harness.bus.emit({ type: 'wiki.changed' });
16
+ },
17
+ 'wiki.search': (params) => notes.search(root(), typeof params.query === 'string' ? params.query : '', { project: optional(params, 'project'), limit: typeof params.limit === 'number' ? params.limit : undefined }),
18
+ 'wiki.graph': () => notes.graph(root()),
19
+ 'wiki.versions': (params) => notes.versions(root(), text(params, 'path')),
20
+ 'wiki.version': (params) => notes.version(root(), text(params, 'path'), text(params, 'id')),
21
+ 'wiki.ensureProject': () => {
22
+ notes.ensureLayout(root(), harness.env.productName);
23
+ harness.bus.emit({ type: 'wiki.changed' });
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,11 @@
1
+ import { Worktrees } from "./service.js";
2
+ import type { WorktreeRecord } from "./types.js";
3
+ export declare const WORKTREE_USAGE = "Usage: xagent worktree <command> [--state <dir>]\n\n list Worktrees of the repository you are in\n new [name] [--branch <b>] [--permanent] [--from <ref>] [--no-carry]\n Make a worktree (default: managed, detached, with your\n uncommitted changes carried over)\n status <name|id> Changes and commits since it started\n branch <name|id> <branch> Give a detached worktree a branch\n handoff <name|id> to-main|to-worktree [--copy]\n Move changes between it and your main checkout\n (--copy leaves the origin as it was)\n rm <name|id> [--force] Remove it; with --force, keep a snapshot of unsaved work\n restore <name|id> Bring back a removed worktree, with its snapshot\n prune [--keep <n>] Keep the most recent managed worktrees (default 15)\n adopt <path> Manage an existing linked worktree\n trust-setup Trust this repository's .xharness/worktree-setup as it is now\n path <name|id> Print its folder (cd \"$(xagent worktree path name)\")\n\nStart the terminal in a new managed worktree with: xagent --worktree [name]\n";
4
+ interface Dependencies {
5
+ home?: string;
6
+ cwd?: string;
7
+ write?: (text: string) => void;
8
+ }
9
+ export declare function findWorktree(worktrees: Worktrees, cwd: string, key: string, removed?: boolean): Promise<WorktreeRecord>;
10
+ export declare function runWorktreeCli(argv: string[], dependencies?: Dependencies): Promise<number>;
11
+ export {};
@@ -0,0 +1,146 @@
1
+ // `xagent worktree`: managed worktrees of the repository you are in, from a shell.
2
+ import { homedir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import { Worktrees } from "./service.js";
5
+ export const WORKTREE_USAGE = `Usage: xagent worktree <command> [--state <dir>]
6
+
7
+ list Worktrees of the repository you are in
8
+ new [name] [--branch <b>] [--permanent] [--from <ref>] [--no-carry]
9
+ Make a worktree (default: managed, detached, with your
10
+ uncommitted changes carried over)
11
+ status <name|id> Changes and commits since it started
12
+ branch <name|id> <branch> Give a detached worktree a branch
13
+ handoff <name|id> to-main|to-worktree [--copy]
14
+ Move changes between it and your main checkout
15
+ (--copy leaves the origin as it was)
16
+ rm <name|id> [--force] Remove it; with --force, keep a snapshot of unsaved work
17
+ restore <name|id> Bring back a removed worktree, with its snapshot
18
+ prune [--keep <n>] Keep the most recent managed worktrees (default 15)
19
+ adopt <path> Manage an existing linked worktree
20
+ trust-setup Trust this repository's .xharness/worktree-setup as it is now
21
+ path <name|id> Print its folder (cd "$(xagent worktree path name)")
22
+
23
+ Start the terminal in a new managed worktree with: xagent --worktree [name]
24
+ `;
25
+ function describe(record) {
26
+ const state = record.branch ?? 'detached';
27
+ const lease = record.lease ? ' · in use' : '';
28
+ return `${record.name.padEnd(20)} ${record.kind.padEnd(9)} ${state.padEnd(24)} ${record.path}${lease}`;
29
+ }
30
+ export async function findWorktree(worktrees, cwd, key, removed = false) {
31
+ const repo = await worktrees.repositoryOf(cwd);
32
+ const pool = removed ? worktrees.removed(repo) : await worktrees.list(repo);
33
+ const match = pool.find((record) => record.id === key || record.name === key);
34
+ if (!match)
35
+ throw new Error(`No ${removed ? 'removed ' : ''}worktree named ${key} in this repository.`);
36
+ return match;
37
+ }
38
+ export async function runWorktreeCli(argv, dependencies = {}) {
39
+ const write = dependencies.write ?? ((text) => void process.stdout.write(text));
40
+ const cwd = resolve(dependencies.cwd ?? process.cwd());
41
+ const args = [];
42
+ let stateDir = join(dependencies.home ?? homedir(), '.xingular-harness', 'state');
43
+ for (let index = 0; index < argv.length; index += 1) {
44
+ if (argv[index] === '--state')
45
+ stateDir = resolve(argv[++index] ?? stateDir);
46
+ else
47
+ args.push(argv[index]);
48
+ }
49
+ const [command, ...rest] = args;
50
+ const flag = (name) => rest.includes(name);
51
+ const value = (name) => {
52
+ const index = rest.indexOf(name);
53
+ return index >= 0 ? rest[index + 1] : undefined;
54
+ };
55
+ const positional = rest.filter((item, index) => !item.startsWith('--') && !(index > 0 && ['--branch', '--from', '--keep'].includes(rest[index - 1])));
56
+ const worktrees = new Worktrees(stateDir);
57
+ switch (command) {
58
+ case 'list': {
59
+ const records = await worktrees.list(cwd);
60
+ write(records.length ? `${records.map(describe).join('\n')}\n` : 'No worktrees for this repository.\n');
61
+ return 0;
62
+ }
63
+ case 'new': {
64
+ const result = await worktrees.create({
65
+ repo: cwd,
66
+ ...(positional[0] ? { name: positional[0] } : {}),
67
+ ...(value('--branch') ? { branch: value('--branch') } : {}),
68
+ ...(value('--from') ? { baseRef: value('--from') } : {}),
69
+ kind: flag('--permanent') ? 'permanent' : 'managed',
70
+ ...(flag('--no-carry') ? { carryChanges: false } : {})
71
+ });
72
+ write(`${describe(result.worktree)}\n`);
73
+ if (result.carried.copied.length || result.carried.patched)
74
+ write(`Carried your uncommitted changes${result.carried.copied.length ? ` and ${result.carried.copied.length} untracked file(s)` : ''}.\n`);
75
+ if (result.included.copied.length)
76
+ write(`Copied ${result.included.copied.length} file(s) named in .worktreeinclude.\n`);
77
+ if (result.setup.state === 'untrusted')
78
+ write('The repository has a setup script you have not trusted. Read .xharness/worktree-setup, then run: xagent worktree trust-setup\n');
79
+ if (result.setup.state === 'failed')
80
+ write(`The setup script failed; see ${result.setup.log}.\n`);
81
+ return 0;
82
+ }
83
+ case 'status': {
84
+ const status = await worktrees.status((await findWorktree(worktrees, cwd, positional[0] ?? '')).id);
85
+ write(`${describe(status.record)}\n${status.changes} changed path(s), ${status.ahead} commit(s) since it started.\n`);
86
+ return 0;
87
+ }
88
+ case 'branch': {
89
+ const record = await worktrees.branch((await findWorktree(worktrees, cwd, positional[0] ?? '')).id, positional[1] ?? '');
90
+ write(`${describe(record)}\n`);
91
+ return 0;
92
+ }
93
+ case 'handoff': {
94
+ const direction = positional[1] === 'to-main' ? 'toSource' : positional[1] === 'to-worktree' ? 'toWorktree' : null;
95
+ if (!direction) {
96
+ write(WORKTREE_USAGE);
97
+ return 2;
98
+ }
99
+ const result = await worktrees.handoff((await findWorktree(worktrees, cwd, positional[0] ?? '')).id, direction, { move: !flag('--copy') });
100
+ write(result.patched || result.copied.length ? `Handed off${result.copied.length ? ` (and ${result.copied.length} untracked file(s))` : ''}.${result.stash ? ` The origin's copy is kept in git stash ${result.stash.slice(0, 12)}.` : ''}\n` : 'Nothing to hand off.\n');
101
+ return 0;
102
+ }
103
+ case 'rm': {
104
+ const { worktree, snapshot } = await worktrees.remove((await findWorktree(worktrees, cwd, positional[0] ?? '')).id, { force: flag('--force') });
105
+ write(`Removed ${worktree.name}.${snapshot ? ` Its unsaved work is kept; xagent worktree restore ${worktree.name} brings it back.` : ''}\n`);
106
+ return 0;
107
+ }
108
+ case 'restore': {
109
+ const record = await worktrees.restore((await findWorktree(worktrees, cwd, positional[0] ?? '', true)).id);
110
+ write(`${describe(record)}\n`);
111
+ return 0;
112
+ }
113
+ case 'prune': {
114
+ const keep = value('--keep') ? Number(value('--keep')) : undefined;
115
+ const result = await worktrees.prune({ repo: cwd, ...(keep !== undefined && Number.isFinite(keep) ? { keep } : {}) });
116
+ write(`Removed ${result.removed.length} worktree(s)${result.removed.some((entry) => entry.snapshot) ? ', keeping snapshots of unsaved work' : ''}.\n`);
117
+ for (const failure of result.failed)
118
+ write(`Could not remove ${failure.id}: ${failure.error}\n`);
119
+ return result.failed.length ? 1 : 0;
120
+ }
121
+ case 'adopt': {
122
+ const record = await worktrees.adopt(resolve(cwd, positional[0] ?? ''));
123
+ write(`${describe(record)}\n`);
124
+ return 0;
125
+ }
126
+ case 'trust-setup': {
127
+ const { setupScript } = await import("./setup.js");
128
+ const repo = await worktrees.repositoryOf(cwd);
129
+ const script = setupScript(repo);
130
+ if (!script) {
131
+ write('This repository has no .xharness/worktree-setup.\n');
132
+ return 1;
133
+ }
134
+ await worktrees.trustSetup(repo, script.hash);
135
+ write(`Trusted the setup script (sha256 ${script.hash.slice(0, 12)}…). A changed script asks again.\n`);
136
+ return 0;
137
+ }
138
+ case 'path': {
139
+ write(`${(await findWorktree(worktrees, cwd, positional[0] ?? '')).path}\n`);
140
+ return 0;
141
+ }
142
+ default:
143
+ write(WORKTREE_USAGE);
144
+ return command && command !== 'help' ? 2 : 0;
145
+ }
146
+ }
@@ -0,0 +1,9 @@
1
+ import type { CopyReport } from "./types.js";
2
+ export declare const INCLUDE_FILE = ".worktreeinclude";
3
+ export declare function copyFiles(from: string, to: string, paths: readonly string[]): CopyReport;
4
+ /** Untracked files that are not ignored: new work a person has not added yet. */
5
+ export declare function untrackedFiles(checkout: string): Promise<string[]>;
6
+ /** All ignored user files that must be bounded and captured before a worktree is removed. */
7
+ export declare function ignoredFiles(checkout: string): Promise<string[]>;
8
+ /** Ignored files that match `.worktreeinclude`, matched by git itself. */
9
+ export declare function includedFiles(checkout: string): Promise<string[]>;
@@ -0,0 +1,111 @@
1
+ // Copies files git does not track into a worktree: untracked work carried from the source, and the
2
+ // ignored files `.worktreeinclude` names (local settings, `.env`). A file is never overwritten, a
3
+ // symlink is never followed, and sizes are capped.
4
+ import { copyFileSync, existsSync, lstatSync, mkdirSync } from 'node:fs';
5
+ import { dirname, join } from 'node:path';
6
+ import { HarnessError } from "../core/errors.js";
7
+ import { safeRelative } from "../core/fs.js";
8
+ import { git, nulList } from "./git.js";
9
+ export const INCLUDE_FILE = '.worktreeinclude';
10
+ const FILE_CAP = 5 * 1024 * 1024;
11
+ const TOTAL_CAP = 100 * 1024 * 1024;
12
+ const IGNORED_FILE_COUNT_CAP = 10_000;
13
+ const IGNORED_PATH_BYTES_CAP = 1024 * 1024;
14
+ export function copyFiles(from, to, paths) {
15
+ const report = { copied: [], skipped: [] };
16
+ let total = 0;
17
+ for (const path of paths) {
18
+ let relative;
19
+ try {
20
+ relative = safeRelative(path);
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ const source = join(from, relative);
26
+ const target = join(to, relative);
27
+ if (existsSync(target)) {
28
+ report.skipped.push({ path: relative, reason: 'exists' });
29
+ continue;
30
+ }
31
+ let stat;
32
+ try {
33
+ stat = lstatSync(source);
34
+ }
35
+ catch {
36
+ continue;
37
+ }
38
+ if (stat.isSymbolicLink()) {
39
+ report.skipped.push({ path: relative, reason: 'symlink' });
40
+ continue;
41
+ }
42
+ if (!stat.isFile()) {
43
+ report.skipped.push({ path: relative, reason: 'not_file' });
44
+ continue;
45
+ }
46
+ if (stat.size > FILE_CAP) {
47
+ report.skipped.push({ path: relative, reason: 'too_large' });
48
+ continue;
49
+ }
50
+ if (total + stat.size > TOTAL_CAP) {
51
+ report.skipped.push({ path: relative, reason: 'over_total' });
52
+ continue;
53
+ }
54
+ mkdirSync(dirname(target), { recursive: true });
55
+ copyFileSync(source, target);
56
+ total += stat.size;
57
+ report.copied.push(relative);
58
+ }
59
+ return report;
60
+ }
61
+ /** Untracked files that are not ignored: new work a person has not added yet. */
62
+ export async function untrackedFiles(checkout) {
63
+ return nulList((await git(checkout, ['ls-files', '--others', '--exclude-standard', '-z'])).stdout);
64
+ }
65
+ /** All ignored user files that must be bounded and captured before a worktree is removed. */
66
+ export async function ignoredFiles(checkout) {
67
+ const paths = nulList((await git(checkout, ['ls-files', '--others', '--ignored', '--exclude-standard', '-z'])).stdout);
68
+ if (paths.length > IGNORED_FILE_COUNT_CAP)
69
+ throw ignoredFilesError(`there are more than ${IGNORED_FILE_COUNT_CAP} ignored files`);
70
+ let total = 0;
71
+ let pathBytes = 0;
72
+ return paths.map((path) => {
73
+ let relative;
74
+ try {
75
+ relative = safeRelative(path);
76
+ }
77
+ catch {
78
+ throw ignoredFilesError('an ignored path is unsafe');
79
+ }
80
+ pathBytes += Buffer.byteLength(relative) + 10;
81
+ if (pathBytes > IGNORED_PATH_BYTES_CAP)
82
+ throw ignoredFilesError('the ignored paths exceed the snapshot limit');
83
+ let stat;
84
+ try {
85
+ stat = lstatSync(join(checkout, relative));
86
+ }
87
+ catch {
88
+ throw ignoredFilesError(`could not inspect ${relative}`);
89
+ }
90
+ if (!stat.isFile() && !stat.isSymbolicLink())
91
+ throw ignoredFilesError(`${relative} is not a regular file or symlink`);
92
+ if (stat.size > FILE_CAP)
93
+ throw ignoredFilesError(`${relative} exceeds the ${FILE_CAP}-byte per-file limit`);
94
+ total += stat.size;
95
+ if (total > TOTAL_CAP)
96
+ throw ignoredFilesError(`ignored files exceed the ${TOTAL_CAP}-byte total limit`);
97
+ return relative;
98
+ });
99
+ }
100
+ function ignoredFilesError(reason) {
101
+ return new HarnessError('worktree_has_work', `Ignored files cannot be safely preserved: ${reason}.`, 'Move or remove those files, then try again.');
102
+ }
103
+ /** Ignored files that match `.worktreeinclude`, matched by git itself. */
104
+ export async function includedFiles(checkout) {
105
+ const patterns = join(checkout, INCLUDE_FILE);
106
+ if (!existsSync(patterns))
107
+ return [];
108
+ const ignored = new Set(nulList((await git(checkout, ['ls-files', '--others', '--ignored', '--exclude-standard', '-z'])).stdout));
109
+ const matching = nulList((await git(checkout, ['ls-files', '--others', '--ignored', `--exclude-from=${patterns}`, '-z'])).stdout);
110
+ return matching.filter((path) => ignored.has(path));
111
+ }
@@ -0,0 +1,16 @@
1
+ export interface GitResult {
2
+ code: number;
3
+ stdout: string;
4
+ stderr: string;
5
+ }
6
+ export interface GitOptions {
7
+ input?: string | Buffer;
8
+ env?: Record<string, string>;
9
+ /** Return a failed result instead of throwing. */
10
+ allowFail?: boolean;
11
+ }
12
+ export declare function git(cwd: string, args: readonly string[], options?: GitOptions): Promise<GitResult>;
13
+ /** The trimmed standard output of a git command that must succeed. */
14
+ export declare function gitText(cwd: string, args: readonly string[], options?: GitOptions): Promise<string>;
15
+ /** NUL-separated output as a list, without the empty last entry. */
16
+ export declare function nulList(text: string): string[];
@@ -0,0 +1,81 @@
1
+ // Git, run without a shell. Every configured clean/smudge/process filter is disabled so Harness
2
+ // operations never run repository-provided filter code, and git never stops to ask.
3
+ import { spawn } from 'node:child_process';
4
+ import { HarnessError } from "../core/errors.js";
5
+ const QUIET_FILTERS = ['-c', 'filter.lfs.smudge=', '-c', 'filter.lfs.process=', '-c', 'filter.lfs.required=false', '-c', 'core.hooksPath=/dev/null'];
6
+ const FILTER_KEYS = /^filter\.(.*)\.(?:clean|smudge|process|required)$/i;
7
+ function configuredFilters(cwd, env) {
8
+ return new Promise((resolve, reject) => {
9
+ const child = spawn('git', ['config', '--null', '--name-only', '--get-regexp', '^filter\\..*\\.(clean|smudge|process|required)$'], {
10
+ cwd,
11
+ env,
12
+ stdio: ['ignore', 'pipe', 'pipe']
13
+ });
14
+ const out = [];
15
+ const err = [];
16
+ child.stdout.on('data', (chunk) => out.push(chunk));
17
+ child.stderr.on('data', (chunk) => err.push(chunk));
18
+ child.once('error', (error) => reject(new HarnessError('git_unavailable', `Git could not be started: ${error.message}`, 'Install git and make sure it is on PATH.')));
19
+ child.once('close', (code) => {
20
+ const stdout = Buffer.concat(out).toString('utf8');
21
+ if (code !== 0 && code !== 1) {
22
+ const stderr = Buffer.concat(err).toString('utf8').trim();
23
+ reject(new HarnessError('git_failed', `git config failed: ${stderr || `exit ${code ?? 1}`}`));
24
+ return;
25
+ }
26
+ const filters = new Set();
27
+ for (const key of stdout.split('\0').filter(Boolean)) {
28
+ const match = FILTER_KEYS.exec(key);
29
+ if (match)
30
+ filters.add(match[1]);
31
+ }
32
+ resolve([...filters].flatMap((filter) => [
33
+ '-c', `filter.${filter}.clean=`,
34
+ '-c', `filter.${filter}.smudge=`,
35
+ '-c', `filter.${filter}.process=`,
36
+ '-c', `filter.${filter}.required=false`
37
+ ]));
38
+ });
39
+ });
40
+ }
41
+ export async function git(cwd, args, options = {}) {
42
+ const env = { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_LFS_SKIP_SMUDGE: '1', LC_ALL: 'C', ...options.env };
43
+ const filters = await configuredFilters(cwd, env);
44
+ return new Promise((resolve, reject) => {
45
+ const input = options.input;
46
+ const hasInput = input !== undefined && input.length > 0;
47
+ const child = spawn('git', [...QUIET_FILTERS, ...filters, ...args], {
48
+ cwd,
49
+ env,
50
+ stdio: [hasInput ? 'pipe' : 'ignore', 'pipe', 'pipe']
51
+ });
52
+ const out = [];
53
+ const err = [];
54
+ child.stdout.on('data', (chunk) => out.push(chunk));
55
+ child.stderr.on('data', (chunk) => err.push(chunk));
56
+ child.once('error', (error) => reject(new HarnessError('git_unavailable', `Git could not be started: ${error.message}`, 'Install git and make sure it is on PATH.')));
57
+ child.once('close', (code) => {
58
+ const result = { code: code ?? 1, stdout: Buffer.concat(out).toString('utf8'), stderr: Buffer.concat(err).toString('utf8') };
59
+ if (result.code !== 0 && !options.allowFail) {
60
+ reject(new HarnessError('git_failed', `git ${args.join(' ')} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`));
61
+ return;
62
+ }
63
+ resolve(result);
64
+ });
65
+ if (hasInput && child.stdin) {
66
+ child.stdin.on('error', (error) => {
67
+ if (error.code !== 'EPIPE')
68
+ reject(new HarnessError('git_failed', `git ${args.join(' ')} could not read input: ${error.message}`));
69
+ });
70
+ child.stdin.end(input);
71
+ }
72
+ });
73
+ }
74
+ /** The trimmed standard output of a git command that must succeed. */
75
+ export async function gitText(cwd, args, options = {}) {
76
+ return (await git(cwd, args, options)).stdout.trim();
77
+ }
78
+ /** NUL-separated output as a list, without the empty last entry. */
79
+ export function nulList(text) {
80
+ return text.split('\0').filter(Boolean);
81
+ }
@@ -0,0 +1,3 @@
1
+ import type { Harness } from "../harness.js";
2
+ import type { Handler } from "../server/rpc.js";
3
+ export declare function worktreeServerMethods(harness: Harness): Record<string, Handler>;
@@ -0,0 +1,30 @@
1
+ import { invalid } from "../core/errors.js";
2
+ import { optional, text } from "../server/params.js";
3
+ export function worktreeServerMethods(harness) {
4
+ return {
5
+ 'worktrees.list': (params) => harness.worktrees.list(optional(params, 'repo')),
6
+ 'worktrees.removed': (params) => harness.worktrees.removed(optional(params, 'repo')),
7
+ 'worktrees.status': (params) => harness.worktrees.status(text(params, 'id')),
8
+ 'worktrees.create': (params) => harness.worktrees.create({
9
+ repo: text(params, 'repo'),
10
+ ...(optional(params, 'name') ? { name: optional(params, 'name') } : {}),
11
+ ...(optional(params, 'baseRef') ? { baseRef: optional(params, 'baseRef') } : {}),
12
+ ...(optional(params, 'branch') ? { branch: optional(params, 'branch') } : {}),
13
+ ...(params.kind === 'permanent' || params.kind === 'managed' ? { kind: params.kind } : {}),
14
+ ...(typeof params.carryChanges === 'boolean' ? { carryChanges: params.carryChanges } : {}),
15
+ ...(typeof params.setup === 'boolean' ? { setup: params.setup } : {})
16
+ }),
17
+ 'worktrees.branch': (params) => harness.worktrees.branch(text(params, 'id'), text(params, 'name')),
18
+ 'worktrees.handoff': (params) => {
19
+ const direction = text(params, 'direction');
20
+ if (direction !== 'toSource' && direction !== 'toWorktree')
21
+ throw invalid('direction is toSource or toWorktree.');
22
+ return harness.worktrees.handoff(text(params, 'id'), direction, { move: params.move !== false });
23
+ },
24
+ 'worktrees.remove': (params) => harness.worktrees.remove(text(params, 'id'), { force: params.force === true }),
25
+ 'worktrees.restore': (params) => harness.worktrees.restore(text(params, 'id')),
26
+ 'worktrees.prune': (params) => harness.worktrees.prune({ repo: optional(params, 'repo'), ...(typeof params.keep === 'number' ? { keep: params.keep } : {}) }),
27
+ 'worktrees.adopt': (params) => harness.worktrees.adopt(text(params, 'path')),
28
+ 'worktrees.setup.trust': async (params) => { await harness.worktrees.trustSetup(text(params, 'repo'), text(params, 'hash')); return {}; }
29
+ };
30
+ }
@@ -0,0 +1,73 @@
1
+ import type { CreateWorktreeInput, CreateWorktreeResult, HandoffResult, WorktreeRecord, WorktreesOptions, WorktreeStatus } from "./types.js";
2
+ export declare class Worktrees {
3
+ readonly root: string;
4
+ readonly branchPrefix: string;
5
+ readonly keep: number;
6
+ private readonly registryFile;
7
+ private readonly trust;
8
+ private readonly leaseCounts;
9
+ private chain;
10
+ constructor(stateDir: string, options?: WorktreesOptions);
11
+ private read;
12
+ private write;
13
+ private update;
14
+ /** Runs changes one at a time, so two requests never race on the registry or on git. */
15
+ private serial;
16
+ /** Releases leases whose process is gone. At start-up, this process's own leases are stale too. */
17
+ recover(startup?: boolean): string[];
18
+ get(id: string): WorktreeRecord;
19
+ private active;
20
+ /** The folder for a registered worktree; unknown ids remain opaque to the caller. */
21
+ pathOf(id: string): string | null;
22
+ /** The top folder of the repository a folder belongs to (the main checkout, for a linked worktree). */
23
+ repositoryOf(folder: string): Promise<string>;
24
+ list(repo?: string | null): Promise<WorktreeRecord[]>;
25
+ /** Worktrees removed with a snapshot, which `restore` can bring back. */
26
+ removed(repo?: string | null): WorktreeRecord[];
27
+ status(id: string): Promise<WorktreeStatus>;
28
+ private repoKey;
29
+ private uniqueName;
30
+ private branchName;
31
+ create(input: CreateWorktreeInput): Promise<CreateWorktreeResult>;
32
+ adopt(path: string): Promise<WorktreeRecord>;
33
+ /** A session starts using a worktree. Another live session's lease refuses it. */
34
+ acquire(id: string, holder: string): Promise<WorktreeRecord>;
35
+ release(id: string, holder: string): Promise<void>;
36
+ branch(id: string, name: string): Promise<WorktreeRecord>;
37
+ private reachable;
38
+ /** Records the worktree's commits and working changes under a snapshot ref. */
39
+ private snapshot;
40
+ remove(id: string, options?: {
41
+ force?: boolean;
42
+ }): Promise<{
43
+ worktree: WorktreeRecord;
44
+ snapshot: string | null;
45
+ }>;
46
+ private removeNow;
47
+ restore(id: string): Promise<WorktreeRecord>;
48
+ /** Keeps the most recently used managed worktrees of each repository and removes the rest, with snapshots. */
49
+ prune(options?: {
50
+ repo?: string | null;
51
+ keep?: number;
52
+ }): Promise<{
53
+ removed: {
54
+ worktree: WorktreeRecord;
55
+ snapshot: string | null;
56
+ }[];
57
+ failed: {
58
+ id: string;
59
+ error: string;
60
+ }[];
61
+ }>;
62
+ /**
63
+ * Moves work between the worktree and the main checkout. `toSource` brings the worktree's changes
64
+ * since it started (commits and working changes) to the main checkout; `toWorktree` brings the
65
+ * main checkout's uncommitted changes to the worktree. The target must have no tracked changes,
66
+ * and nothing changes when the patch does not apply. With `move` (the default), what was handed
67
+ * off is stashed away from the origin, where `git stash` can bring it back.
68
+ */
69
+ handoff(id: string, direction: 'toSource' | 'toWorktree', options?: {
70
+ move?: boolean;
71
+ }): Promise<HandoffResult>;
72
+ trustSetup(repo: string, hash: string): Promise<void>;
73
+ }