patchwarden 0.6.1 → 1.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 (261) hide show
  1. package/PatchWarden-Control-Tray.cmd +11 -0
  2. package/PatchWarden-Control.cmd +6 -0
  3. package/PatchWarden-Desktop.cmd +5 -0
  4. package/PatchWarden.cmd +1 -1
  5. package/README.en.md +112 -24
  6. package/README.md +36 -25
  7. package/Restart-PatchWarden-Control.cmd +6 -0
  8. package/Stop-PatchWarden.cmd +11 -0
  9. package/dist/agents/agentRouter.d.ts +40 -0
  10. package/dist/agents/agentRouter.js +95 -0
  11. package/dist/config.d.ts +1 -1
  12. package/dist/config.js +6 -2
  13. package/dist/controlCenter.d.ts +14 -0
  14. package/dist/controlCenter.js +2015 -0
  15. package/dist/doctor.js +35 -4
  16. package/dist/goal/acceptanceEngine.d.ts +62 -0
  17. package/dist/goal/acceptanceEngine.js +103 -0
  18. package/dist/goal/acceptanceTemplate.d.ts +10 -0
  19. package/dist/goal/acceptanceTemplate.js +104 -0
  20. package/dist/goal/goalGraph.d.ts +58 -0
  21. package/dist/goal/goalGraph.js +189 -0
  22. package/dist/goal/goalProgress.d.ts +81 -0
  23. package/dist/goal/goalProgress.js +216 -0
  24. package/dist/goal/goalStatus.d.ts +60 -0
  25. package/dist/goal/goalStatus.js +137 -0
  26. package/dist/goal/goalStore.d.ts +79 -0
  27. package/dist/goal/goalStore.js +211 -0
  28. package/dist/goal/handoffExport.d.ts +34 -0
  29. package/dist/goal/handoffExport.js +183 -0
  30. package/dist/goal/subgoalSync.d.ts +40 -0
  31. package/dist/goal/subgoalSync.js +72 -0
  32. package/dist/goal/worktreeManager.d.ts +96 -0
  33. package/dist/goal/worktreeManager.js +292 -0
  34. package/dist/logging.d.ts +44 -0
  35. package/dist/logging.js +68 -0
  36. package/dist/release/releaseGate.d.ts +99 -0
  37. package/dist/release/releaseGate.js +475 -0
  38. package/dist/runner/postTaskCleanup.d.ts +13 -0
  39. package/dist/runner/postTaskCleanup.js +147 -0
  40. package/dist/runner/runTask.js +50 -10
  41. package/dist/security/discoveryTokenStore.d.ts +62 -0
  42. package/dist/security/discoveryTokenStore.js +100 -0
  43. package/dist/security/toolInvocationGuard.d.ts +35 -0
  44. package/dist/security/toolInvocationGuard.js +153 -0
  45. package/dist/smoke-test.js +18 -10
  46. package/dist/taskRuntime.d.ts +17 -0
  47. package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
  48. package/dist/test/unit/acceptance-engine.test.js +228 -0
  49. package/dist/test/unit/agent-router.test.d.ts +1 -0
  50. package/dist/test/unit/agent-router.test.js +287 -0
  51. package/dist/test/unit/audit-checks.test.d.ts +1 -0
  52. package/dist/test/unit/audit-checks.test.js +350 -0
  53. package/dist/test/unit/diagnose-task.test.d.ts +1 -0
  54. package/dist/test/unit/diagnose-task.test.js +457 -0
  55. package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
  56. package/dist/test/unit/discovery-token-store.test.js +139 -0
  57. package/dist/test/unit/goal-graph.test.d.ts +1 -0
  58. package/dist/test/unit/goal-graph.test.js +298 -0
  59. package/dist/test/unit/goal-progress.test.d.ts +1 -0
  60. package/dist/test/unit/goal-progress.test.js +381 -0
  61. package/dist/test/unit/goal-status.test.d.ts +1 -0
  62. package/dist/test/unit/goal-status.test.js +215 -0
  63. package/dist/test/unit/goal-store.test.d.ts +1 -0
  64. package/dist/test/unit/goal-store.test.js +253 -0
  65. package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
  66. package/dist/test/unit/goal-subgoal-task.test.js +55 -0
  67. package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
  68. package/dist/test/unit/goal-tools-registry.test.js +190 -0
  69. package/dist/test/unit/handoff-export.test.d.ts +1 -0
  70. package/dist/test/unit/handoff-export.test.js +263 -0
  71. package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
  72. package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
  73. package/dist/test/unit/logging.test.js +127 -5
  74. package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
  75. package/dist/test/unit/post-task-cleanup.test.js +48 -0
  76. package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
  77. package/dist/test/unit/reconcile-tasks.test.js +456 -0
  78. package/dist/test/unit/release-gate.test.d.ts +1 -0
  79. package/dist/test/unit/release-gate.test.js +242 -0
  80. package/dist/test/unit/safe-views.test.d.ts +1 -0
  81. package/dist/test/unit/safe-views.test.js +171 -0
  82. package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
  83. package/dist/test/unit/schema-drift-check.test.js +175 -0
  84. package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
  85. package/dist/test/unit/subgoal-sync.test.js +183 -0
  86. package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
  87. package/dist/test/unit/tool-invocation-guard.test.js +432 -0
  88. package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
  89. package/dist/test/unit/tool-usage-stats.test.js +300 -0
  90. package/dist/test/unit/toolSearch.test.d.ts +1 -0
  91. package/dist/test/unit/toolSearch.test.js +571 -0
  92. package/dist/test/unit/worktree-manager.test.d.ts +1 -0
  93. package/dist/test/unit/worktree-manager.test.js +176 -0
  94. package/dist/tools/auditTask.d.ts +103 -1
  95. package/dist/tools/auditTask.js +461 -8
  96. package/dist/tools/cancelTask.js +1 -1
  97. package/dist/tools/checkReleaseGate.d.ts +21 -0
  98. package/dist/tools/checkReleaseGate.js +22 -0
  99. package/dist/tools/createTask.d.ts +18 -2
  100. package/dist/tools/createTask.js +29 -1
  101. package/dist/tools/diagnoseTask.d.ts +45 -0
  102. package/dist/tools/diagnoseTask.js +347 -0
  103. package/dist/tools/discardWorktree.d.ts +24 -0
  104. package/dist/tools/discardWorktree.js +27 -0
  105. package/dist/tools/discoverTools.d.ts +11 -0
  106. package/dist/tools/discoverTools.js +39 -0
  107. package/dist/tools/explainTool.d.ts +11 -0
  108. package/dist/tools/explainTool.js +21 -0
  109. package/dist/tools/getTaskSummary.js +2 -1
  110. package/dist/tools/goalSubgoalTask.d.ts +51 -0
  111. package/dist/tools/goalSubgoalTask.js +110 -0
  112. package/dist/tools/healthCheck.d.ts +1 -0
  113. package/dist/tools/healthCheck.js +5 -1
  114. package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
  115. package/dist/tools/invokeDiscoveredTool.js +112 -0
  116. package/dist/tools/listTasks.d.ts +3 -1
  117. package/dist/tools/listTasks.js +14 -1
  118. package/dist/tools/mergeWorktree.d.ts +24 -0
  119. package/dist/tools/mergeWorktree.js +27 -0
  120. package/dist/tools/reconcileTasks.d.ts +41 -0
  121. package/dist/tools/reconcileTasks.js +352 -0
  122. package/dist/tools/registry.js +550 -0
  123. package/dist/tools/safeStatus.d.ts +31 -1
  124. package/dist/tools/safeStatus.js +43 -1
  125. package/dist/tools/safeViews.d.ts +256 -0
  126. package/dist/tools/safeViews.js +250 -0
  127. package/dist/tools/schemaDriftCheck.d.ts +46 -0
  128. package/dist/tools/schemaDriftCheck.js +80 -0
  129. package/dist/tools/toolCatalog.d.ts +4 -3
  130. package/dist/tools/toolCatalog.js +33 -11
  131. package/dist/tools/toolRegistry.d.ts +61 -0
  132. package/dist/tools/toolRegistry.js +724 -0
  133. package/dist/tools/toolSearch.d.ts +110 -0
  134. package/dist/tools/toolSearch.js +331 -0
  135. package/dist/tools/toolUsageStats.d.ts +41 -0
  136. package/dist/tools/toolUsageStats.js +116 -0
  137. package/dist/tools/waitForTask.js +1 -0
  138. package/dist/version.d.ts +2 -2
  139. package/dist/version.js +2 -2
  140. package/dist/watcherStatus.d.ts +29 -0
  141. package/dist/watcherStatus.js +92 -1
  142. package/docs/control-center/README.md +33 -0
  143. package/docs/control-center/control-center-daily-driver.md +211 -0
  144. package/docs/control-center/control-center-mvp.md +205 -0
  145. package/docs/control-center/control-center-phase2.md +159 -0
  146. package/docs/demo.md +3 -0
  147. package/docs/release-v0.6.4.md +45 -0
  148. package/examples/openai-tunnel/README.md +5 -5
  149. package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
  150. package/package.json +25 -16
  151. package/scripts/README.md +47 -0
  152. package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
  153. package/scripts/checks/control-center-smoke.js +1098 -0
  154. package/scripts/{control-smoke.js → checks/control-smoke.js} +19 -5
  155. package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +2 -1
  156. package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +2 -2
  157. package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +25 -21
  158. package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +54 -7
  159. package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +40 -11
  160. package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
  161. package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
  162. package/scripts/{unit-tests.js → checks/unit-tests.js} +1 -1
  163. package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +13 -7
  164. package/scripts/control/control-center-tray.ps1 +281 -0
  165. package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
  166. package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
  167. package/scripts/control/restart-control-center.ps1 +173 -0
  168. package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
  169. package/scripts/control/start-control-center.ps1 +263 -0
  170. package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
  171. package/scripts/control/stop-patchwarden.ps1 +114 -0
  172. package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
  173. package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
  174. package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
  175. package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
  176. package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
  177. package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
  178. package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
  179. package/scripts/{pack-clean.js → release/pack-clean.js} +9 -1
  180. package/src/agents/agentRouter.ts +149 -0
  181. package/src/config.ts +9 -3
  182. package/src/controlCenter.ts +2166 -0
  183. package/src/doctor.ts +40 -5
  184. package/src/goal/acceptanceEngine.ts +160 -0
  185. package/src/goal/acceptanceTemplate.ts +121 -0
  186. package/src/goal/goalGraph.ts +225 -0
  187. package/src/goal/goalProgress.ts +301 -0
  188. package/src/goal/goalStatus.ts +234 -0
  189. package/src/goal/goalStore.ts +306 -0
  190. package/src/goal/handoffExport.ts +211 -0
  191. package/src/goal/subgoalSync.ts +82 -0
  192. package/src/goal/worktreeManager.ts +404 -0
  193. package/src/logging.ts +91 -0
  194. package/src/release/releaseGate.ts +567 -0
  195. package/src/runner/postTaskCleanup.ts +154 -0
  196. package/src/runner/runTask.ts +49 -10
  197. package/src/security/discoveryTokenStore.ts +157 -0
  198. package/src/security/toolInvocationGuard.ts +251 -0
  199. package/src/smoke-test.ts +15 -7
  200. package/src/taskRuntime.ts +17 -0
  201. package/src/test/unit/acceptance-engine.test.ts +261 -0
  202. package/src/test/unit/agent-router.test.ts +342 -0
  203. package/src/test/unit/audit-checks.test.ts +567 -0
  204. package/src/test/unit/diagnose-task.test.ts +544 -0
  205. package/src/test/unit/discovery-token-store.test.ts +181 -0
  206. package/src/test/unit/goal-graph.test.ts +347 -0
  207. package/src/test/unit/goal-progress.test.ts +538 -0
  208. package/src/test/unit/goal-status.test.ts +270 -0
  209. package/src/test/unit/goal-store.test.ts +318 -0
  210. package/src/test/unit/goal-subgoal-task.test.ts +72 -0
  211. package/src/test/unit/goal-tools-registry.test.ts +243 -0
  212. package/src/test/unit/handoff-export.test.ts +295 -0
  213. package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
  214. package/src/test/unit/logging.test.ts +177 -5
  215. package/src/test/unit/post-task-cleanup.test.ts +53 -0
  216. package/src/test/unit/reconcile-tasks.test.ts +551 -0
  217. package/src/test/unit/release-gate.test.ts +314 -0
  218. package/src/test/unit/safe-views.test.ts +184 -0
  219. package/src/test/unit/schema-drift-check.test.ts +258 -0
  220. package/src/test/unit/subgoal-sync.test.ts +236 -0
  221. package/src/test/unit/tool-invocation-guard.test.ts +542 -0
  222. package/src/test/unit/tool-usage-stats.test.ts +384 -0
  223. package/src/test/unit/toolSearch.test.ts +756 -0
  224. package/src/test/unit/worktree-manager.test.ts +247 -0
  225. package/src/tools/auditTask.ts +831 -402
  226. package/src/tools/cancelTask.ts +1 -1
  227. package/src/tools/checkReleaseGate.ts +45 -0
  228. package/src/tools/createTask.ts +64 -6
  229. package/src/tools/diagnoseTask.ts +460 -0
  230. package/src/tools/discardWorktree.ts +42 -0
  231. package/src/tools/discoverTools.ts +51 -0
  232. package/src/tools/explainTool.ts +26 -0
  233. package/src/tools/getTaskSummary.ts +2 -1
  234. package/src/tools/goalSubgoalTask.ts +170 -0
  235. package/src/tools/healthCheck.ts +4 -1
  236. package/src/tools/invokeDiscoveredTool.ts +163 -0
  237. package/src/tools/listTasks.ts +16 -2
  238. package/src/tools/mergeWorktree.ts +42 -0
  239. package/src/tools/reconcileTasks.ts +464 -0
  240. package/src/tools/registry.ts +618 -1
  241. package/src/tools/safeStatus.ts +73 -2
  242. package/src/tools/safeViews.ts +271 -0
  243. package/src/tools/schemaDriftCheck.ts +120 -0
  244. package/src/tools/toolCatalog.ts +34 -11
  245. package/src/tools/toolRegistry.ts +786 -0
  246. package/src/tools/toolSearch.ts +464 -0
  247. package/src/tools/toolUsageStats.ts +151 -0
  248. package/src/tools/waitForTask.ts +1 -0
  249. package/src/version.ts +2 -2
  250. package/src/watcherStatus.ts +97 -1
  251. package/ui/colors_and_type.css +141 -0
  252. package/ui/pages/audit.html +743 -0
  253. package/ui/pages/dashboard.html +1154 -0
  254. package/ui/pages/direct-sessions.html +652 -0
  255. package/ui/pages/logs.html +502 -0
  256. package/ui/pages/task-detail.html +1229 -0
  257. package/ui/pages/tasks.html +702 -0
  258. package/ui/pages/workspace.html +947 -0
  259. package/ui/partials/project-shell.html +362 -0
  260. package/ui/vendor/lucide.js +12 -0
  261. package/ui/vendor/tailwindcss-browser.js +947 -0
