harness-engineering-agent 0.1.7 → 0.1.9

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 (276) hide show
  1. package/README.md +161 -62
  2. package/dist/agent/harness-agent-capabilities.d.ts +4 -0
  3. package/dist/agent/harness-agent-capabilities.d.ts.map +1 -0
  4. package/dist/agent/harness-agent-capabilities.js +4 -0
  5. package/dist/agent/harness-agent-capabilities.js.map +1 -0
  6. package/dist/agent/harness-agent-capability-builders.d.ts +11 -0
  7. package/dist/agent/harness-agent-capability-builders.d.ts.map +1 -0
  8. package/dist/agent/harness-agent-capability-builders.js +57 -0
  9. package/dist/agent/harness-agent-capability-builders.js.map +1 -0
  10. package/dist/agent/harness-agent-capability-data.d.ts +3 -0
  11. package/dist/agent/harness-agent-capability-data.d.ts.map +1 -0
  12. package/dist/agent/harness-agent-capability-data.js +23 -0
  13. package/dist/agent/harness-agent-capability-data.js.map +1 -0
  14. package/dist/agent/harness-agent-capability-types.d.ts +50 -0
  15. package/dist/agent/harness-agent-capability-types.d.ts.map +1 -0
  16. package/dist/agent/harness-agent-capability-types.js +3 -0
  17. package/dist/agent/harness-agent-capability-types.js.map +1 -0
  18. package/dist/agent/harness-role-capability-directory-data.d.ts +3 -0
  19. package/dist/agent/harness-role-capability-directory-data.d.ts.map +1 -0
  20. package/dist/agent/harness-role-capability-directory-data.js +208 -0
  21. package/dist/agent/harness-role-capability-directory-data.js.map +1 -0
  22. package/dist/agent/harness-role-capability-directory.d.ts +43 -0
  23. package/dist/agent/harness-role-capability-directory.d.ts.map +1 -0
  24. package/dist/agent/harness-role-capability-directory.js +57 -0
  25. package/dist/agent/harness-role-capability-directory.js.map +1 -0
  26. package/dist/agent/index.d.ts +7 -0
  27. package/dist/agent/index.d.ts.map +1 -0
  28. package/dist/agent/index.js +6 -0
  29. package/dist/agent/index.js.map +1 -0
  30. package/dist/config/config-defaults.d.ts +22 -0
  31. package/dist/config/config-defaults.d.ts.map +1 -1
  32. package/dist/config/config-defaults.js +22 -0
  33. package/dist/config/config-defaults.js.map +1 -1
  34. package/dist/config/config-merge.d.ts.map +1 -1
  35. package/dist/config/config-merge.js +30 -0
  36. package/dist/config/config-merge.js.map +1 -1
  37. package/dist/config/config-schema.d.ts +59 -0
  38. package/dist/config/config-schema.d.ts.map +1 -1
  39. package/dist/config/config-schema.js +18 -1
  40. package/dist/config/config-schema.js.map +1 -1
  41. package/dist/config/config-types.d.ts +5 -1
  42. package/dist/config/config-types.d.ts.map +1 -1
  43. package/dist/config/harness-config.d.ts +2 -1
  44. package/dist/config/harness-config.d.ts.map +1 -1
  45. package/dist/config/harness-config.js +1 -0
  46. package/dist/config/harness-config.js.map +1 -1
  47. package/dist/config/index.d.ts +1 -1
  48. package/dist/config/index.d.ts.map +1 -1
  49. package/dist/config/index.js.map +1 -1
  50. package/dist/config/opencode-compatibility.d.ts +37 -0
  51. package/dist/config/opencode-compatibility.d.ts.map +1 -0
  52. package/dist/config/opencode-compatibility.js +99 -0
  53. package/dist/config/opencode-compatibility.js.map +1 -0
  54. package/dist/controller/agent-output.d.ts +1 -1
  55. package/dist/controller/agent-output.d.ts.map +1 -1
  56. package/dist/controller/agent-output.js +18 -1
  57. package/dist/controller/agent-output.js.map +1 -1
  58. package/dist/controller/agent-session-types.d.ts +56 -0
  59. package/dist/controller/agent-session-types.d.ts.map +1 -0
  60. package/dist/controller/agent-session-types.js +2 -0
  61. package/dist/controller/agent-session-types.js.map +1 -0
  62. package/dist/controller/agent-session-utils.d.ts +52 -0
  63. package/dist/controller/agent-session-utils.d.ts.map +1 -0
  64. package/dist/controller/agent-session-utils.js +118 -0
  65. package/dist/controller/agent-session-utils.js.map +1 -0
  66. package/dist/controller/agent-session.d.ts +2 -40
  67. package/dist/controller/agent-session.d.ts.map +1 -1
  68. package/dist/controller/agent-session.js +126 -128
  69. package/dist/controller/agent-session.js.map +1 -1
  70. package/dist/controller/contracts.d.ts +4 -2
  71. package/dist/controller/contracts.d.ts.map +1 -1
  72. package/dist/controller/contracts.js.map +1 -1
  73. package/dist/controller/errors.d.ts +5 -0
  74. package/dist/controller/errors.d.ts.map +1 -1
  75. package/dist/controller/errors.js +8 -0
  76. package/dist/controller/errors.js.map +1 -1
  77. package/dist/controller/evidence-factory.js +7 -1
  78. package/dist/controller/evidence-factory.js.map +1 -1
  79. package/dist/controller/failure-analysis.d.ts +12 -0
  80. package/dist/controller/failure-analysis.d.ts.map +1 -1
  81. package/dist/controller/failure-analysis.js +41 -0
  82. package/dist/controller/failure-analysis.js.map +1 -1
  83. package/dist/controller/failure-analyzer-schemas.d.ts +87 -0
  84. package/dist/controller/failure-analyzer-schemas.d.ts.map +1 -0
  85. package/dist/controller/failure-analyzer-schemas.js +117 -0
  86. package/dist/controller/failure-analyzer-schemas.js.map +1 -0
  87. package/dist/controller/graph-validator.js +18 -4
  88. package/dist/controller/graph-validator.js.map +1 -1
  89. package/dist/controller/index.d.ts +7 -2
  90. package/dist/controller/index.d.ts.map +1 -1
  91. package/dist/controller/index.js +4 -1
  92. package/dist/controller/index.js.map +1 -1
  93. package/dist/controller/repair-loop.d.ts +1 -1
  94. package/dist/controller/repair-loop.d.ts.map +1 -1
  95. package/dist/controller/repair-loop.js +28 -0
  96. package/dist/controller/repair-loop.js.map +1 -1
  97. package/dist/controller/report-factory.d.ts.map +1 -1
  98. package/dist/controller/report-factory.js +49 -21
  99. package/dist/controller/report-factory.js.map +1 -1
  100. package/dist/controller/role-prompts.d.ts +2 -1
  101. package/dist/controller/role-prompts.d.ts.map +1 -1
  102. package/dist/controller/role-prompts.js +3 -9
  103. package/dist/controller/role-prompts.js.map +1 -1
  104. package/dist/controller/run-acceptance.d.ts +17 -0
  105. package/dist/controller/run-acceptance.d.ts.map +1 -0
  106. package/dist/controller/run-acceptance.js +179 -0
  107. package/dist/controller/run-acceptance.js.map +1 -0
  108. package/dist/controller/run-controller.d.ts.map +1 -1
  109. package/dist/controller/run-controller.js +379 -96
  110. package/dist/controller/run-controller.js.map +1 -1
  111. package/dist/controller/run-state.d.ts +1 -0
  112. package/dist/controller/run-state.d.ts.map +1 -1
  113. package/dist/controller/run-state.js +12 -0
  114. package/dist/controller/run-state.js.map +1 -1
  115. package/dist/controller/session-tool-authorization.d.ts +14 -0
  116. package/dist/controller/session-tool-authorization.d.ts.map +1 -0
  117. package/dist/controller/session-tool-authorization.js +88 -0
  118. package/dist/controller/session-tool-authorization.js.map +1 -0
  119. package/dist/controller/task-agent-output.d.ts +13 -2
  120. package/dist/controller/task-agent-output.d.ts.map +1 -1
  121. package/dist/controller/task-agent-output.js +58 -1
  122. package/dist/controller/task-agent-output.js.map +1 -1
  123. package/dist/controller/task-agent-schemas.d.ts +745 -26
  124. package/dist/controller/task-agent-schemas.d.ts.map +1 -1
  125. package/dist/controller/task-agent-schemas.js +70 -21
  126. package/dist/controller/task-agent-schemas.js.map +1 -1
  127. package/dist/controller/task-executor.d.ts +2 -1
  128. package/dist/controller/task-executor.d.ts.map +1 -1
  129. package/dist/controller/task-executor.js +304 -105
  130. package/dist/controller/task-executor.js.map +1 -1
  131. package/dist/doctor/cli.d.ts +3 -0
  132. package/dist/doctor/cli.d.ts.map +1 -0
  133. package/dist/doctor/cli.js +10 -0
  134. package/dist/doctor/cli.js.map +1 -0
  135. package/dist/doctor/index.d.ts +7 -0
  136. package/dist/doctor/index.d.ts.map +1 -0
  137. package/dist/doctor/index.js +16 -0
  138. package/dist/doctor/index.js.map +1 -0
  139. package/dist/doctor/local-mcp-probe.d.ts +3 -0
  140. package/dist/doctor/local-mcp-probe.d.ts.map +1 -0
  141. package/dist/doctor/local-mcp-probe.js +151 -0
  142. package/dist/doctor/local-mcp-probe.js.map +1 -0
  143. package/dist/doctor/opencode-config.d.ts +43 -0
  144. package/dist/doctor/opencode-config.d.ts.map +1 -0
  145. package/dist/doctor/opencode-config.js +103 -0
  146. package/dist/doctor/opencode-config.js.map +1 -0
  147. package/dist/doctor/run-harness-doctor.d.ts +23 -0
  148. package/dist/doctor/run-harness-doctor.d.ts.map +1 -0
  149. package/dist/doctor/run-harness-doctor.js +176 -0
  150. package/dist/doctor/run-harness-doctor.js.map +1 -0
  151. package/dist/doctor/types.d.ts +69 -0
  152. package/dist/doctor/types.d.ts.map +1 -0
  153. package/dist/doctor/types.js +8 -0
  154. package/dist/doctor/types.js.map +1 -0
  155. package/dist/domain/acceptance-criteria.d.ts +9 -0
  156. package/dist/domain/acceptance-criteria.d.ts.map +1 -0
  157. package/dist/domain/acceptance-criteria.js +38 -0
  158. package/dist/domain/acceptance-criteria.js.map +1 -0
  159. package/dist/domain/context-package.d.ts +43 -0
  160. package/dist/domain/context-package.d.ts.map +1 -0
  161. package/dist/domain/context-package.js +54 -0
  162. package/dist/domain/context-package.js.map +1 -0
  163. package/dist/domain/evidence.d.ts +1 -0
  164. package/dist/domain/evidence.d.ts.map +1 -1
  165. package/dist/domain/evidence.js +1 -2
  166. package/dist/domain/evidence.js.map +1 -1
  167. package/dist/domain/index.d.ts +12 -4
  168. package/dist/domain/index.d.ts.map +1 -1
  169. package/dist/domain/index.js +6 -2
  170. package/dist/domain/index.js.map +1 -1
  171. package/dist/domain/plan-artifacts.d.ts +170 -0
  172. package/dist/domain/plan-artifacts.d.ts.map +1 -0
  173. package/dist/domain/plan-artifacts.js +187 -0
  174. package/dist/domain/plan-artifacts.js.map +1 -0
  175. package/dist/domain/report.d.ts +608 -11
  176. package/dist/domain/report.d.ts.map +1 -1
  177. package/dist/domain/report.js +6 -4
  178. package/dist/domain/report.js.map +1 -1
  179. package/dist/domain/requirement.d.ts +22 -2
  180. package/dist/domain/requirement.d.ts.map +1 -1
  181. package/dist/domain/requirement.js +26 -3
  182. package/dist/domain/requirement.js.map +1 -1
  183. package/dist/domain/run-acceptance.d.ts +33 -0
  184. package/dist/domain/run-acceptance.d.ts.map +1 -0
  185. package/dist/domain/run-acceptance.js +29 -0
  186. package/dist/domain/run-acceptance.js.map +1 -0
  187. package/dist/domain/run.d.ts +596 -7
  188. package/dist/domain/run.d.ts.map +1 -1
  189. package/dist/domain/run.js +58 -0
  190. package/dist/domain/run.js.map +1 -1
  191. package/dist/domain/shared.d.ts +40 -2
  192. package/dist/domain/shared.d.ts.map +1 -1
  193. package/dist/domain/shared.js +26 -0
  194. package/dist/domain/shared.js.map +1 -1
  195. package/dist/domain/task-review.d.ts +57 -0
  196. package/dist/domain/task-review.d.ts.map +1 -0
  197. package/dist/domain/task-review.js +63 -0
  198. package/dist/domain/task-review.js.map +1 -0
  199. package/dist/domain/task.d.ts +24 -2
  200. package/dist/domain/task.d.ts.map +1 -1
  201. package/dist/domain/task.js +20 -12
  202. package/dist/domain/task.js.map +1 -1
  203. package/dist/index.d.ts +5 -0
  204. package/dist/index.d.ts.map +1 -1
  205. package/dist/index.js +5 -0
  206. package/dist/index.js.map +1 -1
  207. package/dist/plugin/create-harness-plugin.d.ts +4 -28
  208. package/dist/plugin/create-harness-plugin.d.ts.map +1 -1
  209. package/dist/plugin/create-harness-plugin.js +33 -118
  210. package/dist/plugin/create-harness-plugin.js.map +1 -1
  211. package/dist/plugin/harness-agent-config.d.ts +7 -0
  212. package/dist/plugin/harness-agent-config.d.ts.map +1 -1
  213. package/dist/plugin/harness-agent-config.js +72 -118
  214. package/dist/plugin/harness-agent-config.js.map +1 -1
  215. package/dist/plugin/harness-chat-hook.d.ts +1 -1
  216. package/dist/plugin/harness-chat-hook.d.ts.map +1 -1
  217. package/dist/plugin/harness-chat-hook.js +15 -23
  218. package/dist/plugin/harness-chat-hook.js.map +1 -1
  219. package/dist/plugin/harness-chat-protocol.d.ts +2 -2
  220. package/dist/plugin/harness-chat-protocol.d.ts.map +1 -1
  221. package/dist/plugin/harness-chat-protocol.js +13 -2
  222. package/dist/plugin/harness-chat-protocol.js.map +1 -1
  223. package/dist/plugin/harness-coordinator.d.ts +4 -0
  224. package/dist/plugin/harness-coordinator.d.ts.map +1 -1
  225. package/dist/plugin/harness-coordinator.js +7 -1
  226. package/dist/plugin/harness-coordinator.js.map +1 -1
  227. package/dist/plugin/harness-plugin-identifiers.d.ts +5 -0
  228. package/dist/plugin/harness-plugin-identifiers.d.ts.map +1 -0
  229. package/dist/plugin/harness-plugin-identifiers.js +24 -0
  230. package/dist/plugin/harness-plugin-identifiers.js.map +1 -0
  231. package/dist/plugin/harness-plugin-runtime.d.ts +64 -0
  232. package/dist/plugin/harness-plugin-runtime.d.ts.map +1 -0
  233. package/dist/plugin/harness-plugin-runtime.js +125 -0
  234. package/dist/plugin/harness-plugin-runtime.js.map +1 -0
  235. package/dist/plugin/harness-plugin-scheduler.d.ts +16 -0
  236. package/dist/plugin/harness-plugin-scheduler.d.ts.map +1 -0
  237. package/dist/plugin/harness-plugin-scheduler.js +51 -0
  238. package/dist/plugin/harness-plugin-scheduler.js.map +1 -0
  239. package/dist/plugin/harness-request-router.d.ts.map +1 -1
  240. package/dist/plugin/harness-request-router.js +14 -23
  241. package/dist/plugin/harness-request-router.js.map +1 -1
  242. package/dist/plugin/harness-requirement.d.ts +1 -1
  243. package/dist/plugin/harness-requirement.d.ts.map +1 -1
  244. package/dist/plugin/harness-requirement.js +2 -3
  245. package/dist/plugin/harness-requirement.js.map +1 -1
  246. package/dist/session/contracts.d.ts +9 -0
  247. package/dist/session/contracts.d.ts.map +1 -1
  248. package/dist/session/contracts.js.map +1 -1
  249. package/dist/session/fake-session-adapter.d.ts.map +1 -1
  250. package/dist/session/fake-session-adapter.js +1 -0
  251. package/dist/session/fake-session-adapter.js.map +1 -1
  252. package/dist/session/index.d.ts +1 -1
  253. package/dist/session/index.d.ts.map +1 -1
  254. package/dist/session/index.js.map +1 -1
  255. package/dist/session/opencode-session-adapter.d.ts +2 -1
  256. package/dist/session/opencode-session-adapter.d.ts.map +1 -1
  257. package/dist/session/opencode-session-adapter.js +7 -2
  258. package/dist/session/opencode-session-adapter.js.map +1 -1
  259. package/dist/session/opencode-session-prompts.d.ts +2 -10
  260. package/dist/session/opencode-session-prompts.d.ts.map +1 -1
  261. package/dist/session/opencode-session-prompts.js +7 -9
  262. package/dist/session/opencode-session-prompts.js.map +1 -1
  263. package/dist/store/run-event.d.ts +596 -7
  264. package/dist/store/run-event.d.ts.map +1 -1
  265. package/dist/tools/harness-research-tools.d.ts +22 -0
  266. package/dist/tools/harness-research-tools.d.ts.map +1 -1
  267. package/dist/tools/harness-research-tools.js +173 -64
  268. package/dist/tools/harness-research-tools.js.map +1 -1
  269. package/dist/tools/harness-tool-capabilities.d.ts +342 -0
  270. package/dist/tools/harness-tool-capabilities.d.ts.map +1 -0
  271. package/dist/tools/harness-tool-capabilities.js +170 -0
  272. package/dist/tools/harness-tool-capabilities.js.map +1 -0
  273. package/dist/verification/command-policy.d.ts.map +1 -1
  274. package/dist/verification/command-policy.js +2 -2
  275. package/dist/verification/command-policy.js.map +1 -1
  276. package/package.json +6 -1
