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
package/dist/watcherStatus.js
CHANGED
|
@@ -157,7 +157,7 @@ export function derivePendingReason(task, watcher) {
|
|
|
157
157
|
return "queued_but_watcher_unreadable";
|
|
158
158
|
return "queued_waiting_for_watcher";
|
|
159
159
|
}
|
|
160
|
-
if (task.status !== "running")
|
|
160
|
+
if (task.status !== "running" && task.status !== "collecting_artifacts")
|
|
161
161
|
return null;
|
|
162
162
|
if (task.phase === "executing_agent")
|
|
163
163
|
return "agent_running";
|
|
@@ -167,3 +167,94 @@ export function derivePendingReason(task, watcher) {
|
|
|
167
167
|
return "collecting_artifacts";
|
|
168
168
|
return "preparing";
|
|
169
169
|
}
|
|
170
|
+
// ── v0.7.0: Watcher ownership helpers ─────────────────────────────
|
|
171
|
+
/**
|
|
172
|
+
* Read the current watcher's instance_id from the heartbeat file.
|
|
173
|
+
* Returns null when the heartbeat is missing or unreadable.
|
|
174
|
+
*
|
|
175
|
+
* This is the source of truth for "which watcher is currently alive".
|
|
176
|
+
* Tasks whose runtime.watcher_instance_id differs from this value are
|
|
177
|
+
* considered orphaned by the diagnosis layer.
|
|
178
|
+
*/
|
|
179
|
+
export function readWatcherInstanceId(config = getConfig()) {
|
|
180
|
+
const heartbeatPath = getWatcherHeartbeatPath(config);
|
|
181
|
+
if (!existsSync(heartbeatPath))
|
|
182
|
+
return null;
|
|
183
|
+
try {
|
|
184
|
+
const raw = readFileSync(heartbeatPath, "utf-8");
|
|
185
|
+
const data = JSON.parse(raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw);
|
|
186
|
+
return typeof data.instance_id === "string" ? data.instance_id : null;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* v0.7.0: Determine whether the current watcher still owns a task.
|
|
194
|
+
*
|
|
195
|
+
* Ownership is established by comparing the watcher_instance_id recorded in
|
|
196
|
+
* the task's runtime.json with the instance_id of the currently alive
|
|
197
|
+
* watcher. A task is considered "owned" only when:
|
|
198
|
+
* - the current watcher heartbeat is healthy, AND
|
|
199
|
+
* - the task runtime recorded a watcher_instance_id, AND
|
|
200
|
+
* - both instance IDs match.
|
|
201
|
+
*
|
|
202
|
+
* If the task has no recorded watcher_instance_id (legacy tasks created
|
|
203
|
+
* before v0.7.0), ownership cannot be confirmed and this returns false.
|
|
204
|
+
* The diagnosis layer treats "no owner" as a stale signal, not as active.
|
|
205
|
+
*/
|
|
206
|
+
export function isWatcherOwningTask(taskDir, config = getConfig()) {
|
|
207
|
+
const runtimePath = join(taskDir, "runtime.json");
|
|
208
|
+
let taskWatcherInstanceId = null;
|
|
209
|
+
if (existsSync(runtimePath)) {
|
|
210
|
+
try {
|
|
211
|
+
const runtime = JSON.parse(readFileSync(runtimePath, "utf-8"));
|
|
212
|
+
if (typeof runtime.watcher_instance_id === "string") {
|
|
213
|
+
taskWatcherInstanceId = runtime.watcher_instance_id;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// corrupted runtime — treat as no record
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!taskWatcherInstanceId) {
|
|
221
|
+
return {
|
|
222
|
+
owned: false,
|
|
223
|
+
reason: "no_runtime_record",
|
|
224
|
+
task_watcher_instance_id: null,
|
|
225
|
+
current_watcher_instance_id: null,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
const watcher = readWatcherStatus(config);
|
|
229
|
+
if (watcher.status !== "healthy") {
|
|
230
|
+
return {
|
|
231
|
+
owned: false,
|
|
232
|
+
reason: watcher.status === "missing" ? "watcher_missing" : "watcher_unhealthy",
|
|
233
|
+
task_watcher_instance_id: taskWatcherInstanceId,
|
|
234
|
+
current_watcher_instance_id: watcher.instance_id,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const currentInstanceId = watcher.instance_id;
|
|
238
|
+
if (!currentInstanceId) {
|
|
239
|
+
return {
|
|
240
|
+
owned: false,
|
|
241
|
+
reason: "watcher_missing",
|
|
242
|
+
task_watcher_instance_id: taskWatcherInstanceId,
|
|
243
|
+
current_watcher_instance_id: null,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
if (currentInstanceId !== taskWatcherInstanceId) {
|
|
247
|
+
return {
|
|
248
|
+
owned: false,
|
|
249
|
+
reason: "instance_mismatch",
|
|
250
|
+
task_watcher_instance_id: taskWatcherInstanceId,
|
|
251
|
+
current_watcher_instance_id: currentInstanceId,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
owned: true,
|
|
256
|
+
reason: "owned",
|
|
257
|
+
task_watcher_instance_id: taskWatcherInstanceId,
|
|
258
|
+
current_watcher_instance_id: currentInstanceId,
|
|
259
|
+
};
|
|
260
|
+
}
|
package/docs/chatgpt-usage.md
CHANGED
|
@@ -92,6 +92,37 @@ safety boundaries and complete evidence.
|
|
|
92
92
|
7. Changes remain uncommitted for review; commit, push, and publish are outside
|
|
93
93
|
the ordinary task scope.
|
|
94
94
|
|
|
95
|
+
For routine guarded work, `run_task_loop` can perform the assess-only preflight,
|
|
96
|
+
task creation, waiting, safe summary review, audit, and bounded `fix_tests`
|
|
97
|
+
follow-up cycle in one tool call. It still uses the existing Watcher and
|
|
98
|
+
allow-listed verification commands, stops at local confirmation boundaries, and
|
|
99
|
+
returns a `lineage_id` for `get_task_lineage` instead of full logs or diffs.
|
|
100
|
+
|
|
101
|
+
For v1.4 Direct-assisted verification, set `direct_verify=true` only when the
|
|
102
|
+
local Direct profile is enabled and the desired Direct verification commands are
|
|
103
|
+
already allow-listed. The loop creates a Direct session after the normal task
|
|
104
|
+
and audit have succeeded, runs verification, safe-finalizes, safe-audits, and
|
|
105
|
+
stores bounded Direct evidence in lineage. It does not call Direct patching
|
|
106
|
+
tools, publish, push, tag, create releases, or restart live services.
|
|
107
|
+
|
|
108
|
+
For v1.5 isolated loop work, set `agent="auto"` when you want PatchWarden to
|
|
109
|
+
pick from configured local agents using bounded routing, and set
|
|
110
|
+
`isolation_mode="worktree"` only when the target repo is a git repository and
|
|
111
|
+
you want the task to run in an isolated worktree. Worktree mode records evidence
|
|
112
|
+
in lineage but never auto-merges or auto-deletes the worktree. After a loop
|
|
113
|
+
finishes, call `export_task_evidence_pack(lineage_id)` to write bounded
|
|
114
|
+
`evidence.json` and `EVIDENCE.md` files without stdout/stderr tails, full diffs,
|
|
115
|
+
verification logs, or sensitive file content.
|
|
116
|
+
|
|
117
|
+
For v1.3 policy-aware work, call `get_project_policy` before release-oriented
|
|
118
|
+
changes. It reads the bounded effective `.patchwarden/project-policy.json`
|
|
119
|
+
summary and release readiness without granting new command permissions. Release
|
|
120
|
+
mode tools are full-profile only: `release_check` wraps the existing release
|
|
121
|
+
gate, `release_prepare` runs only already allow-listed local commands,
|
|
122
|
+
`release_verify` performs read-only npm/GitHub/CI checks, and `release_cleanup`
|
|
123
|
+
defaults to dry run. None of these tools publish, push, tag, create GitHub
|
|
124
|
+
Releases, restart live tunnels/watchers, or return full logs/diffs.
|
|
125
|
+
|
|
95
126
|
`needs_confirm` assessments must be confirmed locally with
|
|
96
127
|
`patchwarden-confirm <full_assessment_id>`. The confirmation command is not an
|
|
97
128
|
MCP tool. A `blocked` assessment cannot be confirmed.
|
|
@@ -27,6 +27,15 @@ automation-friendly commands.
|
|
|
27
27
|
|
|
28
28
|
## Design Notes
|
|
29
29
|
|
|
30
|
+
- v1.3 Dashboard panels show bounded task lineage, project policy, and release
|
|
31
|
+
readiness summaries. The backing APIs are read-only and do not expose full
|
|
32
|
+
stdout/stderr, full diffs, long logs, or secret-bearing files.
|
|
33
|
+
- v1.4 extends the lineage panel with Direct-assisted verification status and
|
|
34
|
+
exposes a safe Direct session summary API without stdout/stderr tails or diffs.
|
|
35
|
+
- v1.5 adds an Evidence Pack dashboard card plus read-only
|
|
36
|
+
`/api/evidence-packs` and `/api/evidence-packs/:lineage_id` routes. These
|
|
37
|
+
APIs return bounded lineage/policy/catalog evidence and omit stdout/stderr,
|
|
38
|
+
full diffs, verification logs, and sensitive file content.
|
|
30
39
|
- `control-center-mvp.md`: first Web dashboard scope.
|
|
31
40
|
- `control-center-phase2.md`: follow-up management and diagnostics scope.
|
|
32
41
|
- `control-center-daily-driver.md`: current daily-use contract.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwarden",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Secure local MCP bridge for ChatGPT and local agents, with workspace-scoped task loops, safe summaries, audits, and Direct editing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"test:watcher-supervisor": "npm run build && node scripts/checks/watcher-supervisor-smoke.js",
|
|
51
51
|
"check:tool-manifest": "npm run build && node scripts/checks/mcp-manifest-check.js",
|
|
52
52
|
"check:direct-tool-manifest": "npm run build && node scripts/checks/mcp-manifest-check.js --profile chatgpt_direct",
|
|
53
|
+
"check:search-tool-manifest": "npm run build && node scripts/checks/mcp-manifest-check.js --profile chatgpt_search",
|
|
53
54
|
"check:brand": "node scripts/checks/brand-check.js",
|
|
54
55
|
"test:mcp": "npm run build && node scripts/checks/mcp-smoke.js",
|
|
55
56
|
"test:http-mcp": "npm run build && node scripts/checks/http-mcp-smoke.js",
|
|
@@ -229,6 +229,7 @@ async function testStaticFiles() {
|
|
|
229
229
|
if (!rootCt.includes("text/html")) checks.push(`GET / -> Content-Type ${rootCt} (expected text/html)`);
|
|
230
230
|
if (!root.body.includes("setup-checklist-card")) checks.push("dashboard missing setup checklist card");
|
|
231
231
|
if (!root.body.includes("Show Core / Direct log tails")) checks.push("dashboard activity log is not collapsed behind a summary");
|
|
232
|
+
if (!root.body.includes("evidence-pack-card")) checks.push("dashboard missing v1.5 evidence pack card");
|
|
232
233
|
|
|
233
234
|
if (vendor.status !== 200) checks.push(`GET /vendor/tailwindcss-browser.js -> status ${vendor.status}`);
|
|
234
235
|
const vendorCt = vendor.headers["content-type"] || "";
|
|
@@ -667,6 +668,92 @@ async function testOtherGetApis() {
|
|
|
667
668
|
problems.push(`/api/audit error: ${err.message}`);
|
|
668
669
|
}
|
|
669
670
|
|
|
671
|
+
// /api/lineages
|
|
672
|
+
try {
|
|
673
|
+
const res = await httpGet(`${BASE_URL}/api/lineages`);
|
|
674
|
+
if (res.status !== 200) problems.push(`/api/lineages -> ${res.status} (expected 200)`);
|
|
675
|
+
else {
|
|
676
|
+
const json = tryJson(res.body);
|
|
677
|
+
if (!json || !Array.isArray(json.lineages) || typeof json.total !== "number") {
|
|
678
|
+
problems.push(`/api/lineages missing bounded lineage summary fields: ${res.body.slice(0, 120)}`);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
} catch (err) {
|
|
682
|
+
problems.push(`/api/lineages error: ${err.message}`);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// /api/project-policy
|
|
686
|
+
try {
|
|
687
|
+
const res = await httpGet(`${BASE_URL}/api/project-policy?repo_path=.`);
|
|
688
|
+
if (res.status !== 200) problems.push(`/api/project-policy -> ${res.status} (expected 200)`);
|
|
689
|
+
else {
|
|
690
|
+
const json = tryJson(res.body);
|
|
691
|
+
if (!json || !("effective_policy" in json) || !Array.isArray(json.issues)) {
|
|
692
|
+
problems.push(`/api/project-policy missing policy summary fields: ${res.body.slice(0, 120)}`);
|
|
693
|
+
}
|
|
694
|
+
const text = res.body.toLowerCase();
|
|
695
|
+
if (text.includes("stdout") || text.includes("stderr") || text.includes("diff_patch")) {
|
|
696
|
+
problems.push("/api/project-policy leaked log/diff-shaped fields");
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
} catch (err) {
|
|
700
|
+
problems.push(`/api/project-policy error: ${err.message}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// /api/release/status
|
|
704
|
+
try {
|
|
705
|
+
const res = await httpGet(`${BASE_URL}/api/release/status?repo_path=.`);
|
|
706
|
+
if (res.status !== 200) problems.push(`/api/release/status -> ${res.status} (expected 200)`);
|
|
707
|
+
else {
|
|
708
|
+
const json = tryJson(res.body);
|
|
709
|
+
if (!json || !("release_readiness" in json) || json.remote_write_performed !== false) {
|
|
710
|
+
problems.push(`/api/release/status missing read-only release status fields: ${res.body.slice(0, 120)}`);
|
|
711
|
+
}
|
|
712
|
+
const text = res.body.toLowerCase();
|
|
713
|
+
if (text.includes("stdout") || text.includes("stderr") || text.includes("diff_patch")) {
|
|
714
|
+
problems.push("/api/release/status leaked log/diff-shaped fields");
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} catch (err) {
|
|
718
|
+
problems.push(`/api/release/status error: ${err.message}`);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// /api/evidence-packs
|
|
722
|
+
try {
|
|
723
|
+
const res = await httpGet(`${BASE_URL}/api/evidence-packs`);
|
|
724
|
+
if (res.status !== 200) problems.push(`/api/evidence-packs -> ${res.status} (expected 200)`);
|
|
725
|
+
else {
|
|
726
|
+
const json = tryJson(res.body);
|
|
727
|
+
if (!json || !Array.isArray(json.evidence_packs) || typeof json.total !== "number") {
|
|
728
|
+
problems.push(`/api/evidence-packs missing bounded evidence summary fields: ${res.body.slice(0, 120)}`);
|
|
729
|
+
}
|
|
730
|
+
const text = res.body.toLowerCase();
|
|
731
|
+
if (text.includes("stdout_tail") || text.includes("stderr_tail") || text.includes("diff.patch") || text.includes("verification.log")) {
|
|
732
|
+
problems.push("/api/evidence-packs leaked log/diff-shaped fields");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
} catch (err) {
|
|
736
|
+
problems.push(`/api/evidence-packs error: ${err.message}`);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// /api/direct-sessions/:id/summary (safe, bounded route; missing id is still non-leaky)
|
|
740
|
+
try {
|
|
741
|
+
const res = await httpGet(`${BASE_URL}/api/direct-sessions/direct_missing_summary/summary`);
|
|
742
|
+
if (res.status !== 200) problems.push(`/api/direct-sessions/:id/summary -> ${res.status} (expected 200)`);
|
|
743
|
+
else {
|
|
744
|
+
const json = tryJson(res.body);
|
|
745
|
+
if (!json || json.session_id !== "direct_missing_summary") {
|
|
746
|
+
problems.push(`/api/direct-sessions/:id/summary missing safe summary envelope: ${res.body.slice(0, 120)}`);
|
|
747
|
+
}
|
|
748
|
+
const text = res.body.toLowerCase();
|
|
749
|
+
if (text.includes("stdout_tail") || text.includes("stderr_tail") || text.includes("diff_patch") || text.includes("verification.log")) {
|
|
750
|
+
problems.push("/api/direct-sessions/:id/summary leaked log/diff-shaped fields");
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
} catch (err) {
|
|
754
|
+
problems.push(`/api/direct-sessions/:id/summary error: ${err.message}`);
|
|
755
|
+
}
|
|
756
|
+
|
|
670
757
|
// /api/logs/core
|
|
671
758
|
try {
|
|
672
759
|
const res = await httpGet(`${BASE_URL}/api/logs/core`);
|
|
@@ -76,7 +76,7 @@ try {
|
|
|
76
76
|
ready: false,
|
|
77
77
|
pid: null,
|
|
78
78
|
tool_profile: "chatgpt_direct",
|
|
79
|
-
tool_count:
|
|
79
|
+
tool_count: 14,
|
|
80
80
|
tools_ready: true,
|
|
81
81
|
}), "utf8");
|
|
82
82
|
writeFileSync(join(directRuntime, "tunnel-client.pid"), String(fakeTunnel.pid), "utf8");
|
|
@@ -114,7 +114,7 @@ try {
|
|
|
114
114
|
reason_code: "stale_fixture",
|
|
115
115
|
last_error: "stale failure",
|
|
116
116
|
tool_profile: "chatgpt_core",
|
|
117
|
-
tool_count:
|
|
117
|
+
tool_count: 26,
|
|
118
118
|
tools_ready: true,
|
|
119
119
|
}), "utf8");
|
|
120
120
|
healthServer = spawn(
|
|
@@ -42,6 +42,7 @@ try {
|
|
|
42
42
|
"[OK] Task directory writable",
|
|
43
43
|
"[OK] Example task directory read/write",
|
|
44
44
|
"[OK] allowedTestCommands is non-empty",
|
|
45
|
+
"[OK] Release gate module loadable",
|
|
45
46
|
];
|
|
46
47
|
for (const line of requiredLines) {
|
|
47
48
|
if (!result.stdout.includes(line)) throw new Error(`doctor output missing stable line: ${line}`);
|
|
@@ -12,6 +12,10 @@ import { tmpdir } from "node:os";
|
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
14
14
|
|
|
15
|
+
// v0.7.2: tasks complete with "done_by_agent" (pending acceptance) instead of "done".
|
|
16
|
+
// Both are valid successful completion statuses for lifecycle smoke tests.
|
|
17
|
+
const isDoneStatus = (s) => s === "done" || s === "done_by_agent";
|
|
18
|
+
|
|
15
19
|
const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-lifecycle-"));
|
|
16
20
|
const workspaceRoot = join(tempRoot, "workspace");
|
|
17
21
|
const repoPath = join(workspaceRoot, "repo");
|
|
@@ -178,7 +182,7 @@ try {
|
|
|
178
182
|
verify_commands: ["node --check main.js", "node --check second.js"],
|
|
179
183
|
});
|
|
180
184
|
const result = await runTask(task.task_id);
|
|
181
|
-
if (result.status
|
|
185
|
+
if (!isDoneStatus(result.status)) throw new Error(`Writer task ended ${result.status}: ${result.error}`);
|
|
182
186
|
const status = getTaskStatus(task.task_id);
|
|
183
187
|
const changed = status.changed_files || [];
|
|
184
188
|
if (!changed.some((file) => file.path === "README.md" && file.change === "modified")) {
|
|
@@ -253,7 +257,7 @@ try {
|
|
|
253
257
|
});
|
|
254
258
|
const result = await runTask(task.task_id);
|
|
255
259
|
const verify = JSON.parse(readFileSync(join(task.path, "verify.json"), "utf-8"));
|
|
256
|
-
if (result.status
|
|
260
|
+
if (!isDoneStatus(result.status) || verify.status !== "passed" || verify.commands.length !== 1) {
|
|
257
261
|
throw new Error(`Legacy verification mismatch: ${JSON.stringify({ result, verify })}`);
|
|
258
262
|
}
|
|
259
263
|
if (verify.commands[0].cwd !== repoPath || !("stdout_tail" in verify.commands[0]) || !("stderr_tail" in verify.commands[0])) {
|
|
@@ -265,7 +269,7 @@ try {
|
|
|
265
269
|
const plan = savePlan({ title: "No diff", content: "Do not change files." });
|
|
266
270
|
const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo" });
|
|
267
271
|
const result = await runTask(task.task_id);
|
|
268
|
-
if (result.status
|
|
272
|
+
if (!isDoneStatus(result.status)) throw new Error(`No-op task failed: ${JSON.stringify(result)}`);
|
|
269
273
|
const diff = getDiff(task.task_id);
|
|
270
274
|
const verify = JSON.parse(readFileSync(join(task.path, "verify.json"), "utf-8"));
|
|
271
275
|
if (verify.status !== "skipped") throw new Error(`Expected skipped verification: ${JSON.stringify(verify)}`);
|
|
@@ -286,7 +290,7 @@ try {
|
|
|
286
290
|
const plan = savePlan({ title: "Large diff", content: "Create a large text file." });
|
|
287
291
|
const task = createTask({ plan_id: plan.plan_id, agent: "largewriter", repo_path: "repo" });
|
|
288
292
|
const result = await runTask(task.task_id);
|
|
289
|
-
if (result.status
|
|
293
|
+
if (!isDoneStatus(result.status)) throw new Error(`Large task failed: ${JSON.stringify(result)}`);
|
|
290
294
|
const diff = getDiff(task.task_id);
|
|
291
295
|
const patchSize = readFileSync(join(task.path, "diff.patch"), "utf-8").length;
|
|
292
296
|
if (!diff.truncated || !diff.patch_head || !diff.diff_patch_path || patchSize <= diff.content.length) {
|
|
@@ -298,7 +302,7 @@ try {
|
|
|
298
302
|
const plan = savePlan({ title: "Non-Git evidence", content: "Modify files in a non-Git repository." });
|
|
299
303
|
const task = createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "plain-repo" });
|
|
300
304
|
const result = await runTask(task.task_id);
|
|
301
|
-
if (result.status
|
|
305
|
+
if (!isDoneStatus(result.status)) throw new Error(`Non-Git task failed: ${JSON.stringify(result)}`);
|
|
302
306
|
const diff = getDiff(task.task_id);
|
|
303
307
|
if (
|
|
304
308
|
diff.patch_mode !== "hash_only" ||
|
|
@@ -313,7 +317,7 @@ try {
|
|
|
313
317
|
const plan = savePlan({ title: "Binary evidence", content: "Create a binary fixture." });
|
|
314
318
|
const task = createTask({ plan_id: plan.plan_id, agent: "binarywriter", repo_path: "repo" });
|
|
315
319
|
const result = await runTask(task.task_id);
|
|
316
|
-
if (result.status
|
|
320
|
+
if (!isDoneStatus(result.status)) throw new Error(`Binary task failed: ${JSON.stringify(result)}`);
|
|
317
321
|
const diff = getDiff(task.task_id);
|
|
318
322
|
if (diff.patch_mode !== "textual" || !diff.content.includes("GIT binary patch")) {
|
|
319
323
|
throw new Error(`Binary patch evidence mismatch: ${JSON.stringify(diff)}`);
|
|
@@ -324,7 +328,7 @@ try {
|
|
|
324
328
|
const plan = savePlan({ title: "Artifact hygiene", content: "Generate representative task outputs." });
|
|
325
329
|
const task = createTask({ plan_id: plan.plan_id, agent: "artifactwriter", repo_path: "repo" });
|
|
326
330
|
const result = await runTask(task.task_id);
|
|
327
|
-
if (result.status
|
|
331
|
+
if (!isDoneStatus(result.status)) throw new Error(`Artifact task failed: ${JSON.stringify(result)}`);
|
|
328
332
|
const standard = getTaskSummary(task.task_id);
|
|
329
333
|
const compact = getTaskSummary(task.task_id, { view: "compact", max_items: 1 });
|
|
330
334
|
const counts = standard.artifact_hygiene?.counts || {};
|
|
@@ -353,7 +357,7 @@ try {
|
|
|
353
357
|
const plan = savePlan({ title: "Delete fixture", content: "Delete the designated fixture file." });
|
|
354
358
|
const task = createTask({ plan_id: plan.plan_id, agent: "deleter", repo_path: "repo" });
|
|
355
359
|
const result = await runTask(task.task_id);
|
|
356
|
-
if (result.status
|
|
360
|
+
if (!isDoneStatus(result.status)) throw new Error(`Delete task failed: ${JSON.stringify(result)}`);
|
|
357
361
|
const diff = getDiff(task.task_id);
|
|
358
362
|
if (!diff.file_stats?.some((file) => file.path === "delete-me.txt" && file.status === "deleted")) {
|
|
359
363
|
throw new Error(`Deleted file stats missing: ${JSON.stringify(diff.file_stats)}`);
|
|
@@ -504,7 +508,7 @@ try {
|
|
|
504
508
|
const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo", verify_commands: ["node --check main.js"] });
|
|
505
509
|
const result = await runTask(task.task_id);
|
|
506
510
|
|
|
507
|
-
if (result.status
|
|
511
|
+
if (!isDoneStatus(result.status)) {
|
|
508
512
|
throw new Error(`Expected done, got: ${JSON.stringify(result)}`);
|
|
509
513
|
}
|
|
510
514
|
const structured = JSON.parse(readFileSync(join(task.path, "result.json"), "utf-8"));
|
|
@@ -4,18 +4,20 @@ import { resolve } from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
6
6
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
7
|
-
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
|
|
7
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
|
|
8
8
|
|
|
9
9
|
const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
10
10
|
const jsonOnly = process.argv.includes("--json");
|
|
11
11
|
const profileIndex = process.argv.indexOf("--profile");
|
|
12
12
|
const profile = profileIndex >= 0 ? process.argv[profileIndex + 1] : "chatgpt_core";
|
|
13
|
-
if (profile !== "chatgpt_core" && profile !== "chatgpt_direct") {
|
|
13
|
+
if (profile !== "chatgpt_core" && profile !== "chatgpt_direct" && profile !== "chatgpt_search") {
|
|
14
14
|
throw new Error(`Unsupported manifest profile "${profile}".`);
|
|
15
15
|
}
|
|
16
16
|
const expectedTools = profile === "chatgpt_direct"
|
|
17
17
|
? [...CHATGPT_DIRECT_TOOL_NAMES]
|
|
18
|
-
:
|
|
18
|
+
: profile === "chatgpt_search"
|
|
19
|
+
? [...CHATGPT_SEARCH_TOOL_NAMES]
|
|
20
|
+
: [...CHATGPT_CORE_TOOL_NAMES];
|
|
19
21
|
const defaultConfigPath = resolve(root, "patchwarden.config.json");
|
|
20
22
|
const transportEnv = {
|
|
21
23
|
...process.env,
|
|
@@ -102,13 +104,37 @@ try {
|
|
|
102
104
|
if (!summaryProperties.includes("view") || !summaryProperties.includes("max_items")) {
|
|
103
105
|
throw new Error("get_task_summary schema must expose view and max_items");
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
const safeRequirements = {
|
|
108
|
+
run_task_loop: ["repo_path", "goal", "verify_commands", "direct_verify", "isolation_mode", "worktree_cleanup"],
|
|
109
|
+
recommend_agent_for_task: ["repo_path", "goal"],
|
|
110
|
+
get_task_lineage: ["lineage_id"],
|
|
111
|
+
export_task_evidence_pack: ["lineage_id"],
|
|
112
|
+
get_project_policy: ["repo_path"],
|
|
113
|
+
safe_result: ["task_id"],
|
|
114
|
+
safe_audit: ["task_id"],
|
|
115
|
+
safe_test_summary: ["task_id"],
|
|
116
|
+
safe_diff_summary: ["task_id"],
|
|
117
|
+
};
|
|
118
|
+
for (const [toolName, requiredProperties] of Object.entries(safeRequirements)) {
|
|
119
|
+
const tool = listed.tools.find((entry) => entry.name === toolName);
|
|
120
|
+
const properties = Object.keys(tool?.inputSchema?.properties || {});
|
|
121
|
+
for (const property of requiredProperties) {
|
|
122
|
+
if (!properties.includes(property)) {
|
|
123
|
+
throw new Error(`${toolName} schema is missing ${property}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
} else if (profile === "chatgpt_direct") {
|
|
106
128
|
const directRequirements = {
|
|
107
129
|
create_direct_session: ["repo_path"],
|
|
108
130
|
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
109
131
|
run_verification: ["session_id", "command"],
|
|
132
|
+
run_direct_verification_bundle: ["session_id", "commands"],
|
|
110
133
|
finalize_direct_session: ["session_id"],
|
|
111
134
|
audit_session: ["session_id"],
|
|
135
|
+
safe_direct_summary: ["session_id"],
|
|
136
|
+
safe_finalize_direct_session: ["session_id"],
|
|
137
|
+
safe_audit_direct_session: ["session_id"],
|
|
112
138
|
};
|
|
113
139
|
for (const [toolName, requiredProperties] of Object.entries(directRequirements)) {
|
|
114
140
|
const tool = listed.tools.find((entry) => entry.name === toolName);
|
|
@@ -119,6 +145,21 @@ try {
|
|
|
119
145
|
}
|
|
120
146
|
}
|
|
121
147
|
}
|
|
148
|
+
} else {
|
|
149
|
+
const searchRequirements = {
|
|
150
|
+
discover_tools: ["query"],
|
|
151
|
+
explain_tool: ["name"],
|
|
152
|
+
invoke_discovered_tool: ["toolName", "arguments", "discoveryToken"],
|
|
153
|
+
};
|
|
154
|
+
for (const [toolName, requiredProperties] of Object.entries(searchRequirements)) {
|
|
155
|
+
const tool = listed.tools.find((entry) => entry.name === toolName);
|
|
156
|
+
const properties = Object.keys(tool?.inputSchema?.properties || {});
|
|
157
|
+
for (const property of requiredProperties) {
|
|
158
|
+
if (!properties.includes(property)) {
|
|
159
|
+
throw new Error(`${toolName} schema is missing ${property}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
122
163
|
}
|
|
123
164
|
const healthResult = await client.callTool({ name: "health_check", arguments: {} });
|
|
124
165
|
if (healthResult.isError) throw new Error(String(healthResult.content?.[0]?.text || "health_check failed"));
|
|
@@ -127,7 +168,8 @@ try {
|
|
|
127
168
|
health.tool_profile !== profile ||
|
|
128
169
|
health.tool_count !== expectedTools.length ||
|
|
129
170
|
!health.tool_manifest_sha256 ||
|
|
130
|
-
(profile === "chatgpt_direct" && health.direct_profile_enabled !== true)
|
|
171
|
+
(profile === "chatgpt_direct" && health.direct_profile_enabled !== true) ||
|
|
172
|
+
(profile === "chatgpt_search" && health.search_tool_count !== expectedTools.length)
|
|
131
173
|
) {
|
|
132
174
|
throw new Error(`health_check catalog mismatch: ${JSON.stringify(health)}`);
|
|
133
175
|
}
|
|
@@ -164,12 +206,29 @@ try {
|
|
|
164
206
|
health_check: ["detail"],
|
|
165
207
|
list_tasks: ["repo_path", "active_only"],
|
|
166
208
|
get_task_summary: ["view", "max_items"],
|
|
167
|
-
|
|
209
|
+
run_task_loop: ["repo_path", "goal", "verify_commands", "direct_verify", "isolation_mode", "worktree_cleanup"],
|
|
210
|
+
recommend_agent_for_task: ["repo_path", "goal"],
|
|
211
|
+
get_task_lineage: ["lineage_id"],
|
|
212
|
+
export_task_evidence_pack: ["lineage_id"],
|
|
213
|
+
get_project_policy: ["repo_path"],
|
|
214
|
+
safe_result: ["task_id"],
|
|
215
|
+
safe_audit: ["task_id"],
|
|
216
|
+
safe_test_summary: ["task_id"],
|
|
217
|
+
safe_diff_summary: ["task_id"],
|
|
218
|
+
} : profile === "chatgpt_direct" ? {
|
|
168
219
|
create_direct_session: ["repo_path"],
|
|
169
220
|
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
170
221
|
run_verification: ["session_id", "command"],
|
|
222
|
+
run_direct_verification_bundle: ["session_id", "commands"],
|
|
171
223
|
finalize_direct_session: ["session_id"],
|
|
172
224
|
audit_session: ["session_id"],
|
|
225
|
+
safe_direct_summary: ["session_id"],
|
|
226
|
+
safe_finalize_direct_session: ["session_id"],
|
|
227
|
+
safe_audit_direct_session: ["session_id"],
|
|
228
|
+
} : {
|
|
229
|
+
discover_tools: ["query"],
|
|
230
|
+
explain_tool: ["name"],
|
|
231
|
+
invoke_discovered_tool: ["toolName", "arguments", "discoveryToken"],
|
|
173
232
|
},
|
|
174
233
|
};
|
|
175
234
|
console.log(jsonOnly ? JSON.stringify(output) : JSON.stringify(output, null, 2));
|