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
@@ -1,4 +1,3 @@
1
- // @ts-nocheck
2
1
  import { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from "node:fs";
3
2
  import { join, resolve, relative, basename, sep, isAbsolute } from "node:path";
4
3
  import { getTasksDir, getConfig } from "../config.js";
@@ -288,9 +287,10 @@ function readPackageScripts(repoPath) {
288
287
  try {
289
288
  guardSensitivePath(pkgJsonPath);
290
289
  const parsed = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
291
- if (!parsed.scripts || typeof parsed.scripts !== "object" || Array.isArray(parsed.scripts))
290
+ const scripts = parsed.scripts;
291
+ if (!scripts || typeof scripts !== "object" || Array.isArray(scripts))
292
292
  return {};
293
- return Object.fromEntries(Object.entries(parsed.scripts).filter(([, value]) => typeof value === "string"));
293
+ return Object.fromEntries(Object.entries(scripts).filter(([, value]) => typeof value === "string"));
294
294
  }
295
295
  catch {
296
296
  return {};
@@ -370,8 +370,8 @@ function findHighRiskCommandEvidence(text) {
370
370
  return [...new Set(found)];
371
371
  }
372
372
  export function checkUnrecordedCommandExecution(testLogContent, resultMdContent, verifyCommands, testCommand, repoPath = null) {
373
- const hasLog = testLogContent && testLogContent.length > 0;
374
- const hasResult = resultMdContent && resultMdContent.length > 0;
373
+ const hasLog = !!testLogContent && testLogContent.length > 0;
374
+ const hasResult = !!resultMdContent && resultMdContent.length > 0;
375
375
  if (!hasLog && !hasResult)
376
376
  return null;
377
377
  const combined = `${testLogContent || ""}\n${resultMdContent || ""}`;
@@ -381,9 +381,9 @@ export function checkUnrecordedCommandExecution(testLogContent, resultMdContent,
381
381
  }
382
382
  const whitelist = buildCommandWhitelist(verifyCommands, testCommand, repoPath);
383
383
  const discovered = [];
384
- if (hasLog)
384
+ if (hasLog && testLogContent)
385
385
  discovered.push(...extractCommands(testLogContent));
386
- if (hasResult)
386
+ if (hasResult && resultMdContent)
387
387
  discovered.push(...extractCommands(resultMdContent));
388
388
  const unrecorded = new Set();
389
389
  for (const cmd of discovered) {
@@ -804,7 +804,11 @@ export function auditTask(taskId) {
804
804
  return null;
805
805
  try {
806
806
  const v = JSON.parse(readFileSync(verifyJsonFile, "utf-8"));
807
- return v.status === "passed" ? "passed" : v.status === "failed" ? "failed" : "skipped";
807
+ if (v.status === "passed")
808
+ return "passed";
809
+ if (v.status === "failed")
810
+ return "failed";
811
+ return "skipped";
808
812
  }
809
813
  catch {
810
814
  return null;
@@ -11,4 +11,4 @@ export interface CreateDirectSessionOutput {
11
11
  expires_at: string;
12
12
  next_action: string;
13
13
  }
14
- export declare function createDirectSession(input: CreateDirectSessionInput): CreateDirectSessionOutput;
14
+ export declare function createDirectSession(input: CreateDirectSessionInput): Promise<CreateDirectSessionOutput>;
@@ -6,7 +6,7 @@ import { guardRuntimeSelfModification } from "../security/runtimeGuard.js";
6
6
  import { captureRepoSnapshot } from "../runner/changeCapture.js";
7
7
  import { createDirectSession as createDirectSessionRecord, } from "../direct/directSessionStore.js";
8
8
  // ── Tool implementation ────────────────────────────────────────────
9
- export function createDirectSession(input) {
9
+ export async function createDirectSession(input) {
10
10
  const config = getConfig();
11
11
  // ── Validate repo_path ───────────────────────────────────────────
12
12
  if (!input.repo_path || input.repo_path.trim() === "") {
@@ -33,7 +33,7 @@ export function createDirectSession(input) {
33
33
  // ── Runtime self-modification protection ─────────────────────────
34
34
  guardRuntimeSelfModification(resolvedRepoPath);
35
35
  // ── Capture repo snapshot ────────────────────────────────────────
36
- const snapshot = captureRepoSnapshot(resolvedRepoPath);
36
+ const snapshot = await captureRepoSnapshot(resolvedRepoPath);
37
37
  // ── Create session record ────────────────────────────────────────
38
38
  const session = createDirectSessionRecord({
39
39
  repo_path: input.repo_path,
@@ -101,5 +101,5 @@ export interface CreateTaskOutput {
101
101
  }
102
102
  export declare function createTask(input: CreateTaskInput & {
103
103
  execution_mode: "assess_only";
104
- }): AssessOnlyOutput;
105
- export declare function createTask(input: CreateTaskInput): CreateTaskOutput;
104
+ }): Promise<AssessOnlyOutput>;
105
+ export declare function createTask(input: CreateTaskInput): Promise<CreateTaskOutput>;
@@ -18,7 +18,7 @@ import { PATCHWARDEN_VERSION } from "../version.js";
18
18
  import { getLastToolCatalogSnapshot, resolveToolProfile } from "./toolCatalog.js";
19
19
  import { derivePendingReason, readWatcherStatus, } from "../watcherStatus.js";
20
20
  import { routeAgent } from "../agents/agentRouter.js";
21
- export function createTask(input) {
21
+ export async function createTask(input) {
22
22
  const config = getConfig();
23
23
  const tasksDir = getTasksDir(config);
24
24
  const plansDir = getPlansDir(config);
@@ -151,7 +151,7 @@ export function createTask(input) {
151
151
  planBlocked = true;
152
152
  planBlockReason = e instanceof PatchWardenError ? e.reason : "plan_content_blocked";
153
153
  }
154
- const snapshot = captureRepoSnapshot(safeRepoPath);
154
+ const snapshot = await captureRepoSnapshot(safeRepoPath);
155
155
  const snapshotTruncated = snapshot.warnings.some((w) => w.includes("snapshot limited"));
156
156
  let riskResult;
157
157
  if (planBlocked) {
@@ -208,7 +208,7 @@ export function createTask(input) {
208
208
  preGeneratedAssessmentId = generateAssessmentId();
209
209
  preGeneratedAssessmentDir = createAssessmentDir(preGeneratedAssessmentId);
210
210
  const assessorAgentName = config.agentAssessmentAgentName || effectiveInput.agent;
211
- agentAssessmentSummary = runAgentAssessment({
211
+ agentAssessmentSummary = await runAgentAssessment({
212
212
  assessmentId: preGeneratedAssessmentId,
213
213
  assessmentDir: preGeneratedAssessmentDir,
214
214
  agentName: assessorAgentName,
@@ -330,7 +330,7 @@ export function createTask(input) {
330
330
  }
331
331
  // ── execute mode with assessment_id: validate freshness ──
332
332
  if (assessmentRecord) {
333
- const snapshot = captureRepoSnapshot(safeRepoPath);
333
+ const snapshot = await captureRepoSnapshot(safeRepoPath);
334
334
  const validation = validateAssessmentFreshness(input.assessment_id, snapshot);
335
335
  if (!validation.valid) {
336
336
  throw new PatchWardenError(validation.failure_reason || "assessment_validation_failed", `Assessment "${input.assessment_id}" is no longer valid: ${validation.failure_reason}`, "Re-run create_task with execution_mode=assess_only to get a fresh assessment_id.", true, { assessment_id: input.assessment_id, failure_reason: validation.failure_reason });
@@ -0,0 +1,9 @@
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
+ import type { ToolHandlerMap } from "./types.js";
8
+ export declare const coreHandlers: ToolHandlerMap;
9
+ export declare const runTaskHandler: ToolHandlerMap["run_task"];
@@ -0,0 +1,282 @@
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
+ import { savePlan } from "../savePlan.js";
8
+ import { getPlan } from "../getPlan.js";
9
+ import { createTask } from "../createTask.js";
10
+ import { getTaskStatus } from "../getTaskStatus.js";
11
+ import { getResult, getResultJson, getDiff, getTestLog, getTaskLogTail } from "../taskOutputs.js";
12
+ import { listWorkspace } from "../listWorkspace.js";
13
+ import { readWorkspaceFile } from "../readWorkspaceFile.js";
14
+ import { listTasks } from "../listTasks.js";
15
+ import { cancelTask } from "../cancelTask.js";
16
+ import { killTask } from "../killTask.js";
17
+ import { retryTask } from "../retryTask.js";
18
+ import { getTaskStdoutTail } from "../getTaskStdoutTail.js";
19
+ import { getTaskProgress } from "../getTaskProgress.js";
20
+ import { listAgents } from "../listAgents.js";
21
+ import { healthCheck } from "../healthCheck.js";
22
+ import { getTaskSummary } from "../getTaskSummary.js";
23
+ import { waitForTask } from "../waitForTask.js";
24
+ import { runTaskLoop } from "../runTaskLoop.js";
25
+ import { getTaskLineage } from "../taskLineage.js";
26
+ import { exportTaskEvidencePack } from "../evidencePack.js";
27
+ import { recommendAgentForTask } from "../recommendAgentForTask.js";
28
+ import { auditTask } from "../auditTask.js";
29
+ import { safeStatus } from "../safeStatus.js";
30
+ import { safeAudit, safeDiffSummary, safeResult, safeTestSummary } from "../safeViews.js";
31
+ import { diagnoseTask } from "../diagnoseTask.js";
32
+ import { reconcileTasks } from "../reconcileTasks.js";
33
+ import { checkReleaseGate } from "../checkReleaseGate.js";
34
+ import { getProjectPolicyTool } from "../releaseMode.js";
35
+ import { runTask } from "../../runner/runTask.js";
36
+ import { getConfig } from "../../config.js";
37
+ import { getToolCatalogSnapshot } from "../registry.js";
38
+ import { toResult } from "./types.js";
39
+ // ── Local helpers (moved verbatim from registry.ts) ───────────────
40
+ function normalizeWaitSeconds(args) {
41
+ const legacy = args?.wait_seconds;
42
+ const preferred = args?.timeout_seconds;
43
+ if (legacy !== undefined && preferred !== undefined && Number(legacy) !== Number(preferred)) {
44
+ throw new Error("wait_seconds and timeout_seconds must match when both are supplied.");
45
+ }
46
+ const value = preferred ?? legacy;
47
+ return value === undefined ? undefined : Number(value);
48
+ }
49
+ function normalizeSummaryView(value) {
50
+ if (value === undefined)
51
+ return "standard";
52
+ if (value !== "compact" && value !== "standard") {
53
+ throw new Error('view must be "compact" or "standard".');
54
+ }
55
+ return value;
56
+ }
57
+ // ── Handler map ───────────────────────────────────────────────────
58
+ export const coreHandlers = {
59
+ save_plan: async (args) => {
60
+ return toResult(savePlan({
61
+ title: String(args?.title ?? ""),
62
+ content: args?.content !== undefined ? String(args.content) : "",
63
+ plan_ref: args?.plan_ref ? String(args.plan_ref) : undefined,
64
+ }));
65
+ },
66
+ get_plan: async (args) => {
67
+ return toResult(getPlan({ plan_id: String(args?.plan_id ?? "") }));
68
+ },
69
+ create_task: async (args) => {
70
+ return toResult(await createTask({
71
+ plan_id: args?.plan_id ? String(args.plan_id) : undefined,
72
+ inline_plan: args?.inline_plan ? String(args.inline_plan) : undefined,
73
+ plan_title: args?.plan_title ? String(args.plan_title) : undefined,
74
+ template: args?.template ? String(args.template) : undefined,
75
+ goal: args?.goal ? String(args.goal) : undefined,
76
+ source_task_id: args?.source_task_id ? String(args.source_task_id) : undefined,
77
+ agent: String(args?.agent ?? ""),
78
+ repo_path: args?.repo_path ? String(args.repo_path) : undefined,
79
+ test_command: args?.test_command ? String(args.test_command) : undefined,
80
+ verify_commands: Array.isArray(args?.verify_commands)
81
+ ? args.verify_commands.map((command) => String(command))
82
+ : undefined,
83
+ timeout_seconds: args?.timeout_seconds !== undefined ? Number(args.timeout_seconds) : undefined,
84
+ execution_mode: args?.execution_mode === "assess_only" ? "assess_only" : "execute",
85
+ assessment_id: args?.assessment_id ? String(args.assessment_id) : undefined,
86
+ }));
87
+ },
88
+ run_task_loop: async (args) => {
89
+ return toResult(await runTaskLoop({
90
+ repo_path: String(args?.repo_path ?? ""),
91
+ goal: String(args?.goal ?? ""),
92
+ verify_commands: Array.isArray(args?.verify_commands)
93
+ ? args.verify_commands.map((command) => String(command))
94
+ : [],
95
+ agent: args?.agent ? String(args.agent) : undefined,
96
+ template: args?.template === "inspect_only" || args?.template === "release_check"
97
+ ? args.template
98
+ : "feature_small",
99
+ max_iterations: args?.max_iterations !== undefined ? Number(args.max_iterations) : undefined,
100
+ task_timeout_seconds: args?.task_timeout_seconds !== undefined ? Number(args.task_timeout_seconds) : undefined,
101
+ auto_fix_tests: args?.auto_fix_tests !== undefined ? Boolean(args.auto_fix_tests) : undefined,
102
+ auto_cleanup_artifacts: args?.auto_cleanup_artifacts !== undefined
103
+ ? Boolean(args.auto_cleanup_artifacts)
104
+ : undefined,
105
+ stop_on_high_risk: args?.stop_on_high_risk !== undefined ? Boolean(args.stop_on_high_risk) : undefined,
106
+ direct_verify: args?.direct_verify !== undefined ? Boolean(args.direct_verify) : undefined,
107
+ direct_verify_commands: Array.isArray(args?.direct_verify_commands)
108
+ ? args.direct_verify_commands.map((command) => String(command))
109
+ : undefined,
110
+ direct_verify_timeout_seconds: args?.direct_verify_timeout_seconds !== undefined
111
+ ? Number(args.direct_verify_timeout_seconds)
112
+ : undefined,
113
+ scope_files: Array.isArray(args?.scope_files)
114
+ ? args.scope_files.map((entry) => String(entry))
115
+ : undefined,
116
+ isolation_mode: args?.isolation_mode === "worktree" ? "worktree" : "current_repo",
117
+ worktree_base_branch: args?.worktree_base_branch
118
+ ? String(args.worktree_base_branch)
119
+ : undefined,
120
+ worktree_cleanup: args?.worktree_cleanup === "archive" || args?.worktree_cleanup === "delete_ignored_only"
121
+ ? args.worktree_cleanup
122
+ : "keep",
123
+ }));
124
+ },
125
+ recommend_agent_for_task: async (args) => {
126
+ return toResult(recommendAgentForTask({
127
+ repo_path: String(args?.repo_path ?? ""),
128
+ goal: String(args?.goal ?? ""),
129
+ scope_files: Array.isArray(args?.scope_files)
130
+ ? args.scope_files.map((entry) => String(entry))
131
+ : undefined,
132
+ template: args?.template ? String(args.template) : undefined,
133
+ risk_hint: args?.risk_hint ? String(args.risk_hint) : undefined,
134
+ }));
135
+ },
136
+ get_task_lineage: async (args) => {
137
+ return toResult(getTaskLineage(String(args?.lineage_id ?? ""), {
138
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
139
+ }));
140
+ },
141
+ export_task_evidence_pack: async (args) => {
142
+ return toResult(exportTaskEvidencePack({
143
+ lineage_id: String(args?.lineage_id ?? ""),
144
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
145
+ }));
146
+ },
147
+ get_project_policy: async (args) => {
148
+ return toResult(getProjectPolicyTool(String(args?.repo_path ?? "")));
149
+ },
150
+ get_task_status: async (args) => {
151
+ return toResult(getTaskStatus(String(args?.task_id ?? "")));
152
+ },
153
+ get_result: async (args) => {
154
+ return toResult(getResult(String(args?.task_id ?? "")));
155
+ },
156
+ get_result_json: async (args) => {
157
+ return toResult(getResultJson(String(args?.task_id ?? "")));
158
+ },
159
+ get_diff: async (args) => {
160
+ return toResult(getDiff(String(args?.task_id ?? "")));
161
+ },
162
+ get_test_log: async (args) => {
163
+ return toResult(getTestLog(String(args?.task_id ?? "")));
164
+ },
165
+ list_workspace: async (args) => {
166
+ return toResult(listWorkspace(args?.path ? String(args.path) : undefined));
167
+ },
168
+ read_workspace_file: async (args) => {
169
+ const sessionId = args?.session_id ? String(args.session_id) : undefined;
170
+ return toResult(readWorkspaceFile({
171
+ path: String(args?.path ?? ""),
172
+ session_id: sessionId,
173
+ }));
174
+ },
175
+ list_tasks: async (args) => {
176
+ return toResult(listTasks({
177
+ status: args?.status ? String(args.status) : undefined,
178
+ repo_path: args?.repo_path ? String(args.repo_path) : undefined,
179
+ active_only: args?.active_only !== undefined ? Boolean(args.active_only) : undefined,
180
+ limit: args?.limit ? Number(args.limit) : undefined,
181
+ }));
182
+ },
183
+ list_agents: async () => {
184
+ return toResult(listAgents());
185
+ },
186
+ health_check: async (args) => {
187
+ return toResult(healthCheck(getToolCatalogSnapshot(), {
188
+ detail: args?.detail === "self_diagnostic" ? "self_diagnostic" : "standard",
189
+ }));
190
+ },
191
+ cancel_task: async (args) => {
192
+ return toResult(cancelTask(String(args?.task_id ?? "")));
193
+ },
194
+ kill_task: async (args) => {
195
+ return toResult(killTask(String(args?.task_id ?? "")));
196
+ },
197
+ retry_task: async (args) => {
198
+ return toResult(await retryTask(String(args?.task_id ?? "")));
199
+ },
200
+ get_task_stdout_tail: async (args) => {
201
+ return toResult(getTaskStdoutTail(String(args?.task_id ?? ""), args?.lines ? Number(args.lines) : undefined));
202
+ },
203
+ get_task_log_tail: async (args) => {
204
+ return toResult(getTaskLogTail(String(args?.task_id ?? ""), args?.file || "stdout", {
205
+ lines: args?.lines ? Number(args.lines) : undefined,
206
+ redact: args?.redact !== undefined ? Boolean(args.redact) : undefined,
207
+ }));
208
+ },
209
+ get_task_progress: async (args) => {
210
+ return toResult(getTaskProgress(String(args?.task_id ?? "")));
211
+ },
212
+ wait_for_task: async (args) => {
213
+ const waitSeconds = normalizeWaitSeconds(args);
214
+ return toResult(await waitForTask(String(args?.task_id ?? ""), waitSeconds));
215
+ },
216
+ get_task_summary: async (args) => {
217
+ return toResult(getTaskSummary(String(args?.task_id ?? ""), {
218
+ view: normalizeSummaryView(args?.view),
219
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
220
+ }));
221
+ },
222
+ audit_task: async (args) => {
223
+ return toResult(auditTask(String(args?.task_id ?? "")));
224
+ },
225
+ safe_status: async (args) => {
226
+ return toResult(safeStatus(String(args?.task_id ?? "")));
227
+ },
228
+ safe_result: async (args) => {
229
+ return toResult(safeResult(String(args?.task_id ?? ""), {
230
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
231
+ }));
232
+ },
233
+ safe_audit: async (args) => {
234
+ return toResult(safeAudit(String(args?.task_id ?? ""), {
235
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
236
+ }));
237
+ },
238
+ safe_test_summary: async (args) => {
239
+ return toResult(safeTestSummary(String(args?.task_id ?? "")));
240
+ },
241
+ safe_diff_summary: async (args) => {
242
+ return toResult(safeDiffSummary(String(args?.task_id ?? ""), {
243
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
244
+ }));
245
+ },
246
+ diagnose_task: async (args) => {
247
+ return toResult(diagnoseTask({
248
+ task_id: String(args?.task_id ?? ""),
249
+ include_logs: args?.include_logs !== undefined ? Boolean(args.include_logs) : undefined,
250
+ }));
251
+ },
252
+ reconcile_tasks: async (args) => {
253
+ return toResult(reconcileTasks({
254
+ mode: args?.mode === "safe_fix" ? "safe_fix" : "report_only",
255
+ max_age_minutes: args?.max_age_minutes !== undefined ? Number(args.max_age_minutes) : undefined,
256
+ include_done_candidates: args?.include_done_candidates !== undefined
257
+ ? Boolean(args.include_done_candidates)
258
+ : undefined,
259
+ }));
260
+ },
261
+ check_release_gate: async (args) => {
262
+ return toResult(await checkReleaseGate({
263
+ repo_path: String(args?.repo_path ?? ""),
264
+ target_stage: String(args?.target_stage ?? "local_ready"),
265
+ package_name: args?.package_name ? String(args.package_name) : undefined,
266
+ version: args?.version ? String(args.version) : undefined,
267
+ github_repo: args?.github_repo ? String(args.github_repo) : undefined,
268
+ branch: args?.branch ? String(args.branch) : undefined,
269
+ }));
270
+ },
271
+ };
272
+ // run_task is conditionally registered (only when enableRunTaskTool === true).
273
+ // Exported separately so registry.ts can add it conditionally to the dispatch map.
274
+ export const runTaskHandler = async (args) => {
275
+ const config = getConfig();
276
+ if (config.enableRunTaskTool !== true) {
277
+ throw new Error("run_task is disabled. Set enableRunTaskTool: true in config to enable. Prefer using the local watcher (npm run watch).");
278
+ }
279
+ const taskId = String(args?.task_id ?? "");
280
+ const result = await runTask(taskId);
281
+ return toResult(result);
282
+ };
@@ -0,0 +1,14 @@
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
+ import type { ToolHandlerMap } from "./types.js";
14
+ export declare const diagnosticHandlers: ToolHandlerMap;
@@ -0,0 +1,78 @@
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
+ import { discoverTools } from "../discoverTools.js";
14
+ import { explainTool } from "../explainTool.js";
15
+ import { invokeDiscoveredTool } from "../invokeDiscoveredTool.js";
16
+ import { getConfig } from "../../config.js";
17
+ import { resolveToolProfile } from "../toolCatalog.js";
18
+ import { getToolDefs, handleToolCall } from "../registry.js";
19
+ import { toResult } from "./types.js";
20
+ export const diagnosticHandlers = {
21
+ discover_tools: async (args) => {
22
+ const profile = args?.profile === "full" ||
23
+ args?.profile === "chatgpt_core" ||
24
+ args?.profile === "chatgpt_direct" ||
25
+ args?.profile === "chatgpt_search"
26
+ ? args.profile
27
+ : undefined;
28
+ const mode = args?.mode === "delegate" ||
29
+ args?.mode === "direct" ||
30
+ args?.mode === "audit" ||
31
+ args?.mode === "release" ||
32
+ args?.mode === "diagnostic"
33
+ ? args.mode
34
+ : undefined;
35
+ const riskCeiling = [
36
+ "readonly",
37
+ "workspace_read_sensitive",
38
+ "workspace_write",
39
+ "command",
40
+ "release",
41
+ "credential_sensitive",
42
+ ].includes(String(args?.riskCeiling ?? ""))
43
+ ? String(args?.riskCeiling)
44
+ : undefined;
45
+ return toResult(discoverTools({
46
+ query: String(args?.query ?? ""),
47
+ profile,
48
+ mode,
49
+ maxResults: args?.maxResults !== undefined ? Number(args.maxResults) : undefined,
50
+ riskCeiling,
51
+ includeHighRisk: args?.includeHighRisk !== undefined ? Boolean(args.includeHighRisk) : undefined,
52
+ }, getToolDefs()));
53
+ },
54
+ explain_tool: async (args) => {
55
+ return toResult(explainTool({
56
+ name: String(args?.name ?? ""),
57
+ includeSchema: args?.includeSchema !== undefined ? Boolean(args.includeSchema) : undefined,
58
+ }, getToolDefs()));
59
+ },
60
+ invoke_discovered_tool: async (args) => {
61
+ const profile = resolveToolProfile(getConfig().toolProfile);
62
+ const result = await invokeDiscoveredTool({
63
+ toolName: String(args?.toolName ?? ""),
64
+ arguments: args?.arguments && typeof args.arguments === "object"
65
+ ? args.arguments
66
+ : {},
67
+ discoveryToken: String(args?.discoveryToken ?? ""),
68
+ assessmentId: args?.assessmentId ? String(args.assessmentId) : undefined,
69
+ }, {
70
+ tools: getToolDefs(),
71
+ profile,
72
+ dispatch: async (name, dispatchArgs) => {
73
+ return handleToolCall(name, dispatchArgs);
74
+ },
75
+ });
76
+ return toResult(result);
77
+ },
78
+ };
@@ -0,0 +1,8 @@
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
+ import type { ToolHandlerMap } from "./types.js";
8
+ export declare const directHandlers: ToolHandlerMap;
@@ -0,0 +1,115 @@
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
+ import { getConfig } from "../../config.js";
8
+ import { PatchWardenError } from "../../errors.js";
9
+ import { createDirectSession } from "../createDirectSession.js";
10
+ import { searchWorkspace } from "../searchWorkspace.js";
11
+ import { applyPatch } from "../applyPatch.js";
12
+ import { runVerification } from "../runVerification.js";
13
+ import { runDirectVerificationBundle } from "../runDirectVerificationBundle.js";
14
+ import { finalizeDirectSession } from "../finalizeDirectSession.js";
15
+ import { auditSession } from "../auditSession.js";
16
+ import { syncFile } from "../syncFile.js";
17
+ import { safeAuditDirectSession, safeDirectSummary, safeFinalizeDirectSession, } from "../safeViews.js";
18
+ import { toResult } from "./types.js";
19
+ /** Mirror of guardDirectProfileEnabled from the original registry.ts. */
20
+ function guardDirectProfileEnabled() {
21
+ const config = getConfig();
22
+ if (!config.enableDirectProfile) {
23
+ throw new PatchWardenError("direct_profile_disabled", "Direct profile is disabled by local config.", "Set enableDirectProfile: true in patchwarden.config.json to use Direct session tools.", true, { operation: "direct_tool_call" });
24
+ }
25
+ }
26
+ export const directHandlers = {
27
+ create_direct_session: async (args) => {
28
+ guardDirectProfileEnabled();
29
+ return toResult(await createDirectSession({
30
+ repo_path: String(args?.repo_path ?? ""),
31
+ title: args?.title ? String(args.title) : undefined,
32
+ }));
33
+ },
34
+ search_workspace: async (args) => {
35
+ guardDirectProfileEnabled();
36
+ return toResult(searchWorkspace({
37
+ session_id: String(args?.session_id ?? ""),
38
+ query: String(args?.query ?? ""),
39
+ max_results: args?.max_results ? Number(args.max_results) : undefined,
40
+ case_sensitive: args?.case_sensitive !== undefined ? Boolean(args.case_sensitive) : undefined,
41
+ max_preview_chars: args?.max_preview_chars ? Number(args.max_preview_chars) : undefined,
42
+ include_globs: Array.isArray(args?.include_globs)
43
+ ? args.include_globs.map(String)
44
+ : undefined,
45
+ }));
46
+ },
47
+ apply_patch: async (args) => {
48
+ guardDirectProfileEnabled();
49
+ return toResult(applyPatch({
50
+ session_id: String(args?.session_id ?? ""),
51
+ path: String(args?.path ?? ""),
52
+ expected_sha256: String(args?.expected_sha256 ?? ""),
53
+ operations: Array.isArray(args?.operations) ? args.operations : [],
54
+ }));
55
+ },
56
+ run_verification: async (args) => {
57
+ guardDirectProfileEnabled();
58
+ return toResult(await runVerification({
59
+ session_id: String(args?.session_id ?? ""),
60
+ command: String(args?.command ?? ""),
61
+ timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
62
+ }));
63
+ },
64
+ run_direct_verification_bundle: async (args) => {
65
+ guardDirectProfileEnabled();
66
+ return toResult(await runDirectVerificationBundle({
67
+ session_id: String(args?.session_id ?? ""),
68
+ commands: Array.isArray(args?.commands)
69
+ ? args.commands.map((command) => String(command))
70
+ : [],
71
+ timeout_seconds: args?.timeout_seconds ? Number(args.timeout_seconds) : undefined,
72
+ }));
73
+ },
74
+ finalize_direct_session: async (args) => {
75
+ guardDirectProfileEnabled();
76
+ return toResult(await finalizeDirectSession({
77
+ session_id: String(args?.session_id ?? ""),
78
+ }));
79
+ },
80
+ audit_session: async (args) => {
81
+ guardDirectProfileEnabled();
82
+ return toResult(auditSession({
83
+ session_id: String(args?.session_id ?? ""),
84
+ }));
85
+ },
86
+ safe_direct_summary: async (args) => {
87
+ guardDirectProfileEnabled();
88
+ return toResult(safeDirectSummary(String(args?.session_id ?? ""), {
89
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
90
+ }));
91
+ },
92
+ safe_finalize_direct_session: async (args) => {
93
+ guardDirectProfileEnabled();
94
+ return toResult(await safeFinalizeDirectSession(String(args?.session_id ?? ""), {
95
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
96
+ }));
97
+ },
98
+ safe_audit_direct_session: async (args) => {
99
+ guardDirectProfileEnabled();
100
+ return toResult(safeAuditDirectSession(String(args?.session_id ?? ""), {
101
+ max_items: args?.max_items !== undefined ? Number(args.max_items) : undefined,
102
+ }));
103
+ },
104
+ sync_file: async (args) => {
105
+ guardDirectProfileEnabled();
106
+ return toResult(syncFile(String(args?.session_id ?? ""), String(args?.source_path ?? ""), String(args?.target_path ?? ""), {
107
+ expected_source_sha256: args?.expected_source_sha256
108
+ ? String(args.expected_source_sha256)
109
+ : undefined,
110
+ expected_target_sha256: args?.expected_target_sha256
111
+ ? String(args.expected_target_sha256)
112
+ : undefined,
113
+ }));
114
+ },
115
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Dispatch handlers for Goal Session tools (v0.8.0+).
3
+ *
4
+ * Covers goal lifecycle, subgoal management, Spec Kit import,
5
+ * worktree merge/discard, and goal reporting.
6
+ */
7
+ import type { ToolHandlerMap } from "./types.js";
8
+ export declare const goalHandlers: ToolHandlerMap;