@@ -1,15 +1,36 @@
1
- import { RequirementSchema, RunSchema, TaskGraphSchema, } from "../domain/index.js";
1
+ import { z } from "zod";
2
+ import { PlanReviewSchema, RequirementSchema, RunSchema, SchemaVersionSchema, TaskGraphSchema, } from "../domain/index.js";
2
3
  import { HARNESS_SESSION_ROLE } from "../session/index.js";
3
4
  import { createGitWorkspaceAdapter } from "../workspace/index.js";
4
5
  import { parseAgentJson } from "./agent-output.js";
5
6
  import { createAgentSessionRuntime } from "./agent-session.js";
6
- import { AgentOutputParseError, AgentRunFailedError, GraphValidationError, RunCancellationError, } from "./errors.js";
7
+ import { AgentOutputParseError, AgentRunFailedError, GraphValidationError, RunCancellationError, RunPausedError, } from "./errors.js";
7
8
  import { validateTaskGraph } from "./graph-validator.js";
8
9
  import { createReport } from "./report-factory.js";
9
10
  import { plannerTaskPrompt } from "./role-prompts.js";
10
11
  import { createRunPersistence } from "./run-persistence.js";
11
- import { appendAnswer, appendWaiver, canAnswer, canCancel, canPause, canWaive, consumeBudget, createBlockedQuestionId, createInitialRunBudgets, createInitialRunGovernance, hasAnswer, hasWaiver, isTerminalRunStatus, parsePlannedRun, RunBudgetExceededError, requireTransition, requireWaivableRequirement, setPaused, withGovernance, withStatus, } from "./run-state.js";
12
+ import { appendAnswer, appendWaiver, canAdvanceRun, canAnswer, canCancel, canPause, canWaive, consumeBudget, createBlockedQuestionId, createInitialRunBudgets, createInitialRunGovernance, hasAnswer, hasWaiver, isTerminalRunStatus, parsePlannedRun, RunBudgetExceededError, requireTransition, requireWaivableRequirement, setPaused, withGovernance, withStatus, } from "./run-state.js";
13
+ import { PlannerContextOutputSchema } from "./task-agent-schemas.js";
12
14
  import { createTaskExecutor } from "./task-executor.js";
