@mikode13/harness 4.0.1 → 5.1.0

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 (170) hide show
  1. package/README.md +299 -17
  2. package/dist/agent/domain/agent.d.ts +32 -1
  3. package/dist/agent/domain/agent.js.map +1 -1
  4. package/dist/agent/domain/approval.d.ts +9 -0
  5. package/dist/agent/domain/approval.js.map +1 -1
  6. package/dist/agent/domain/runContext.d.ts +31 -0
  7. package/dist/agent/domain/runContext.js +47 -0
  8. package/dist/agent/domain/runContext.js.map +1 -0
  9. package/dist/diff/domain/unifiedDiff.d.ts +19 -0
  10. package/dist/diff/domain/unifiedDiff.js +166 -0
  11. package/dist/diff/domain/unifiedDiff.js.map +1 -0
  12. package/dist/engines/domain/model/historyFollower.d.ts +41 -0
  13. package/dist/engines/domain/model/historyFollower.js +158 -0
  14. package/dist/engines/domain/model/historyFollower.js.map +1 -0
  15. package/dist/engines/domain/model/llmAgent.d.ts +42 -4
  16. package/dist/engines/domain/model/llmAgent.js +287 -31
  17. package/dist/engines/domain/model/llmAgent.js.map +1 -1
  18. package/dist/factory/infrastructure/agentFactory.d.ts +14 -0
  19. package/dist/factory/infrastructure/agentFactory.js +20 -2
  20. package/dist/factory/infrastructure/agentFactory.js.map +1 -1
  21. package/dist/factory/infrastructure/agentLLMFactory.d.ts +119 -14
  22. package/dist/factory/infrastructure/agentLLMFactory.js +284 -57
  23. package/dist/factory/infrastructure/agentLLMFactory.js.map +1 -1
  24. package/dist/index.d.ts +13 -5
  25. package/dist/index.js +5 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/llm/domain/conversation.d.ts +44 -4
  28. package/dist/llm/domain/conversation.js +56 -7
  29. package/dist/llm/domain/conversation.js.map +1 -1
  30. package/dist/llm/domain/llm.d.ts +3 -1
  31. package/dist/llm/domain/llm.js.map +1 -1
  32. package/dist/llm/domain/tool.d.ts +13 -0
  33. package/dist/llm/domain/tool.js +12 -1
  34. package/dist/llm/domain/tool.js.map +1 -1
  35. package/dist/llm/infrastructure/claudeLLMClient.d.ts +3 -2
  36. package/dist/llm/infrastructure/claudeLLMClient.js +38 -10
  37. package/dist/llm/infrastructure/claudeLLMClient.js.map +1 -1
  38. package/dist/llm/infrastructure/openAILLMClient.d.ts +2 -1
  39. package/dist/llm/infrastructure/openAILLMClient.js +67 -17
  40. package/dist/llm/infrastructure/openAILLMClient.js.map +1 -1
  41. package/dist/orchestration/domain/model/orchestratorAgent.d.ts +17 -3
  42. package/dist/orchestration/domain/model/orchestratorAgent.js +65 -8
  43. package/dist/orchestration/domain/model/orchestratorAgent.js.map +1 -1
  44. package/dist/recovery/domain/fileChanges.d.ts +31 -0
  45. package/dist/recovery/domain/fileChanges.js +48 -0
  46. package/dist/recovery/domain/fileChanges.js.map +1 -0
  47. package/dist/recovery/domain/history.d.ts +114 -0
  48. package/dist/recovery/domain/history.js +62 -0
  49. package/dist/recovery/domain/history.js.map +1 -0
  50. package/dist/recovery/domain/recoveryStore.d.ts +233 -0
  51. package/dist/recovery/domain/recoveryStore.js +32 -0
  52. package/dist/recovery/domain/recoveryStore.js.map +1 -0
  53. package/dist/recovery/domain/retention.d.ts +33 -0
  54. package/dist/recovery/domain/retention.js +90 -0
  55. package/dist/recovery/domain/retention.js.map +1 -0
  56. package/dist/recovery/domain/runChanges.d.ts +37 -0
  57. package/dist/recovery/domain/runChanges.js +109 -0
  58. package/dist/recovery/domain/runChanges.js.map +1 -0
  59. package/dist/recovery/domain/runTree.d.ts +32 -0
  60. package/dist/recovery/domain/runTree.js +90 -0
  61. package/dist/recovery/domain/runTree.js.map +1 -0
  62. package/dist/recovery/infrastructure/createHistory.d.ts +11 -0
  63. package/dist/recovery/infrastructure/createHistory.js +17 -0
  64. package/dist/recovery/infrastructure/createHistory.js.map +1 -0
  65. package/dist/recovery/infrastructure/fileRecoveryStore.d.ts +133 -0
  66. package/dist/recovery/infrastructure/fileRecoveryStore.js +1043 -0
  67. package/dist/recovery/infrastructure/fileRecoveryStore.js.map +1 -0
  68. package/dist/recovery/infrastructure/workspaceFiles.d.ts +30 -0
  69. package/dist/recovery/infrastructure/workspaceFiles.js +125 -0
  70. package/dist/recovery/infrastructure/workspaceFiles.js.map +1 -0
  71. package/dist/tools/domain/accessPolicy.d.ts +30 -0
  72. package/dist/tools/domain/accessPolicy.js +11 -0
  73. package/dist/tools/domain/accessPolicy.js.map +1 -0
  74. package/dist/tools/domain/commands.d.ts +93 -0
  75. package/dist/tools/domain/commands.js +191 -0
  76. package/dist/tools/domain/commands.js.map +1 -0
  77. package/dist/tools/domain/editResponse.d.ts +14 -0
  78. package/dist/tools/domain/editResponse.js +79 -0
  79. package/dist/tools/domain/editResponse.js.map +1 -0
  80. package/dist/tools/domain/fileEdits.d.ts +64 -0
  81. package/dist/tools/domain/fileEdits.js +21 -0
  82. package/dist/tools/domain/fileEdits.js.map +1 -0
  83. package/dist/tools/domain/lineLength.d.ts +7 -0
  84. package/dist/tools/domain/lineLength.js +26 -0
  85. package/dist/tools/domain/lineLength.js.map +1 -0
  86. package/dist/tools/domain/preparedCall.d.ts +44 -0
  87. package/dist/tools/domain/preparedCall.js +26 -0
  88. package/dist/tools/domain/preparedCall.js.map +1 -0
  89. package/dist/tools/domain/readRegistry.d.ts +46 -0
  90. package/dist/tools/domain/readRegistry.js +62 -0
  91. package/dist/tools/domain/readRegistry.js.map +1 -0
  92. package/dist/tools/domain/textLines.d.ts +26 -0
  93. package/dist/tools/domain/textLines.js +44 -0
  94. package/dist/tools/domain/textLines.js.map +1 -0
  95. package/dist/tools/domain/tool.d.ts +14 -0
  96. package/dist/tools/domain/tool.js +7 -1
  97. package/dist/tools/domain/tool.js.map +1 -1
  98. package/dist/tools/domain/v4aPatch.d.ts +17 -0
  99. package/dist/tools/domain/v4aPatch.js +236 -0
  100. package/dist/tools/domain/v4aPatch.js.map +1 -0
  101. package/dist/tools/infrastructure/boundedWorkspace.d.ts +11 -2
  102. package/dist/tools/infrastructure/boundedWorkspace.js +9 -1
  103. package/dist/tools/infrastructure/boundedWorkspace.js.map +1 -1
  104. package/dist/tools/infrastructure/commandCheckpoint.d.ts +55 -0
  105. package/dist/tools/infrastructure/commandCheckpoint.js +178 -0
  106. package/dist/tools/infrastructure/commandCheckpoint.js.map +1 -0
  107. package/dist/tools/infrastructure/commandProcess.d.ts +47 -0
  108. package/dist/tools/infrastructure/commandProcess.js +217 -0
  109. package/dist/tools/infrastructure/commandProcess.js.map +1 -0
  110. package/dist/tools/infrastructure/commandTool.d.ts +36 -0
  111. package/dist/tools/infrastructure/commandTool.js +285 -0
  112. package/dist/tools/infrastructure/commandTool.js.map +1 -0
  113. package/dist/tools/infrastructure/createWorkspace.d.ts +10 -0
  114. package/dist/tools/infrastructure/createWorkspace.js +11 -3
  115. package/dist/tools/infrastructure/createWorkspace.js.map +1 -1
  116. package/dist/tools/infrastructure/defineTool.d.ts +18 -2
  117. package/dist/tools/infrastructure/defineTool.js +52 -6
  118. package/dist/tools/infrastructure/defineTool.js.map +1 -1
  119. package/dist/tools/infrastructure/editFormats.d.ts +22 -0
  120. package/dist/tools/infrastructure/editFormats.js +243 -0
  121. package/dist/tools/infrastructure/editFormats.js.map +1 -0
  122. package/dist/tools/infrastructure/fileEditor.d.ts +64 -0
  123. package/dist/tools/infrastructure/fileEditor.js +306 -0
  124. package/dist/tools/infrastructure/fileEditor.js.map +1 -0
  125. package/dist/tools/infrastructure/fileTools.d.ts +81 -0
  126. package/dist/tools/infrastructure/fileTools.js +152 -0
  127. package/dist/tools/infrastructure/fileTools.js.map +1 -0
  128. package/dist/tools/infrastructure/gitIgnoreRules.d.ts +15 -0
  129. package/dist/tools/infrastructure/gitIgnoreRules.js +74 -0
  130. package/dist/tools/infrastructure/gitIgnoreRules.js.map +1 -0
  131. package/dist/tools/infrastructure/gitWorkspace.d.ts +1 -1
  132. package/dist/tools/infrastructure/gitWorkspace.js +1 -1
  133. package/dist/tools/infrastructure/gitWorkspace.js.map +1 -1
  134. package/dist/tools/infrastructure/ignoreFileChanges.d.ts +14 -0
  135. package/dist/tools/infrastructure/ignoreFileChanges.js +62 -0
  136. package/dist/tools/infrastructure/ignoreFileChanges.js.map +1 -0
  137. package/dist/tools/infrastructure/policyWorkspace.d.ts +52 -0
  138. package/dist/tools/infrastructure/policyWorkspace.js +32 -0
  139. package/dist/tools/infrastructure/policyWorkspace.js.map +1 -0
  140. package/dist/tools/infrastructure/ripgrepWorkspace.d.ts +2 -1
  141. package/dist/tools/infrastructure/ripgrepWorkspace.js +1 -1
  142. package/dist/tools/infrastructure/ripgrepWorkspace.js.map +1 -1
  143. package/dist/tools/infrastructure/rootsAccessPolicy.d.ts +100 -0
  144. package/dist/tools/infrastructure/rootsAccessPolicy.js +301 -0
  145. package/dist/tools/infrastructure/rootsAccessPolicy.js.map +1 -0
  146. package/dist/tools/infrastructure/runProcess.d.ts +4 -1
  147. package/dist/tools/infrastructure/runProcess.js +4 -2
  148. package/dist/tools/infrastructure/runProcess.js.map +1 -1
  149. package/dist/tools/infrastructure/secretPaths.d.ts +8 -0
  150. package/dist/tools/infrastructure/secretPaths.js +76 -0
  151. package/dist/tools/infrastructure/secretPaths.js.map +1 -0
  152. package/dist/tools/infrastructure/showChangesTool.d.ts +12 -0
  153. package/dist/tools/infrastructure/showChangesTool.js +44 -0
  154. package/dist/tools/infrastructure/showChangesTool.js.map +1 -0
  155. package/dist/tools/infrastructure/trackedEdits.d.ts +33 -0
  156. package/dist/tools/infrastructure/trackedEdits.js +70 -0
  157. package/dist/tools/infrastructure/trackedEdits.js.map +1 -0
  158. package/dist/tools/infrastructure/trackedReadFile.d.ts +21 -0
  159. package/dist/tools/infrastructure/trackedReadFile.js +79 -0
  160. package/dist/tools/infrastructure/trackedReadFile.js.map +1 -0
  161. package/dist/tools/infrastructure/workspaceTools.d.ts +34 -0
  162. package/dist/tools/infrastructure/workspaceTools.js +69 -40
  163. package/dist/tools/infrastructure/workspaceTools.js.map +1 -1
  164. package/dist/tools/infrastructure/workspaceWrites.d.ts +23 -0
  165. package/dist/tools/infrastructure/workspaceWrites.js +42 -0
  166. package/dist/tools/infrastructure/workspaceWrites.js.map +1 -0
  167. package/dist/tools/infrastructure/writeSession.d.ts +35 -0
  168. package/dist/tools/infrastructure/writeSession.js +81 -0
  169. package/dist/tools/infrastructure/writeSession.js.map +1 -0
  170. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Agent, AgentResponse, RunOptions } from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport { classifyHostFailure, treatErrors } from '#src/shared/domain/providerFailure';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nexport const plannerInstructions =\n\t'You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet.';\n\nexport const executorInstructions =\n\t\"You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done.\";\n\nexport const reviewerInstructions =\n\t'You are the reviewer agent. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor\\'s narrative. Evaluate the original request first; plan compliance is secondary and provides supporting context. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary.';\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, options, totals);\n\t\t} catch (error) {\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t}\n\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t};\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\toptions,\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\toptions,\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\toptions,\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n}\n"]}
