patchwarden 0.6.4 → 1.5.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.
- package/README.en.md +54 -20
- package/README.md +48 -21
- package/dist/agents/agentRouter.d.ts +40 -0
- package/dist/agents/agentRouter.js +95 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +6 -2
- package/dist/controlCenter.js +212 -3
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +32 -1
- package/dist/goal/acceptanceEngine.d.ts +62 -0
- package/dist/goal/acceptanceEngine.js +103 -0
- package/dist/goal/acceptanceTemplate.d.ts +10 -0
- package/dist/goal/acceptanceTemplate.js +104 -0
- package/dist/goal/goalGraph.d.ts +58 -0
- package/dist/goal/goalGraph.js +189 -0
- package/dist/goal/goalProgress.d.ts +81 -0
- package/dist/goal/goalProgress.js +216 -0
- package/dist/goal/goalStatus.d.ts +60 -0
- package/dist/goal/goalStatus.js +137 -0
- package/dist/goal/goalStore.d.ts +79 -0
- package/dist/goal/goalStore.js +211 -0
- package/dist/goal/handoffExport.d.ts +34 -0
- package/dist/goal/handoffExport.js +183 -0
- package/dist/goal/subgoalSync.d.ts +40 -0
- package/dist/goal/subgoalSync.js +72 -0
- package/dist/goal/worktreeManager.d.ts +96 -0
- package/dist/goal/worktreeManager.js +292 -0
- package/dist/logging.d.ts +44 -0
- package/dist/logging.js +68 -0
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/release/releaseGate.d.ts +99 -0
- package/dist/release/releaseGate.js +475 -0
- package/dist/runner/postTaskCleanup.d.ts +13 -0
- package/dist/runner/postTaskCleanup.js +147 -0
- package/dist/runner/runTask.js +50 -10
- package/dist/security/discoveryTokenStore.d.ts +62 -0
- package/dist/security/discoveryTokenStore.js +100 -0
- package/dist/security/toolInvocationGuard.d.ts +35 -0
- package/dist/security/toolInvocationGuard.js +153 -0
- package/dist/smoke-test.js +18 -10
- package/dist/taskRuntime.d.ts +17 -0
- package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
- package/dist/test/unit/acceptance-engine.test.js +228 -0
- package/dist/test/unit/agent-router.test.d.ts +1 -0
- package/dist/test/unit/agent-router.test.js +287 -0
- package/dist/test/unit/audit-checks.test.d.ts +1 -0
- package/dist/test/unit/audit-checks.test.js +350 -0
- package/dist/test/unit/diagnose-task.test.d.ts +1 -0
- package/dist/test/unit/diagnose-task.test.js +457 -0
- package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
- package/dist/test/unit/discovery-token-store.test.js +139 -0
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/goal-graph.test.d.ts +1 -0
- package/dist/test/unit/goal-graph.test.js +298 -0
- package/dist/test/unit/goal-progress.test.d.ts +1 -0
- package/dist/test/unit/goal-progress.test.js +381 -0
- package/dist/test/unit/goal-status.test.d.ts +1 -0
- package/dist/test/unit/goal-status.test.js +215 -0
- package/dist/test/unit/goal-store.test.d.ts +1 -0
- package/dist/test/unit/goal-store.test.js +253 -0
- package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
- package/dist/test/unit/goal-subgoal-task.test.js +55 -0
- package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
- package/dist/test/unit/goal-tools-registry.test.js +190 -0
- package/dist/test/unit/handoff-export.test.d.ts +1 -0
- package/dist/test/unit/handoff-export.test.js +263 -0
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
- package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
- package/dist/test/unit/logging.test.js +127 -5
- package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
- package/dist/test/unit/post-task-cleanup.test.js +48 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
- package/dist/test/unit/reconcile-tasks.test.js +456 -0
- package/dist/test/unit/release-gate.test.d.ts +1 -0
- package/dist/test/unit/release-gate.test.js +242 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/safe-views.test.d.ts +1 -0
- package/dist/test/unit/safe-views.test.js +171 -0
- package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
- package/dist/test/unit/schema-drift-check.test.js +176 -0
- package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
- package/dist/test/unit/subgoal-sync.test.js +183 -0
- package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
- package/dist/test/unit/tool-invocation-guard.test.js +432 -0
- package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
- package/dist/test/unit/tool-usage-stats.test.js +300 -0
- package/dist/test/unit/toolSearch.test.d.ts +1 -0
- package/dist/test/unit/toolSearch.test.js +571 -0
- package/dist/test/unit/worktree-manager.test.d.ts +1 -0
- package/dist/test/unit/worktree-manager.test.js +176 -0
- package/dist/tools/auditTask.d.ts +103 -1
- package/dist/tools/auditTask.js +461 -8
- package/dist/tools/cancelTask.js +1 -1
- package/dist/tools/checkReleaseGate.d.ts +21 -0
- package/dist/tools/checkReleaseGate.js +22 -0
- package/dist/tools/createTask.d.ts +18 -2
- package/dist/tools/createTask.js +29 -1
- package/dist/tools/diagnoseTask.d.ts +45 -0
- package/dist/tools/diagnoseTask.js +347 -0
- package/dist/tools/discardWorktree.d.ts +24 -0
- package/dist/tools/discardWorktree.js +27 -0
- package/dist/tools/discoverTools.d.ts +11 -0
- package/dist/tools/discoverTools.js +39 -0
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/explainTool.d.ts +11 -0
- package/dist/tools/explainTool.js +21 -0
- package/dist/tools/getTaskSummary.js +2 -1
- package/dist/tools/goalSubgoalTask.d.ts +51 -0
- package/dist/tools/goalSubgoalTask.js +110 -0
- package/dist/tools/healthCheck.d.ts +1 -0
- package/dist/tools/healthCheck.js +5 -1
- package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
- package/dist/tools/invokeDiscoveredTool.js +112 -0
- package/dist/tools/listTasks.d.ts +3 -1
- package/dist/tools/listTasks.js +14 -1
- package/dist/tools/mergeWorktree.d.ts +24 -0
- package/dist/tools/mergeWorktree.js +27 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/reconcileTasks.d.ts +41 -0
- package/dist/tools/reconcileTasks.js +352 -0
- package/dist/tools/registry.js +926 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeStatus.d.ts +31 -1
- package/dist/tools/safeStatus.js +43 -1
- package/dist/tools/safeViews.d.ts +262 -0
- package/dist/tools/safeViews.js +252 -0
- package/dist/tools/schemaDriftCheck.d.ts +46 -0
- package/dist/tools/schemaDriftCheck.js +80 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +39 -11
- package/dist/tools/toolRegistry.d.ts +61 -0
- package/dist/tools/toolRegistry.js +834 -0
- package/dist/tools/toolSearch.d.ts +110 -0
- package/dist/tools/toolSearch.js +331 -0
- package/dist/tools/toolUsageStats.d.ts +41 -0
- package/dist/tools/toolUsageStats.js +116 -0
- package/dist/tools/waitForTask.js +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +29 -0
- package/dist/watcherStatus.js +92 -1
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +3 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/doctor-smoke.js +1 -0
- package/scripts/checks/lifecycle-smoke.js +13 -9
- package/scripts/checks/mcp-manifest-check.js +65 -6
- package/scripts/checks/mcp-smoke.js +60 -9
- package/scripts/checks/watcher-supervisor-smoke.js +2 -2
- package/src/agents/agentRouter.ts +149 -0
- package/src/config.ts +9 -3
- package/src/controlCenter.ts +215 -4
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +36 -1
- package/src/goal/acceptanceEngine.ts +160 -0
- package/src/goal/acceptanceTemplate.ts +121 -0
- package/src/goal/goalGraph.ts +225 -0
- package/src/goal/goalProgress.ts +301 -0
- package/src/goal/goalStatus.ts +234 -0
- package/src/goal/goalStore.ts +306 -0
- package/src/goal/handoffExport.ts +211 -0
- package/src/goal/subgoalSync.ts +82 -0
- package/src/goal/worktreeManager.ts +404 -0
- package/src/logging.ts +91 -0
- package/src/policy/projectPolicy.ts +344 -0
- package/src/release/releaseGate.ts +567 -0
- package/src/runner/postTaskCleanup.ts +154 -0
- package/src/runner/runTask.ts +49 -10
- package/src/security/discoveryTokenStore.ts +157 -0
- package/src/security/toolInvocationGuard.ts +251 -0
- package/src/smoke-test.ts +15 -7
- package/src/taskRuntime.ts +17 -0
- package/src/test/unit/acceptance-engine.test.ts +261 -0
- package/src/test/unit/agent-router.test.ts +342 -0
- package/src/test/unit/audit-checks.test.ts +567 -0
- package/src/test/unit/diagnose-task.test.ts +544 -0
- package/src/test/unit/discovery-token-store.test.ts +181 -0
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/goal-graph.test.ts +347 -0
- package/src/test/unit/goal-progress.test.ts +538 -0
- package/src/test/unit/goal-status.test.ts +270 -0
- package/src/test/unit/goal-store.test.ts +318 -0
- package/src/test/unit/goal-subgoal-task.test.ts +72 -0
- package/src/test/unit/goal-tools-registry.test.ts +243 -0
- package/src/test/unit/handoff-export.test.ts +295 -0
- package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
- package/src/test/unit/logging.test.ts +177 -5
- package/src/test/unit/post-task-cleanup.test.ts +53 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/reconcile-tasks.test.ts +551 -0
- package/src/test/unit/release-gate.test.ts +314 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/safe-views.test.ts +184 -0
- package/src/test/unit/schema-drift-check.test.ts +260 -0
- package/src/test/unit/subgoal-sync.test.ts +236 -0
- package/src/test/unit/tool-invocation-guard.test.ts +542 -0
- package/src/test/unit/tool-usage-stats.test.ts +384 -0
- package/src/test/unit/toolSearch.test.ts +756 -0
- package/src/test/unit/worktree-manager.test.ts +247 -0
- package/src/tools/auditTask.ts +831 -402
- package/src/tools/cancelTask.ts +1 -1
- package/src/tools/checkReleaseGate.ts +45 -0
- package/src/tools/createTask.ts +64 -6
- package/src/tools/diagnoseTask.ts +460 -0
- package/src/tools/discardWorktree.ts +42 -0
- package/src/tools/discoverTools.ts +51 -0
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/explainTool.ts +26 -0
- package/src/tools/getTaskSummary.ts +2 -1
- package/src/tools/goalSubgoalTask.ts +170 -0
- package/src/tools/healthCheck.ts +4 -1
- package/src/tools/invokeDiscoveredTool.ts +163 -0
- package/src/tools/listTasks.ts +16 -2
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/mergeWorktree.ts +42 -0
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/reconcileTasks.ts +464 -0
- package/src/tools/registry.ts +1093 -73
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeStatus.ts +73 -2
- package/src/tools/safeViews.ts +273 -0
- package/src/tools/schemaDriftCheck.ts +120 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +40 -11
- package/src/tools/toolRegistry.ts +896 -0
- package/src/tools/toolSearch.ts +464 -0
- package/src/tools/toolUsageStats.ts +151 -0
- package/src/tools/waitForTask.ts +1 -0
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +97 -1
- package/ui/pages/dashboard.html +143 -2
|
@@ -103,17 +103,29 @@ try {
|
|
|
103
103
|
const tools = await client.listTools();
|
|
104
104
|
const names = tools.tools.map((tool) => tool.name).sort();
|
|
105
105
|
const expected = [
|
|
106
|
+
"accept_subgoal",
|
|
106
107
|
"apply_patch",
|
|
107
108
|
"audit_session",
|
|
108
109
|
"audit_task",
|
|
109
110
|
"cancel_task",
|
|
111
|
+
"check_release_gate",
|
|
110
112
|
"create_direct_session",
|
|
113
|
+
"create_goal",
|
|
114
|
+
"create_subgoal_task",
|
|
111
115
|
"create_task",
|
|
116
|
+
"diagnose_task",
|
|
117
|
+
"discard_worktree",
|
|
118
|
+
"discover_tools",
|
|
119
|
+
"explain_tool",
|
|
120
|
+
"export_handoff",
|
|
121
|
+
"export_task_evidence_pack",
|
|
112
122
|
"finalize_direct_session",
|
|
113
123
|
"get_diff",
|
|
114
124
|
"get_plan",
|
|
125
|
+
"get_project_policy",
|
|
115
126
|
"get_result",
|
|
116
127
|
"get_result_json",
|
|
128
|
+
"get_task_lineage",
|
|
117
129
|
"get_task_log_tail",
|
|
118
130
|
"get_task_progress",
|
|
119
131
|
"get_task_status",
|
|
@@ -121,16 +133,38 @@ try {
|
|
|
121
133
|
"get_task_summary",
|
|
122
134
|
"get_test_log",
|
|
123
135
|
"health_check",
|
|
136
|
+
"invoke_discovered_tool",
|
|
124
137
|
"kill_task",
|
|
125
138
|
"list_agents",
|
|
139
|
+
"list_goals",
|
|
126
140
|
"list_tasks",
|
|
127
141
|
"list_workspace",
|
|
142
|
+
"merge_worktree",
|
|
143
|
+
"read_goal",
|
|
128
144
|
"read_workspace_file",
|
|
145
|
+
"recommend_agent_for_task",
|
|
146
|
+
"reconcile_tasks",
|
|
147
|
+
"reject_subgoal",
|
|
148
|
+
"release_check",
|
|
149
|
+
"release_cleanup",
|
|
150
|
+
"release_prepare",
|
|
151
|
+
"release_verify",
|
|
129
152
|
"retry_task",
|
|
153
|
+
"run_direct_verification_bundle",
|
|
154
|
+
"run_task_loop",
|
|
130
155
|
"run_verification",
|
|
156
|
+
"safe_audit",
|
|
157
|
+
"safe_audit_direct_session",
|
|
158
|
+
"safe_diff_summary",
|
|
159
|
+
"safe_direct_summary",
|
|
160
|
+
"safe_finalize_direct_session",
|
|
161
|
+
"safe_result",
|
|
131
162
|
"safe_status",
|
|
163
|
+
"safe_test_summary",
|
|
132
164
|
"save_plan",
|
|
133
165
|
"search_workspace",
|
|
166
|
+
"suggest_next_subgoal",
|
|
167
|
+
"summarize_goal_progress",
|
|
134
168
|
"sync_file",
|
|
135
169
|
"wait_for_task",
|
|
136
170
|
];
|
|
@@ -143,7 +177,7 @@ try {
|
|
|
143
177
|
if (!tools._meta || typeof tools._meta.tool_manifest_sha256 !== "string" || tools._meta.tool_manifest_sha256.length !== 64) {
|
|
144
178
|
throw new Error(`tools/list _meta missing manifest hash: ${JSON.stringify(tools._meta || null)}`);
|
|
145
179
|
}
|
|
146
|
-
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !==
|
|
180
|
+
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 64) {
|
|
147
181
|
throw new Error(`tools/list _meta profile/count mismatch: ${JSON.stringify(tools._meta)}`);
|
|
148
182
|
}
|
|
149
183
|
if (typeof tools._meta.schema_epoch !== "string" || typeof tools._meta.server_version !== "string") {
|
|
@@ -296,8 +330,8 @@ try {
|
|
|
296
330
|
|
|
297
331
|
const statusPath = join(task.path, "status.json");
|
|
298
332
|
const statusAfter = JSON.parse(readFileSync(statusPath, "utf-8"));
|
|
299
|
-
if (statusAfter.status !== "done") {
|
|
300
|
-
throw new Error(`runner status should be done, got ${statusAfter.status}`);
|
|
333
|
+
if (statusAfter.status !== "done" && statusAfter.status !== "done_by_agent") {
|
|
334
|
+
throw new Error(`runner status should be done or done_by_agent, got ${statusAfter.status}`);
|
|
301
335
|
}
|
|
302
336
|
for (const fileName of ["result.md", "result.json", "diff.patch", "git.diff", "file-stats.json", "test.log", "verify.json", "verify.log"]) {
|
|
303
337
|
if (!existsSync(join(task.path, fileName))) {
|
|
@@ -393,7 +427,7 @@ try {
|
|
|
393
427
|
await disabledClient.close();
|
|
394
428
|
ok("chatgpt_direct disabled exposes only health_check with diagnostic");
|
|
395
429
|
|
|
396
|
-
// 2. chatgpt_direct enabled:
|
|
430
|
+
// 2. chatgpt_direct enabled: 14 tools + minimal create_direct_session
|
|
397
431
|
const enabledConfigPath = join(tempRoot, "direct-enabled.json");
|
|
398
432
|
const directRepo = join(workspaceRoot, "direct-fixture");
|
|
399
433
|
mkdirSync(join(directRepo, "src"), { recursive: true });
|
|
@@ -449,15 +483,19 @@ try {
|
|
|
449
483
|
"health_check",
|
|
450
484
|
"list_workspace",
|
|
451
485
|
"read_workspace_file",
|
|
486
|
+
"run_direct_verification_bundle",
|
|
452
487
|
"run_verification",
|
|
488
|
+
"safe_audit_direct_session",
|
|
489
|
+
"safe_direct_summary",
|
|
490
|
+
"safe_finalize_direct_session",
|
|
453
491
|
"search_workspace",
|
|
454
492
|
"sync_file",
|
|
455
493
|
];
|
|
456
494
|
if (JSON.stringify(enabledNames) !== JSON.stringify(expectedDirect)) {
|
|
457
495
|
throw new Error(`chatgpt_direct enabled tools mismatch: ${enabledNames.join(", ")}`);
|
|
458
496
|
}
|
|
459
|
-
if (enabledTools._meta.tool_count !==
|
|
460
|
-
throw new Error(`chatgpt_direct enabled tool_count should be
|
|
497
|
+
if (enabledTools._meta.tool_count !== 14) {
|
|
498
|
+
throw new Error(`chatgpt_direct enabled tool_count should be 14, got ${enabledTools._meta.tool_count}`);
|
|
461
499
|
}
|
|
462
500
|
|
|
463
501
|
// Minimal create_direct_session
|
|
@@ -470,6 +508,19 @@ try {
|
|
|
470
508
|
if (!sessionResult.session_id || !sessionResult.session_id.startsWith("direct_")) {
|
|
471
509
|
throw new Error(`create_direct_session failed: ${JSON.stringify(sessionResult)}`);
|
|
472
510
|
}
|
|
511
|
+
const bundleResult = JSON.parse(
|
|
512
|
+
(await enabledClient.callTool({
|
|
513
|
+
name: "run_direct_verification_bundle",
|
|
514
|
+
arguments: { session_id: sessionResult.session_id, commands: ["npm test"], timeout_seconds: 30 },
|
|
515
|
+
})).content?.[0]?.text || "{}"
|
|
516
|
+
);
|
|
517
|
+
if (bundleResult.status !== "passed" || bundleResult.command_count !== 1 || bundleResult.passed_commands !== 1) {
|
|
518
|
+
throw new Error(`run_direct_verification_bundle failed: ${JSON.stringify(bundleResult)}`);
|
|
519
|
+
}
|
|
520
|
+
const bundleText = JSON.stringify(bundleResult).toLowerCase();
|
|
521
|
+
if (bundleText.includes("stdout_tail") || bundleText.includes("stderr_tail") || bundleText.includes("verification.log")) {
|
|
522
|
+
throw new Error("run_direct_verification_bundle leaked log tail fields");
|
|
523
|
+
}
|
|
473
524
|
|
|
474
525
|
const enabledHealth = JSON.parse(
|
|
475
526
|
(await enabledClient.callTool({ name: "health_check", arguments: {} })).content?.[0]?.text || "{}"
|
|
@@ -477,12 +528,12 @@ try {
|
|
|
477
528
|
if (enabledHealth.direct_profile_enabled !== true) {
|
|
478
529
|
throw new Error(`direct_profile_enabled should be true, got ${enabledHealth.direct_profile_enabled}`);
|
|
479
530
|
}
|
|
480
|
-
if (enabledHealth.direct_tool_count !==
|
|
481
|
-
throw new Error(`direct_tool_count should be
|
|
531
|
+
if (enabledHealth.direct_tool_count !== 14) {
|
|
532
|
+
throw new Error(`direct_tool_count should be 14, got ${enabledHealth.direct_tool_count}`);
|
|
482
533
|
}
|
|
483
534
|
|
|
484
535
|
await enabledClient.close();
|
|
485
|
-
ok("chatgpt_direct enabled exposes
|
|
536
|
+
ok("chatgpt_direct enabled exposes 14 tools and create_direct_session works");
|
|
486
537
|
} catch (error) {
|
|
487
538
|
fail("MCP smoke test", error);
|
|
488
539
|
} finally {
|
|
@@ -105,7 +105,7 @@ function createFixture(name) {
|
|
|
105
105
|
server_version: "0.6.0",
|
|
106
106
|
schema_epoch: "2026-06-22-v6",
|
|
107
107
|
tool_profile: "chatgpt_core",
|
|
108
|
-
tool_count:
|
|
108
|
+
tool_count: 26,
|
|
109
109
|
tool_names: [],
|
|
110
110
|
tool_manifest_sha256: "a".repeat(64),
|
|
111
111
|
});
|
|
@@ -167,7 +167,7 @@ function fixtureEnv(fixture, mode, lifetimeMs) {
|
|
|
167
167
|
...process.env,
|
|
168
168
|
APPDATA: fixture.appData,
|
|
169
169
|
LOCALAPPDATA: fixture.localAppData,
|
|
170
|
-
CONTROL_PLANE_API_KEY: "
|
|
170
|
+
CONTROL_PLANE_API_KEY: "test-key",
|
|
171
171
|
WATCHER_FIXTURE_MODE: mode,
|
|
172
172
|
TUNNEL_FIXTURE_LIFETIME_MS: String(lifetimeMs),
|
|
173
173
|
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { PatchWardenError } from "../errors.js";
|
|
2
|
+
|
|
3
|
+
// ── Types ─────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export interface AgentRouteInput {
|
|
6
|
+
goal?: string;
|
|
7
|
+
scope?: string[];
|
|
8
|
+
inline_plan?: string;
|
|
9
|
+
template?: string;
|
|
10
|
+
configuredAgents: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AgentRouteResult {
|
|
14
|
+
recommended_agent: string;
|
|
15
|
+
reason: string;
|
|
16
|
+
fallback: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── Routing rules (roadmap 9.3) ───────────────────────────────────
|
|
20
|
+
|
|
21
|
+
export const AGENT_ROUTING_RULES = {
|
|
22
|
+
/** 大规模代码修改 → opencode */
|
|
23
|
+
largeScope: {
|
|
24
|
+
agent: "opencode",
|
|
25
|
+
threshold: 10, // scope.length > threshold
|
|
26
|
+
},
|
|
27
|
+
/** 单文件修改 → patchwarden-direct */
|
|
28
|
+
singleFile: {
|
|
29
|
+
agent: "patchwarden-direct",
|
|
30
|
+
},
|
|
31
|
+
/** 验收审计 → patchwarden-audit */
|
|
32
|
+
audit: {
|
|
33
|
+
agent: "patchwarden-audit",
|
|
34
|
+
keywords: ["审计", "audit", "验收", "verify"],
|
|
35
|
+
},
|
|
36
|
+
/** 复杂推理和重构 → codex */
|
|
37
|
+
refactor: {
|
|
38
|
+
agent: "codex",
|
|
39
|
+
keywords: ["重构", "refactor", "跨模块", "redesign"],
|
|
40
|
+
},
|
|
41
|
+
/** 文档整理 → claude(fallback 到 opencode) */
|
|
42
|
+
documentation: {
|
|
43
|
+
agent: "claude",
|
|
44
|
+
fallbackAgent: "opencode",
|
|
45
|
+
keywords: ["文档", "readme", "changelog", "doc"],
|
|
46
|
+
},
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function containsAny(text: string, keywords: readonly string[]): boolean {
|
|
52
|
+
return keywords.some((keyword) => text.includes(keyword));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveRoute(
|
|
56
|
+
preferred: string,
|
|
57
|
+
reason: string,
|
|
58
|
+
configuredAgents: string[]
|
|
59
|
+
): AgentRouteResult {
|
|
60
|
+
if (configuredAgents.includes(preferred)) {
|
|
61
|
+
return { recommended_agent: preferred, reason, fallback: false };
|
|
62
|
+
}
|
|
63
|
+
const fallback = configuredAgents[0];
|
|
64
|
+
return {
|
|
65
|
+
recommended_agent: fallback,
|
|
66
|
+
reason: `fallback: ${preferred} not configured, using ${fallback}`,
|
|
67
|
+
fallback: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── Route agent ───────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export function routeAgent(input: AgentRouteInput): AgentRouteResult {
|
|
74
|
+
if (!input.configuredAgents || input.configuredAgents.length === 0) {
|
|
75
|
+
throw new PatchWardenError(
|
|
76
|
+
"no_agent_configured",
|
|
77
|
+
"No agent is configured for routing.",
|
|
78
|
+
"Configure at least one agent in patchwarden.config.json under the agents field."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const scopeCount = input.scope?.length ?? 0;
|
|
83
|
+
const text = `${input.goal || ""} ${input.inline_plan || ""}`.toLowerCase();
|
|
84
|
+
|
|
85
|
+
// Rule 1: 大规模代码修改 → opencode (scope files > 10)
|
|
86
|
+
if (scopeCount > AGENT_ROUTING_RULES.largeScope.threshold) {
|
|
87
|
+
return resolveRoute(
|
|
88
|
+
AGENT_ROUTING_RULES.largeScope.agent,
|
|
89
|
+
`large scope (${scopeCount} files)`,
|
|
90
|
+
input.configuredAgents
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Rule 2: 单文件修改 → patchwarden-direct (scope files === 1)
|
|
95
|
+
if (scopeCount === 1) {
|
|
96
|
+
return resolveRoute(
|
|
97
|
+
AGENT_ROUTING_RULES.singleFile.agent,
|
|
98
|
+
"single file scope",
|
|
99
|
+
input.configuredAgents
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Rule 3: 验收审计 → patchwarden-audit
|
|
104
|
+
if (containsAny(text, AGENT_ROUTING_RULES.audit.keywords)) {
|
|
105
|
+
return resolveRoute(
|
|
106
|
+
AGENT_ROUTING_RULES.audit.agent,
|
|
107
|
+
"audit keywords",
|
|
108
|
+
input.configuredAgents
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Rule 4: 复杂推理和重构 → codex
|
|
113
|
+
if (containsAny(text, AGENT_ROUTING_RULES.refactor.keywords)) {
|
|
114
|
+
return resolveRoute(
|
|
115
|
+
AGENT_ROUTING_RULES.refactor.agent,
|
|
116
|
+
"refactor keywords",
|
|
117
|
+
input.configuredAgents
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Rule 5: 文档整理 → claude(fallback 到 opencode)
|
|
122
|
+
if (containsAny(text, AGENT_ROUTING_RULES.documentation.keywords)) {
|
|
123
|
+
const preferred = AGENT_ROUTING_RULES.documentation.agent;
|
|
124
|
+
const fallbackAgent = AGENT_ROUTING_RULES.documentation.fallbackAgent;
|
|
125
|
+
if (input.configuredAgents.includes(preferred)) {
|
|
126
|
+
return { recommended_agent: preferred, reason: "documentation keywords", fallback: false };
|
|
127
|
+
}
|
|
128
|
+
if (input.configuredAgents.includes(fallbackAgent)) {
|
|
129
|
+
return {
|
|
130
|
+
recommended_agent: fallbackAgent,
|
|
131
|
+
reason: `fallback: ${preferred} not configured, using ${fallbackAgent}`,
|
|
132
|
+
fallback: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const firstAgent = input.configuredAgents[0];
|
|
136
|
+
return {
|
|
137
|
+
recommended_agent: firstAgent,
|
|
138
|
+
reason: `fallback: ${preferred} not configured, using ${firstAgent}`,
|
|
139
|
+
fallback: true,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Rule 6: 无匹配 → fallback 到 configuredAgents[0]
|
|
144
|
+
return {
|
|
145
|
+
recommended_agent: input.configuredAgents[0],
|
|
146
|
+
reason: `no specific routing rule matched, using default agent ${input.configuredAgents[0]}`,
|
|
147
|
+
fallback: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface PatchWardenConfig {
|
|
|
21
21
|
defaultTaskTimeoutSeconds: number;
|
|
22
22
|
maxTaskTimeoutSeconds: number;
|
|
23
23
|
watcherStaleSeconds: number;
|
|
24
|
-
toolProfile?: "full" | "chatgpt_core" | "chatgpt_direct";
|
|
24
|
+
toolProfile?: "full" | "chatgpt_core" | "chatgpt_direct" | "chatgpt_search";
|
|
25
25
|
repoAliases?: Record<string, string>;
|
|
26
26
|
enableAgentAssessment?: boolean;
|
|
27
27
|
agentAssessmentTimeoutSeconds?: number;
|
|
@@ -224,8 +224,14 @@ function normalizeConfig(config: PatchWardenConfig): PatchWardenConfig {
|
|
|
224
224
|
if (!Number.isInteger(config.watcherStaleSeconds) || config.watcherStaleSeconds < 5 || config.watcherStaleSeconds > 3600) {
|
|
225
225
|
throw new Error("watcherStaleSeconds must be an integer from 5 to 3600");
|
|
226
226
|
}
|
|
227
|
-
if (
|
|
228
|
-
|
|
227
|
+
if (
|
|
228
|
+
config.toolProfile !== undefined &&
|
|
229
|
+
config.toolProfile !== "full" &&
|
|
230
|
+
config.toolProfile !== "chatgpt_core" &&
|
|
231
|
+
config.toolProfile !== "chatgpt_direct" &&
|
|
232
|
+
config.toolProfile !== "chatgpt_search"
|
|
233
|
+
) {
|
|
234
|
+
throw new Error('toolProfile must be "full", "chatgpt_core", "chatgpt_direct", or "chatgpt_search"');
|
|
229
235
|
}
|
|
230
236
|
if (config.enableAgentAssessment !== undefined && typeof config.enableAgentAssessment !== "boolean") {
|
|
231
237
|
throw new Error("enableAgentAssessment must be a boolean");
|
package/src/controlCenter.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* PatchWarden Control Center — local HTTP dashboard service.
|
|
4
4
|
*
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { createServer, get as httpGet, type IncomingMessage, type ServerResponse } from "node:http";
|
|
16
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
17
17
|
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
18
18
|
import { fileURLToPath } from "node:url";
|
|
19
19
|
import { randomUUID } from "node:crypto";
|
|
@@ -27,11 +27,16 @@ import {
|
|
|
27
27
|
type PatchWardenConfig,
|
|
28
28
|
} from "./config.js";
|
|
29
29
|
import { listTasks, type TaskEntry } from "./tools/listTasks.js";
|
|
30
|
+
import type { AcceptanceStatus } from "./tools/createTask.js";
|
|
30
31
|
import { listAgents, type AgentAvailability } from "./tools/listAgents.js";
|
|
31
32
|
import { readWatcherStatus, type WatcherStatusSnapshot } from "./watcherStatus.js";
|
|
32
33
|
import { redactSensitiveContent } from "./security/contentRedaction.js";
|
|
33
34
|
import { guardWorkspacePath } from "./security/pathGuard.js";
|
|
34
35
|
import { auditTask } from "./tools/auditTask.js";
|
|
36
|
+
import { getProjectPolicySummary } from "./policy/projectPolicy.js";
|
|
37
|
+
import { safeDirectSummary } from "./tools/safeViews.js";
|
|
38
|
+
import { toSafeTaskLineage, type TaskLineageRecord } from "./tools/taskLineage.js";
|
|
39
|
+
import { listEvidencePacks, readEvidencePack } from "./tools/evidencePack.js";
|
|
35
40
|
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "./version.js";
|
|
36
41
|
|
|
37
42
|
// ── Paths ─────────────────────────────────────────────────────────
|
|
@@ -249,6 +254,25 @@ function readJsonFileSafe<T = unknown>(filePath: string): T | null {
|
|
|
249
254
|
}
|
|
250
255
|
}
|
|
251
256
|
|
|
257
|
+
function isPathInside(root: string, target: string): boolean {
|
|
258
|
+
const rel = relative(root, target);
|
|
259
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function readJsonFileSafeUnder<T = unknown>(root: string, relPath: string): T | null {
|
|
263
|
+
const base = resolve(root);
|
|
264
|
+
const target = resolve(base, relPath);
|
|
265
|
+
if (!isPathInside(base, target) || !existsSync(target)) return null;
|
|
266
|
+
try {
|
|
267
|
+
const realBase = realpathSync(base);
|
|
268
|
+
const realTarget = realpathSync(target);
|
|
269
|
+
if (!isPathInside(realBase, realTarget)) return null;
|
|
270
|
+
return readJsonFileSafe<T>(realTarget);
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
252
276
|
function readTextFileSafe(filePath: string): string | null {
|
|
253
277
|
if (!existsSync(filePath)) return null;
|
|
254
278
|
try {
|
|
@@ -464,6 +488,7 @@ interface StaleClassification {
|
|
|
464
488
|
|
|
465
489
|
const TERMINAL_TASK_STATUSES = new Set([
|
|
466
490
|
"done",
|
|
491
|
+
"done_by_agent",
|
|
467
492
|
"failed",
|
|
468
493
|
"failed_verification",
|
|
469
494
|
"failed_scope_violation",
|
|
@@ -1018,6 +1043,108 @@ function handleStaleTasks(res: ServerResponse): void {
|
|
|
1018
1043
|
}
|
|
1019
1044
|
}
|
|
1020
1045
|
|
|
1046
|
+
function handleLineages(res: ServerResponse): void {
|
|
1047
|
+
try {
|
|
1048
|
+
const root = join(config.workspaceRoot, ".patchwarden", "lineages");
|
|
1049
|
+
if (!existsSync(root)) {
|
|
1050
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: null });
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const lineages = readdirSync(root, { withFileTypes: true })
|
|
1054
|
+
.filter((entry) => entry.isDirectory())
|
|
1055
|
+
.map((entry) => readJsonFileSafeUnder<TaskLineageRecord>(root, join(entry.name, "lineage.json")))
|
|
1056
|
+
.filter((entry): entry is TaskLineageRecord => entry !== null)
|
|
1057
|
+
.map((entry) => toSafeTaskLineage(entry, 6))
|
|
1058
|
+
.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
1059
|
+
.slice(0, 50);
|
|
1060
|
+
sendJson(res, 200, { lineages, total: lineages.length, reason: null });
|
|
1061
|
+
} catch (err) {
|
|
1062
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: errorMessage(err) });
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
function handleLineageDetail(res: ServerResponse, lineageId: string): void {
|
|
1067
|
+
try {
|
|
1068
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
1069
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const data = readJsonFileSafeUnder<TaskLineageRecord>(
|
|
1073
|
+
join(config.workspaceRoot, ".patchwarden", "lineages"),
|
|
1074
|
+
join(lineageId, "lineage.json")
|
|
1075
|
+
);
|
|
1076
|
+
if (!data) {
|
|
1077
|
+
sendJson(res, 404, { error: "Lineage not found" });
|
|
1078
|
+
return;
|
|
1079
|
+
}
|
|
1080
|
+
sendJson(res, 200, toSafeTaskLineage(data, 20));
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err) });
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function handleProjectPolicy(res: ServerResponse, repoPath: string): void {
|
|
1087
|
+
try {
|
|
1088
|
+
sendJson(res, 200, getProjectPolicySummary(repoPath || "."));
|
|
1089
|
+
} catch (err) {
|
|
1090
|
+
sendJson(res, 200, {
|
|
1091
|
+
repo_path: repoPath || ".",
|
|
1092
|
+
valid: false,
|
|
1093
|
+
issues: [{ code: "policy_unavailable", severity: "error", field: "repo_path", message: errorMessage(err) }],
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function handleReleaseStatus(res: ServerResponse, repoPath: string): void {
|
|
1099
|
+
try {
|
|
1100
|
+
const policy = getProjectPolicySummary(repoPath || ".");
|
|
1101
|
+
sendJson(res, 200, {
|
|
1102
|
+
repo_path: repoPath || ".",
|
|
1103
|
+
resolved_repo_path: policy.resolved_repo_path,
|
|
1104
|
+
policy_valid: policy.valid,
|
|
1105
|
+
policy_issue_count: policy.issues.length,
|
|
1106
|
+
policy_issues: policy.issues.slice(0, 10),
|
|
1107
|
+
release_readiness: policy.release_readiness,
|
|
1108
|
+
next_action: policy.valid && policy.release_readiness.version_consistent !== false
|
|
1109
|
+
? "Run release_check or release_prepare from the full MCP profile when ready."
|
|
1110
|
+
: "Fix project-policy or version consistency issues before release preparation.",
|
|
1111
|
+
remote_write_performed: false,
|
|
1112
|
+
});
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
sendJson(res, 200, {
|
|
1115
|
+
repo_path: repoPath || ".",
|
|
1116
|
+
policy_valid: false,
|
|
1117
|
+
error: errorMessage(err),
|
|
1118
|
+
remote_write_performed: false,
|
|
1119
|
+
});
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function handleEvidencePacks(res: ServerResponse): void {
|
|
1124
|
+
try {
|
|
1125
|
+
sendJson(res, 200, listEvidencePacks({ max_items: 50 }));
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
sendJson(res, 200, { evidence_packs: [], total: 0, reason: errorMessage(err) });
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function handleEvidencePackDetail(res: ServerResponse, lineageId: string): void {
|
|
1132
|
+
try {
|
|
1133
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
1134
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
const pack = readEvidencePack(lineageId);
|
|
1138
|
+
if (!pack) {
|
|
1139
|
+
sendJson(res, 404, { error: "Evidence pack not found" });
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
sendJson(res, 200, pack);
|
|
1143
|
+
} catch (err) {
|
|
1144
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err), bounded: true });
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1021
1148
|
/**
|
|
1022
1149
|
* Reconcile a stale task. Does NOT delete the task. Reads the task files,
|
|
1023
1150
|
* decides whether it is safe to mark the task as stale/archived, writes a
|
|
@@ -1049,13 +1176,19 @@ function handleReconcile(res: ServerResponse, taskId: string): void {
|
|
|
1049
1176
|
const runtimeData = readJsonFileSafe<Record<string, unknown>>(runtimePath) ?? {};
|
|
1050
1177
|
|
|
1051
1178
|
const watcher = readWatcherStatusSafe();
|
|
1179
|
+
const VALID_ACCEPTANCE = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
1180
|
+
const taskStatus = String(statusData.status || "pending");
|
|
1181
|
+
const taskAcceptanceStatus = taskStatus === "done_by_agent"
|
|
1182
|
+
? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE.includes(statusData.acceptance_status) ? (statusData.acceptance_status as AcceptanceStatus) : "pending" as AcceptanceStatus)
|
|
1183
|
+
: null;
|
|
1052
1184
|
const taskEntry: TaskEntry = {
|
|
1053
1185
|
task_id: taskId,
|
|
1054
1186
|
plan_id: String(statusData.plan_id || ""),
|
|
1055
1187
|
title: "",
|
|
1056
1188
|
agent: String(statusData.agent || ""),
|
|
1057
|
-
status:
|
|
1189
|
+
status: taskStatus as TaskEntry["status"],
|
|
1058
1190
|
phase: String(runtimeData.phase || statusData.phase || "queued") as TaskEntry["phase"],
|
|
1191
|
+
acceptance_status: taskAcceptanceStatus,
|
|
1059
1192
|
created_at: String(statusData.created_at || ""),
|
|
1060
1193
|
updated_at: String(statusData.updated_at || ""),
|
|
1061
1194
|
workspace_root: String(statusData.workspace_root || config.workspaceRoot),
|
|
@@ -1199,13 +1332,19 @@ function handleTaskDetail(res: ServerResponse, taskId: string): void {
|
|
|
1199
1332
|
let stale: StaleClassification | null = null;
|
|
1200
1333
|
if (statusData) {
|
|
1201
1334
|
const watcher = readWatcherStatusSafe();
|
|
1335
|
+
const VALID_ACCEPTANCE2 = ["pending", "accepted", "rejected", "needs_fix", "blocked"];
|
|
1336
|
+
const taskStatus2 = String(statusData.status || "pending");
|
|
1337
|
+
const taskAcceptanceStatus2 = taskStatus2 === "done_by_agent"
|
|
1338
|
+
? (typeof statusData.acceptance_status === "string" && VALID_ACCEPTANCE2.includes(statusData.acceptance_status) ? (statusData.acceptance_status as AcceptanceStatus) : "pending" as AcceptanceStatus)
|
|
1339
|
+
: null;
|
|
1202
1340
|
const entry: TaskEntry = {
|
|
1203
1341
|
task_id: taskId,
|
|
1204
1342
|
plan_id: String(statusData.plan_id || ""),
|
|
1205
1343
|
title: "",
|
|
1206
1344
|
agent: String(statusData.agent || ""),
|
|
1207
|
-
status:
|
|
1345
|
+
status: taskStatus2 as TaskEntry["status"],
|
|
1208
1346
|
phase: String(runtimeData?.phase || statusData.phase || "queued") as TaskEntry["phase"],
|
|
1347
|
+
acceptance_status: taskAcceptanceStatus2,
|
|
1209
1348
|
created_at: String(statusData.created_at || ""),
|
|
1210
1349
|
updated_at: String(statusData.updated_at || ""),
|
|
1211
1350
|
workspace_root: String(statusData.workspace_root || config.workspaceRoot),
|
|
@@ -1641,6 +1780,29 @@ function handleDirectSessionDetail(res: ServerResponse, sessionId: string): void
|
|
|
1641
1780
|
}
|
|
1642
1781
|
}
|
|
1643
1782
|
|
|
1783
|
+
function handleDirectSessionSafeSummary(res: ServerResponse, sessionId: string): void {
|
|
1784
|
+
try {
|
|
1785
|
+
if (
|
|
1786
|
+
sessionId === "." ||
|
|
1787
|
+
sessionId === ".." ||
|
|
1788
|
+
sessionId.includes("/") ||
|
|
1789
|
+
sessionId.includes("\\") ||
|
|
1790
|
+
sessionId.includes("\0")
|
|
1791
|
+
) {
|
|
1792
|
+
sendJson(res, 400, { error: "Invalid session id" });
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
sendJson(res, 200, safeDirectSummary(sessionId, { max_items: 12 }));
|
|
1796
|
+
} catch (err) {
|
|
1797
|
+
sendJson(res, 200, {
|
|
1798
|
+
session_id: sessionId,
|
|
1799
|
+
error: errorMessage(err),
|
|
1800
|
+
large_logs_omitted: true,
|
|
1801
|
+
diff_omitted: true,
|
|
1802
|
+
});
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1644
1806
|
function parseReviewVerdict(content: string): string | null {
|
|
1645
1807
|
// independent-review.md format: "**Verdict**: PASS" (case-insensitive)
|
|
1646
1808
|
const m = content.match(/\*\*Verdict\*\*\s*:\s*([A-Za-z]+)/);
|
|
@@ -1938,6 +2100,44 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
1938
2100
|
handleStaleTasks(res);
|
|
1939
2101
|
return;
|
|
1940
2102
|
}
|
|
2103
|
+
if (method === "GET" && pathname === "/api/lineages") {
|
|
2104
|
+
handleLineages(res);
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
const lineageMatch = pathname.match(/^\/api\/lineages\/([^/]+)$/);
|
|
2108
|
+
if (method === "GET" && lineageMatch) {
|
|
2109
|
+
let lineageId: string;
|
|
2110
|
+
try {
|
|
2111
|
+
lineageId = decodeURIComponent(lineageMatch[1]);
|
|
2112
|
+
} catch {
|
|
2113
|
+
lineageId = lineageMatch[1];
|
|
2114
|
+
}
|
|
2115
|
+
handleLineageDetail(res, lineageId);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (method === "GET" && pathname === "/api/project-policy") {
|
|
2119
|
+
handleProjectPolicy(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
2120
|
+
return;
|
|
2121
|
+
}
|
|
2122
|
+
if (method === "GET" && pathname === "/api/release/status") {
|
|
2123
|
+
handleReleaseStatus(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
if (method === "GET" && pathname === "/api/evidence-packs") {
|
|
2127
|
+
handleEvidencePacks(res);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
const evidencePackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)$/);
|
|
2131
|
+
if (method === "GET" && evidencePackMatch) {
|
|
2132
|
+
let lineageId: string;
|
|
2133
|
+
try {
|
|
2134
|
+
lineageId = decodeURIComponent(evidencePackMatch[1]);
|
|
2135
|
+
} catch {
|
|
2136
|
+
lineageId = evidencePackMatch[1];
|
|
2137
|
+
}
|
|
2138
|
+
handleEvidencePackDetail(res, lineageId);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
1941
2141
|
const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
|
|
1942
2142
|
if (method === "GET" && taskMatch) {
|
|
1943
2143
|
let taskId: string;
|
|
@@ -1996,6 +2196,17 @@ async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise
|
|
|
1996
2196
|
handleDirectSessions(res);
|
|
1997
2197
|
return;
|
|
1998
2198
|
}
|
|
2199
|
+
const directSessionSummaryMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/summary$/);
|
|
2200
|
+
if (method === "GET" && directSessionSummaryMatch) {
|
|
2201
|
+
let sessionId: string;
|
|
2202
|
+
try {
|
|
2203
|
+
sessionId = decodeURIComponent(directSessionSummaryMatch[1]);
|
|
2204
|
+
} catch {
|
|
2205
|
+
sessionId = directSessionSummaryMatch[1];
|
|
2206
|
+
}
|
|
2207
|
+
handleDirectSessionSafeSummary(res, sessionId);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
1999
2210
|
const directSessionMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)$/);
|
|
2000
2211
|
if (method === "GET" && directSessionMatch) {
|
|
2001
2212
|
let sessionId: string;
|
|
@@ -69,6 +69,13 @@ export async function runDirectVerification(
|
|
|
69
69
|
exit_code: result.exitCode,
|
|
70
70
|
passed,
|
|
71
71
|
timed_out: result.timedOut,
|
|
72
|
+
redacted: stdoutRedacted.redacted || stderrRedacted.redacted,
|
|
73
|
+
redaction_categories: [
|
|
74
|
+
...new Set([
|
|
75
|
+
...stdoutRedacted.redaction_categories,
|
|
76
|
+
...stderrRedacted.redaction_categories,
|
|
77
|
+
]),
|
|
78
|
+
],
|
|
72
79
|
stdout_tail: stdoutTail,
|
|
73
80
|
stderr_tail: stderrTail,
|
|
74
81
|
started_at: startedAt,
|