15
+ const PlanReviewVerifierOutputSchema = z
16
+ .object({
17
+ schemaVersion: SchemaVersionSchema,
18
+ role: z.literal("verifier"),
19
+ status: z.enum(["approved", "rejected"]),
20
+ summary: z.string().trim().min(1),
21
+ evidenceIds: z.array(z.string().trim().min(1)).readonly(),
22
+ planReview: PlanReviewSchema,
23
+ })
24
+ .strict()
25
+ .readonly();
26
+ class PlanningBlockedError extends Error {
27
+ run;
28
+ name = "PlanningBlockedError";
29
+ constructor(run) {
30
+ super(`planning blocked: ${run.status}`);
31
+ this.run = run;
32
+ }
33
+ }
13
34
  export const createRunController = (options) => {
14
35
  const workspacePolicy = options.workspacePolicy ?? { allowDirty: false };
15
36
  const workspaceAdapter = options.workspaceAdapter ?? createGitWorkspaceAdapter(options.workingDirectory);
@@ -20,11 +41,188 @@ export const createRunController = (options) => {
20
41
  store: options.store,
21
42
  workingDirectory: options.workingDirectory,
22
43
  });
44
+ const loadRunWithRetry = async (runId) => {
45
+ for (let attempt = 0; attempt < 3; attempt += 1) {
46
+ try {
47
+ return await options.store.loadState(runId);
48
+ }
49
+ catch (error) {
50
+ if (!isMissingRunError(error, runId) || attempt === 2) {
51
+ throw error;
52
+ }
53
+ await waitForNextTurn();
54
+ }
55
+ }
56
+ throw new TypeError(`missing run ${runId}`);
57
+ };
23
58
  const { runTasks } = createTaskExecutor({
24
59
  options,
25
60
  runtime,
26
61
  writer,
62
+ loadRun: loadRunWithRetry,
27
63
  });