@@ -0,0 +1,110 @@
1
+ /**
2
+ * v0.8.0: createSubgoalTask — 原子地将一个新 subgoal 关联到一个新任务。
3
+ *
4
+ * 流程(原子语义):
5
+ * 1. 读取 goal_status.json(不存在则抛 goal_not_found)
6
+ * 2. addSubgoal(校验 depends_on 引用已有 subgoal)
7
+ * 3. createTask 创建关联任务(写入 goal_id / subgoal_id 到 task status.json)
8
+ * 4. linkTaskToSubgoal(将 task_id 关联到 subgoal)
9
+ * 5. updateSubgoalStatus(running)(ready → running)
10
+ * 6. 写回 goal_status.json
11
+ *
12
+ * 注意:
13
+ * - createSubgoalTask 强制 execution_mode 为 "execute",因为 assess_only 不产生可关联的 task。
14
+ * - createTask 内部使用 getConfig(),因此 workspaceRoot 由全局配置决定。
15
+ */
16
+ import { readGoalStatus, writeGoalStatus } from "../goal/goalStore.js";
17
+ import { addSubgoal, linkTaskToSubgoal, updateSubgoalStatus } from "../goal/goalStatus.js";
18
+ import { createTask } from "./createTask.js";
19
+ import { createWorktree, discardWorktree } from "../goal/worktreeManager.js";
20
+ import { getConfig } from "../config.js";
21
+ import { PatchWardenError } from "../errors.js";
22
+ // ── 函数实现 ──────────────────────────────────────────────────────
23
+ /**
24
+ * 原子地创建一个 subgoal 并关联到一个新任务。
25
+ *
26
+ * @throws PatchWardenError("goal_not_found") 当 goal_id 不存在
27
+ * @throws PatchWardenError("invalid_dependency") 当 depends_on 引用不存在的 subgoal
28
+ * @throws PatchWardenError("invalid_execution_mode") 当 execution_mode 为 "assess_only"
29
+ */
30
+ export function createSubgoalTask(input) {
31
+ // assess_only 不产生 task,无法关联到 subgoal
32
+ if (input.execution_mode === "assess_only") {
33
+ throw new PatchWardenError("invalid_execution_mode", "createSubgoalTask does not support execution_mode \"assess_only\"", "Use execution_mode \"execute\" (default) so a task is created and linked to the subgoal.", true, { goal_id: input.goal_id, execution_mode: input.execution_mode });
34
+ }
35
+ // 1. 读取 goal_status.json,如果不存在抛 goal_not_found
36
+ let goalStatus;
37
+ try {
38
+ goalStatus = readGoalStatus(input.goal_id);
39
+ }
40
+ catch {
41
+ throw new PatchWardenError("goal_not_found", `Goal "${input.goal_id}" not found`, "Call list_goals to see available goals.", true, { goal_id: input.goal_id });
42
+ }
43
+ // 2. addSubgoal(校验 depends_on)
44
+ const { goalStatus: withSubgoal, subgoalId } = addSubgoal(goalStatus, input.subgoal_title, input.depends_on ?? []);
45
+ // 2.5 Worktree 隔离(默认开启):为该 subgoal task 创建独立 git worktree,
46
+ // 用 worktree 路径作为 createTask 的 repo_path,避免并发 task 互相污染。
47
+ // isolate_worktree === false 时退化为原行为(使用 input.repo_path)。
48
+ const isolate = input.isolate_worktree !== false;
49
+ let repoPathForTask = input.repo_path;
50
+ let worktreeId = null;
51
+ if (isolate) {
52
+ const workspaceRoot = getConfig().workspaceRoot;
53
+ const wt = createWorktree(input.goal_id, subgoalId, workspaceRoot);
54
+ repoPathForTask = wt.worktreePath;
55
+ worktreeId = wt.worktreeId;
56
+ }
57
+ // 3. 调用 createTask 创建关联任务(强制 execute 模式,确保返回 CreateTaskOutput)
58
+ let taskId;
59
+ try {
60
+ const taskResult = createTask({
61
+ plan_id: input.plan_id,
62
+ inline_plan: input.inline_plan,
63
+ plan_title: input.plan_title,
64
+ template: input.template,
65
+ goal: input.goal,
66
+ agent: input.agent,
67
+ repo_path: repoPathForTask,
68
+ test_command: input.test_command,
69
+ verify_commands: input.verify_commands,
70
+ timeout_seconds: input.timeout_seconds,
71
+ execution_mode: "execute",
72
+ assessment_id: input.assessment_id,
73
+ scope: input.scope,
74
+ forbidden: input.forbidden,
75
+ verification: input.verification,
76
+ done_evidence: input.done_evidence,
77
+ goal_id: input.goal_id,
78
+ subgoal_id: subgoalId,
79
+ });
80
+ // execution_mode 为 "execute",createTask 返回 CreateTaskOutput(含 task_id)
81
+ taskId = taskResult.task_id;
82
+ }
83
+ catch (createTaskErr) {
84
+ // createTask 失败:清理已创建的隔离 worktree,避免遗留半成品(best effort)
85
+ if (isolate && worktreeId) {
86
+ try {
87
+ discardWorktree(worktreeId, getConfig().workspaceRoot);
88
+ }
89
+ catch { /* ignore cleanup failure,向上抛出原始 createTask 错误 */ }
90
+ }
91
+ throw createTaskErr;
92
+ }
93
+ // 4. linkTaskToSubgoal + updateSubgoalStatus(running)
94
+ const withTask = linkTaskToSubgoal(withSubgoal, subgoalId, taskId);
95
+ let withRunning = updateSubgoalStatus(withTask, subgoalId, "running");
96
+ // 4.5 隔离模式下,把 worktree_id 记录到 subgoal 对象(写入 goal_status.json)
97
+ if (isolate && worktreeId) {
98
+ const newSubgoals = withRunning.subgoals.map((s) => s.id === subgoalId
99
+ ? { ...s, worktree_id: worktreeId }
100
+ : s);
101
+ withRunning = { ...withRunning, subgoals: newSubgoals };
102
+ }
103
+ // 5. 写回 goal_status.json
104
+ writeGoalStatus(input.goal_id, withRunning);
105
+ return {
106
+ subgoal_id: subgoalId,
107
+ task_id: taskId,
108
+ subgoal_status: "running",
109
+ };
110
+ }
@@ -69,6 +69,7 @@ export declare function healthCheck(catalog?: ToolCatalogSnapshot, input?: Healt
69
69
  agent_status: {
70
70
  [k: string]: string;
71
71
  };
72
+ search_tool_count?: 5 | undefined;
72
73
  direct_tool_count?: number | undefined;
73
74
  status: string;
74
75
  server_version: string;
@@ -5,7 +5,7 @@ import { getConfig, getTasksDir, getDirectSessionsDir, resolveWorkspaceRoot } fr
5
5
  import { listAgents } from "./listAgents.js";
6
6
  import { redactSensitiveContent, redactSensitiveValue } from "../security/contentRedaction.js";
7
7
  import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
8
- import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, resolveToolProfile } from "./toolCatalog.js";
8
+ import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES, resolveToolProfile } from "./toolCatalog.js";
9
9
  import { readWatcherStatus } from "../watcherStatus.js";