1
+ {"version":3,"file":"orchestratorAgent.js","sourceRoot":"","sources":["../../../../src/orchestration/domain/model/orchestratorAgent.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAgent,\n\tAgentResponse,\n\tProgressAgent,\n\tProgressEvent,\n\tRunOptions,\n} from '#src/agent/domain/agent';\nimport { addTokens, type Tokens } from '#src/shared/domain/tokens';\nimport { RecoverableError, UnrecoverableError, withSpentTokens } from '#src/shared/domain/errors';\nimport type { ReviewerDecision } from './reviewerDecision.ts';\nimport type { Validator } from '../interface/validator.ts';\nimport type { ILogger } from '#src/shared/domain/logger';\nimport {\n\tclassifyHostFailure,\n\tdescribeFailure,\n\ttreatErrors,\n} from '#src/shared/domain/providerFailure';\nimport {\n\tRunContext,\n\ttype RunEnd,\n\trunContextOf,\n\twithRunContext,\n} from '#src/agent/domain/runContext';\nimport { isAbortError } from '#src/shared/domain/isAbortError';\n\n// Each role's standing instructions, kept apart from the data of a round so an agent with a\n// system prompt can hold them there instead of receiving them again in every prompt. An agent\n// without one gets them at the head of each prompt; see `InstructedAgent`.\nconst conciseCommunication =\n\t'Communicate tersely and professionally. In reasoning updates, state only the next concrete action or a verified finding, in one short sentence. Do not narrate routine intentions, uncertainty, self-reflection, encouragement, or a desire to be thorough. Do not restate tool results. Once you decide what to do, act immediately. Use only the detail required by your role in the final response.';\n\nexport const plannerInstructions = `You are the planner agent. Read the relevant repository code and create a concise, engineering-grade plan for the executor. Cover the requested behaviour, structural issues, affected files or symbols, API contracts and boundaries, important failure paths, and meaningful tests. Keep the plan focused on the current request and do not require an architecture redesign yet. ${conciseCommunication}`;\n\nconst executorTask = `You are the executor agent. Read the relevant repository code and implement the original user request according to the planner's current plan. Preserve contracts and boundaries, handle important failure paths, keep the change focused, and add or update meaningful tests. Inspect and change the code; do not merely describe what should be done. ${conciseCommunication}`;\n\n// For an executor that can run the project's tests and checks: an Agent SDK one, which has a\n// shell, or a model-API one given commands. The harness cannot tell which command runs the\n// tests, so any command counts.\nexport const executorInstructions = `${executorTask} Before you finish, run the project's tests and checks, and finish only when they pass. Once they pass and the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. If you cannot make them pass, finish by saying which ones fail and why.`;\n\n// For a model-API executor with no command, which cannot run anything: told so, it neither\n// spends its steps looking for a way to test nor claims a result it could not have.\nexport const executorInstructionsWithoutCommands = `${executorTask} You have no command that runs the project's tests or checks, so you cannot run them. Once the plan is carried out, finish at once: do not add work beyond the plan, because the reviewer asks for what is missing. Say in your summary that the tests and checks were not run.`;\n\nconst reviewerTask = `Review the code. Independently inspect the repository, the current diff, and relevant surrounding code; do not rely on the executor's narrative. Judge it against the original request, and any issue it names, first: check that the plan is right for it, and that the change fulfills it. Check correctness and logic errors, important failure paths, unused or artificial abstractions, races or shared state, API contract breaks, boundary violations, regressions, scope, and test quality. If it is correct, respond with JSON only: {\"decision\":\"approved\"}. If it is not correct, respond with JSON only: {\"decision\":\"rejected\",\"feedback\":\"list concrete, prioritized findings and the required direction for each\"}. The feedback must contain actionable engineering findings, not a general summary. ${conciseCommunication}`;\n\n// Each goes with the executor instructions of the same name.\nexport const reviewerInstructions = `You are the reviewer agent. The executor has already run the tests and checks, and finished only when they passed: do not run them again. ${reviewerTask}`;\n\nexport const reviewerInstructionsWithoutCommands = `You are the reviewer agent. Nobody has run the tests or checks: the executor had no command for them. Judge from the code whether the tests it added or changed would pass, and do not approve a change whose tests could not. ${reviewerTask}`;\n\nconst getPlannerPrompt = (userPrompt: string, previousFailureReason?: string) => {\n\tconst feedback = previousFailureReason\n\t\t? `\\n\\nFeedback from the previous attempt:\\n---\\n${previousFailureReason}\\n---`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---${feedback}`;\n};\n\nconst getExecutorPrompt = (userPrompt: string, plannerPrompt: string) =>\n\t`Original user request:\n---\n${userPrompt}\n---\n\nCurrent implementation plan:\n---\n${plannerPrompt}\n---`;\n\nconst getReviewerPrompt = (\n\tuserPrompt: string,\n\tplannerPrompt: string,\n\texecutorResult: string,\n\tparseFailureReason?: string,\n) => {\n\tconst retryNotice = parseFailureReason\n\t\t? `\\n\\nYour previous response could not be used: ${parseFailureReason} Respond with JSON only, matching the schema exactly, with no surrounding text and no markdown code fences.`\n\t\t: '';\n\n\treturn `Original user request:\n---\n${userPrompt}\n---\n\nPlanner's current plan:\n---\n${plannerPrompt}\n---\n\nExecutor response (context only; verify it independently):\n---\n${executorResult}\n---${retryNotice}`;\n};\n\ninterface RunTotals {\n\ttokens: Tokens | undefined;\n\t/** A role answered without usage, so the run's total cannot be known. */\n\tunreported: boolean;\n}\n\n// Every role's response counts, even an empty one that starts another round: it was billed.\nfunction addToTotals(totals: RunTotals, response: AgentResponse): void {\n\tif (!response.tokens) totals.unreported = true;\n\ttotals.tokens = addTokens(totals.tokens, response.tokens);\n}\n\n/** Unknown rather than partial: a sum missing a billed call must not look complete. */\nfunction runTokens(totals: RunTotals): Tokens | undefined {\n\treturn totals.unreported ? undefined : totals.tokens;\n}\n\nfunction stripCodeFence(text: string): string {\n\tconst match = /^```(?:json)?\\s*([\\s\\S]*?)\\s*```$/.exec(text.trim());\n\treturn match?.[1] ?? text;\n}\n\nfunction parseReviewerDecision(\n\tresponse: AgentResponse,\n\treviewerDecisionValidator: Validator<ReviewerDecision>,\n): ReviewerDecision {\n\tif (!response.response) {\n\t\tthrow new RecoverableError(\"There's no decision from the reviewer, something went wrong\", {\n\t\t\tcause: 'Reviewer decision is missing.',\n\t\t});\n\t}\n\n\tlet parsedResponse: unknown;\n\ttry {\n\t\tparsedResponse = JSON.parse(stripCodeFence(response.response));\n\t} catch {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response must be valid JSON.',\n\t\t});\n\t}\n\n\tconst decision = reviewerDecisionValidator.validate(parsedResponse);\n\tif (!decision) {\n\t\tthrow new RecoverableError('Reviewer returned an invalid decision', {\n\t\t\tcause: 'Reviewer response did not match the decision schema.',\n\t\t});\n\t}\n\n\treturn decision;\n}\n\nexport class OrchestratorAgent implements Agent {\n\tprivate plannerAgent: Agent;\n\tprivate executorAgent: Agent;\n\tprivate reviewerAgent: Agent;\n\tprivate reviewerDecisionValidator: Validator<ReviewerDecision>;\n\tprivate maxAttempts: number;\n\tprivate logger: ILogger;\n\n\tconstructor({\n\t\tplannerAgent,\n\t\texecutorAgent,\n\t\treviewerAgent,\n\t\treviewerDecisionValidator,\n\t\tmaxAttempts = 3,\n\t\tlogger,\n\t}: {\n\t\tplannerAgent: Agent;\n\t\texecutorAgent: Agent;\n\t\treviewerAgent: Agent;\n\t\treviewerDecisionValidator: Validator<ReviewerDecision>;\n\t\tmaxAttempts?: number;\n\t\tlogger: ILogger;\n\t}) {\n\t\tif (!Number.isInteger(maxAttempts) || maxAttempts < 1) {\n\t\t\tthrow new RangeError('maxAttempts must be a positive integer');\n\t\t}\n\n\t\tthis.plannerAgent = plannerAgent;\n\t\tthis.executorAgent = executorAgent;\n\t\tthis.reviewerAgent = reviewerAgent;\n\t\tthis.reviewerDecisionValidator = reviewerDecisionValidator;\n\t\tthis.maxAttempts = maxAttempts;\n\t\tthis.logger = logger;\n\t}\n\n\t/**\n\t * Every role, round and retry of one run shares one run context, so what the executor writes\n\t * is one run of the workspace's history, and the planner's and reviewer's turns are tied to\n\t * it. The orchestrator ends that context with the run, unless an outer agent owns it.\n\t */\n\tasync run(prompt: string, options: RunOptions): Promise<AgentResponse> {\n\t\tconst start = Date.now();\n\t\t// Per invocation, not per instance: the CLI keeps one orchestrator for a whole session.\n\t\tconst totals: RunTotals = { tokens: undefined, unreported: false };\n\t\tconst inherited = runContextOf(options);\n\t\tconst context = inherited ?? new RunContext();\n\t\tlet end: RunEnd = 'failed';\n\n\t\ttry {\n\t\t\tawait this.runRounds(prompt, inherited ? options : withRunContext(options, context), totals);\n\t\t\tend = 'completed';\n\t\t} catch (error) {\n\t\t\tif (isAbortError(error)) end = 'cancelled';\n\t\t\t// A failing role carries its own tokens; the roles before it are in the totals.\n\t\t\tthrow withSpentTokens(error, totals.tokens, totals.unreported);\n\t\t} finally {\n\t\t\tif (!inherited) await this.finishContext(context, end);\n\t\t}\n\n\t\t// Read once the run ended: a run that changed nothing left the history then.\n\t\tconst { runId } = context.historyRun;\n\t\treturn {\n\t\t\tresponse: 'All job has finished',\n\t\t\ttokens: runTokens(totals),\n\t\t\t// The whole run's wall clock, so it matches what the consumer waited.\n\t\t\tduration: (Date.now() - start) / 1000,\n\t\t\t...(!inherited && runId ? { runId } : {}),\n\t\t};\n\t}\n\n\t/** The run's records end here, but its result stands: what it changed is already on disk. */\n\tprivate async finishContext(context: RunContext, end: RunEnd): Promise<void> {\n\t\ttry {\n\t\t\tawait context.finish(end);\n\t\t} catch (error) {\n\t\t\tthis.warn(`The run could not close its records: ${describeFailure(error)}`);\n\t\t}\n\t}\n\n\tprivate async runRounds(prompt: string, options: RunOptions, totals: RunTotals): Promise<void> {\n\t\tlet lastFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst isLastAttempt = attempt === this.maxAttempts;\n\n\t\t\tconst plannerResponse = await this.plannerAgent.run(\n\t\t\t\tgetPlannerPrompt(prompt, lastFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'planner'),\n\t\t\t);\n\t\t\taddToTotals(totals, plannerResponse);\n\n\t\t\tif (!plannerResponse.response) {\n\t\t\t\tlastFailureReason = 'The planner produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the planner produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst executorResponse = await this.executorAgent.run(\n\t\t\t\tgetExecutorPrompt(prompt, plannerResponse.response),\n\t\t\t\tthis.optionsForRole(options, 'executor'),\n\t\t\t);\n\t\t\taddToTotals(totals, executorResponse);\n\n\t\t\tif (!executorResponse.response) {\n\t\t\t\tlastFailureReason = 'The executor produced no response.';\n\t\t\t\tif (isLastAttempt) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the executor produced no response; starting another round`,\n\t\t\t\t);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst reviewerDecision = await this.getReviewerDecision(\n\t\t\t\tprompt,\n\t\t\t\tplannerResponse.response,\n\t\t\t\texecutorResponse.response,\n\t\t\t\toptions,\n\t\t\t\ttotals,\n\t\t\t);\n\n\t\t\tif (reviewerDecision.decision === 'approved') return;\n\n\t\t\tlastFailureReason = reviewerDecision.feedback;\n\t\t\tif (isLastAttempt) {\n\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: lastFailureReason });\n\t\t\t}\n\n\t\t\tthis.warn(\n\t\t\t\t`Attempt ${String(attempt)}/${String(this.maxAttempts)}: the reviewer rejected the round; starting another with its feedback`,\n\t\t\t\tlastFailureReason,\n\t\t\t);\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: lastFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\t// Retries only the reviewer call on a malformed decision, instead of redoing\n\t// planning/execution — a bad or unparsable reviewer response is not evidence\n\t// the plan or the implementation were wrong.\n\tprivate async getReviewerDecision(\n\t\tuserPrompt: string,\n\t\tplannerPrompt: string,\n\t\texecutorResult: string,\n\t\toptions: RunOptions,\n\t\ttotals: RunTotals,\n\t): Promise<ReviewerDecision> {\n\t\tlet parseFailureReason: string | undefined;\n\n\t\tfor (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n\t\t\tconst reviewerResponse = await this.reviewerAgent.run(\n\t\t\t\tgetReviewerPrompt(userPrompt, plannerPrompt, executorResult, parseFailureReason),\n\t\t\t\tthis.optionsForRole(options, 'reviewer'),\n\t\t\t);\n\n\t\t\taddToTotals(totals, reviewerResponse);\n\n\t\t\ttry {\n\t\t\t\treturn parseReviewerDecision(reviewerResponse, this.reviewerDecisionValidator);\n\t\t\t} catch (error) {\n\t\t\t\tif (!(error instanceof RecoverableError)) throw error;\n\n\t\t\t\tparseFailureReason = error.cause;\n\t\t\t\tif (attempt === this.maxAttempts) {\n\t\t\t\t\tthrow new UnrecoverableError('Max attempts exhausted', { cause: parseFailureReason });\n\t\t\t\t}\n\n\t\t\t\tthis.warn(\n\t\t\t\t\t`Reviewer decision ${String(attempt)}/${String(this.maxAttempts)} was unusable; asking the reviewer again`,\n\t\t\t\t\tparseFailureReason,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthrow new UnrecoverableError('Max attempts exhausted', {\n\t\t\tcause: parseFailureReason ?? 'Unknown failure.',\n\t\t});\n\t}\n\n\tprivate warn(...args: unknown[]): void {\n\t\ttreatErrors(\n\t\t\t() => {\n\t\t\t\tthis.logger.warn(...args);\n\t\t\t},\n\t\t\tclassifyHostFailure,\n\t\t\t'Orchestrator logger failed while reporting a retry',\n\t\t);\n\t}\n\n\t/**\n\t * Keep the options object extensible and the private run context intact while making the\n\t * orchestrator's attribution authoritative over anything a nested agent supplied.\n\t */\n\tprivate optionsForRole(options: RunOptions, agent: ProgressAgent): RunOptions {\n\t\tconst onProgress = options.onProgress;\n\t\treturn {\n\t\t\t...options,\n\t\t\tonProgress: onProgress\n\t\t\t\t? (event: ProgressEvent) => {\n\t\t\t\t\t\ttreatErrors(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tonProgress({ ...event, agent });\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tclassifyHostFailure,\n\t\t\t\t\t\t\t'Orchestrator progress callback failed',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { FileState, JournalEntry } from './recoveryStore.ts';
2
+ /** What one run did to one file, from before its first change to after its last. */
3
+ export interface FileChange {
4
+ path: string;
5
+ before: FileState;
6
+ after: FileState;
7
+ /**
8
+ * Each change started from the state the previous one left. When it did not, someone else
9
+ * wrote to the file between two of the run's changes, and going back to `before` would
10
+ * discard that work.
11
+ */
12
+ continuous: boolean;
13
+ /** The run stopped while changing this file, and the file held neither state afterwards. */
14
+ uncertain: boolean;
15
+ /**
16
+ * The run is changing this file now: that change is recorded but may still be abandoned, so
17
+ * `after` leaves it out.
18
+ */
19
+ pending: boolean;
20
+ /** Folders the run created for this file, outermost first. */
21
+ createdFolders: string[];
22
+ /** The commands seen changing it, for changes no tool of the harness made itself. */
23
+ observedBy: string[];
24
+ }
25
+ export declare function sameState(a: FileState, b: FileState): boolean;
26
+ /**
27
+ * One change per path, in the order the run first touched each one. Abandoned changes were
28
+ * never made, so they are left out. `stopped` says the run is over, so a change still
29
+ * `prepared` is one nobody could settle.
30
+ */
31
+ export declare function fileChanges(entries: JournalEntry[], stopped: boolean): FileChange[];
@@ -0,0 +1,48 @@
1
+ export function sameState(a, b) {
2
+ if (!a.exists || !b.exists)
3
+ return a.exists === b.exists;
4
+ return a.hash === b.hash && a.mode === b.mode;
5
+ }
6
+ /**
7
+ * One change per path, in the order the run first touched each one. Abandoned changes were
8
+ * never made, so they are left out. `stopped` says the run is over, so a change still
9
+ * `prepared` is one nobody could settle.
10
+ */
11
+ export function fileChanges(entries, stopped) {
12
+ const byPath = new Map();
13
+ for (const entry of entries) {
14
+ if (entry.status === 'abandoned')
15
+ continue;
16
+ const uncertain = stopped && entry.status === 'prepared';
17
+ const pending = !stopped && entry.status === 'prepared';
18
+ const folders = entry.createdFolders ?? [];
19
+ const known = byPath.get(entry.path);
20
+ if (known) {
21
+ if (pending) {
22
+ known.pending = true;
23
+ continue;
24
+ }
25
+ known.continuous &&= sameState(known.after, entry.before);
26
+ known.after = entry.after;
27
+ known.uncertain ||= uncertain;
28
+ known.createdFolders.push(...folders.filter(folder => !known.createdFolders.includes(folder)));
29
+ if (entry.observedBy !== undefined && !known.observedBy.includes(entry.observedBy)) {
30
+ known.observedBy.push(entry.observedBy);
31
+ }
32
+ }
33
+ else {
34
+ byPath.set(entry.path, {
35
+ path: entry.path,
36
+ before: entry.before,
37
+ after: pending ? entry.before : entry.after,
38
+ continuous: true,
39
+ uncertain,
40
+ pending,
41
+ createdFolders: [...folders],
42
+ observedBy: entry.observedBy === undefined ? [] : [entry.observedBy],
43
+ });
44
+ }
45
+ }
46
+ return [...byPath.values()];
47
+ }
48
+ //# sourceMappingURL=fileChanges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileChanges.js","sourceRoot":"","sources":["../../../src/recovery/domain/fileChanges.ts"],"names":[],"mappings":"AA0BA,MAAM,UAAU,SAAS,CAAC,CAAY,EAAE,CAAY;IACnD,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,CAAC;IACzD,OAAO,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,OAAuB,EAAE,OAAgB;IACpE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC7C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW;YAAE,SAAS;QAC3C,MAAM,SAAS,GAAG,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAC;QACzD,MAAM,OAAO,GAAG,CAAC,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,UAAU,CAAC;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,cAAc,IAAI,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrC,IAAI,KAAK,EAAE,CAAC;YACX,IAAI,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC;gBACrB,SAAS;YACV,CAAC;YACD,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC1D,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;YAC1B,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC;YAC9B,KAAK,CAAC,cAAc,CAAC,IAAI,CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CACnE,CAAC;YACF,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;gBACpF,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACzC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE;gBACtB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,MAAM;gBACpB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK;gBAC3C,UAAU,EAAE,IAAI;gBAChB,SAAS;gBACT,OAAO;gBACP,cAAc,EAAE,CAAC,GAAG,OAAO,CAAC;gBAC5B,UAAU,EAAE,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC;aACpE,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC7B,CAAC","sourcesContent":["import type { FileState, JournalEntry } from './recoveryStore.ts';\n\n/** What one run did to one file, from before its first change to after its last. */\nexport interface FileChange {\n\tpath: string;\n\tbefore: FileState;\n\tafter: FileState;\n\t/**\n\t * Each change started from the state the previous one left. When it did not, someone else\n\t * wrote to the file between two of the run's changes, and going back to `before` would\n\t * discard that work.\n\t */\n\tcontinuous: boolean;\n\t/** The run stopped while changing this file, and the file held neither state afterwards. */\n\tuncertain: boolean;\n\t/**\n\t * The run is changing this file now: that change is recorded but may still be abandoned, so\n\t * `after` leaves it out.\n\t */\n\tpending: boolean;\n\t/** Folders the run created for this file, outermost first. */\n\tcreatedFolders: string[];\n\t/** The commands seen changing it, for changes no tool of the harness made itself. */\n\tobservedBy: string[];\n}\n\nexport function sameState(a: FileState, b: FileState): boolean {\n\tif (!a.exists || !b.exists) return a.exists === b.exists;\n\treturn a.hash === b.hash && a.mode === b.mode;\n}\n\n/**\n * One change per path, in the order the run first touched each one. Abandoned changes were\n * never made, so they are left out. `stopped` says the run is over, so a change still\n * `prepared` is one nobody could settle.\n */\nexport function fileChanges(entries: JournalEntry[], stopped: boolean): FileChange[] {\n\tconst byPath = new Map<string, FileChange>();\n\tfor (const entry of entries) {\n\t\tif (entry.status === 'abandoned') continue;\n\t\tconst uncertain = stopped && entry.status === 'prepared';\n\t\tconst pending = !stopped && entry.status === 'prepared';\n\t\tconst folders = entry.createdFolders ?? [];\n\t\tconst known = byPath.get(entry.path);\n\t\tif (known) {\n\t\t\tif (pending) {\n\t\t\t\tknown.pending = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tknown.continuous &&= sameState(known.after, entry.before);\n\t\t\tknown.after = entry.after;\n\t\t\tknown.uncertain ||= uncertain;\n\t\t\tknown.createdFolders.push(\n\t\t\t\t...folders.filter(folder => !known.createdFolders.includes(folder)),\n\t\t\t);\n\t\t\tif (entry.observedBy !== undefined && !known.observedBy.includes(entry.observedBy)) {\n\t\t\t\tknown.observedBy.push(entry.observedBy);\n\t\t\t}\n\t\t} else {\n\t\t\tbyPath.set(entry.path, {\n\t\t\t\tpath: entry.path,\n\t\t\t\tbefore: entry.before,\n\t\t\t\tafter: pending ? entry.before : entry.after,\n\t\t\t\tcontinuous: true,\n\t\t\t\tuncertain,\n\t\t\t\tpending,\n\t\t\t\tcreatedFolders: [...folders],\n\t\t\t\tobservedBy: entry.observedBy === undefined ? [] : [entry.observedBy],\n\t\t\t});\n\t\t}\n\t}\n\treturn [...byPath.values()];\n}\n"]}
@@ -0,0 +1,114 @@
1
+ import type { MoveOptions, RecoveryStore, RunStatus } from './recoveryStore.ts';
2
+ /** One run that wrote to the workspace. */
3
+ export interface HistoryRun {
4
+ runId: string;
5
+ /** The run the workspace was at when this one started; absent when it started at the start. */
6
+ parentRunId?: string;
7
+ /**
8
+ * Runs whose changes retention chained into this one, oldest first: going back past this run
9
+ * goes back past them too. An id an earlier `AgentResponse` gave may be found here.
10
+ */
11
+ absorbed?: string[];
12
+ status: RunStatus;
13
+ startedAt: string;
14
+ finishedAt?: string;
15
+ /**
16
+ * The files it changed, in the order it first touched each one: relative to the root, or the
17
+ * real path of a file outside it. A host decides whether to show them.
18
+ */
19
+ files: string[];
20
+ }
21
+ /** One move of the workspace through its history. */
22
+ export interface Move {
23
+ /** The run the workspace was at; absent for the start of the history. */
24
+ from?: string;
25
+ /** The run the workspace is at now; absent for the start of the history. */
26
+ to?: string;
27
+ at: string;
28
+ reason?: string;
29
+ /** Every file reached the state the history expected. Otherwise `conflicts` names those left. */
30
+ complete: boolean;
31
+ /**
32
+ * The files left as they were because they did not hold what the history expected, such as
33
+ * one the user edited since, and the run whose change could not be undone or redone.
34
+ */
35
+ conflicts: {
36
+ path: string;
37
+ runId: string;
38
+ }[];
39
+ }
40
+ /**
41
+ * The history of the runs that wrote to one workspace: what each run changed, and moving the
42
+ * workspace back and forward through them. Runs form a tree: going back and writing again
43
+ * starts a new branch, and the earlier one stays reachable with `goTo`.
44
+ */
45
+ export interface History {
46
+ /**
47
+ * Every run kept, oldest first, and the run the workspace is at (`head`), absent at the start
48
+ * of the history. A run whose process died while writing is `interrupted`.
49
+ */
50
+ list(): Promise<{
51
+ head?: string;
52
+ runs: HistoryRun[];
53
+ }>;
54
+ /**
55
+ * What a run changed, as a git-style diff per file. What does not fit in `maxLines`, 1000 by
56
+ * default, is named instead.
57
+ *
58
+ * @throws {UnknownRunError} when `runId` was never a run of this workspace.
59
+ * @throws {HistoryExpiredError} when retention removed it or chained it into a later run.
60
+ */
61
+ changes(runId: string, options?: {
62
+ maxLines?: number;
63
+ }): Promise<string>;
64
+ /**
65
+ * Moves the workspace to the state right after `target`, or to how it was before any run with
66
+ * `historyStart`. A file that does not hold what the history expects is left as it is, and the
67
+ * move is then not `complete`. With `from`, it moves only if the workspace is still there: a
68
+ * host that showed the user what a move will do passes where the workspace was, so a run that
69
+ * ended meanwhile cannot make it do something else.
70
+ *
71
+ * @throws {WorkspaceBusyError} when a run or another move holds the workspace.
72
+ * @throws {WorkspaceMovedError} when the workspace is not at `from`.
73
+ * @throws {UnknownRunError} when `target` is not in the history.
74
+ * @throws {HistoryExpiredError} when retention chained `target` into a later run.
75
+ */
76
+ goTo(target: string, options?: MoveOptions): Promise<Move>;
77
+ /**
78
+ * Moves the workspace to the run before the one it is at.
79
+ *
80
+ * @throws {NothingToMoveError} at the start of the history.
81
+ * @throws {WorkspaceBusyError} when a run or another move holds the workspace.
82
+ * @throws {WorkspaceMovedError} when the workspace is not at `from`.
83
+ */
84
+ undo(options?: MoveOptions): Promise<Move>;
85
+ /**
86
+ * Moves the workspace to the next run: towards where it was most recently, or else the newest
87
+ * branch.
88
+ *
89
+ * @throws {NothingToMoveError} when no run follows the one it is at.
90
+ * @throws {WorkspaceBusyError} when a run or another move holds the workspace.
91
+ * @throws {WorkspaceMovedError} when the workspace is not at `from`.
92
+ */
93
+ redo(options?: MoveOptions): Promise<Move>;
94
+ }
95
+ /** `History` over a recovery store, with paths shown relative to the workspace's real root. */
96
+ export declare class StoreHistory implements History {
97
+ private readonly store;
98
+ private readonly root;
99
+ constructor({ store, root }: {
100
+ store: RecoveryStore;
101
+ root: string;
102
+ });
103
+ list(): Promise<{
104
+ head?: string;
105
+ runs: HistoryRun[];
106
+ }>;
107
+ changes(runId: string, options?: {
108
+ maxLines?: number;
109
+ }): Promise<string>;
110
+ goTo(target: string, options?: MoveOptions): Promise<Move>;
111
+ undo(options?: MoveOptions): Promise<Move>;
112
+ redo(options?: MoveOptions): Promise<Move>;
113
+ private moveOf;
114
+ }
@@ -0,0 +1,62 @@
1
+ import { changedFiles, displayPath, showChanges } from "./runChanges.js";
2
+ import { HistoryExpiredError } from "./runTree.js";
3
+ /** `History` over a recovery store, with paths shown relative to the workspace's real root. */
4
+ export class StoreHistory {
5
+ store;
6
+ root;
7
+ constructor({ store, root }) {
8
+ this.store = store;
9
+ this.root = root;
10
+ }
11
+ async list() {
12
+ const { head, runs } = await this.store.listRuns();
13
+ const listed = [];
14
+ for (const run of runs) {
15
+ const read = await this.store.readRun(run.runId).catch((error) => {
16
+ // Retention took it since it was listed: it is no longer in the history.
17
+ if (error instanceof HistoryExpiredError)
18
+ return undefined;
19
+ throw error;
20
+ });
21
+ if (!read)
22
+ continue;
23
+ const { record, entries } = read;
24
+ listed.push({
25
+ runId: record.runId,
26
+ ...(record.parentRunId ? { parentRunId: record.parentRunId } : {}),
27
+ ...(record.absorbed ? { absorbed: [...record.absorbed] } : {}),
28
+ status: record.status,
29
+ startedAt: record.startedAt,
30
+ ...(record.finishedAt ? { finishedAt: record.finishedAt } : {}),
31
+ files: changedFiles(record, entries).map(change => displayPath(record.root, change.path)),
32
+ });
33
+ }
34
+ return { ...(head ? { head } : {}), runs: listed };
35
+ }
36
+ changes(runId, options = {}) {
37
+ return showChanges(this.store, runId, options);
38
+ }
39
+ async goTo(target, options = {}) {
40
+ return this.moveOf(await this.store.goTo(target, options));
41
+ }
42
+ async undo(options = {}) {
43
+ return this.moveOf(await this.store.undo(options));
44
+ }
45
+ async redo(options = {}) {
46
+ return this.moveOf(await this.store.redo(options));
47
+ }
48
+ moveOf(revision) {
49
+ return {
50
+ ...(revision.from ? { from: revision.from } : {}),
51
+ ...(revision.to ? { to: revision.to } : {}),
52
+ at: revision.at,
53
+ ...(revision.reason ? { reason: revision.reason } : {}),
54
+ complete: revision.complete,
55
+ conflicts: revision.conflicts.map(({ path, runId }) => ({
56
+ path: displayPath(this.root, path),
57
+ runId,
58
+ })),
59
+ };
60
+ }
61
+ }
62
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../../src/recovery/domain/history.ts"],"names":[],"mappings":"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","sourcesContent":["import { changedFiles, displayPath, showChanges } from './runChanges.ts';\nimport type { MoveOptions, RecoveryStore, Revision, RunStatus } from './recoveryStore.ts';\nimport { HistoryExpiredError } from './runTree.ts';\n\n/** One run that wrote to the workspace. */\nexport interface HistoryRun {\n\trunId: string;\n\t/** The run the workspace was at when this one started; absent when it started at the start. */\n\tparentRunId?: string;\n\t/**\n\t * Runs whose changes retention chained into this one, oldest first: going back past this run\n\t * goes back past them too. An id an earlier `AgentResponse` gave may be found here.\n\t */\n\tabsorbed?: string[];\n\tstatus: RunStatus;\n\tstartedAt: string;\n\tfinishedAt?: string;\n\t/**\n\t * The files it changed, in the order it first touched each one: relative to the root, or the\n\t * real path of a file outside it. A host decides whether to show them.\n\t */\n\tfiles: string[];\n}\n\n/** One move of the workspace through its history. */\nexport interface Move {\n\t/** The run the workspace was at; absent for the start of the history. */\n\tfrom?: string;\n\t/** The run the workspace is at now; absent for the start of the history. */\n\tto?: string;\n\tat: string;\n\treason?: string;\n\t/** Every file reached the state the history expected. Otherwise `conflicts` names those left. */\n\tcomplete: boolean;\n\t/**\n\t * The files left as they were because they did not hold what the history expected, such as\n\t * one the user edited since, and the run whose change could not be undone or redone.\n\t */\n\tconflicts: { path: string; runId: string }[];\n}\n\n/**\n * The history of the runs that wrote to one workspace: what each run changed, and moving the\n * workspace back and forward through them. Runs form a tree: going back and writing again\n * starts a new branch, and the earlier one stays reachable with `goTo`.\n */\nexport interface History {\n\t/**\n\t * Every run kept, oldest first, and the run the workspace is at (`head`), absent at the start\n\t * of the history. A run whose process died while writing is `interrupted`.\n\t */\n\tlist(): Promise<{ head?: string; runs: HistoryRun[] }>;\n\t/**\n\t * What a run changed, as a git-style diff per file. What does not fit in `maxLines`, 1000 by\n\t * default, is named instead.\n\t *\n\t * @throws {UnknownRunError} when `runId` was never a run of this workspace.\n\t * @throws {HistoryExpiredError} when retention removed it or chained it into a later run.\n\t */\n\tchanges(runId: string, options?: { maxLines?: number }): Promise<string>;\n\t/**\n\t * Moves the workspace to the state right after `target`, or to how it was before any run with\n\t * `historyStart`. A file that does not hold what the history expects is left as it is, and the\n\t * move is then not `complete`. With `from`, it moves only if the workspace is still there: a\n\t * host that showed the user what a move will do passes where the workspace was, so a run that\n\t * ended meanwhile cannot make it do something else.\n\t *\n\t * @throws {WorkspaceBusyError} when a run or another move holds the workspace.\n\t * @throws {WorkspaceMovedError} when the workspace is not at `from`.\n\t * @throws {UnknownRunError} when `target` is not in the history.\n\t * @throws {HistoryExpiredError} when retention chained `target` into a later run.\n\t */\n\tgoTo(target: string, options?: MoveOptions): Promise<Move>;\n\t/**\n\t * Moves the workspace to the run before the one it is at.\n\t *\n\t * @throws {NothingToMoveError} at the start of the history.\n\t * @throws {WorkspaceBusyError} when a run or another move holds the workspace.\n\t * @throws {WorkspaceMovedError} when the workspace is not at `from`.\n\t */\n\tundo(options?: MoveOptions): Promise<Move>;\n\t/**\n\t * Moves the workspace to the next run: towards where it was most recently, or else the newest\n\t * branch.\n\t *\n\t * @throws {NothingToMoveError} when no run follows the one it is at.\n\t * @throws {WorkspaceBusyError} when a run or another move holds the workspace.\n\t * @throws {WorkspaceMovedError} when the workspace is not at `from`.\n\t */\n\tredo(options?: MoveOptions): Promise<Move>;\n}\n\n/** `History` over a recovery store, with paths shown relative to the workspace's real root. */\nexport class StoreHistory implements History {\n\tprivate readonly store: RecoveryStore;\n\tprivate readonly root: string;\n\n\tconstructor({ store, root }: { store: RecoveryStore; root: string }) {\n\t\tthis.store = store;\n\t\tthis.root = root;\n\t}\n\n\tasync list(): Promise<{ head?: string; runs: HistoryRun[] }> {\n\t\tconst { head, runs } = await this.store.listRuns();\n\t\tconst listed: HistoryRun[] = [];\n\t\tfor (const run of runs) {\n\t\t\tconst read = await this.store.readRun(run.runId).catch((error: unknown) => {\n\t\t\t\t// Retention took it since it was listed: it is no longer in the history.\n\t\t\t\tif (error instanceof HistoryExpiredError) return undefined;\n\t\t\t\tthrow error;\n\t\t\t});\n\t\t\tif (!read) continue;\n\t\t\tconst { record, entries } = read;\n\t\t\tlisted.push({\n\t\t\t\trunId: record.runId,\n\t\t\t\t...(record.parentRunId ? { parentRunId: record.parentRunId } : {}),\n\t\t\t\t...(record.absorbed ? { absorbed: [...record.absorbed] } : {}),\n\t\t\t\tstatus: record.status,\n\t\t\t\tstartedAt: record.startedAt,\n\t\t\t\t...(record.finishedAt ? { finishedAt: record.finishedAt } : {}),\n\t\t\t\tfiles: changedFiles(record, entries).map(change => displayPath(record.root, change.path)),\n\t\t\t});\n\t\t}\n\t\treturn { ...(head ? { head } : {}), runs: listed };\n\t}\n\n\tchanges(runId: string, options: { maxLines?: number } = {}): Promise<string> {\n\t\treturn showChanges(this.store, runId, options);\n\t}\n\n\tasync goTo(target: string, options: MoveOptions = {}): Promise<Move> {\n\t\treturn this.moveOf(await this.store.goTo(target, options));\n\t}\n\n\tasync undo(options: MoveOptions = {}): Promise<Move> {\n\t\treturn this.moveOf(await this.store.undo(options));\n\t}\n\n\tasync redo(options: MoveOptions = {}): Promise<Move> {\n\t\treturn this.moveOf(await this.store.redo(options));\n\t}\n\n\tprivate moveOf(revision: Revision): Move {\n\t\treturn {\n\t\t\t...(revision.from ? { from: revision.from } : {}),\n\t\t\t...(revision.to ? { to: revision.to } : {}),\n\t\t\tat: revision.at,\n\t\t\t...(revision.reason ? { reason: revision.reason } : {}),\n\t\t\tcomplete: revision.complete,\n\t\t\tconflicts: revision.conflicts.map(({ path, runId }) => ({\n\t\t\t\tpath: displayPath(this.root, path),\n\t\t\t\trunId,\n\t\t\t})),\n\t\t};\n\t}\n}\n"]}
@@ -0,0 +1,233 @@
1
+ /**
2
+ * What one path held at one moment: nothing, or a regular file with this content and mode.
3
+ * `hash` names the content in the store, so the bytes themselves never travel in a record.
4
+ */
5
+ export type FileState = {
6
+ exists: false;
7
+ } | {
8
+ exists: true;
9
+ hash: string;
10
+ mode: number;
11
+ };
12
+ /**
13
+ * One change to one file. It is recorded as `prepared` before the file is touched, and only
14
+ * then applied, so a run that stops halfway always leaves a record of what it may have done.
15
+ * `abandoned` means the change was prepared but never happened.
16
+ */
17
+ export interface JournalEntry {
18
+ sequence: number;
19
+ /** The file's real path, absolute: a run may write to more than one root of its workspace. */
20
+ path: string;
21
+ before: FileState;
22
+ after: FileState;
23
+ /** Folders the change creates for a new file, outermost first: undo removes them if empty. */
24
+ createdFolders?: string[];
25
+ /**
26
+ * The command whose run the change was seen around, for a change no tool of the harness made
27
+ * itself. It was found by comparing the file before and after, not journaled before it was made.
28
+ */
29
+ observedBy?: string;
30
+ status: 'prepared' | 'applied' | 'abandoned';
31
+ }
32
+ /**
33
+ * What a group of files held right before something outside the journal ran, such as a
34
+ * command: the content of each, by its real path, absolute. It is stored as content, and the
35
+ * journal names its hash before the command starts.
36
+ */
37
+ export interface Manifest {
38
+ files: Record<string, FileState>;
39
+ }
40
+ /** What one observation cost: before and after together. */
41
+ export interface ObservationMeasure {
42
+ /** Files captured before, and compared after. */
43
+ files: number;
44
+ bytesRead: number;
45
+ /** New content stored, which the store did not hold already. */
46
+ bytesStored: number;
47
+ /** Spent capturing and comparing, not running what was observed. */
48
+ durationMs: number;
49
+ }
50
+ /**
51
+ * `interrupted` is a run whose process died while it was writing. The next run to take the
52
+ * workspace settles each change it left `prepared`, from what the file holds now.
53
+ */
54
+ export type RunStatus = 'running' | 'completed' | 'failed' | 'cancelled' | 'interrupted';
55
+ export interface RunRecord {
56
+ runId: string;
57
+ /** The real path of the workspace root the run wrote to. */
58
+ root: string;
59
+ /**
60
+ * The run the workspace was at when this one started, so runs form a tree: going back to an
61
+ * earlier run and writing again starts a new branch. Absent for the first run.
62
+ */
63
+ parentRunId?: string;
64
+ /**
65
+ * Runs whose changes retention chained into this one, oldest first. They are no longer in
66
+ * the history: this run now goes back to where the oldest of them started.
67
+ */
68
+ absorbed?: string[];
69
+ /** The journal file that holds the run's changes. Absent while it is the first one written. */
70
+ journal?: string;
71
+ startedAt: string;
72
+ finishedAt?: string;
73
+ status: RunStatus;
74
+ /** The process that ran it, so a run whose process died can be told from a live one. */
75
+ pid: number;
76
+ }
77
+ /**
78
+ * The recovery record of one run that writes. Every method returns only once what it records
79
+ * is on disk: a change is never made before the record that would undo it exists. Once a step
80
+ * fails to reach the journal, the journal refuses every later step, because a line written
81
+ * after a broken one could not be read back.
82
+ */
83
+ export interface RunJournal {
84
+ readonly runId: string;
85
+ /** Stores `content` and returns the hash that names it. Content stored before is reused. */
86
+ saveContent(content: Buffer): Promise<string>;
87
+ /** Records a change about to be made, and returns its sequence number. */
88
+ prepare(change: Omit<JournalEntry, 'sequence' | 'status'>): Promise<number>;
89
+ /** The change was made. */
90
+ applied(sequence: number): Promise<void>;
91
+ /** The change was not made, and the file still holds its `before`. */
92
+ abandoned(sequence: number): Promise<void>;
93
+ /**
94
+ * Records that `by`, such as a command, is about to change files the journal cannot see it
95
+ * change, and that the stored `manifest` holds what they hold now. Each change it makes is
96
+ * then recorded as its own entry, prepared and applied at once, before `observed`. If the run
97
+ * stops first, the next run settles it: every file of the manifest that changed since is
98
+ * recorded as changed by `by`. A file it created is not in the manifest, and is then missed.
99
+ */
100
+ observe(manifest: string, by: string): Promise<number>;
101
+ /**
102
+ * Every change the observation found is recorded. `measure`, what capturing and comparing the
103
+ * files cost, is kept with it.
104
+ */
105
+ observed(sequence: number, measure?: ObservationMeasure): Promise<void>;
106
+ /** How many bytes of content this run stored that the store did not hold yet. */
107
+ readonly storedBytes: number;
108
+ /**
109
+ * Whether a change it recorded may have reached a file: one prepared and not abandoned. It
110
+ * holds whatever happens to the record afterwards.
111
+ */
112
+ readonly changed: boolean;
113
+ /**
114
+ * Ends the run and releases the workspace for the next writer. A run that `changed` nothing
115
+ * leaves the history, and the workspace is back at the run before it. If removing it fails,
116
+ * the store still treats it as gone, and the next run or move finishes removing it.
117
+ */
118
+ finish(status: Exclude<RunStatus, 'running' | 'interrupted'>): Promise<void>;
119
+ }
120
+ /** The point before any run: going there takes the workspace back to how it was. */
121
+ export declare const historyStart = "start";
122
+ /** A file a move left as it was, because it did not hold what the history expected there. */
123
+ export interface MoveConflict {
124
+ path: string;
125
+ /** The run whose change could not be undone or redone. */
126
+ runId: string;
127
+ }
128
+ /**
129
+ * One move of the workspace through its history, kept so an agent that runs next can tell
130
+ * that its conversation is about a state the workspace has left.
131
+ */
132
+ export interface Revision {
133
+ /** Counts the moves of this workspace, from 1. */
134
+ revision: number;
135
+ /** The run the workspace was at; absent for the start of the history. */
136
+ from?: string;
137
+ /** The run the workspace is at now; absent for the start of the history. */
138
+ to?: string;
139
+ at: string;
140
+ /** Why the user went there, when the host asked. */
141
+ reason?: string;
142
+ /** Every file reached the state the history expected. Otherwise `conflicts` names those that did not. */
143
+ complete: boolean;
144
+ conflicts: MoveConflict[];
145
+ }
146
+ export interface MoveOptions {
147
+ /** Why the user is moving the workspace, recorded with the move. */
148
+ reason?: string;
149
+ /**
150
+ * Where the workspace must be for the move to happen: a run, or `historyStart`. Checked under
151
+ * the workspace lock, so a move confirmed against one state never runs from another.
152
+ */
153
+ from?: string;
154
+ }
155
+ /** There is nothing to undo at the start of the history, or nothing to redo at its tip. */
156
+ export declare class NothingToMoveError extends Error {
157
+ constructor(direction: 'undo' | 'redo');
158
+ }
159
+ /**
160
+ * Where the runs that write to one workspace keep what is needed to undo them, outside the
161
+ * workspace itself. Only one run writes to a workspace at a time.
162
+ */
163
+ export interface RecoveryStore {
164
+ /**
165
+ * Starts recording a run and holds the workspace for it until `finish`. The new run's parent
166
+ * is the run the workspace is at, and the workspace is at the new run from then on. Runs a
167
+ * dead process left `running` are settled first.
168
+ *
169
+ * @throws {WorkspaceBusyError} when another live run is writing to the workspace.
170
+ */
171
+ startRun(): Promise<RunJournal>;
172
+ /**
173
+ * Every run, oldest first, and the run the workspace is at (`head`), absent before the first.
174
+ * A run still recorded as `running` whose process is gone is listed as `interrupted`.
175
+ */
176
+ listRuns(): Promise<{
177
+ head?: string;
178
+ runs: RunRecord[];
179
+ }>;
180
+ /**
181
+ * Moves the workspace to the state right after `target`, or to how it was before any run
182
+ * with `historyStart`. It undoes runs one by one back to the closest common ancestor, then
183
+ * redoes runs one by one forward to `target`. Each file is written only if it holds the
184
+ * state the history expects there; one that does not is left as it is and reported, and the
185
+ * move is then not `complete`. A move the process did not finish is finished by the next
186
+ * move or run.
187
+ *
188
+ * @throws {WorkspaceBusyError} when a run or another move holds the workspace.
189
+ * @throws {WorkspaceMovedError} when `from` was given and the workspace is elsewhere.
190
+ * @throws {UnknownRunError} when `target` is not in the history.
191
+ * @throws {HistoryExpiredError} when retention chained `target` into a later run.
192
+ */
193
+ goTo(target: string, options?: MoveOptions): Promise<Revision>;
194
+ /**
195
+ * Moves the workspace to the run before the one it is at.
196
+ *
197
+ * @throws {NothingToMoveError} at the start of the history.
198
+ */
199
+ undo(options?: MoveOptions): Promise<Revision>;
200
+ /**
201
+ * Moves the workspace to the next run: towards where it was most recently, or the newest
202
+ * branch.
203
+ *
204
+ * @throws {NothingToMoveError} when no run follows the one it is at.
205
+ */
206
+ redo(options?: MoveOptions): Promise<Revision>;
207
+ /** Every move of the workspace, oldest first. */
208
+ listRevisions(): Promise<Revision[]>;
209
+ /**
210
+ * A run's record and its changes, in the order they were prepared.
211
+ *
212
+ * @throws {UnknownRunError} when `runId` was never a run of this workspace.
213
+ * @throws {HistoryExpiredError} when retention removed `runId` or chained it into a later run.
214
+ */
215
+ readRun(runId: string): Promise<{
216
+ record: RunRecord;
217
+ entries: JournalEntry[];
218
+ }>;
219
+ /** The bytes a hash names. */
220
+ readContent(hash: string): Promise<Buffer>;
221
+ }
222
+ /** A move was asked from one point of the history, and the workspace is at another. */
223
+ export declare class WorkspaceMovedError extends Error {
224
+ /** Where the move expected the workspace: a run, or `historyStart`. */
225
+ readonly expected: string;
226
+ /** Where the workspace is: a run, or `historyStart`. */
227
+ readonly actual: string;
228
+ constructor(expected: string, actual: string);
229
+ }
230
+ export declare class WorkspaceBusyError extends Error {
231
+ readonly runId: string;
232
+ constructor(runId: string);
233
+ }