64
+ const advancingRuns = new Map();
65
+ const persistPlanBlockedState = async (input) => {
66
+ const nextRun = withStatus(withGovernance(input.run, {
67
+ ...input.run.governance,
68
+ blockingQuestionId: input.status === "clarification_required" ? createBlockedQuestionId(input.run) : null,
69
+ }), input.status);
70
+ await writer.persist(nextRun, writer.createEvent(input.run.runId, input.eventType, {
71
+ reason: input.reason,
72
+ ...(input.status === "clarification_required"
73
+ ? { blockingQuestionId: nextRun.governance.blockingQuestionId }
74
+ : {}),
75
+ ...input.payload,
76
+ }));
77
+ return nextRun;
78
+ };
79
+ const requestContextPackage = async (run, requirement) => {
80
+ const outputText = await runtime.sendAgent({
81
+ runId: run.runId,
82
+ role: HARNESS_SESSION_ROLE.planner,
83
+ title: `${requirement.title} context`,
84
+ brief: JSON.stringify({ requirement, runId: run.runId }),
85
+ constraints: [
86
+ "先输出可审计 ContextPackage。",
87
+ "上下文不足时只能返回 evidence_gap 或 clarification_required。",
88
+ "不要生成 TaskGraph,不要推进状态。",
89
+ ],
90
+ evidence: [],
91
+ outputContract: {
92
+ schemaVersion: "1.0.0",
93
+ type: "PlannerContextOutput",
94
+ requiredFields: ["schemaVersion", "role", "status"],
95
+ },
96
+ systemPrompt: "你是 Fuxi。先输出 PlannerContextOutput JSON。",
97
+ messageLabel: `planner-context-${run.runId}`,
98
+ });
99
+ const output = parseAgentJson(outputText, PlannerContextOutputSchema, "planner");
100
+ switch (output.status) {
101
+ case "completed":
102
+ return { kind: "completed", contextPackage: output.contextPackage };
103
+ case "clarification_required":
104
+ return {
105
+ kind: "blocked",
106
+ run: await persistPlanBlockedState({
107
+ run,
108
+ status: "clarification_required",
109
+ reason: output.summary,
110
+ eventType: "run.planning.context_gap",
111
+ payload: { missingContext: output.missingContext },
112
+ }),
113
+ };
114
+ case "evidence_gap":
115
+ return {
116
+ kind: "blocked",
117
+ run: await persistPlanBlockedState({
118
+ run,
119
+ status: "evidence_gap",
120
+ reason: output.summary,
121
+ eventType: "run.planning.context_gap",
122
+ payload: { missingContext: output.missingContext },
123
+ }),
124
+ };
125
+ default:
126
+ return assertNever(output);
127
+ }
128
+ };
129
+ const requestTaskGraph = async (input) => {
130
+ const output = await runtime.sendAgent({
131
+ runId: input.run.runId,
132
+ role: HARNESS_SESSION_ROLE.planner,
133
+ title: input.requirement.title,
134
+ brief: plannerTaskPrompt({
135
+ requirement: input.requirement,
136
+ runId: input.run.runId,
137
+ contextPackage: input.contextPackage,
138
+ }),
139
+ constraints: [
140
+ "只允许根据 requirement 和 contextPackage 生成任务图。",
141
+ "不得执行任务或请求父会话完整历史。",
142
+ "只输出符合 TaskGraph 契约的 JSON。",
143
+ ],
144
+ evidence: [
145
+ {
146
+ refId: input.contextPackage.contextPackageId,
147
+ kind: "artifact",
148
+ summary: input.contextPackage.summary,
149
+ },
150
+ ],
151
+ outputContract: {
152
+ schemaVersion: "1.0.0",
153
+ type: "TaskGraph",
154
+ requiredFields: ["schemaVersion", "graphId", "runId", "version", "tasks", "edges"],
155
+ },
156
+ systemPrompt: "你是 Fuxi。使用已持久化的 ContextPackage 生成 TaskGraph JSON。",
157
+ messageLabel: `planner-graph-${input.run.runId}`,
158
+ });
159
+ return parsePlannerGraph(output);
160
+ };
161
+ const persistPlanningArtifacts = async (input) => {
162
+ const nextRun = RunSchema.parse({
163
+ ...input.run,
164
+ ...(input.contextPackage === undefined ? {} : { contextPackage: input.contextPackage }),
165
+ ...(input.planReview === undefined ? {} : { planReview: input.planReview }),
166
+ ...(input.taskGraph === undefined
167
+ ? {}
168
+ : { taskGraph: input.taskGraph, status: "plan_ready" }),
169
+ });
170
+ await writer.persist(nextRun, writer.createEvent(input.run.runId, input.eventType, input.payload));
171
+ return nextRun;
172
+ };
173
+ const requestPlanReview = async (input) => {
174
+ const outputText = await runtime.sendAgent({
175
+ runId: input.run.runId,
176
+ role: HARNESS_SESSION_ROLE.verifier,
177
+ title: `Review ${input.requirement.title}`,
178
+ brief: JSON.stringify({
179
+ requirement: input.requirement,
180
+ contextPackage: input.contextPackage,
181
+ taskGraph: input.taskGraph,
182
+ }),
183
+ constraints: [
184
+ "独立审查每个 criterion/path/dependency/verification/category。",
185
+ "只输出包含完整 PlanReview 的 VerifierOutput JSON。",
186
+ "计划未覆盖 criterionId、未知类别、非法路径或不可执行验证时必须 rejected。",
187
+ ],
188
+ evidence: [
189
+ {
190
+ refId: input.contextPackage.contextPackageId,
191
+ kind: "artifact",
192
+ summary: input.contextPackage.summary,
193
+ },
194
+ {
195
+ refId: input.taskGraph.graphId,
196
+ kind: "artifact",
197
+ summary: `Task graph ${input.taskGraph.graphId}`,
198
+ },
199
+ ],
200
+ outputContract: {
201
+ schemaVersion: "1.0.0",
202
+ type: "VerifierOutput",
203
+ requiredFields: ["schemaVersion", "role", "status", "summary", "evidenceIds", "planReview"],
204
+ },
205
+ systemPrompt: "你是 Zhulong。只输出 PlanReview 版 VerifierOutput JSON。",
206
+ messageLabel: `plan-review-${input.run.runId}`,
207
+ });
208
+ const output = PlanReviewVerifierOutputSchema.parse(JSON.parse(outputText));
209
+ if (output.status !== "approved") {
210
+ throw new GraphValidationError(output.planReview.findings.map((finding) => finding.message).length === 0
211
+ ? [output.summary]
212
+ : output.planReview.findings.map((finding) => finding.message));
213
+ }
214
+ return output.planReview;
215
+ };
216
+ const ensureExecutionCategoriesConfigured = (graph) => {
217
+ const missingCategories = graph.tasks
218
+ .map((task) => task.executionCategory)
219
+ .filter((category) => options.executionCategories?.[category] === undefined);
220
+ if (missingCategories.length > 0) {
221
+ throw new GraphValidationError([
222
+ `Unsupported executionCategory mappings: ${[...new Set(missingCategories)].join(", ")}`,
223
+ ]);
224
+ }
225
+ };
28
226
  const requestPlan = async (run, requirement) => {
29
227
  const maxPlannerAttempts = run.budgets.planner.limit;
30
228
  let planningRun = run;
@@ -33,28 +231,54 @@ export const createRunController = (options) => {
33
231
  try {
34
232
  planningRun = consumeBudget(planningRun, "planner");
35
233
  planningRun = consumeBudget(planningRun, "overall");
36
- const output = await runtime.sendAgent({
37
- runId: planningRun.runId,
38
- role: HARNESS_SESSION_ROLE.planner,
39
- title: requirement.title,
40
- brief: plannerTaskPrompt({ requirement, runId: planningRun.runId }),
41
- constraints: [
42
- "只允许根据 requirement 生成任务图。",
43
- "不得执行任务或请求父会话完整历史。",
44
- "只输出符合 TaskGraph 契约的 JSON。",
45
- ],
46
- evidence: [],
47
- outputContract: {
48
- schemaVersion: "1.0.0",
49
- type: "TaskGraph",
50
- requiredFields: ["schemaVersion", "graphId", "runId", "version", "tasks", "edges"],
234
+ const contextResult = await requestContextPackage(planningRun, requirement);
235
+ if (contextResult.kind === "blocked") {
236
+ throw new PlanningBlockedError(contextResult.run);
237
+ }
238
+ planningRun = await persistPlanningArtifacts({
239
+ run: planningRun,
240
+ contextPackage: contextResult.contextPackage,
241
+ eventType: "run.planning.context_captured",
242
+ payload: {
243
+ contextPackageId: contextResult.contextPackage.contextPackageId,
244
+ requirementId: contextResult.contextPackage.requirementId,
51
245
  },
52
- messageLabel: `planner-${planningRun.runId}-${attempt}`,
53
246
  });
54
- return parsePlannerGraph(output);
247
+ const taskGraph = await requestTaskGraph({
248
+ run: planningRun,
249
+ requirement,
250
+ contextPackage: contextResult.contextPackage,
251
+ });
252
+ validateTaskGraph(taskGraph, requirement, run.runId);
253
+ ensureExecutionCategoriesConfigured(taskGraph);
254
+ const planReview = await requestPlanReview({
255
+ run: planningRun,
256
+ requirement,
257
+ contextPackage: contextResult.contextPackage,
258
+ taskGraph,
259
+ });
260
+ planningRun = await persistPlanningArtifacts({
261
+ run: planningRun,
262
+ contextPackage: contextResult.contextPackage,
263
+ taskGraph,
264
+ planReview,
265
+ eventType: "run.planning.plan_review_approved",
266
+ payload: {
267
+ graphId: taskGraph.graphId,
268
+ reviewId: planReview.reviewId,
269
+ },
270
+ });
271
+ return {
272
+ contextPackage: contextResult.contextPackage,
273
+ taskGraph,
274
+ planReview,
275
+ };
55
276
  }
56
277
  catch (error) {
57
- if (error instanceof RunBudgetExceededError) {
278
+ if (error instanceof RunBudgetExceededError || error instanceof RunCancellationError) {
279
+ throw error;
280
+ }
281
+ if (error instanceof PlanningBlockedError) {
58
282
  throw error;
59
283
  }
60
284
  if (!(error instanceof AgentRunFailedError || error instanceof AgentOutputParseError)) {
@@ -72,21 +296,124 @@ export const createRunController = (options) => {
72
296
  }
73
297
  throw lastError;
74
298
  };
75
- const loadRunWithRetry = async (runId) => {
76
- for (let attempt = 0; attempt < 3; attempt += 1) {
77
- try {
78
- return await options.store.loadState(runId);
299
+ const persistPlanningState = async (run) => {
300
+ if (run.status === "planning") {
301
+ return run;
302
+ }
303
+ const planning = withStatus(run, "planning");
304
+ await writer.persist(planning, writer.createEvent(run.runId, "run.planning", { status: "planning" }));
305
+ return planning;
306
+ };
307
+ const persistExecutingState = async (run) => {
308
+ const executing = parsePlannedRun({ ...run, status: "executing" });
309
+ await writer.persist(executing, writer.createEvent(executing.runId, "run.executing", { status: "executing" }));
310
+ return executing;
311
+ };
312
+ const continueRun = async (runId) => {
313
+ const current = await loadRunWithRetry(runId);
314
+ if (!canAdvanceRun(current)) {
315
+ return current;
316
+ }
317
+ const reconciliation = await runtime.reconcileOrphanSessions({
318
+ runId,
319
+ actorId: "harness-recovery",
320
+ });
321
+ if (reconciliation.kind === "rebound") {
322
+ return current;
323
+ }
324
+ if (reconciliation.kind === "abandoned") {
325
+ const interrupted = "taskGraph" in current
326
+ ? withStatus(current, "paused")
327
+ : withStatus(withGovernance(current, {
328
+ ...current.governance,
329
+ blockingQuestionId: createBlockedQuestionId(current),
330
+ }), "clarification_required");
331
+ await writer.persist(interrupted, writer.createEvent(runId, "run.recovery.session_abandoned", {
332
+ sessionIds: reconciliation.sessionIds,
333
+ status: interrupted.status,
334
+ }));
335
+ return interrupted;
336
+ }
337
+ if (current.status === "created" ||
338
+ current.status === "analyzing" ||
339
+ current.status === "planning") {
340
+ const planning = await persistPlanningState(current);
341
+ const planningArtifacts = await requestPlan(planning, planning.requirement);
342
+ validateTaskGraph(planningArtifacts.taskGraph, planning.requirement, runId);
343
+ runtime.clearInterruption(runId);
344
+ return await runTasks(await persistExecutingState(parsePlannedRun({
345
+ ...planning,
346
+ status: "executing",
347
+ taskGraph: planningArtifacts.taskGraph,
348
+ contextPackage: planningArtifacts.contextPackage,
349
+ planReview: planningArtifacts.planReview,
350
+ })));
351
+ }
352
+ const planned = parsePlannedRun(setPaused(current, null));
353
+ runtime.clearInterruption(runId);
354
+ if (planned.status !== "executing" || current.governance.paused !== null) {
355
+ return await runTasks(await persistExecutingState(planned));
356
+ }
357
+ return await runTasks(planned);
358
+ };
359
+ const finalizeAdvanceError = async (runId, error) => {
360
+ if (error instanceof GraphValidationError ||
361
+ error instanceof PlanningBlockedError ||
362
+ error instanceof AgentRunFailedError ||
363
+ error instanceof AgentOutputParseError ||
364
+ error instanceof RunBudgetExceededError) {
365
+ const latest = await loadRunWithRetry(runId);
366
+ if (error instanceof PlanningBlockedError) {
367
+ return error.run;
79
368
  }
80
- catch (error) {
81
- if (!isMissingRunError(error, runId) || attempt === 2) {
82
- throw error;
83
- }
84
- await waitForNextTurn();
369
+ const planning = withStatus(latest, "planning");
370
+ const blocked = withStatus(withGovernance(planning, {
371
+ ...planning.governance,
372
+ blockingQuestionId: error instanceof RunBudgetExceededError ? createBlockedQuestionId(planning) : null,
373
+ }), error instanceof RunBudgetExceededError ? "paused" : "failed");
374
+ await writer.persist(blocked, writer.createEvent(runId, error instanceof RunBudgetExceededError ? "run.paused" : "run.failed", {
375
+ reason: error.message,
376
+ ...(error instanceof RunBudgetExceededError
377
+ ? {
378
+ budget: error.budget,
379
+ blockingQuestionId: blocked.governance.blockingQuestionId,
380
+ }
381
+ : {}),
382
+ }));
383
+ return blocked;
384
+ }
385
+ if (error instanceof RunPausedError) {
386
+ const latest = await loadRunWithRetry(runId);
387
+ if (latest.status === "paused") {
388
+ return latest;
85
389
  }
390
+ return withStatus(setPaused(latest, latest.governance.paused), "paused");
86
391
  }
87
- throw new TypeError(`missing run ${runId}`);
392
+ if (error instanceof RunCancellationError) {
393
+ const latest = await loadRunWithRetry(runId);
394
+ if (latest.status === "cancelled") {
395
+ return latest;
396
+ }
397
+ const cancelled = withStatus(latest, "cancelled");
398
+ await writer.persist(cancelled, writer.createEvent(runId, "run.cancelled", { status: "cancelled" }));
399
+ return cancelled;
400
+ }
401
+ throw error;
88
402
  };
89
- return {
403
+ const advanceRun = async (runId) => {
404
+ const active = advancingRuns.get(runId);
405
+ if (active !== undefined) {
406
+ return active;
407
+ }
408
+ const pending = continueRun(runId)
409
+ .catch(async (error) => finalizeAdvanceError(runId, error))
410
+ .finally(() => {
411
+ advancingRuns.delete(runId);
412
+ });
413
+ advancingRuns.set(runId, pending);
414
+ return pending;
415
+ };
416
+ const controller = {
90
417
  async start(input) {
91
418
  const requirement = RequirementSchema.parse({ schemaVersion: "1.0.0", ...input });
92
419
  const created = RunSchema.parse({
@@ -124,75 +451,35 @@ export const createRunController = (options) => {
124
451
  }));
125
452
  return blocked;
126
453
  }
127
- const planning = withStatus(created, "planning");
128
- await writer.persist(planning, writer.createEvent(created.runId, "run.planning", { status: "planning" }));
129
- try {
130
- const graph = await requestPlan(created, requirement);
131
- validateTaskGraph(graph, requirement, created.runId);
132
- const executing = parsePlannedRun({ ...planning, status: "executing", taskGraph: graph });
133
- await writer.persist(executing, writer.createEvent(created.runId, "run.executing", { status: "executing" }));
134
- return await runTasks(executing);
135
- }
136
- catch (error) {
137
- if (error instanceof GraphValidationError ||
138
- error instanceof AgentRunFailedError ||
139
- error instanceof AgentOutputParseError ||
140
- error instanceof RunBudgetExceededError) {
141
- const failed = withStatus(withGovernance(planning, {
142
- ...planning.governance,
143
- blockingQuestionId: error instanceof RunBudgetExceededError ? createBlockedQuestionId(planning) : null,
144
- }), error instanceof RunBudgetExceededError ? "paused" : "failed");
145
- await writer.persist(failed, writer.createEvent(created.runId, error instanceof RunBudgetExceededError ? "run.paused" : "run.failed", {
146
- reason: error.message,
147
- ...(error instanceof RunBudgetExceededError
148
- ? {
149
- budget: error.budget,
150
- blockingQuestionId: failed.governance.blockingQuestionId,
151
- }
152
- : {}),
153
- }));
154
- if (error instanceof RunBudgetExceededError) {
155
- return failed;
156
- }
157
- }
158
- if (error instanceof RunCancellationError) {
159
- const latest = await options.store.loadState(created.runId);
160
- if (latest.status === "cancelled") {
161
- return latest;
162
- }
163
- const cancelled = withStatus(latest, "cancelled");
164
- await writer.persist(cancelled, writer.createEvent(created.runId, "run.cancelled", { status: "cancelled" }));
165
- return cancelled;
166
- }
167
- throw error;
168
- }
454
+ return created;
455
+ },
456
+ async advanceRun(runId) {
457
+ return advanceRun(runId);
169
458
  },
170
459
  async status(runId) {
171
460
  return loadRunWithRetry(runId);
172
461
  },
173
462
  async pause(input) {
174
463
  const current = await loadRunWithRetry(input.runId);
175
- if (current.status === "paused") {
176
- return current;
177
- }
178
- if (isTerminalRunStatus(current)) {
464
+ if (current.status === "paused" || isTerminalRunStatus(current)) {
179
465
  return current;
180
466
  }
181
467
  requireTransition(current, "pause", canPause);
182
- await runtime.abortActiveSession(input.runId);
183
- const pausedAt = options.clock.now().toISOString();
184
468
  const paused = withStatus(setPaused(current, {
185
469
  pauseId: options.ids.createEventId(`pause-${input.runId}`),
186
470
  actor: input.actor,
187
471
  reason: input.reason,
188
- pausedAt,
472
+ pausedAt: options.clock.now().toISOString(),
189
473
  }), "paused");
474
+ runtime.markPaused(input.runId);
190
475
  await writer.persist(paused, writer.createEvent(input.runId, "run.paused", {
191
476
  pauseId: paused.governance.paused?.pauseId ?? null,
192
477
  actor: input.actor,
193
478
  reason: input.reason,
194
479
  }));
195
- return paused;
480
+ await runtime.abortActiveSession(input.runId);
481
+ const latest = await loadRunWithRetry(input.runId);
482
+ return latest.status === "paused" ? latest : paused;
196
483
  },
197
484
  async answer(input) {
198
485
  const current = await loadRunWithRetry(input.runId);
@@ -200,13 +487,12 @@ export const createRunController = (options) => {
200
487
  return current;
201
488
  }
202
489
  requireTransition(current, "answer", canAnswer);
203
- const answeredAt = options.clock.now().toISOString();
204
490
  const updated = withStatus(appendAnswer(current, {
205
491
  answerId: options.ids.createEventId(`answer-${input.questionId}`),
206
492
  questionId: input.questionId,
207
493
  answer: input.answer,
208
494
  actor: input.actor,
209
- answeredAt,
495
+ answeredAt: options.clock.now().toISOString(),
210
496
  }), "planning");
211
497
  await writer.persist(updated, writer.createEvent(input.runId, "run.answered", {
212
498
  answerId: updated.governance.answers.at(-1)?.answerId ?? null,
@@ -221,37 +507,30 @@ export const createRunController = (options) => {
221
507
  case "completed":
222
508
  case "cancelled":
223
509
  case "failed":
510
+ case "clarification_required":
224
511
  return run;
225
512
  case "created":
226
513
  case "analyzing":
227
514
  case "planning":
228
- case "clarification_required":
229
- return run;
230
515
  case "plan_ready":
231
516
  case "executing":
232
517
  case "verifying":
233
518
  case "paused":
234
- if (runtime.hasActiveSession(runId)) {
235
- return run;
236
- }
237
- return "taskGraph" in run ? await runTasks(parsePlannedRun(setPaused(run, null))) : run;
519
+ return advanceRun(runId);
238
520
  default:
239
521
  return run;
240
522
  }
241
523
  },
242
524
  async cancel(runId) {
243
525
  const current = await loadRunWithRetry(runId);
244
- if (current.status === "completed" ||
245
- current.status === "cancelled" ||
246
- current.status === "failed") {
526
+ if (isTerminalRunStatus(current)) {
247
527
  return current;
248
528
  }
249
529
  requireTransition(current, "cancel", canCancel);
530
+ runtime.markCancelled(runId);
250
531
  await runtime.abortActiveSession(runId);
251
532
  const latest = await loadRunWithRetry(runId);
252
- if (latest.status === "completed" ||
253
- latest.status === "cancelled" ||
254
- latest.status === "failed") {
533
+ if (isTerminalRunStatus(latest)) {
255
534
  return latest;
256
535
  }
257
536
  const cancelled = withStatus(latest, "cancelled");
@@ -282,10 +561,14 @@ export const createRunController = (options) => {
282
561
  return createReport(run, options.clock);
283
562
  },
284
563
  };
564
+ return controller;
285
565
  };
286
566
  const parsePlannerGraph = (text) => parseAgentJson(text, TaskGraphSchema, "planner");
287
567
  const waitForNextTurn = async () => await new Promise((resolve) => {
288
568
  setTimeout(resolve, 0);
289
569
  });
290
570
  const isMissingRunError = (error, runId) => error instanceof TypeError && error.message === `missing run ${runId}`;
571
+ const assertNever = (value) => {
572
+ throw new TypeError(`Unexpected planner output: ${JSON.stringify(value)}`);
573
+ };
291
574
  //# sourceMappingURL=run-controller.js.map