10
10
  import { listTasks } from "./listTasks.js";
11
11
  const SERVER_STARTED_AT = Date.now();
@@ -39,6 +39,9 @@ export function healthCheck(catalog, input = {}) {
39
39
  profileErrors.push("Direct profile is disabled (enableDirectProfile=false). Only health_check is available. Set enableDirectProfile: true to enable Direct session tools.");
40
40
  profileConsistent = false;
41
41
  }
42
+ else if (activeProfile === "chatgpt_search") {
43
+ expectedNames = [...CHATGPT_SEARCH_TOOL_NAMES];
44
+ }
42
45
  if (expectedNames) {
43
46
  const catalogNames = new Set(catalog.tool_names);
44
47
  for (const name of expectedNames) {
@@ -95,6 +98,7 @@ export function healthCheck(catalog, input = {}) {
95
98
  direct_sessions_dir: directSessions,
96
99
  direct_session_ttl_seconds: config.directSessionTtlSeconds,
97
100
  ...(activeProfile === "chatgpt_direct" ? { direct_tool_count: config.enableDirectProfile ? CHATGPT_DIRECT_TOOL_NAMES.length : 1 } : {}),
101
+ ...(activeProfile === "chatgpt_search" ? { search_tool_count: CHATGPT_SEARCH_TOOL_NAMES.length } : {}),
98
102
  connector_visibility: {
99
103
  status: "not_observable_server_side",
100
104
  verification: "Refresh or reconnect the Connector and verify tools/list from a new ChatGPT conversation.",
@@ -0,0 +1,37 @@
1
+ /**
2
+ * v0.8.1: invoke_discovered_tool MCP 工具封装层。
3
+ *
4
+ * 受控动态调用链:agent 必须先 discover_tools 获取 discoveryToken,
5
+ * 再凭 token 调用工具。本模块串联 consumeToken → toolMeta 查找 →
6
+ * 防递归 → checkInvocation → dispatch → logToolInvocation,
7
+ * 任一环节失败都记审计日志并返回错误结果,不向上层抛错。
8
+ */
9
+ import type { ToolProfile } from "./toolCatalog.js";
10
+ import type { ToolDef } from "./registry.js";
11
+ export interface InvokeDiscoveredToolInput {
12
+ toolName: string;
13
+ arguments: Record<string, unknown>;
14
+ discoveryToken: string;
15
+ assessmentId?: string;
16
+ }
17
+ export type ToolDispatch = (name: string, args: Record<string, unknown>) => Promise<unknown>;
18
+ export interface InvokeDiscoveredToolContext {
19
+ tools: ToolDef[];
20
+ profile: ToolProfile;
21
+ dispatch: ToolDispatch;
22
+ }
23
+ export interface InvokeDiscoveredToolResult {
24
+ ok: boolean;
25
+ result?: unknown;
26
+ error?: {
27
+ reason: string;
28
+ message: string;
29
+ };
30
+ invocation_log_written: boolean;
31
+ }
32
+ /**
33
+ * 受控动态调用入口。串联 token 消费、toolMeta 查找、防递归、
34
+ * 8 项守卫校验、工具分发和审计日志。任何环节失败都记日志并返回
35
+ * 错误结果,不向上层抛错。
36
+ */
37
+ export declare function invokeDiscoveredTool(input: InvokeDiscoveredToolInput, context: InvokeDiscoveredToolContext): Promise<InvokeDiscoveredToolResult>;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * v0.8.1: invoke_discovered_tool MCP 工具封装层。
3
+ *
4
+ * 受控动态调用链:agent 必须先 discover_tools 获取 discoveryToken,
5
+ * 再凭 token 调用工具。本模块串联 consumeToken → toolMeta 查找 →
6
+ * 防递归 → checkInvocation → dispatch → logToolInvocation,
7
+ * 任一环节失败都记审计日志并返回错误结果,不向上层抛错。
8
+ */
9
+ import { consumeToken } from "../security/discoveryTokenStore.js";
10
+ import { checkInvocation } from "../security/toolInvocationGuard.js";
11
+ import { logToolInvocation, computeArgumentsDigest } from "../logging.js";
12
+ import { buildToolRegistry } from "./toolRegistry.js";
13
+ import { PatchWardenError } from "../errors.js";
14
+ // ── 内部错误码常量 ────────────────────────────────────────────────
15
+ const RECURSIVE_INVOCATION_BLOCKED = "recursive_invocation_blocked";
16
+ const TOOL_NOT_REGISTERED = "tool_not_registered";
17
+ const DISPATCH_ERROR = "dispatch_error";
18
+ // ── 公共 API ──────────────────────────────────────────────────────
19
+ /**
20
+ * 受控动态调用入口。串联 token 消费、toolMeta 查找、防递归、
21
+ * 8 项守卫校验、工具分发和审计日志。任何环节失败都记日志并返回
22
+ * 错误结果,不向上层抛错。
23
+ */
24
+ export async function invokeDiscoveredTool(input, context) {
25
+ const startTime = Date.now();
26
+ const argumentsDigest = computeArgumentsDigest(input.arguments);
27
+ let tokenRecord = null;
28
+ let toolMeta = null;
29
+ const writeLog = (result, errorCode) => {
30
+ const durationMs = Date.now() - startTime;
31
+ logToolInvocation({
32
+ timestamp: new Date().toISOString(),
33
+ toolName: input.toolName,
34
+ discoveryToken: input.discoveryToken,
35
+ risk: toolMeta?.risk ?? "unknown",
36
+ profile: context.profile,
37
+ arguments_digest: argumentsDigest,
38
+ ...(tokenRecord?.allowedScope ? { allowedScope: tokenRecord.allowedScope } : {}),
39
+ result,
40
+ ...(errorCode ? { error_code: errorCode } : {}),
41
+ duration_ms: durationMs,
42
+ });
43
+ };
44
+ const fail = (reason, message) => {
45
+ writeLog("error", reason);
46
+ return {
47
+ ok: false,
48
+ error: { reason, message },
49
+ invocation_log_written: true,
50
+ };
51
+ };
52
+ try {
53
+ // 1. consumeToken(单次使用语义,token_not_found / token_expired 在此抛出)
54
+ try {
55
+ tokenRecord = consumeToken(input.discoveryToken);
56
+ }
57
+ catch (err) {
58
+ if (err instanceof PatchWardenError) {
59
+ return fail(err.reason, err.message);
60
+ }
61
+ return fail("token_error", err instanceof Error ? err.message : String(err));
62
+ }
63
+ // 2. 查 toolMeta(防止调用未注册工具)
64
+ const registry = buildToolRegistry(context.tools);
65
+ toolMeta = registry.find((meta) => meta.name === input.toolName) ?? null;
66
+ if (!toolMeta) {
67
+ return fail(TOOL_NOT_REGISTERED, `Tool "${input.toolName}" is not registered in the tool registry.`);
68
+ }
69
+ // 3. 防递归:禁止调用 invoke_discovered_tool 自身(防止绕过守卫)
70
+ if (input.toolName === "invoke_discovered_tool") {
71
+ return fail(RECURSIVE_INVOCATION_BLOCKED, "Recursive invocation of invoke_discovered_tool is blocked. invoke_discovered_tool cannot call itself.");
72
+ }
73
+ // 4. checkInvocation(8 项调用前强制校验)
74
+ try {
75
+ checkInvocation({
76
+ toolName: input.toolName,
77
+ toolMeta,
78
+ args: input.arguments,
79
+ discoveryTokenRecord: tokenRecord,
80
+ profile: context.profile,
81
+ assessmentId: input.assessmentId,
82
+ });
83
+ }
84
+ catch (err) {
85
+ if (err instanceof PatchWardenError) {
86
+ return fail(err.reason, err.message);
87
+ }
88
+ return fail("guard_error", err instanceof Error ? err.message : String(err));
89
+ }
90
+ // 5. dispatch(调用实际工具 handler)
91
+ let dispatchResult;
92
+ try {
93
+ dispatchResult = await context.dispatch(input.toolName, input.arguments);
94
+ }
95
+ catch (err) {
96
+ const message = err instanceof Error ? err.message : String(err);
97
+ return fail(DISPATCH_ERROR, `Dispatch failed for tool "${input.toolName}": ${message}`);
98
+ }
99
+ // 6. 成功 → 记审计日志 → 返回成功结果
100
+ writeLog("ok");
101
+ return {
102
+ ok: true,
103
+ result: dispatchResult,
104
+ invocation_log_written: true,
105
+ };
106
+ }
107
+ catch (err) {
108
+ // 兜底:任何意外错误都记日志并返回错误结果(不抛给上层)
109
+ const message = err instanceof Error ? err.message : String(err);
110
+ return fail("unexpected_error", `Unexpected error during invoke_discovered_tool: ${message}`);
111
+ }
112
+ }
@@ -1,4 +1,4 @@
1
- import type { TaskPhase, TaskStatus } from "./createTask.js";
1
+ import type { TaskPhase, TaskStatus, AcceptanceStatus } from "./createTask.js";
2
2
  import { type PendingReason, type WatcherState, type WatcherStatusSnapshot } from "../watcherStatus.js";
3
3
  export interface TaskEntry {
4
4
  task_id: string;
@@ -7,6 +7,7 @@ export interface TaskEntry {
7
7
  agent: string;
8
8
  status: TaskStatus;
9
9
  phase: TaskPhase;
10
+ acceptance_status: AcceptanceStatus;
10
11
  created_at: string;
11
12
  updated_at: string;
12
13
  workspace_root: string;
@@ -25,6 +26,7 @@ export interface ListTasksInput {
25
26
  status?: string;
26
27
  repo_path?: string;
27
28
  active_only?: boolean;
29
+ acceptance_status?: string;
28
30
  limit?: number;
29
31
  }
30
32
  export interface ListTasksOutput {
@@ -9,6 +9,7 @@ export function listTasks(input) {
9
9
  const plansDir = getPlansDir(config);
10
10
  const limit = input?.limit && input.limit > 0 ? Math.min(input.limit, 100) : 20;
11
11
  const filterStatus = input?.status || null;
12
+ const filterAcceptance = input?.acceptance_status || null;
12
13
  const filterRepo = input?.repo_path?.trim().replace(/\\/g, "/") || null;
13
14
  const watcher = readWatcherStatus(config);
14
15
  if (!existsSync(tasksDir)) {
@@ -40,7 +41,14 @@ export function listTasks(input) {
40
41
  const runtime = readTaskRuntime(taskDir);
41
42
  if (filterStatus && data.status !== filterStatus)
42
43
  continue;
43
- if (input?.active_only && data.status !== "pending" && data.status !== "running")
44
+ if (filterAcceptance) {
45
+ const taskAcceptance = data.status === "done_by_agent"
46
+ ? (typeof data.acceptance_status === "string" ? data.acceptance_status : "pending")
47
+ : null;
48
+ if (taskAcceptance !== filterAcceptance)
49
+ continue;
50
+ }
51
+ if (input?.active_only && !["pending", "running", "collecting_artifacts"].includes(String(data.status)))
44
52
  continue;
45
53
  const normalizedRepo = String(data.repo_path || ".").replace(/\\/g, "/");
46
54
  const normalizedResolvedRepo = String(data.resolved_repo_path || "").replace(/\\/g, "/");
@@ -64,6 +72,10 @@ export function listTasks(input) {
64
72
  }
65
73
  }
66
74
  const phase = runtime.phase || data.phase || "queued";
75
+ const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
76
+ const acceptanceStatus = data.status === "done_by_agent"
77
+ ? (typeof data.acceptance_status === "string" && VALID_ACCEPTANCE.includes(data.acceptance_status) ? data.acceptance_status : "pending")
78
+ : null;
67
79
  tasks.push({
68
80
  task_id: taskId,
69
81
  plan_id: data.plan_id || "",
@@ -71,6 +83,7 @@ export function listTasks(input) {
71
83
  agent: data.agent || "",
72
84
  status: data.status || "pending",
73
85
  phase,
86
+ acceptance_status: acceptanceStatus,
74
87
  created_at: data.created_at || "",
75
88
  updated_at: data.updated_at || "",
76
89
  workspace_root: data.workspace_root || config.workspaceRoot,
@@ -0,0 +1,24 @@
1
+ /**
2
+ * v1.0.0 Part B: merge_worktree MCP 工具 handler — 把隔离 worktree 的 branch
3
+ * 合并回主工作区,并将 worktree 状态置为 "merged"。
4
+ *
5
+ * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
+ */
7
+ export interface MergeWorktreeInput {
8
+ worktree_id: string;
9
+ repo_path: string;
10
+ }
11
+ export interface MergeWorktreeOutput {
12
+ worktree_id: string;
13
+ status: "merged";
14
+ merged_at: string;
15
+ }
16
+ /**
17
+ * 合并指定 worktree 的 branch 回 repo_path(主工作区)。
18
+ *
19
+ * @throws PatchWardenError("workspace_path_escape") 当 repo_path 逃逸出 workspaceRoot
20
+ * @throws PatchWardenError("worktree_not_found") 当 worktree 不存在
21
+ * @throws PatchWardenError("invalid_worktree_state") 当 worktree 非 active
22
+ * @throws PatchWardenError("worktree_merge_failed") 当 git merge 失败
23
+ */
24
+ export declare function mergeWorktreeTool(input: MergeWorktreeInput): MergeWorktreeOutput;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * v1.0.0 Part B: merge_worktree MCP 工具 handler — 把隔离 worktree 的 branch
3
+ * 合并回主工作区,并将 worktree 状态置为 "merged"。
4
+ *
5
+ * 参考 src/tools/auditTask.ts 的导出/返回风格。
6
+ */
7
+ import { getConfig } from "../config.js";
8
+ import { guardWorkspacePath } from "../security/pathGuard.js";
9
+ import { mergeWorktree } from "../goal/worktreeManager.js";
10
+ /**
11
+ * 合并指定 worktree 的 branch 回 repo_path(主工作区)。
12
+ *
13
+ * @throws PatchWardenError("workspace_path_escape") 当 repo_path 逃逸出 workspaceRoot
14
+ * @throws PatchWardenError("worktree_not_found") 当 worktree 不存在
15
+ * @throws PatchWardenError("invalid_worktree_state") 当 worktree 非 active
16
+ * @throws PatchWardenError("worktree_merge_failed") 当 git merge 失败
17
+ */
18
+ export function mergeWorktreeTool(input) {
19
+ const config = getConfig();
20
+ const repoPathSafe = guardWorkspacePath(input.repo_path, config.workspaceRoot);
21
+ mergeWorktree(input.worktree_id, repoPathSafe);
22
+ return {
23
+ worktree_id: input.worktree_id,
24
+ status: "merged",
25
+ merged_at: new Date().toISOString(),
26
+ };
27
+ }
@@ -0,0 +1,41 @@
1
+ import { type PatchWardenConfig } from "../config.js";
2
+ import { type DiagnosisType, type DiagnosisConfidence, type SafeAction } from "./diagnoseTask.js";
3
+ export type ReconcileMode = "report_only" | "safe_fix";
4
+ export interface ReconcileTasksInput {
5
+ max_age_minutes?: number;
6
+ mode?: ReconcileMode;
7
+ include_done_candidates?: boolean;
8
+ }
9
+ export interface ReconcileTaskReport {
10
+ task_id: string;
11
+ status: string;
12
+ phase: string | null;
13
+ diagnosis: DiagnosisType;
14
+ confidence: DiagnosisConfidence;
15
+ reasons: string[];
16
+ safe_actions: SafeAction[];
17
+ age_seconds: number | null;
18
+ action_taken: "left_unchanged" | "marked_failed_stale" | "marked_orphaned" | "marked_done_by_agent";
19
+ previous_status: string | null;
20
+ new_status: string | null;
21
+ applied_at: string | null;
22
+ applied_by: string | null;
23
+ evidence_summary: {
24
+ heartbeat_age_seconds: number | null;
25
+ stdout_age_seconds: number | null;
26
+ child_pid: number | null;
27
+ child_pid_alive: boolean | null;
28
+ watcher_owns_task: boolean;
29
+ };
30
+ }
31
+ export interface ReconcileTasksOutput {
32
+ mode: ReconcileMode;
33
+ scanned: number;
34
+ candidates: number;
35
+ reconciled: number;
36
+ skipped_low_confidence: number;
37
+ skipped_active_watcher: number;
38
+ reports: ReconcileTaskReport[];
39
+ reconcile_log_path: string | null;
40
+ }
41
+ export declare function reconcileTasks(input?: ReconcileTasksInput, config?: PatchWardenConfig): ReconcileTasksOutput;