patchwarden 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.en.md +43 -2
  2. package/README.md +43 -2
  3. package/dist/assessments/agentAssessor.d.ts +1 -1
  4. package/dist/assessments/agentAssessor.js +3 -3
  5. package/dist/assessments/assessmentStore.d.ts +1 -1
  6. package/dist/assessments/assessmentStore.js +2 -2
  7. package/dist/assessments/confirmCli.js +5 -4
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.js +34 -0
  10. package/dist/control/middleware/auth.d.ts +10 -0
  11. package/dist/control/middleware/auth.js +8 -0
  12. package/dist/control/middleware/static.d.ts +3 -0
  13. package/dist/control/middleware/static.js +65 -0
  14. package/dist/control/routes/audit.d.ts +15 -0
  15. package/dist/control/routes/audit.js +277 -0
  16. package/dist/control/routes/evidence.d.ts +4 -0
  17. package/dist/control/routes/evidence.js +96 -0
  18. package/dist/control/routes/lineage.d.ts +3 -0
  19. package/dist/control/routes/lineage.js +71 -0
  20. package/dist/control/routes/policy.d.ts +3 -0
  21. package/dist/control/routes/policy.js +81 -0
  22. package/dist/control/routes/process.d.ts +5 -0
  23. package/dist/control/routes/process.js +200 -0
  24. package/dist/control/routes/sessions.d.ts +22 -0
  25. package/dist/control/routes/sessions.js +224 -0
  26. package/dist/control/routes/status.d.ts +6 -0
  27. package/dist/control/routes/status.js +250 -0
  28. package/dist/control/routes/taskActions.d.ts +21 -0
  29. package/dist/control/routes/taskActions.js +233 -0
  30. package/dist/control/routes/tasks.d.ts +20 -0
  31. package/dist/control/routes/tasks.js +310 -0
  32. package/dist/control/routes/workspace.d.ts +15 -0
  33. package/dist/control/routes/workspace.js +193 -0
  34. package/dist/control/runtime.d.ts +91 -0
  35. package/dist/control/runtime.js +392 -0
  36. package/dist/control/server.d.ts +13 -0
  37. package/dist/control/server.js +479 -0
  38. package/dist/control/shared.d.ts +35 -0
  39. package/dist/control/shared.js +288 -0
  40. package/dist/controlCenter.d.ts +6 -0
  41. package/dist/controlCenter.js +7 -2197
  42. package/dist/direct/directGuards.js +30 -8
  43. package/dist/doctor.d.ts +18 -1
  44. package/dist/doctor.js +579 -348
  45. package/dist/goal/goalReport.d.ts +54 -0
  46. package/dist/goal/goalReport.js +204 -0
  47. package/dist/goal/goalStatus.d.ts +6 -0
  48. package/dist/goal/specKitImport.d.ts +63 -0
  49. package/dist/goal/specKitImport.js +220 -0
  50. package/dist/goal/subgoalSync.js +2 -1
  51. package/dist/httpServer.js +15 -12
  52. package/dist/index.js +7 -4
  53. package/dist/logging.d.ts +7 -1
  54. package/dist/logging.js +8 -0
  55. package/dist/runner/changeCapture.d.ts +3 -3
  56. package/dist/runner/changeCapture.js +63 -39
  57. package/dist/runner/cli.js +7 -6
  58. package/dist/runner/postTaskCleanup.js +26 -2
  59. package/dist/runner/runTask.js +245 -221
  60. package/dist/runner/simpleProcess.js +4 -4
  61. package/dist/runner/watch.js +17 -14
  62. package/dist/security/contentRedaction.d.ts +6 -0
  63. package/dist/security/contentRedaction.js +22 -0
  64. package/dist/smoke-test.js +257 -251
  65. package/dist/test/unit/apply-patch.test.d.ts +1 -0
  66. package/dist/test/unit/apply-patch.test.js +225 -0
  67. package/dist/test/unit/create-task.test.d.ts +1 -0
  68. package/dist/test/unit/create-task.test.js +197 -0
  69. package/dist/test/unit/direct-guards.test.js +124 -9
  70. package/dist/test/unit/evidence-pack.test.js +95 -1
  71. package/dist/test/unit/get-task-status.test.d.ts +1 -0
  72. package/dist/test/unit/get-task-status.test.js +174 -0
  73. package/dist/test/unit/get-task-summary.test.d.ts +1 -0
  74. package/dist/test/unit/get-task-summary.test.js +146 -0
  75. package/dist/test/unit/goal-report.test.d.ts +1 -0
  76. package/dist/test/unit/goal-report.test.js +159 -0
  77. package/dist/test/unit/goal-subgoal-task.test.js +6 -6
  78. package/dist/test/unit/goal-tools-registry.test.js +6 -4
  79. package/dist/test/unit/path-guard.test.js +24 -0
  80. package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
  81. package/dist/test/unit/spec-kit-import.test.js +341 -0
  82. package/dist/test/unit/wait-for-task.test.d.ts +1 -0
  83. package/dist/test/unit/wait-for-task.test.js +144 -0
  84. package/dist/tools/auditTask.d.ts +8 -63
  85. package/dist/tools/auditTask.js +12 -8
  86. package/dist/tools/createDirectSession.d.ts +1 -1
  87. package/dist/tools/createDirectSession.js +2 -2
  88. package/dist/tools/createTask.d.ts +2 -2
  89. package/dist/tools/createTask.js +4 -4
  90. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  91. package/dist/tools/dispatch/coreDispatch.js +282 -0
  92. package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
  93. package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
  94. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  95. package/dist/tools/dispatch/directDispatch.js +115 -0
  96. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  97. package/dist/tools/dispatch/goalDispatch.js +91 -0
  98. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  99. package/dist/tools/dispatch/releaseDispatch.js +45 -0
  100. package/dist/tools/dispatch/types.d.ts +23 -0
  101. package/dist/tools/dispatch/types.js +15 -0
  102. package/dist/tools/evidencePack.d.ts +6 -0
  103. package/dist/tools/evidencePack.js +213 -6
  104. package/dist/tools/finalizeDirectSession.d.ts +1 -1
  105. package/dist/tools/finalizeDirectSession.js +3 -3
  106. package/dist/tools/goalSubgoalTask.d.ts +1 -1
  107. package/dist/tools/goalSubgoalTask.js +2 -2
  108. package/dist/tools/healthCheck.js +3 -3
  109. package/dist/tools/registry.d.ts +3 -3
  110. package/dist/tools/registry.js +60 -503
  111. package/dist/tools/retryTask.d.ts +2 -2
  112. package/dist/tools/retryTask.js +2 -2
  113. package/dist/tools/runTaskLoop.js +4 -4
  114. package/dist/tools/safeViews.d.ts +2 -2
  115. package/dist/tools/safeViews.js +2 -2
  116. package/dist/tools/toolRegistry.js +22 -0
  117. package/dist/version.d.ts +1 -1
  118. package/dist/version.js +1 -1
  119. package/docs/CODE_WIKI.md +983 -0
  120. package/docs/agentseal-integration.md +150 -0
  121. package/docs/architecture.md +63 -0
  122. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  123. package/docs/dashboard-overview.md +86 -0
  124. package/docs/demo.md +8 -0
  125. package/docs/direct-session-workflow.md +98 -0
  126. package/docs/evidence-pack-schema.md +215 -0
  127. package/docs/execution-plan-2026-07-09.md +315 -0
  128. package/docs/lineage-evidence-pack-workflow.md +127 -0
  129. package/docs/mcp-inspector-testing.md +200 -0
  130. package/docs/open-source-application.md +162 -0
  131. package/docs/opencode-worker.md +151 -0
  132. package/docs/openhands-worker.md +181 -0
  133. package/docs/release-evidence.md +72 -0
  134. package/docs/roadmap-execution-and-acceptance.md +365 -0
  135. package/docs/spec-kit-integration.md +131 -0
  136. package/docs/task-safe-review-workflow.md +98 -0
  137. package/docs/threat-model.md +79 -0
  138. package/docs/user-feedback.md +40 -0
  139. package/docs/why-patchwarden.md +110 -0
  140. package/package.json +2 -2
  141. package/scripts/checks/control-center-smoke.js +356 -0
  142. package/scripts/checks/lifecycle-smoke.js +29 -23
  143. package/scripts/checks/mcp-smoke.js +3 -1
  144. package/scripts/generate-demo-gif.py +320 -0
  145. package/src/assessments/agentAssessor.ts +3 -3
  146. package/src/assessments/assessmentStore.ts +2 -2
  147. package/src/assessments/confirmCli.ts +5 -4
  148. package/src/config.ts +37 -0
  149. package/src/control/middleware/auth.ts +17 -0
  150. package/src/control/middleware/static.ts +71 -0
  151. package/src/control/routes/audit.ts +321 -0
  152. package/src/control/routes/evidence.ts +107 -0
  153. package/src/control/routes/lineage.ts +92 -0
  154. package/src/control/routes/policy.ts +81 -0
  155. package/src/control/routes/process.ts +204 -0
  156. package/src/control/routes/sessions.ts +251 -0
  157. package/src/control/routes/status.ts +325 -0
  158. package/src/control/routes/taskActions.ts +248 -0
  159. package/src/control/routes/tasks.ts +323 -0
  160. package/src/control/routes/workspace.ts +203 -0
  161. package/src/control/runtime.ts +472 -0
  162. package/src/control/server.ts +471 -0
  163. package/src/control/shared.ts +294 -0
  164. package/src/controlCenter.ts +7 -2347
  165. package/src/direct/directGuards.ts +28 -7
  166. package/src/doctor.ts +741 -481
  167. package/src/goal/goalReport.ts +271 -0
  168. package/src/goal/goalStatus.ts +6 -0
  169. package/src/goal/specKitImport.ts +355 -0
  170. package/src/goal/subgoalSync.ts +4 -2
  171. package/src/httpServer.ts +17 -14
  172. package/src/index.ts +7 -4
  173. package/src/logging.ts +10 -1
  174. package/src/runner/changeCapture.ts +70 -42
  175. package/src/runner/cli.ts +7 -6
  176. package/src/runner/postTaskCleanup.ts +26 -2
  177. package/src/runner/runTask.ts +325 -223
  178. package/src/runner/simpleProcess.ts +4 -4
  179. package/src/runner/watch.ts +17 -14
  180. package/src/security/contentRedaction.ts +29 -0
  181. package/src/smoke-test.ts +252 -250
  182. package/src/test/unit/apply-patch.test.ts +293 -0
  183. package/src/test/unit/create-task.test.ts +255 -0
  184. package/src/test/unit/direct-guards.test.ts +178 -8
  185. package/src/test/unit/evidence-pack.test.ts +110 -1
  186. package/src/test/unit/get-task-status.test.ts +203 -0
  187. package/src/test/unit/get-task-summary.test.ts +173 -0
  188. package/src/test/unit/goal-report.test.ts +189 -0
  189. package/src/test/unit/goal-subgoal-task.test.ts +6 -6
  190. package/src/test/unit/goal-tools-registry.test.ts +7 -5
  191. package/src/test/unit/path-guard.test.ts +30 -0
  192. package/src/test/unit/spec-kit-import.test.ts +429 -0
  193. package/src/test/unit/wait-for-task.test.ts +176 -0
  194. package/src/tools/auditTask.ts +99 -59
  195. package/src/tools/createDirectSession.ts +3 -3
  196. package/src/tools/createTask.ts +7 -7
  197. package/src/tools/dispatch/coreDispatch.ts +374 -0
  198. package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
  199. package/src/tools/dispatch/directDispatch.ts +167 -0
  200. package/src/tools/dispatch/goalDispatch.ts +127 -0
  201. package/src/tools/dispatch/releaseDispatch.ts +65 -0
  202. package/src/tools/dispatch/types.ts +24 -0
  203. package/src/tools/evidencePack.ts +291 -6
  204. package/src/tools/finalizeDirectSession.ts +4 -4
  205. package/src/tools/goalSubgoalTask.ts +2 -2
  206. package/src/tools/healthCheck.ts +3 -3
  207. package/src/tools/registry.ts +68 -628
  208. package/src/tools/retryTask.ts +2 -2
  209. package/src/tools/runTaskLoop.ts +4 -4
  210. package/src/tools/safeViews.ts +2 -2
  211. package/src/tools/toolRegistry.ts +22 -0
  212. package/src/version.ts +1 -1
  213. package/ui/pages/audit.html +192 -3
  214. package/ui/pages/dashboard.html +959 -44
  215. package/ui/pages/direct-sessions.html +505 -53
  216. package/ui/pages/task-detail.html +456 -438
  217. package/ui/pages/tasks.html +598 -61
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Dispatch handlers for core task-management tools.
3
+ *
4
+ * Each handler directly calls the existing tool function — no logic is
5
+ * rewritten. Extracted from the original handleToolCallInternal switch.
6
+ */
7
+
8
+ import { savePlan } from "../savePlan.js";
9
+ import { getPlan } from "../getPlan.js";
10
+ import { createTask } from "../createTask.js";
11
+ import { getTaskStatus } from "../getTaskStatus.js";
12
+ import { getResult, getResultJson, getDiff, getTestLog, getTaskLogTail } from "../taskOutputs.js";
13
+ import { listWorkspace } from "../listWorkspace.js";
14
+ import { readWorkspaceFile } from "../readWorkspaceFile.js";
15
+ import { listTasks } from "../listTasks.js";
16
+ import { cancelTask } from "../cancelTask.js";
17
+ import { killTask } from "../killTask.js";
18
+ import { retryTask } from "../retryTask.js";
19
+ import { getTaskStdoutTail } from "../getTaskStdoutTail.js";
20
+ import { getTaskProgress } from "../getTaskProgress.js";
21
+ import { listAgents } from "../listAgents.js";
22
+ import { healthCheck } from "../healthCheck.js";
23
+ import { getTaskSummary } from "../getTaskSummary.js";
24
+ import { waitForTask } from "../waitForTask.js";
25
+ import { runTaskLoop } from "../runTaskLoop.js";
26
+ import { getTaskLineage } from "../taskLineage.js";
27
+ import { exportTaskEvidencePack } from "../evidencePack.js";
28
+ import { recommendAgentForTask } from "../recommendAgentForTask.js";
29
+ import { auditTask } from "../auditTask.js";
30
+ import { safeStatus } from "../safeStatus.js";
31
+ import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../safeViews.js";
32
+ import { diagnoseTask } from "../diagnoseTask.js";
33
+ import { reconcileTasks } from "../reconcileTasks.js";
34
+ import { checkReleaseGate } from "../checkReleaseGate.js";
35
+ import { getProjectPolicyTool } from "../releaseMode.js";
36
+ import { runTask } from "../../runner/runTask.js";
37
+ import { getConfig } from "../../config.js";
38
+ import { getToolCatalogSnapshot } from "../registry.js";
39
+ import type { ToolHandlerMap } from "./types.js";
40
+ import { toResult } from "./types.js";
41
+
42
+ // ── Local helpers (moved verbatim from registry.ts) ───────────────
43
+
44
+ function normalizeWaitSeconds(args: Record<string, unknown> | undefined): number | undefined {
45
+ const legacy = args?.wait_seconds;
46
+ const preferred = args?.timeout_seconds;
47
+ if (legacy !== undefined && preferred !== undefined && Number(legacy) !== Number(preferred)) {
48
+ throw new Error("wait_seconds and timeout_seconds must match when both are supplied.");
49
+ }
50
+ const value = preferred ?? legacy;
51
+ return value === undefined ? undefined : Number(value);
52
+ }
53
+
54
+ function normalizeSummaryView(value: unknown): "compact" | "standard" {
55
+ if (value === undefined) return "standard";
56
+ if (value !== "compact" && value !== "standard") {
57
+ throw new Error('view must be "compact" or "standard".');
58
+ }
59
+ return value;
60
+ }
61
+
62
+ // ── Handler map ───────────────────────────────────────────────────
63
+
64
+ export const coreHandlers: ToolHandlerMap = {
65
+ save_plan: async (args) => {
66
+ return toResult(
67
+ savePlan({
68
+ title: String(args?.title ?? ""),
69
+ content: args?.content !== undefined ? String(args.content) : "",
70
+ plan_ref: args?.plan_ref ? String(args.plan_ref) : undefined,
71
+ }),
72
+ );
73
+ },
74
+
75
+ get_plan: async (args) => {
76
+ return toResult(getPlan({ plan_id: String(args?.plan_id ?? "") }));
77
+ },
78
+
79
+ create_task: async (args) => {
80
+ return toResult(
81
+ await createTask({
82
+ plan_id: args?.plan_id ? String(args.plan_id) : undefined,
83
+ inline_plan: args?.inline_plan ? String(args.inline_plan) : undefined,
84
+ plan_title: args?.plan_title ? String(args.plan_title) : undefined,
85
+ template: args?.template ? (String(args.template) as any) : undefined,
86
+ goal: args?.goal ? String(args.goal) : undefined,
87
+ source_task_id: args?.source_task_id ? String(args.source_task_id) : undefined,
88
+ agent: String(args?.agent ?? ""),
89
+ repo_path: args?.repo_path ? String(args.repo_path) : undefined,
90
+ test_command: args?.test_command ? String(args.test_command) : undefined,
91
+ verify_commands: Array.isArray(args?.verify_commands)
92
+ ? args.verify_commands.map((command) => String(command))
93
+ : undefined,
94
+ timeout_seconds:
95
+ args?.timeout_seconds !== undefined ? Number(args.timeout_seconds) : undefined,
96
+ execution_mode: args?.execution_mode === "assess_only" ? "assess_only" : "execute",
97
+ assessment_id: args?.assessment_id ? String(args.assessment_id) : undefined,
98
+ }),
99
+ );
100
+ },
101
+
102
+ run_task_loop: async (args) => {
103
+ return toResult(
104
+ await runTaskLoop({
105
+ repo_path: String(args?.repo_path ?? ""),
106
+ goal: String(args?.goal ?? ""),
107
+ verify_commands: Array.isArray(args?.verify_commands)
108
+ ? args.verify_commands.map((command) => String(command))
109
+ : [],
110
+ agent: args?.agent ? String(args.agent) : undefined,
111
+ template:
112
+ args?.template === "inspect_only" || args?.template === "release_check"
113
+ ? args.template
114
+ : "feature_small",
115
+ max_iterations: args?.max_iterations !== undefined ? Number(args.max_iterations) : undefined,
116
+ task_timeout_seconds:
117
+ args?.task_timeout_seconds !== undefined ? Number(args.task_timeout_seconds) : undefined,
118
+ auto_fix_tests: args?.auto_fix_tests !== undefined ? Boolean(args.auto_fix_tests) : undefined,
119
+ auto_cleanup_artifacts:
120
+ args?.auto_cleanup_artifacts !== undefined
121
+ ? Boolean(args.auto_cleanup_artifacts)
122
+ : undefined,
123
+ stop_on_high_risk:
124
+ args?.stop_on_high_risk !== undefined ? Boolean(args.stop_on_high_risk) : undefined,
125
+ direct_verify: args?.direct_verify !== undefined ? Boolean(args.direct_verify) : undefined,
126
+ direct_verify_commands: Array.isArray(args?.direct_verify_commands)
127
+ ? args.direct_verify_commands.map((command) => String(command))
128
+ : undefined,
129
+ direct_verify_timeout_seconds:
130
+ args?.direct_verify_timeout_seconds !== undefined
131
+ ? Number(args.direct_verify_timeout_seconds)
132
+ : undefined,
133
+ scope_files: Array.isArray(args?.scope_files)
134
+ ? args.scope_files.map((entry) => String(entry))
135
+ : undefined,
136
+ isolation_mode: args?.isolation_mode === "worktree" ? "worktree" : "current_repo",
137
+ worktree_base_branch: args?.worktree_base_branch
138
+ ? String(args.worktree_base_branch)
139
+ : undefined,
140
+ worktree_cleanup:
141
+ args?.worktree_cleanup === "archive" || args?.worktree_cleanup === "delete_ignored_only"
142
+ ? args.worktree_cleanup
143
+ : "keep",
144
+ }),
145
+ );
146
+ },
147
+
148
+ recommend_agent_for_task: async (args) => {
149
+ return toResult(
150
+ recommendAgentForTask({
151
+ repo_path: String(args?.repo_path ?? ""),
152
+ goal: String(args?.goal ?? ""),
153
+ scope_files: Array.isArray(args?.scope_files)
154
+ ? args.scope_files.map((entry) => String(entry))
155
+ : undefined,
156
+ template: args?.template ? String(args.template) : undefined,
157
+ risk_hint: args?.risk_hint ? String(args.risk_hint) : undefined,
158
+ }),
159
+ );
160
+ },
161
+
162
+ get_task_lineage: async (args) => {
163
+ return toResult(
164
+ getTaskLineage(String(args?.lineage_id ?? ""), {
165
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
166
+ }),
167
+ );
168
+ },
169
+
170
+ export_task_evidence_pack: async (args) => {
171
+ return toResult(
172
+ exportTaskEvidencePack({
173
+ lineage_id: String(args?.lineage_id ?? ""),
174
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
175
+ }),
176
+ );
177
+ },
178
+
179
+ get_project_policy: async (args) => {
180
+ return toResult(getProjectPolicyTool(String(args?.repo_path ?? "")));
181
+ },
182
+
183
+ get_task_status: async (args) => {
184
+ return toResult(getTaskStatus(String(args?.task_id ?? "")));
185
+ },
186
+
187
+ get_result: async (args) => {
188
+ return toResult(getResult(String(args?.task_id ?? "")));
189
+ },
190
+
191
+ get_result_json: async (args) => {
192
+ return toResult(getResultJson(String(args?.task_id ?? "")));
193
+ },
194
+
195
+ get_diff: async (args) => {
196
+ return toResult(getDiff(String(args?.task_id ?? "")));
197
+ },
198
+
199
+ get_test_log: async (args) => {
200
+ return toResult(getTestLog(String(args?.task_id ?? "")));
201
+ },
202
+
203
+ list_workspace: async (args) => {
204
+ return toResult(listWorkspace(args?.path ? String(args.path) : undefined));
205
+ },
206
+
207
+ read_workspace_file: async (args) => {
208
+ const sessionId = args?.session_id ? String(args.session_id) : undefined;
209
+ return toResult(
210
+ readWorkspaceFile({
211
+ path: String(args?.path ?? ""),
212
+ session_id: sessionId,
213
+ }),
214
+ );
215
+ },
216
+
217
+ list_tasks: async (args) => {
218
+ return toResult(
219
+ listTasks({
220
+ status: args?.status ? String(args.status) : undefined,
221
+ repo_path: args?.repo_path ? String(args.repo_path) : undefined,
222
+ active_only: args?.active_only !== undefined ? Boolean(args.active_only) : undefined,
223
+ limit: args?.limit ? Number(args.limit) : undefined,
224
+ }),
225
+ );
226
+ },
227
+
228
+ list_agents: async () => {
229
+ return toResult(listAgents());
230
+ },
231
+
232
+ health_check: async (args) => {
233
+ return toResult(
234
+ healthCheck(getToolCatalogSnapshot(), {
235
+ detail: args?.detail === "self_diagnostic" ? "self_diagnostic" : "standard",
236
+ }),
237
+ );
238
+ },
239
+
240
+ cancel_task: async (args) => {
241
+ return toResult(cancelTask(String(args?.task_id ?? "")));
242
+ },
243
+
244
+ kill_task: async (args) => {
245
+ return toResult(killTask(String(args?.task_id ?? "")));
246
+ },
247
+
248
+ retry_task: async (args) => {
249
+ return toResult(await retryTask(String(args?.task_id ?? "")));
250
+ },
251
+
252
+ get_task_stdout_tail: async (args) => {
253
+ return toResult(
254
+ getTaskStdoutTail(String(args?.task_id ?? ""), args?.lines ? Number(args.lines) : undefined),
255
+ );
256
+ },
257
+
258
+ get_task_log_tail: async (args) => {
259
+ return toResult(
260
+ getTaskLogTail(
261
+ String(args?.task_id ?? ""),
262
+ (args?.file as "stdout" | "stderr" | "test" | "verify") || "stdout",
263
+ {
264
+ lines: args?.lines ? Number(args.lines) : undefined,
265
+ redact: args?.redact !== undefined ? Boolean(args.redact) : undefined,
266
+ },
267
+ ),
268
+ );
269
+ },
270
+
271
+ get_task_progress: async (args) => {
272
+ return toResult(getTaskProgress(String(args?.task_id ?? "")));
273
+ },
274
+
275
+ wait_for_task: async (args) => {
276
+ const waitSeconds = normalizeWaitSeconds(args);
277
+ return toResult(await waitForTask(String(args?.task_id ?? ""), waitSeconds));
278
+ },
279
+
280
+ get_task_summary: async (args) => {
281
+ return toResult(
282
+ getTaskSummary(String(args?.task_id ?? ""), {
283
+ view: normalizeSummaryView(args?.view),
284
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
285
+ }),
286
+ );
287
+ },
288
+
289
+ audit_task: async (args) => {
290
+ return toResult(auditTask(String(args?.task_id ?? "")));
291
+ },
292
+
293
+ safe_status: async (args) => {
294
+ return toResult(safeStatus(String(args?.task_id ?? "")));
295
+ },
296
+
297
+ safe_result: async (args) => {
298
+ return toResult(
299
+ safeResult(String(args?.task_id ?? ""), {
300
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
301
+ }),
302
+ );
303
+ },
304
+
305
+ safe_audit: async (args) => {
306
+ return toResult(
307
+ safeAudit(String(args?.task_id ?? ""), {
308
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
309
+ }),
310
+ );
311
+ },
312
+
313
+ safe_test_summary: async (args) => {
314
+ return toResult(safeTestSummary(String(args?.task_id ?? "")));
315
+ },
316
+
317
+ safe_diff_summary: async (args) => {
318
+ return toResult(
319
+ safeDiffSummary(String(args?.task_id ?? ""), {
320
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
321
+ }),
322
+ );
323
+ },
324
+
325
+ diagnose_task: async (args) => {
326
+ return toResult(
327
+ diagnoseTask({
328
+ task_id: String(args?.task_id ?? ""),
329
+ include_logs: args?.include_logs !== undefined ? Boolean(args.include_logs) : undefined,
330
+ }),
331
+ );
332
+ },
333
+
334
+ reconcile_tasks: async (args) => {
335
+ return toResult(
336
+ reconcileTasks({
337
+ mode: args?.mode === "safe_fix" ? "safe_fix" : "report_only",
338
+ max_age_minutes:
339
+ args?.max_age_minutes !== undefined ? Number(args.max_age_minutes) : undefined,
340
+ include_done_candidates:
341
+ args?.include_done_candidates !== undefined
342
+ ? Boolean(args.include_done_candidates)
343
+ : undefined,
344
+ }),
345
+ );
346
+ },
347
+
348
+ check_release_gate: async (args) => {
349
+ return toResult(
350
+ await checkReleaseGate({
351
+ repo_path: String(args?.repo_path ?? ""),
352
+ target_stage: String(args?.target_stage ?? "local_ready") as any,
353
+ package_name: args?.package_name ? String(args.package_name) : undefined,
354
+ version: args?.version ? String(args.version) : undefined,
355
+ github_repo: args?.github_repo ? String(args.github_repo) : undefined,
356
+ branch: args?.branch ? String(args.branch) : undefined,
357
+ }),
358
+ );
359
+ },
360
+ };
361
+
362
+ // run_task is conditionally registered (only when enableRunTaskTool === true).
363
+ // Exported separately so registry.ts can add it conditionally to the dispatch map.
364
+ export const runTaskHandler: ToolHandlerMap["run_task"] = async (args) => {
365
+ const config = getConfig();
366
+ if (config.enableRunTaskTool !== true) {
367
+ throw new Error(
368
+ "run_task is disabled. Set enableRunTaskTool: true in config to enable. Prefer using the local watcher (npm run watch).",
369
+ );
370
+ }
371
+ const taskId = String(args?.task_id ?? "");
372
+ const result = await runTask(taskId);
373
+ return toResult(result);
374
+ };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Dispatch handlers for diagnostic / discovery tools (v0.7.1+).
3
+ *
4
+ * discover_tools and explain_tool are read-only catalog queries.
5
+ * invoke_discovered_tool consumes a discovery token and dispatches to
6
+ * the target tool via handleToolCall.
7
+ *
8
+ * Note: This module imports getToolDefs and handleToolCall from
9
+ * registry.ts. The import is a live ESM binding resolved at runtime,
10
+ * so the circular dependency (registry → diagnosticDispatch → registry)
11
+ * is safe: neither binding is touched during module evaluation.
12
+ */
13
+
14
+ import { discoverTools } from "../discoverTools.js";
15
+ import { explainTool } from "../explainTool.js";
16
+ import { invokeDiscoveredTool } from "../invokeDiscoveredTool.js";
17
+ import { getConfig } from "../../config.js";
18
+ import { resolveToolProfile } from "../toolCatalog.js";
19
+ import { getToolDefs, handleToolCall } from "../registry.js";
20
+ import type { ToolHandlerMap } from "./types.js";
21
+ import { toResult } from "./types.js";
22
+
23
+ export const diagnosticHandlers: ToolHandlerMap = {
24
+ discover_tools: async (args) => {
25
+ const profile =
26
+ args?.profile === "full" ||
27
+ args?.profile === "chatgpt_core" ||
28
+ args?.profile === "chatgpt_direct" ||
29
+ args?.profile === "chatgpt_search"
30
+ ? args.profile
31
+ : undefined;
32
+ const mode =
33
+ args?.mode === "delegate" ||
34
+ args?.mode === "direct" ||
35
+ args?.mode === "audit" ||
36
+ args?.mode === "release" ||
37
+ args?.mode === "diagnostic"
38
+ ? args.mode
39
+ : undefined;
40
+ const riskCeiling = [
41
+ "readonly",
42
+ "workspace_read_sensitive",
43
+ "workspace_write",
44
+ "command",
45
+ "release",
46
+ "credential_sensitive",
47
+ ].includes(String(args?.riskCeiling ?? ""))
48
+ ? (String(args?.riskCeiling) as any)
49
+ : undefined;
50
+ return toResult(
51
+ discoverTools(
52
+ {
53
+ query: String(args?.query ?? ""),
54
+ profile,
55
+ mode,
56
+ maxResults: args?.maxResults !== undefined ? Number(args.maxResults) : undefined,
57
+ riskCeiling,
58
+ includeHighRisk:
59
+ args?.includeHighRisk !== undefined ? Boolean(args.includeHighRisk) : undefined,
60
+ },
61
+ getToolDefs(),
62
+ ),
63
+ );
64
+ },
65
+
66
+ explain_tool: async (args) => {
67
+ return toResult(
68
+ explainTool(
69
+ {
70
+ name: String(args?.name ?? ""),
71
+ includeSchema:
72
+ args?.includeSchema !== undefined ? Boolean(args.includeSchema) : undefined,
73
+ },
74
+ getToolDefs(),
75
+ ),
76
+ );
77
+ },
78
+
79
+ invoke_discovered_tool: async (args) => {
80
+ const profile = resolveToolProfile(getConfig().toolProfile);
81
+ const result = await invokeDiscoveredTool(
82
+ {
83
+ toolName: String(args?.toolName ?? ""),
84
+ arguments:
85
+ args?.arguments && typeof args.arguments === "object"
86
+ ? (args.arguments as Record<string, unknown>)
87
+ : {},
88
+ discoveryToken: String(args?.discoveryToken ?? ""),
89
+ assessmentId: args?.assessmentId ? String(args.assessmentId) : undefined,
90
+ },
91
+ {
92
+ tools: getToolDefs(),
93
+ profile,
94
+ dispatch: async (name, dispatchArgs) => {
95
+ return handleToolCall(name, dispatchArgs);
96
+ },
97
+ },
98
+ );
99
+ return toResult(result);
100
+ },
101
+ };
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Dispatch handlers for Direct session tools.
3
+ *
4
+ * Direct sessions allow ChatGPT to apply patches directly within a
5
+ * repo-scoped workspace. All handlers guard on enableDirectProfile.
6
+ */
7
+
8
+ import { getConfig } from "../../config.js";
9
+ import { PatchWardenError } from "../../errors.js";
10
+ import { createDirectSession } from "../createDirectSession.js";
11
+ import { searchWorkspace } from "../searchWorkspace.js";
12
+ import { applyPatch } from "../applyPatch.js";
13
+ import { runVerification } from "../runVerification.js";
14
+ import { runDirectVerificationBundle } from "../runDirectVerificationBundle.js";
15
+ import { finalizeDirectSession } from "../finalizeDirectSession.js";
16
+ import { auditSession } from "../auditSession.js";
17
+ import { syncFile } from "../syncFile.js";
18
+ import {
19
+ safeAuditDirectSession,
20
+ safeDirectSummary,
21
+ safeFinalizeDirectSession,
22
+ } from "../safeViews.js";
23
+ import type { ToolHandlerMap } from "./types.js";
24
+ import { toResult } from "./types.js";
25
+
26
+ /** Mirror of guardDirectProfileEnabled from the original registry.ts. */
27
+ function guardDirectProfileEnabled(): void {
28
+ const config = getConfig();
29
+ if (!config.enableDirectProfile) {
30
+ throw new PatchWardenError(
31
+ "direct_profile_disabled",
32
+ "Direct profile is disabled by local config.",
33
+ "Set enableDirectProfile: true in patchwarden.config.json to use Direct session tools.",
34
+ true,
35
+ { operation: "direct_tool_call" },
36
+ );
37
+ }
38
+ }
39
+
40
+ export const directHandlers: ToolHandlerMap = {
41
+ create_direct_session: async (args) => {
42
+ guardDirectProfileEnabled();
43
+ return toResult(
44
+ await createDirectSession({
45
+ repo_path: String(args?.repo_path ?? ""),
46
+ title: args?.title ? String(args.title) : undefined,
47
+ }),
48
+ );
49
+ },
50
+
51
+ search_workspace: async (args) => {
52
+ guardDirectProfileEnabled();
53
+ return toResult(
54
+ searchWorkspace({
55
+ session_id: String(args?.session_id ?? ""),
56
+ query: String(args?.query ?? ""),
57
+ max_results: args?.max_results ? Number(args.max_results) : undefined,
58
+ case_sensitive:
59
+ args?.case_sensitive !== undefined ? Boolean(args.case_sensitive) : undefined,
60
+ max_preview_chars: args?.max_preview_chars ? Number(args.max_preview_chars) : undefined,
61
+ include_globs: Array.isArray(args?.include_globs)
62
+ ? args.include_globs.map(String)
63
+ : undefined,
64
+ }),
65
+ );
66
+ },
67
+
68
+ apply_patch: async (args) => {
69
+ guardDirectProfileEnabled();
70
+ return toResult(
71
+ applyPatch({
72
+ session_id: String(args?.session_id ?? ""),
73
+ path: String(args?.path ?? ""),
74
+ expected_sha256: String(args?.expected_sha256 ?? ""),
75
+ operations: Array.isArray(args?.operations) ? (args.operations as any) : [],
76
+ }),
77
+ );
78
+ },
79
+
80
+ run_verification: async (args) => {
81
+ guardDirectProfileEnabled();
82
+ return toResult(
83
+ await runVerification({
84
+ session_id: String(args?.session_id ?? ""),
85
+ command: String(args?.command ?? ""),
86
+ timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
87
+ }),
88
+ );
89
+ },
90
+
91
+ run_direct_verification_bundle: async (args) => {
92
+ guardDirectProfileEnabled();
93
+ return toResult(
94
+ await runDirectVerificationBundle({
95
+ session_id: String(args?.session_id ?? ""),
96
+ commands: Array.isArray(args?.commands)
97
+ ? args.commands.map((command) => String(command))
98
+ : [],
99
+ timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
100
+ }),
101
+ );
102
+ },
103
+
104
+ finalize_direct_session: async (args) => {
105
+ guardDirectProfileEnabled();
106
+ return toResult(
107
+ await finalizeDirectSession({
108
+ session_id: String(args?.session_id ?? ""),
109
+ }),
110
+ );
111
+ },
112
+
113
+ audit_session: async (args) => {
114
+ guardDirectProfileEnabled();
115
+ return toResult(
116
+ auditSession({
117
+ session_id: String(args?.session_id ?? ""),
118
+ }),
119
+ );
120
+ },
121
+
122
+ safe_direct_summary: async (args) => {
123
+ guardDirectProfileEnabled();
124
+ return toResult(
125
+ safeDirectSummary(String(args?.session_id ?? ""), {
126
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
127
+ }),
128
+ );
129
+ },
130
+
131
+ safe_finalize_direct_session: async (args) => {
132
+ guardDirectProfileEnabled();
133
+ return toResult(
134
+ await safeFinalizeDirectSession(String(args?.session_id ?? ""), {
135
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
136
+ }),
137
+ );
138
+ },
139
+
140
+ safe_audit_direct_session: async (args) => {
141
+ guardDirectProfileEnabled();
142
+ return toResult(
143
+ safeAuditDirectSession(String(args?.session_id ?? ""), {
144
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
145
+ }),
146
+ );
147
+ },
148
+
149
+ sync_file: async (args) => {
150
+ guardDirectProfileEnabled();
151
+ return toResult(
152
+ syncFile(
153
+ String(args?.session_id ?? ""),
154
+ String(args?.source_path ?? ""),
155
+ String(args?.target_path ?? ""),
156
+ {
157
+ expected_source_sha256: args?.expected_source_sha256
158
+ ? String(args.expected_source_sha256)
159
+ : undefined,
160
+ expected_target_sha256: args?.expected_target_sha256
161
+ ? String(args.expected_target_sha256)
162
+ : undefined,
163
+ },
164
+ ),
165
+ );
166
+ },
167
+ };