patchwarden 1.6.1 → 1.6.2
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 +17 -12
- package/README.md +14 -11
- package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
- package/dist/assessments/assessmentDiagnostics.js +132 -0
- package/dist/assessments/assessmentStore.d.ts +39 -0
- package/dist/assessments/assessmentStore.js +166 -12
- package/dist/assessments/securitySnapshot.d.ts +50 -0
- package/dist/assessments/securitySnapshot.js +158 -0
- package/dist/control/routes/status.d.ts +14 -0
- package/dist/control/routes/status.js +22 -0
- package/dist/control/runtime.js +2 -10
- package/dist/control/server.js +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
- package/dist/direct/directSessionStore.js +5 -4
- package/dist/doctor.js +3 -5
- package/dist/runner/runTask.d.ts +1 -0
- package/dist/runner/runTask.js +47 -6
- package/dist/runner/taskRuntime.d.ts +2 -0
- package/dist/runner/taskStatusStore.js +12 -1
- package/dist/runner/watch.js +64 -1
- package/dist/smoke-test.js +3 -3
- package/dist/tools/definitions/toolDefs.js +3 -3
- package/dist/tools/diagnostics/auditTask.d.ts +1 -0
- package/dist/tools/diagnostics/auditTask.js +35 -7
- package/dist/tools/diagnostics/healthCheck.d.ts +12 -0
- package/dist/tools/diagnostics/healthCheck.js +29 -1
- package/dist/tools/tasks/cancelTask.js +2 -1
- package/dist/tools/tasks/createTask.d.ts +2 -2
- package/dist/tools/tasks/createTask.js +70 -9
- package/dist/tools/tasks/diagnoseTask.js +4 -8
- package/dist/tools/tasks/getTaskStatus.js +6 -1
- package/dist/tools/tasks/getTaskSummary.js +2 -10
- package/dist/tools/tasks/listTasks.js +2 -1
- package/dist/tools/tasks/reconcileTasks.d.ts +2 -1
- package/dist/tools/tasks/reconcileTasks.js +185 -39
- package/dist/tools/tasks/runTaskLoop.js +5 -11
- package/dist/tools/tasks/taskOutputs.d.ts +2 -2
- package/dist/tools/tasks/taskStates.d.ts +6 -0
- package/dist/tools/tasks/taskStates.js +52 -0
- package/dist/tools/tasks/waitForTask.js +3 -11
- package/dist/tools/workspace/listAgents.d.ts +6 -0
- package/dist/tools/workspace/listAgents.js +12 -2
- package/dist/utils/atomicFile.js +20 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/CODE_WIKI.md +4 -4
- package/docs/open-source-application.md +11 -12
- package/docs/release-evidence.md +33 -47
- package/package.json +1 -1
- package/scripts/checks/control-center-smoke.js +11 -4
- package/scripts/checks/lifecycle-smoke.js +5 -2
- package/scripts/checks/mcp-smoke.js +31 -1
- package/scripts/checks/watcher-supervisor-smoke.js +29 -1
- package/scripts/control/start-patchwarden-tunnel.ps1 +65 -1
- package/scripts/e2e/demo-runtime.mjs +153 -0
- package/scripts/release/desktop-preflight.js +1 -1
- package/src/assessments/assessmentDiagnostics.ts +172 -0
- package/src/assessments/assessmentStore.ts +237 -12
- package/src/assessments/securitySnapshot.ts +231 -0
- package/src/control/routes/status.ts +37 -0
- package/src/control/runtime.ts +2 -10
- package/src/control/server.ts +1 -0
- package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
- package/src/direct/directSessionStore.ts +6 -4
- package/src/doctor.ts +3 -5
- package/src/runner/runTask.ts +59 -6
- package/src/runner/taskRuntime.ts +2 -0
- package/src/runner/taskStatusStore.ts +16 -1
- package/src/runner/watch.ts +67 -1
- package/src/smoke-test.ts +2 -2
- package/src/tools/definitions/toolDefs.ts +3 -3
- package/src/tools/diagnostics/auditTask.ts +37 -7
- package/src/tools/diagnostics/healthCheck.ts +29 -1
- package/src/tools/tasks/cancelTask.ts +2 -1
- package/src/tools/tasks/createTask.ts +113 -8
- package/src/tools/tasks/diagnoseTask.ts +4 -8
- package/src/tools/tasks/getTaskStatus.ts +6 -1
- package/src/tools/tasks/getTaskSummary.ts +2 -11
- package/src/tools/tasks/listTasks.ts +2 -1
- package/src/tools/tasks/reconcileTasks.ts +162 -13
- package/src/tools/tasks/runTaskLoop.ts +4 -12
- package/src/tools/tasks/taskStates.ts +54 -0
- package/src/tools/tasks/waitForTask.ts +3 -12
- package/src/tools/workspace/listAgents.ts +17 -3
- package/src/utils/atomicFile.ts +17 -1
- package/src/version.ts +1 -1
- package/ui/pages/dashboard.html +4 -0
- package/ui/pages/logs.html +29 -0
- package/ui/pages/settings.html +3 -3
- package/ui/settings.js +26 -17
|
@@ -56,24 +56,24 @@ evidence packs.
|
|
|
56
56
|
|
|
57
57
|
## Current Public Evidence
|
|
58
58
|
|
|
59
|
-
Remote snapshot checked on 2026-07-
|
|
59
|
+
Remote snapshot checked on 2026-07-23:
|
|
60
60
|
|
|
61
61
|
- GitHub repository: `jiezeng2004-design/PatchWarden`
|
|
62
62
|
- Stars: 2
|
|
63
63
|
- Forks: 0
|
|
64
|
-
- Open issues:
|
|
65
|
-
- Open pull requests:
|
|
66
|
-
- GitHub latest release: `v1.6.
|
|
67
|
-
- npm latest dist-tag: `patchwarden@1.
|
|
64
|
+
- Open issues: 5
|
|
65
|
+
- Open pull requests: 1
|
|
66
|
+
- GitHub latest release: `v1.6.1`, published on 2026-07-23
|
|
67
|
+
- npm latest dist-tag: `patchwarden@1.6.1`
|
|
68
68
|
|
|
69
69
|
Local workspace facts, kept separate from the public snapshot:
|
|
70
70
|
|
|
71
71
|
- Local source version in `package.json`: `1.6.1`
|
|
72
|
-
-
|
|
72
|
+
- Verified release commit: `2ab2b7405d89b12c4ef10febf56404de59c04053`
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
The local source, GitHub Release, and npm `latest` are synchronized at `v1.6.1`.
|
|
75
|
+
The GitHub Release contains reviewed Core and Windows Desktop assets plus
|
|
76
|
+
SHA-256 manifests.
|
|
77
77
|
|
|
78
78
|
## Existing Project Evidence
|
|
79
79
|
|
|
@@ -117,9 +117,8 @@ Known gaps before submission:
|
|
|
117
117
|
- collect 3 to 5 real user reports through GitHub issues or discussions
|
|
118
118
|
- record a live screencast if stronger evidence is needed; a privacy-safe
|
|
119
119
|
scripted GIF now exists for the public maintainer workflow
|
|
120
|
-
-
|
|
121
|
-
|
|
122
|
-
/ `dist-tags.latest` are `1.5.1`
|
|
120
|
+
- keep future source, GitHub Release, and npm publication facts synchronized
|
|
121
|
+
through the reviewed release workflow
|
|
123
122
|
- add small contributor-friendly roadmap issues such as macOS docs, Codex CLI
|
|
124
123
|
examples, and a minimal demo repository
|
|
125
124
|
|
package/docs/release-evidence.md
CHANGED
|
@@ -5,28 +5,24 @@ before submitting external applications or publishing a release.
|
|
|
5
5
|
|
|
6
6
|
## Current Snapshot
|
|
7
7
|
|
|
8
|
-
Remote facts checked on 2026-07-
|
|
9
|
-
from the PatchWarden workspace and are not evidence that a public package was
|
|
10
|
-
published.
|
|
8
|
+
Remote facts checked on 2026-07-23 after the v1.6.1 release completed.
|
|
11
9
|
|
|
12
10
|
| Surface | Current evidence |
|
|
13
11
|
| --- | --- |
|
|
14
|
-
|
|
|
12
|
+
| Verified merge commit | `2ab2b7405d89b12c4ef10febf56404de59c04053` |
|
|
15
13
|
| Local `package.json` | `patchwarden@1.6.1` |
|
|
16
|
-
| GitHub latest release | `v1.6.
|
|
17
|
-
| GitHub release URL | https://github.com/jiezeng2004-design/PatchWarden/releases/tag/v1.6.
|
|
18
|
-
| GitHub release published on | 2026-07-
|
|
19
|
-
| npm latest | `patchwarden@1.
|
|
20
|
-
| npm `dist-tags.latest` | `1.
|
|
14
|
+
| GitHub latest release | `v1.6.1` |
|
|
15
|
+
| GitHub release URL | https://github.com/jiezeng2004-design/PatchWarden/releases/tag/v1.6.1 |
|
|
16
|
+
| GitHub release published on | 2026-07-23 |
|
|
17
|
+
| npm latest | `patchwarden@1.6.1` |
|
|
18
|
+
| npm `dist-tags.latest` | `1.6.1` |
|
|
21
19
|
| GitHub stars | 2 |
|
|
22
20
|
| GitHub forks | 0 |
|
|
23
|
-
| Open issues |
|
|
24
|
-
| Open pull requests |
|
|
21
|
+
| Open issues | 5 |
|
|
22
|
+
| Open pull requests | 1 |
|
|
25
23
|
|
|
26
|
-
Conclusion:
|
|
27
|
-
`
|
|
28
|
-
release surfaces as synchronized until GitHub, npm, and `dist-tags.latest` are
|
|
29
|
-
independently verified at the intended version.
|
|
24
|
+
Conclusion: source, Git tag, GitHub Release, npm package version, and
|
|
25
|
+
`dist-tags.latest` are independently verified at `v1.6.1`.
|
|
30
26
|
|
|
31
27
|
## Commands Used
|
|
32
28
|
|
|
@@ -34,49 +30,39 @@ Windows PowerShell:
|
|
|
34
30
|
|
|
35
31
|
```powershell
|
|
36
32
|
gh repo view jiezeng2004-design/PatchWarden --json nameWithOwner,stargazerCount,forkCount,issues,pullRequests,defaultBranchRef,pushedAt,url,description,licenseInfo,repositoryTopics,latestRelease,hasDiscussionsEnabled,hasIssuesEnabled,securityPolicyUrl
|
|
37
|
-
gh release view v1.6.
|
|
33
|
+
gh release view v1.6.1 --repo jiezeng2004-design/PatchWarden --json tagName,name,publishedAt,isDraft,isPrerelease,url
|
|
38
34
|
gh release view --repo jiezeng2004-design/PatchWarden --json tagName,name,publishedAt,isDraft,isPrerelease,url
|
|
39
35
|
npm.cmd view patchwarden version dist-tags --json --cache .\.npm-cache
|
|
40
36
|
```
|
|
41
37
|
|
|
42
|
-
The npm query was verified on 2026-07-
|
|
43
|
-
`dist-tags.latest=1.
|
|
38
|
+
The npm query was verified on 2026-07-23: `version=1.6.1` and
|
|
39
|
+
`dist-tags.latest=1.6.1`.
|
|
44
40
|
|
|
45
|
-
##
|
|
41
|
+
## v1.6.1 Verification Snapshot
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
lifecycle tests: 8 passed.
|
|
54
|
-
- `npm.cmd test`: passed on the second baseline run, including 139/139 security
|
|
55
|
-
checks, 747 passed and 2 skipped unit tests, 22/22 lifecycle checks, and
|
|
56
|
-
32/32 Control Center checks. An earlier run exposed one Git commit setup
|
|
57
|
-
flake; the test setup was then hardened.
|
|
58
|
-
- `npm.cmd run test:mcp`: passed.
|
|
59
|
-
- `npm.cmd run test:http-mcp`: 13 passed, 0 failed.
|
|
60
|
-
- `npm.cmd run desktop:test`: 41 passed, 0 failed at the baseline snapshot.
|
|
43
|
+
- GitHub CI: Node.js 18, Ubuntu Node.js 20, Windows Node.js 20, secret scan,
|
|
44
|
+
Windows Desktop installer, and final CI gate passed.
|
|
45
|
+
- Core security smoke: 141/141; unit tests: 855 passed and 3 skipped;
|
|
46
|
+
lifecycle: 22/22; HTTP MCP: 17/17; MCP smoke and Doctor passed.
|
|
47
|
+
- Desktop tests: 54/54; clean-source Desktop preflight and packaged UI smoke
|
|
48
|
+
passed from the verified merge commit.
|
|
61
49
|
- Root and Desktop dependency audits: 0 known vulnerabilities.
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
overwrites those package artifacts, so run it only during explicitly approved
|
|
66
|
-
release preparation.
|
|
50
|
+
- Clean release directory: 532 files; npm package manifest: 531 files.
|
|
51
|
+
- GitHub Release: six reviewed assets with GitHub-computed and local SHA-256
|
|
52
|
+
digests; installer signature status is explicitly documented as unsigned.
|
|
67
53
|
|
|
68
54
|
## Release Verification Checklist
|
|
69
55
|
|
|
70
|
-
- [
|
|
71
|
-
- [
|
|
56
|
+
- [x] Confirm the target version in `package.json`.
|
|
57
|
+
- [x] Confirm `src/version.ts`, package metadata, README version text, and
|
|
72
58
|
changelog agree.
|
|
73
|
-
- [
|
|
74
|
-
- [
|
|
75
|
-
- [
|
|
76
|
-
- [
|
|
77
|
-
- [
|
|
78
|
-
- [
|
|
79
|
-
- [
|
|
59
|
+
- [x] Run the complete local gate chain from `AGENTS.md`.
|
|
60
|
+
- [x] Open a PR and wait for the GitHub CI gate.
|
|
61
|
+
- [x] Merge only after review.
|
|
62
|
+
- [x] Create the tag from the verified merge commit.
|
|
63
|
+
- [x] Create the GitHub Release with reviewed artifacts and checksums.
|
|
64
|
+
- [x] Publish `patchwarden` to npm using process-scoped authentication.
|
|
65
|
+
- [x] Verify the remote tag, GitHub Release, npm package version, and
|
|
80
66
|
`dist-tags.latest`.
|
|
81
67
|
|
|
82
68
|
Do not publish new versions under the frozen pre-rename package name.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwarden",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
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",
|
|
@@ -251,6 +251,7 @@ async function testStaticFiles() {
|
|
|
251
251
|
const css = await httpGet(`${BASE_URL}/colors_and_type.css`);
|
|
252
252
|
const desktopCss = await httpGet(`${BASE_URL}/desktop.css`);
|
|
253
253
|
const desktopBridge = await httpGet(`${BASE_URL}/desktop-bridge.js`);
|
|
254
|
+
const logParser = await httpGet(`${BASE_URL}/log-parser.js`);
|
|
254
255
|
const i18n = await httpGet(`${BASE_URL}/i18n.js`);
|
|
255
256
|
const gettingStarted = await httpGet(`${BASE_URL}/pages/getting-started.html`);
|
|
256
257
|
const gettingStartedJs = await httpGet(`${BASE_URL}/getting-started.js`);
|
|
@@ -280,6 +281,9 @@ async function testStaticFiles() {
|
|
|
280
281
|
if (desktopBridge.status !== 200 || !(desktopBridge.headers["content-type"] || "").includes("javascript")) {
|
|
281
282
|
checks.push("desktop bridge is not served as JavaScript");
|
|
282
283
|
}
|
|
284
|
+
if (logParser.status !== 200 || !(logParser.headers["content-type"] || "").includes("javascript") || !logParser.body.includes("PatchWardenLogParser")) {
|
|
285
|
+
checks.push("log parser is not served as JavaScript");
|
|
286
|
+
}
|
|
283
287
|
if (i18n.status !== 200 || !i18n.body.includes("applyTranslations") || !i18n.body.includes('"zh-CN"') || !i18n.body.includes("en:")) {
|
|
284
288
|
checks.push("shared Chinese/English translation dictionary is unavailable");
|
|
285
289
|
}
|
|
@@ -940,9 +944,9 @@ async function testReconcileNoToken() {
|
|
|
940
944
|
}
|
|
941
945
|
}
|
|
942
946
|
|
|
943
|
-
// ── Test 14: GET /api/direct-sessions returns
|
|
947
|
+
// ── Test 14: GET /api/direct-sessions returns a bounded list (not 500)
|
|
944
948
|
async function testDirectSessionsEmptyList() {
|
|
945
|
-
const name = "Test 14: /api/direct-sessions returns
|
|
949
|
+
const name = "Test 14: /api/direct-sessions returns a bounded list (not 500)";
|
|
946
950
|
try {
|
|
947
951
|
const res = await httpGet(`${BASE_URL}/api/direct-sessions`);
|
|
948
952
|
if (res.status !== 200) {
|
|
@@ -961,8 +965,11 @@ async function testDirectSessionsEmptyList() {
|
|
|
961
965
|
if (!isObject(json.filters) || !isObject(json.filters.options)) problems.push("'filters' contract is missing");
|
|
962
966
|
if (typeof json.direct_profile_enabled !== "boolean") problems.push("'direct_profile_enabled' is not a boolean");
|
|
963
967
|
if (json.reason !== null && typeof json.reason !== "string") problems.push("'reason' is neither null nor string");
|
|
964
|
-
if (Array.isArray(json.sessions) && json.sessions.length
|
|
965
|
-
problems.push(`sessions.length (${json.sessions.length})
|
|
968
|
+
if (Array.isArray(json.sessions) && json.sessions.length > json.total) {
|
|
969
|
+
problems.push(`sessions.length (${json.sessions.length}) exceeds total (${json.total})`);
|
|
970
|
+
}
|
|
971
|
+
if (Array.isArray(json.sessions) && json.sessions.length < json.total && typeof json.nextCursor !== "string") {
|
|
972
|
+
problems.push("paginated direct sessions response is missing nextCursor");
|
|
966
973
|
}
|
|
967
974
|
const invalidDetail = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id`);
|
|
968
975
|
const invalidSummary = await httpGet(`${BASE_URL}/api/direct-sessions/invalid.id/summary`);
|
|
@@ -170,6 +170,9 @@ try {
|
|
|
170
170
|
if (result.total !== 8 || result.agents.some((agent) => !agent.available)) {
|
|
171
171
|
throw new Error(`Unexpected agent availability: ${JSON.stringify(result)}`);
|
|
172
172
|
}
|
|
173
|
+
if (result.agents.some((agent) => agent.availability_scope !== "executable_only" || agent.provider_status !== "not_checked")) {
|
|
174
|
+
throw new Error(`Agent readiness scope was not explicit: ${JSON.stringify(result)}`);
|
|
175
|
+
}
|
|
173
176
|
});
|
|
174
177
|
|
|
175
178
|
await test("git diff captures tracked and untracked task changes", async () => {
|
|
@@ -803,12 +806,12 @@ try {
|
|
|
803
806
|
const task = await createTask({ plan_id: plan.plan_id, agent: "slow", repo_path: "repo", timeout_seconds: 1 });
|
|
804
807
|
const started = Date.now();
|
|
805
808
|
const result = await runTask(task.task_id);
|
|
806
|
-
if (result.status !== "
|
|
809
|
+
if (result.status !== "timeout" || !result.error?.includes("timed out")) {
|
|
807
810
|
throw new Error(`Expected timeout failure, got ${JSON.stringify(result)}`);
|
|
808
811
|
}
|
|
809
812
|
if (Date.now() - started > 10000) throw new Error("Timeout took too long to stop the process");
|
|
810
813
|
const status = getTaskStatus(task.task_id);
|
|
811
|
-
if (status.phase !== "
|
|
814
|
+
if (status.phase !== "timeout" || status.termination_reason !== "timeout" || !status.last_heartbeat_at) {
|
|
812
815
|
throw new Error(`Missing timeout phase/heartbeat: ${JSON.stringify(status)}`);
|
|
813
816
|
}
|
|
814
817
|
if (!existsSync(join(task.path, "progress.md"))) throw new Error("progress.md missing");
|
|
@@ -14,7 +14,10 @@ import { fileURLToPath } from "node:url";
|
|
|
14
14
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
15
15
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const sourceRoot = resolve(fileURLToPath(new URL("../..", import.meta.url)));
|
|
18
|
+
const root = process.env.PATCHWARDEN_TEST_RUNTIME_ROOT
|
|
19
|
+
? resolve(process.env.PATCHWARDEN_TEST_RUNTIME_ROOT)
|
|
20
|
+
: sourceRoot;
|
|
18
21
|
const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
|
|
19
22
|
const expectedServerVersion = packageJson.version;
|
|
20
23
|
const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-mcp-"));
|
|
@@ -292,6 +295,33 @@ try {
|
|
|
292
295
|
}
|
|
293
296
|
ok("create_task accepts inline plans and guarded templates");
|
|
294
297
|
|
|
298
|
+
const assessment = await parseToolJson("create_task", {
|
|
299
|
+
execution_mode: "assess_only",
|
|
300
|
+
template: "inspect_only",
|
|
301
|
+
goal: "Inspect package metadata without changing files.",
|
|
302
|
+
agent: "codex",
|
|
303
|
+
repo_path: ".",
|
|
304
|
+
verify_commands: [],
|
|
305
|
+
scope: ["package.json"],
|
|
306
|
+
forbidden: ["release/**"],
|
|
307
|
+
});
|
|
308
|
+
const assessmentFile = join(workspaceRoot, ".patchwarden", "assessments", assessment.assessment_id, "assessment.json");
|
|
309
|
+
const assessmentRecord = JSON.parse(readFileSync(assessmentFile, "utf-8"));
|
|
310
|
+
if (
|
|
311
|
+
!/^[a-f0-9]{64}$/.test(assessmentRecord.execution_config_hash || "") ||
|
|
312
|
+
assessmentRecord.assessment_security_snapshot_version !== "assessment-security-v2"
|
|
313
|
+
) {
|
|
314
|
+
throw new Error(`assessment security fingerprint missing: ${JSON.stringify(assessmentRecord)}`);
|
|
315
|
+
}
|
|
316
|
+
const assessedTask = await parseToolJson("create_task", {
|
|
317
|
+
execution_mode: "execute",
|
|
318
|
+
assessment_id: assessment.assessment_id,
|
|
319
|
+
});
|
|
320
|
+
if (assessedTask.status !== "pending" || !assessedTask.task_id) {
|
|
321
|
+
throw new Error(`two-stage assessment did not create a pending task: ${JSON.stringify(assessedTask)}`);
|
|
322
|
+
}
|
|
323
|
+
ok("assess_only then execute preserves the versioned security fingerprint");
|
|
324
|
+
|
|
295
325
|
const blockedAgent = await client.callTool({
|
|
296
326
|
name: "create_task",
|
|
297
327
|
arguments: { plan_id: plan.plan_id, agent: "missing-agent", repo_path: "." },
|
|
@@ -36,8 +36,9 @@ try {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
await runReusedPidLockScenario();
|
|
39
40
|
await runExternalScenario();
|
|
40
|
-
console.log("ok - watcher supervisor isolates environment and handles exit, stale heartbeat, retry limit, and external ownership");
|
|
41
|
+
console.log("ok - watcher supervisor isolates environment and handles exit, stale heartbeat, PID reuse, retry limit, and external ownership");
|
|
41
42
|
} finally {
|
|
42
43
|
for (const child of children) {
|
|
43
44
|
if (child.exitCode === null) child.kill("SIGKILL");
|
|
@@ -45,6 +46,32 @@ try {
|
|
|
45
46
|
rmSync(temp, { recursive: true, force: true });
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
async function runReusedPidLockScenario() {
|
|
50
|
+
const fixture = createFixture("reused-pid-lock");
|
|
51
|
+
mkdirSync(dirname(fixture.lockPath), { recursive: true });
|
|
52
|
+
writeFileSync(fixture.lockPath, JSON.stringify({
|
|
53
|
+
pid: process.pid,
|
|
54
|
+
instance_id: "stale-reused-pid",
|
|
55
|
+
launcher_pid: 999999,
|
|
56
|
+
started_at: "2026-01-01T00:00:00.000Z",
|
|
57
|
+
}), "utf-8");
|
|
58
|
+
writeFileSync(fixture.heartbeatPath, JSON.stringify({
|
|
59
|
+
status: "running",
|
|
60
|
+
pid: process.pid,
|
|
61
|
+
instance_id: "stale-reused-pid",
|
|
62
|
+
launcher_pid: 999999,
|
|
63
|
+
started_at: "2026-01-01T00:00:00.000Z",
|
|
64
|
+
last_heartbeat_at: "2026-01-01T00:00:00.000Z",
|
|
65
|
+
}), "utf-8");
|
|
66
|
+
|
|
67
|
+
const result = runLauncher(fixture, "healthy", 1, 7_000);
|
|
68
|
+
if (result.error && result.error.code === "ETIMEDOUT") throw result.error;
|
|
69
|
+
const attempts = Number(readFileSync(fixture.watcherAttemptPath, "utf-8")) || 0;
|
|
70
|
+
if (attempts < 1 || !result.stdout.includes("Removed stale watcher lock after PID identity verification")) {
|
|
71
|
+
throw new Error(`Reused PID lock was not safely retired: ${JSON.stringify({ attempts, stdout: result.stdout, stderr: result.stderr })}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
async function runScenario(label, mode, maxRestarts, lifetimeMs, assertState) {
|
|
49
76
|
const fixture = createFixture(label.replace(/[^a-z0-9]+/gi, "-"));
|
|
50
77
|
const result = runLauncher(fixture, mode, maxRestarts, lifetimeMs);
|
|
@@ -139,6 +166,7 @@ function createFixture(name) {
|
|
|
139
166
|
watcherPath,
|
|
140
167
|
watcherAttemptPath,
|
|
141
168
|
heartbeatPath: join(workspace, ".patchwarden", "watcher-heartbeat.json"),
|
|
169
|
+
lockPath: join(workspace, ".patchwarden", "watcher.lock"),
|
|
142
170
|
watcherStatusPath: join(localAppData, "patchwarden", "runtime", "watcher-status.json"),
|
|
143
171
|
mockCmd,
|
|
144
172
|
localAppData,
|
|
@@ -80,6 +80,15 @@ if (-not $ConfigPath) {
|
|
|
80
80
|
}
|
|
81
81
|
$McpLauncherName = if ($ToolProfile -eq "chatgpt_direct") { "patchwarden-mcp-direct.cmd" } else { "patchwarden-mcp-stdio.cmd" }
|
|
82
82
|
$McpStdioLauncher = Join-Path $ProjectRoot "scripts\mcp\$McpLauncherName"
|
|
83
|
+
$McpEntryPath = Join-Path $ProjectRoot "dist\index.js"
|
|
84
|
+
$WatcherEntryPath = Join-Path $ProjectRoot "dist\runner\watch.js"
|
|
85
|
+
$RuntimeBuildLayout = if (Test-Path -LiteralPath (Join-Path $ProjectRoot ".git")) {
|
|
86
|
+
"git_worktree"
|
|
87
|
+
} elseif ($ProjectRoot -match '(?i)[\\/]resources[\\/]core$') {
|
|
88
|
+
"desktop_packaged_core"
|
|
89
|
+
} else {
|
|
90
|
+
"standalone_build"
|
|
91
|
+
}
|
|
83
92
|
$McpStdioLauncherForTunnel = $McpStdioLauncher -replace "\\", "/"
|
|
84
93
|
$OpencodeConfigHome = Join-Path $env:LOCALAPPDATA "patchwarden\opencode-config"
|
|
85
94
|
$ProfilePath = Join-Path $env:APPDATA "tunnel-client\$Profile.yaml"
|
|
@@ -243,6 +252,14 @@ function Get-LastDiagnosticLine {
|
|
|
243
252
|
return $Fallback
|
|
244
253
|
}
|
|
245
254
|
|
|
255
|
+
function Get-MaskedTunnelId {
|
|
256
|
+
param([string]$Value)
|
|
257
|
+
if ([string]::IsNullOrWhiteSpace($Value)) { return $null }
|
|
258
|
+
$trimmed = $Value.Trim()
|
|
259
|
+
if ($trimmed.Length -le 8) { return "***" }
|
|
260
|
+
return "$($trimmed.Substring(0, 4))***$($trimmed.Substring($trimmed.Length - 4))"
|
|
261
|
+
}
|
|
262
|
+
|
|
246
263
|
function Write-TunnelStatus {
|
|
247
264
|
param(
|
|
248
265
|
[string]$Status,
|
|
@@ -263,6 +280,8 @@ function Write-TunnelStatus {
|
|
|
263
280
|
status = $Status
|
|
264
281
|
reason_code = $ReasonCode
|
|
265
282
|
ready = $Ready
|
|
283
|
+
profile = $Profile
|
|
284
|
+
tunnel_id_masked = Get-MaskedTunnelId -Value $TunnelId
|
|
266
285
|
attempt = $Attempt
|
|
267
286
|
pid = $ProcessId
|
|
268
287
|
checked_at = (Get-Date).ToUniversalTime().ToString("o")
|
|
@@ -281,6 +300,12 @@ function Write-TunnelStatus {
|
|
|
281
300
|
tool_manifest_sha256 = if ($script:ToolManifest) { $script:ToolManifest.tool_manifest_sha256 } else { $null }
|
|
282
301
|
tools_ready = [bool]($script:ToolManifest -and $script:ToolManifest.ok)
|
|
283
302
|
core_tools_ready = [bool]($ToolProfile -eq "chatgpt_core" -and $script:ToolManifest -and $script:ToolManifest.ok)
|
|
303
|
+
project_root = $ProjectRoot
|
|
304
|
+
config_path = $ConfigPath
|
|
305
|
+
mcp_entry_path = $McpEntryPath
|
|
306
|
+
watcher_entry_path = $WatcherEntryPath
|
|
307
|
+
validator_module_path = (Join-Path $ProjectRoot "dist\assessments\assessmentStore.js")
|
|
308
|
+
runtime_build_layout = $RuntimeBuildLayout
|
|
284
309
|
}
|
|
285
310
|
$temporary = "$StatusFile.tmp"
|
|
286
311
|
$payload | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $temporary -Encoding UTF8
|
|
@@ -344,6 +369,30 @@ function Get-WatcherHeartbeatState {
|
|
|
344
369
|
}
|
|
345
370
|
}
|
|
346
371
|
|
|
372
|
+
function Repair-StaleWatcherLock {
|
|
373
|
+
$settings = Get-WatcherSettings
|
|
374
|
+
$lockPath = Join-Path (Split-Path -Parent $settings.HeartbeatPath) "watcher.lock"
|
|
375
|
+
if (-not (Test-Path -LiteralPath $lockPath)) { return "not_present" }
|
|
376
|
+
try {
|
|
377
|
+
$lock = Get-Content -LiteralPath $lockPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
378
|
+
$lockPid = [int]$lock.pid
|
|
379
|
+
if ($lockPid -le 0) { return "unverified" }
|
|
380
|
+
$candidate = Get-CimInstance Win32_Process -Filter "ProcessId = $lockPid" -ErrorAction Stop
|
|
381
|
+
if ($candidate) {
|
|
382
|
+
$normalizedCommand = ([string]$candidate.CommandLine).Replace("/", "\")
|
|
383
|
+
$normalizedExpected = ([System.IO.Path]::GetFullPath($WatcherEntryPath)).Replace("/", "\")
|
|
384
|
+
$isExpectedWatcher = [string]$candidate.Name -ieq "node.exe" -and
|
|
385
|
+
$normalizedCommand.IndexOf($normalizedExpected, [StringComparison]::OrdinalIgnoreCase) -ge 0
|
|
386
|
+
if ($isExpectedWatcher) { return "expected_watcher_alive" }
|
|
387
|
+
}
|
|
388
|
+
Remove-Item -LiteralPath $lockPath -Force -ErrorAction Stop
|
|
389
|
+
Write-Host "[watch] Removed stale watcher lock after PID identity verification."
|
|
390
|
+
return "removed"
|
|
391
|
+
} catch {
|
|
392
|
+
return "unverified"
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
347
396
|
function Write-WatcherStatus {
|
|
348
397
|
param([string]$Status, [string]$LastError = $null)
|
|
349
398
|
New-Item -ItemType Directory -Force -Path $RuntimeDirectory | Out-Null
|
|
@@ -356,6 +405,11 @@ function Write-WatcherStatus {
|
|
|
356
405
|
restart_attempts = $script:WatcherRestartAttempts
|
|
357
406
|
checked_at = (Get-Date).ToUniversalTime().ToString("o")
|
|
358
407
|
last_error = if ($LastError) { ($LastError -replace '[\r\n]+', ' ').Substring(0, [Math]::Min(500, ($LastError -replace '[\r\n]+', ' ').Length)) } else { $null }
|
|
408
|
+
project_root = $ProjectRoot
|
|
409
|
+
config_path = $ConfigPath
|
|
410
|
+
watcher_entry_path = $WatcherEntryPath
|
|
411
|
+
validator_module_path = (Join-Path $ProjectRoot "dist\assessments\assessmentStore.js")
|
|
412
|
+
runtime_build_layout = $RuntimeBuildLayout
|
|
359
413
|
}
|
|
360
414
|
$temporary = "$WatcherStatusFile.tmp"
|
|
361
415
|
$payload | ConvertTo-Json -Depth 4 | Set-Content -LiteralPath $temporary -Encoding UTF8
|
|
@@ -379,6 +433,7 @@ function Start-OwnedWatcherProcess {
|
|
|
379
433
|
$script:WatcherHealthySince = $null
|
|
380
434
|
$previousWatcherInstanceId = $env:PATCHWARDEN_WATCHER_INSTANCE_ID
|
|
381
435
|
$previousWatcherLauncherPid = $env:PATCHWARDEN_WATCHER_LAUNCHER_PID
|
|
436
|
+
$previousToolProfile = $env:PATCHWARDEN_TOOL_PROFILE
|
|
382
437
|
$previousXdgConfigHome = $env:XDG_CONFIG_HOME
|
|
383
438
|
$previousPath = $env:PATH
|
|
384
439
|
$previousControlPlaneApiKey = $env:CONTROL_PLANE_API_KEY
|
|
@@ -386,17 +441,19 @@ function Start-OwnedWatcherProcess {
|
|
|
386
441
|
try {
|
|
387
442
|
$env:PATCHWARDEN_WATCHER_INSTANCE_ID = $script:WatcherInstanceId
|
|
388
443
|
$env:PATCHWARDEN_WATCHER_LAUNCHER_PID = [string]$PID
|
|
444
|
+
$env:PATCHWARDEN_TOOL_PROFILE = $ToolProfile
|
|
389
445
|
$env:XDG_CONFIG_HOME = $OpencodeConfigHome
|
|
390
446
|
if ($OpencodeBin) { $env:PATH = "$OpencodeBin;$env:PATH" }
|
|
391
447
|
Remove-Item Env:CONTROL_PLANE_API_KEY -ErrorAction SilentlyContinue
|
|
392
448
|
Remove-Item Env:PATCHWARDEN_OWNER_TOKEN -ErrorAction SilentlyContinue
|
|
393
449
|
$script:WatcherProcess = Start-Process -FilePath $node `
|
|
394
|
-
-ArgumentList @(
|
|
450
|
+
-ArgumentList @($WatcherEntryPath) `
|
|
395
451
|
-WorkingDirectory $ProjectRoot -PassThru -WindowStyle Hidden `
|
|
396
452
|
-RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
397
453
|
} finally {
|
|
398
454
|
if ($null -eq $previousWatcherInstanceId) { Remove-Item Env:PATCHWARDEN_WATCHER_INSTANCE_ID -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_WATCHER_INSTANCE_ID = $previousWatcherInstanceId }
|
|
399
455
|
if ($null -eq $previousWatcherLauncherPid) { Remove-Item Env:PATCHWARDEN_WATCHER_LAUNCHER_PID -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_WATCHER_LAUNCHER_PID = $previousWatcherLauncherPid }
|
|
456
|
+
if ($null -eq $previousToolProfile) { Remove-Item Env:PATCHWARDEN_TOOL_PROFILE -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_TOOL_PROFILE = $previousToolProfile }
|
|
400
457
|
if ($null -eq $previousXdgConfigHome) { Remove-Item Env:XDG_CONFIG_HOME -ErrorAction SilentlyContinue } else { $env:XDG_CONFIG_HOME = $previousXdgConfigHome }
|
|
401
458
|
if ($null -eq $previousControlPlaneApiKey) { Remove-Item Env:CONTROL_PLANE_API_KEY -ErrorAction SilentlyContinue } else { $env:CONTROL_PLANE_API_KEY = $previousControlPlaneApiKey }
|
|
402
459
|
if ($null -eq $previousOwnerToken) { Remove-Item Env:PATCHWARDEN_OWNER_TOKEN -ErrorAction SilentlyContinue } else { $env:PATCHWARDEN_OWNER_TOKEN = $previousOwnerToken }
|
|
@@ -414,6 +471,13 @@ function Start-PatchWardenWatcher {
|
|
|
414
471
|
Write-Host "[watch] Existing watcher heartbeat is fresh; it remains external and will not be managed."
|
|
415
472
|
return
|
|
416
473
|
}
|
|
474
|
+
$lockRepair = Repair-StaleWatcherLock
|
|
475
|
+
if ($lockRepair -eq "expected_watcher_alive" -or $lockRepair -eq "unverified") {
|
|
476
|
+
$script:WatcherManaged = $false
|
|
477
|
+
Write-WatcherStatus -Status "external_stale" -LastError "A stale watcher lock could not be safely retired."
|
|
478
|
+
Write-Host "[watch] Stale watcher lock retained because process ownership could not be disproved."
|
|
479
|
+
return
|
|
480
|
+
}
|
|
417
481
|
Start-OwnedWatcherProcess
|
|
418
482
|
}
|
|
419
483
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
|
+
|
|
7
|
+
const runtimeRoot = resolve(process.env.PATCHWARDEN_RUNTIME_ROOT || join(import.meta.dirname, "..", "..", "release", "desktop", "win-unpacked", "resources", "core"));
|
|
8
|
+
const configPath = resolve(process.env.PATCHWARDEN_CONFIG || join(process.env.LOCALAPPDATA || "", "PatchWarden", "patchwarden.config.json"));
|
|
9
|
+
const fixture = resolve(process.env.PATCHWARDEN_DEMO_FIXTURE || join(process.env.PATCHWARDEN_WORKSPACE || "D:/ai_agent/patchwarden_program", "patchwarden-demo-fixture"));
|
|
10
|
+
const evidencePath = resolve(process.env.PATCHWARDEN_DEMO_EVIDENCE || join(process.env.PATCHWARDEN_WORKSPACE || "D:/ai_agent/patchwarden_program", ".patchwarden", "demo-runtime-evidence.json"));
|
|
11
|
+
const mode = process.argv[2] || "core-prepare";
|
|
12
|
+
const profile = mode === "direct" ? "chatgpt_direct" : "chatgpt_core";
|
|
13
|
+
|
|
14
|
+
function decode(result) {
|
|
15
|
+
if (result?.isError) {
|
|
16
|
+
const message = result.content?.find((item) => item.type === "text")?.text || "MCP tool error";
|
|
17
|
+
throw new Error(message);
|
|
18
|
+
}
|
|
19
|
+
const text = result?.content?.find((item) => item.type === "text")?.text;
|
|
20
|
+
if (typeof text !== "string") return result;
|
|
21
|
+
try { return JSON.parse(text); } catch { return { text }; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function connect() {
|
|
25
|
+
const transport = new StdioClientTransport({
|
|
26
|
+
command: process.execPath,
|
|
27
|
+
args: [join(runtimeRoot, "dist", "index.js")],
|
|
28
|
+
cwd: runtimeRoot,
|
|
29
|
+
env: {
|
|
30
|
+
...process.env,
|
|
31
|
+
PATCHWARDEN_CONFIG: configPath,
|
|
32
|
+
PATCHWARDEN_TOOL_PROFILE: profile,
|
|
33
|
+
},
|
|
34
|
+
stderr: "pipe",
|
|
35
|
+
});
|
|
36
|
+
const client = new Client({ name: "patchwarden-demo-e2e", version: "1.0.0" }, { capabilities: {} });
|
|
37
|
+
await client.connect(transport);
|
|
38
|
+
return client;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function call(client, name, args = {}) {
|
|
42
|
+
return decode(await client.callTool({ name, arguments: args }));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function waitForTask(client, taskId) {
|
|
46
|
+
for (;;) {
|
|
47
|
+
const result = await call(client, "wait_for_task", { task_id: taskId, wait_seconds: 30 });
|
|
48
|
+
if (!result?.continuation_required) return result;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function saveEvidence(value) {
|
|
53
|
+
mkdirSync(resolve(evidencePath, ".."), { recursive: true });
|
|
54
|
+
writeFileSync(evidencePath, JSON.stringify(value, null, 2), "utf8");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadEvidence() {
|
|
58
|
+
return JSON.parse(readFileSync(evidencePath, "utf8"));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runCorePrepare(client) {
|
|
62
|
+
const health = await call(client, "health_check", { detail: "self_diagnostic" });
|
|
63
|
+
const agents = await call(client, "list_agents");
|
|
64
|
+
const ordinary = await call(client, "create_task", {
|
|
65
|
+
template: "inspect_only",
|
|
66
|
+
goal: "Read the Demo fixture README and package.json, run the configured tests, and return a short verification summary. Do not modify files.",
|
|
67
|
+
repo_path: "patchwarden-demo-fixture",
|
|
68
|
+
agent: "codex",
|
|
69
|
+
verify_commands: ["npm test", "npm run lint"],
|
|
70
|
+
timeout_seconds: 180,
|
|
71
|
+
});
|
|
72
|
+
const ordinarySummary = await waitForTask(client, ordinary.task_id);
|
|
73
|
+
const ordinaryAudit = await call(client, "audit_task", { task_id: ordinary.task_id });
|
|
74
|
+
const assessmentArgs = {
|
|
75
|
+
execution_mode: "assess_only",
|
|
76
|
+
template: "inspect_only",
|
|
77
|
+
goal: "Read the Demo fixture README and package.json, run npm test and npm run lint, and return a short verification summary. Do not modify files.",
|
|
78
|
+
repo_path: "patchwarden-demo-fixture",
|
|
79
|
+
agent: "codex",
|
|
80
|
+
verify_commands: ["npm test", "npm run lint"],
|
|
81
|
+
timeout_seconds: 180,
|
|
82
|
+
};
|
|
83
|
+
const assessmentBeforeRestart = await call(client, "create_task", assessmentArgs);
|
|
84
|
+
const assessmentPolicyChange = await call(client, "create_task", assessmentArgs);
|
|
85
|
+
saveEvidence({
|
|
86
|
+
created_at: new Date().toISOString(),
|
|
87
|
+
profile,
|
|
88
|
+
runtime_root: runtimeRoot,
|
|
89
|
+
config_path: configPath,
|
|
90
|
+
fixture,
|
|
91
|
+
health,
|
|
92
|
+
agents,
|
|
93
|
+
ordinary_task_id: ordinary.task_id,
|
|
94
|
+
ordinary_summary: ordinarySummary,
|
|
95
|
+
ordinary_audit: ordinaryAudit,
|
|
96
|
+
assessment_before_restart: assessmentBeforeRestart,
|
|
97
|
+
assessment_policy_change: assessmentPolicyChange,
|
|
98
|
+
});
|
|
99
|
+
console.log(JSON.stringify({ ordinary_task_id: ordinary.task_id, assessment_before_restart: assessmentBeforeRestart, assessment_policy_change: assessmentPolicyChange }, null, 2));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function runCoreExecute(client) {
|
|
103
|
+
const evidence = loadEvidence();
|
|
104
|
+
const first = evidence.assessment_before_restart;
|
|
105
|
+
const second = evidence.assessment_policy_change;
|
|
106
|
+
const execute = await call(client, "create_task", { execution_mode: "execute", assessment_id: first.assessment_id });
|
|
107
|
+
const summary = await waitForTask(client, execute.task_id);
|
|
108
|
+
const audit = await call(client, "audit_task", { task_id: execute.task_id });
|
|
109
|
+
const policyDir = join(fixture, ".patchwarden");
|
|
110
|
+
mkdirSync(policyDir, { recursive: true });
|
|
111
|
+
writeFileSync(join(policyDir, "project-policy.json"), JSON.stringify({ allowed_commands: ["node --check main.js"] }, null, 2), "utf8");
|
|
112
|
+
let stale = null;
|
|
113
|
+
try {
|
|
114
|
+
stale = await call(client, "create_task", { execution_mode: "execute", assessment_id: second.assessment_id });
|
|
115
|
+
} catch (error) {
|
|
116
|
+
stale = { error: error instanceof Error ? error.message : String(error) };
|
|
117
|
+
}
|
|
118
|
+
saveEvidence({ ...evidence, execute_task_id: execute.task_id, execute_summary: summary, execute_audit: audit, stale_after_policy_change: stale });
|
|
119
|
+
console.log(JSON.stringify({ execute_task_id: execute.task_id, execute_summary: summary, execute_audit: audit, stale_after_policy_change: stale }, null, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function runDirect(client) {
|
|
123
|
+
const health = await call(client, "health_check", { detail: "self_diagnostic" });
|
|
124
|
+
const session = await call(client, "create_direct_session", { repo_path: "patchwarden-demo-fixture", title: "PatchWarden Demo Direct verification" });
|
|
125
|
+
const file = await call(client, "read_workspace_file", { session_id: session.session_id, path: "main.js" });
|
|
126
|
+
const verification = await call(client, "run_direct_verification_bundle", {
|
|
127
|
+
session_id: session.session_id,
|
|
128
|
+
commands: ["npm test", "npm run lint", "node --check main.js"],
|
|
129
|
+
timeout_seconds: 120,
|
|
130
|
+
});
|
|
131
|
+
const finalized = await call(client, "finalize_direct_session", { session_id: session.session_id });
|
|
132
|
+
const audit = await call(client, "audit_session", { session_id: session.session_id });
|
|
133
|
+
saveEvidence({ direct_health: health, direct_session_id: session.session_id, direct_file: { sha256: file.sha256, bytes: file.bytes }, direct_verification: verification, direct_finalized: finalized, direct_audit: audit });
|
|
134
|
+
console.log(JSON.stringify({ direct_session_id: session.session_id, direct_verification: verification, direct_audit: audit }, null, 2));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function runStatus(client) {
|
|
138
|
+
const health = await call(client, "health_check", { detail: "self_diagnostic" });
|
|
139
|
+
const agents = await call(client, "list_agents");
|
|
140
|
+
const tasks = await call(client, "list_tasks", { active_only: true, limit: 50 });
|
|
141
|
+
console.log(JSON.stringify({ health, agents, active_tasks: tasks }, null, 2));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const client = await connect();
|
|
145
|
+
try {
|
|
146
|
+
if (mode === "core-prepare") await runCorePrepare(client);
|
|
147
|
+
else if (mode === "core-execute") await runCoreExecute(client);
|
|
148
|
+
else if (mode === "direct") await runDirect(client);
|
|
149
|
+
else if (mode === "status") await runStatus(client);
|
|
150
|
+
else throw new Error(`Unsupported demo mode: ${mode}`);
|
|
151
|
+
} finally {
|
|
152
|
+
await client.close();
|
|
153
|
+
}
|
|
@@ -183,7 +183,7 @@ try {
|
|
|
183
183
|
const manifestOutput = run("isolated packaged runtime manifest", process.execPath, ["scripts/checks/mcp-manifest-check.js", "--profile", "chatgpt_core"], isolatedRoot);
|
|
184
184
|
report.runtime_manifest = JSON.parse(manifestOutput);
|
|
185
185
|
if (!report.runtime_manifest.ok || report.runtime_manifest.server_version !== rootPackage.version || report.runtime_manifest.tool_count !== 26) {
|
|
186
|
-
throw new Error(
|
|
186
|
+
throw new Error(`Packaged runtime manifest did not match the ${rootPackage.version} 26-tool contract.`);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
if (!skipUiSmoke) {
|