patchwarden 0.4.0 → 0.6.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/PatchWarden.cmd +51 -0
- package/README.en.md +1022 -0
- package/README.md +897 -358
- package/dist/assessments/agentAssessor.d.ts +15 -0
- package/dist/assessments/agentAssessor.js +293 -0
- package/dist/assessments/assessmentStore.d.ts +133 -0
- package/dist/assessments/assessmentStore.js +238 -0
- package/dist/assessments/confirmCli.d.ts +9 -0
- package/dist/assessments/confirmCli.js +26 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +156 -3
- package/dist/direct/directAudit.d.ts +23 -0
- package/dist/direct/directAudit.js +309 -0
- package/dist/direct/directGuards.d.ts +20 -0
- package/dist/direct/directGuards.js +137 -0
- package/dist/direct/directPatch.d.ts +17 -0
- package/dist/direct/directPatch.js +113 -0
- package/dist/direct/directSessionStore.d.ts +63 -0
- package/dist/direct/directSessionStore.js +192 -0
- package/dist/direct/directVerification.d.ts +12 -0
- package/dist/direct/directVerification.js +96 -0
- package/dist/doctor.js +73 -5
- package/dist/runner/agentInvocation.d.ts +23 -0
- package/dist/runner/agentInvocation.js +106 -0
- package/dist/runner/changeCapture.d.ts +29 -0
- package/dist/runner/changeCapture.js +107 -5
- package/dist/runner/runTask.js +70 -44
- package/dist/runner/simpleProcess.d.ts +21 -0
- package/dist/runner/simpleProcess.js +206 -0
- package/dist/runner/watch.js +17 -2
- package/dist/security/commandGuard.d.ts +2 -1
- package/dist/security/commandGuard.js +25 -3
- package/dist/security/riskEngine.d.ts +27 -0
- package/dist/security/riskEngine.js +118 -0
- package/dist/security/runtimeGuard.d.ts +6 -0
- package/dist/security/runtimeGuard.js +28 -0
- package/dist/smoke-test.js +1353 -7
- package/dist/tools/applyPatch.d.ts +16 -0
- package/dist/tools/applyPatch.js +41 -0
- package/dist/tools/auditSession.d.ts +5 -0
- package/dist/tools/auditSession.js +12 -0
- package/dist/tools/auditTask.d.ts +7 -0
- package/dist/tools/auditTask.js +94 -2
- package/dist/tools/createDirectSession.d.ts +14 -0
- package/dist/tools/createDirectSession.js +54 -0
- package/dist/tools/createTask.d.ts +48 -1
- package/dist/tools/createTask.js +298 -47
- package/dist/tools/finalizeDirectSession.d.ts +19 -0
- package/dist/tools/finalizeDirectSession.js +84 -0
- package/dist/tools/getTaskSummary.d.ts +47 -0
- package/dist/tools/getTaskSummary.js +64 -1
- package/dist/tools/healthCheck.d.ts +22 -12
- package/dist/tools/healthCheck.js +23 -6
- package/dist/tools/readWorkspaceFile.d.ts +7 -1
- package/dist/tools/readWorkspaceFile.js +48 -2
- package/dist/tools/registry.js +217 -19
- package/dist/tools/runVerification.d.ts +16 -0
- package/dist/tools/runVerification.js +32 -0
- package/dist/tools/savePlan.d.ts +1 -0
- package/dist/tools/savePlan.js +38 -7
- package/dist/tools/searchWorkspace.d.ts +19 -0
- package/dist/tools/searchWorkspace.js +205 -0
- package/dist/tools/taskTemplates.js +2 -1
- package/dist/tools/toolCatalog.d.ts +3 -2
- package/dist/tools/toolCatalog.js +32 -5
- package/dist/tools/waitForTask.d.ts +2 -2
- package/dist/tools/waitForTask.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/chatgpt-usage.md +101 -0
- package/docs/release-checklist.md +14 -0
- package/docs/release-v0.6.0.md +71 -0
- package/examples/config.example.json +5 -0
- package/examples/openai-tunnel/README.md +11 -4
- package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
- package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
- package/package.json +12 -8
- package/scripts/brand-check.js +58 -12
- package/scripts/control-smoke.js +206 -0
- package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
- package/scripts/lifecycle-smoke.js +66 -6
- package/scripts/manage-patchwarden.ps1 +639 -0
- package/scripts/mcp-manifest-check.js +100 -58
- package/scripts/mcp-smoke.js +157 -2
- package/scripts/pack-clean.js +3 -4
- package/scripts/package-manifest-check.js +78 -0
- package/scripts/patchwarden-mcp-direct.cmd +7 -0
- package/scripts/patchwarden-mcp-stdio.cmd +1 -1
- package/scripts/restart-patchwarden.ps1 +5 -4
- package/scripts/start-patchwarden-tunnel.ps1 +261 -30
- package/scripts/tunnel-supervisor-smoke.js +36 -4
- package/scripts/watcher-supervisor-smoke.js +10 -6
- package/src/assessments/agentAssessor.ts +324 -0
- package/src/assessments/assessmentStore.ts +426 -0
- package/src/assessments/confirmCli.ts +29 -0
- package/src/config.ts +178 -4
- package/src/direct/directAudit.ts +400 -0
- package/src/direct/directGuards.ts +279 -0
- package/src/direct/directPatch.ts +258 -0
- package/src/direct/directSessionStore.ts +345 -0
- package/src/direct/directVerification.ts +138 -0
- package/src/doctor.ts +103 -7
- package/src/runner/agentInvocation.ts +125 -0
- package/src/runner/changeCapture.ts +140 -5
- package/src/runner/runTask.ts +61 -43
- package/src/runner/simpleProcess.ts +223 -0
- package/src/runner/watch.ts +18 -2
- package/src/security/commandGuard.ts +46 -4
- package/src/security/riskEngine.ts +160 -0
- package/src/security/runtimeGuard.ts +41 -0
- package/src/smoke-test.ts +1291 -4
- package/src/tools/applyPatch.ts +86 -0
- package/src/tools/auditSession.ts +28 -0
- package/src/tools/auditTask.ts +100 -2
- package/src/tools/createDirectSession.ts +113 -0
- package/src/tools/createTask.ts +405 -55
- package/src/tools/finalizeDirectSession.ts +144 -0
- package/src/tools/getTaskSummary.ts +111 -1
- package/src/tools/healthCheck.ts +23 -6
- package/src/tools/readWorkspaceFile.ts +85 -2
- package/src/tools/registry.ts +242 -19
- package/src/tools/runVerification.ts +58 -0
- package/src/tools/savePlan.ts +57 -7
- package/src/tools/searchWorkspace.ts +275 -0
- package/src/tools/taskTemplates.ts +2 -1
- package/src/tools/toolCatalog.ts +35 -6
- package/src/tools/waitForTask.ts +3 -3
- package/src/version.ts +2 -2
- package/tsconfig.json +18 -17
- package/Check-PatchWarden-Health.cmd +0 -6
- package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
- package/Restart-PatchWarden.cmd +0 -19
- package/Start-PatchWarden-Tunnel.cmd +0 -7
- package/docs/release-v0.3.0.md +0 -43
- package/docs/release-v0.4.0.md +0 -74
|
@@ -1,35 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
6
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
|
-
import { CHATGPT_CORE_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
|
|
7
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
|
|
7
8
|
|
|
8
9
|
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
9
10
|
const jsonOnly = process.argv.includes("--json");
|
|
10
|
-
const
|
|
11
|
+
const profileIndex = process.argv.indexOf("--profile");
|
|
12
|
+
const profile = profileIndex >= 0 ? process.argv[profileIndex + 1] : "chatgpt_core";
|
|
13
|
+
if (profile !== "chatgpt_core" && profile !== "chatgpt_direct") {
|
|
14
|
+
throw new Error(`Unsupported manifest profile "${profile}".`);
|
|
15
|
+
}
|
|
16
|
+
const expectedTools = profile === "chatgpt_direct"
|
|
17
|
+
? [...CHATGPT_DIRECT_TOOL_NAMES]
|
|
18
|
+
: [...CHATGPT_CORE_TOOL_NAMES];
|
|
19
|
+
const defaultConfigPath = resolve(root, "patchwarden.config.json");
|
|
20
|
+
const transportEnv = {
|
|
21
|
+
...process.env,
|
|
22
|
+
PATCHWARDEN_TOOL_PROFILE: profile,
|
|
23
|
+
};
|
|
24
|
+
if (process.env.PATCHWARDEN_CONFIG) {
|
|
25
|
+
transportEnv.PATCHWARDEN_CONFIG = process.env.PATCHWARDEN_CONFIG;
|
|
26
|
+
} else if (existsSync(defaultConfigPath)) {
|
|
27
|
+
transportEnv.PATCHWARDEN_CONFIG = defaultConfigPath;
|
|
28
|
+
} else {
|
|
29
|
+
delete transportEnv.PATCHWARDEN_CONFIG;
|
|
30
|
+
}
|
|
11
31
|
|
|
12
|
-
const transport =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
: new StdioClientTransport({
|
|
20
|
-
command: process.execPath,
|
|
21
|
-
args: [resolve(root, "dist", "index.js")],
|
|
22
|
-
cwd: root,
|
|
23
|
-
env: {
|
|
24
|
-
...process.env,
|
|
25
|
-
PATCHWARDEN_CONFIG: resolve(root, "patchwarden.config.json"),
|
|
26
|
-
PATCHWARDEN_TOOL_PROFILE: "chatgpt_core",
|
|
27
|
-
},
|
|
28
|
-
stderr: "pipe",
|
|
29
|
-
});
|
|
32
|
+
const transport = new StdioClientTransport({
|
|
33
|
+
command: process.execPath,
|
|
34
|
+
args: [resolve(root, "dist", "index.js")],
|
|
35
|
+
cwd: root,
|
|
36
|
+
env: transportEnv,
|
|
37
|
+
stderr: "pipe",
|
|
38
|
+
});
|
|
30
39
|
|
|
31
40
|
const client = new Client(
|
|
32
|
-
{ name: "patchwarden-manifest-check", version: "0.
|
|
41
|
+
{ name: "patchwarden-manifest-check", version: "0.6.0" },
|
|
33
42
|
{ capabilities: {} }
|
|
34
43
|
);
|
|
35
44
|
|
|
@@ -38,10 +47,10 @@ try {
|
|
|
38
47
|
const listed = await client.listTools();
|
|
39
48
|
const names = listed.tools.map((tool) => tool.name);
|
|
40
49
|
if (JSON.stringify(names) !== JSON.stringify(expectedTools)) {
|
|
41
|
-
throw new Error(
|
|
50
|
+
throw new Error(`${profile} tool list mismatch. Expected ${expectedTools.join(", ")}; got ${names.join(", ")}`);
|
|
42
51
|
}
|
|
43
52
|
// Layer 2: tools/list must carry _meta with manifest hash for catalog sync
|
|
44
|
-
if (!listed._meta || listed._meta.tool_profile !==
|
|
53
|
+
if (!listed._meta || listed._meta.tool_profile !== profile || listed._meta.tool_count !== expectedTools.length) {
|
|
45
54
|
throw new Error(`tools/list _meta missing or wrong profile/count: ${JSON.stringify(listed._meta || null)}`);
|
|
46
55
|
}
|
|
47
56
|
if (typeof listed._meta.tool_manifest_sha256 !== "string" || listed._meta.tool_manifest_sha256.length !== 64) {
|
|
@@ -50,51 +59,77 @@ try {
|
|
|
50
59
|
if (typeof listed._meta.schema_epoch !== "string" || typeof listed._meta.server_version !== "string") {
|
|
51
60
|
throw new Error(`tools/list _meta missing schema_epoch/server_version: ${JSON.stringify(listed._meta)}`);
|
|
52
61
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
if (profile === "chatgpt_core") {
|
|
63
|
+
const createTask = listed.tools.find((tool) => tool.name === "create_task");
|
|
64
|
+
const createProperties = Object.keys(createTask?.inputSchema?.properties || {});
|
|
65
|
+
for (const requiredProperty of ["inline_plan", "verify_commands"]) {
|
|
66
|
+
if (!createProperties.includes(requiredProperty)) {
|
|
67
|
+
throw new Error(`create_task schema is missing ${requiredProperty}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const waitTool = listed.tools.find((tool) => tool.name === "wait_for_task");
|
|
71
|
+
const waitProperties = Object.keys(waitTool?.inputSchema?.properties || {});
|
|
72
|
+
if (!waitProperties.includes("timeout_seconds") || !waitProperties.includes("wait_seconds")) {
|
|
73
|
+
throw new Error("wait_for_task schema must expose timeout_seconds and wait_seconds");
|
|
74
|
+
}
|
|
75
|
+
const healthProperties = Object.keys(listed.tools.find((tool) => tool.name === "health_check")?.inputSchema?.properties || {});
|
|
76
|
+
if (!healthProperties.includes("detail")) throw new Error("health_check schema must expose detail");
|
|
77
|
+
const listTaskProperties = Object.keys(listed.tools.find((tool) => tool.name === "list_tasks")?.inputSchema?.properties || {});
|
|
78
|
+
if (!listTaskProperties.includes("repo_path") || !listTaskProperties.includes("active_only")) {
|
|
79
|
+
throw new Error("list_tasks schema must expose repo_path and active_only");
|
|
80
|
+
}
|
|
81
|
+
const summaryProperties = Object.keys(listed.tools.find((tool) => tool.name === "get_task_summary")?.inputSchema?.properties || {});
|
|
82
|
+
if (!summaryProperties.includes("view") || !summaryProperties.includes("max_items")) {
|
|
83
|
+
throw new Error("get_task_summary schema must expose view and max_items");
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
const directRequirements = {
|
|
87
|
+
create_direct_session: ["repo_path"],
|
|
88
|
+
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
89
|
+
run_verification: ["session_id", "command"],
|
|
90
|
+
finalize_direct_session: ["session_id"],
|
|
91
|
+
audit_session: ["session_id"],
|
|
92
|
+
};
|
|
93
|
+
for (const [toolName, requiredProperties] of Object.entries(directRequirements)) {
|
|
94
|
+
const tool = listed.tools.find((entry) => entry.name === toolName);
|
|
95
|
+
const properties = Object.keys(tool?.inputSchema?.properties || {});
|
|
96
|
+
for (const property of requiredProperties) {
|
|
97
|
+
if (!properties.includes(property)) {
|
|
98
|
+
throw new Error(`${toolName} schema is missing ${property}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
58
101
|
}
|
|
59
|
-
}
|
|
60
|
-
const waitTool = listed.tools.find((tool) => tool.name === "wait_for_task");
|
|
61
|
-
const waitProperties = Object.keys(waitTool?.inputSchema?.properties || {});
|
|
62
|
-
if (!waitProperties.includes("timeout_seconds") || !waitProperties.includes("wait_seconds")) {
|
|
63
|
-
throw new Error("wait_for_task schema must expose timeout_seconds and wait_seconds");
|
|
64
|
-
}
|
|
65
|
-
const healthProperties = Object.keys(listed.tools.find((tool) => tool.name === "health_check")?.inputSchema?.properties || {});
|
|
66
|
-
if (!healthProperties.includes("detail")) throw new Error("health_check schema must expose detail");
|
|
67
|
-
const listTaskProperties = Object.keys(listed.tools.find((tool) => tool.name === "list_tasks")?.inputSchema?.properties || {});
|
|
68
|
-
if (!listTaskProperties.includes("repo_path") || !listTaskProperties.includes("active_only")) {
|
|
69
|
-
throw new Error("list_tasks schema must expose repo_path and active_only");
|
|
70
102
|
}
|
|
71
103
|
const healthResult = await client.callTool({ name: "health_check", arguments: {} });
|
|
72
104
|
if (healthResult.isError) throw new Error(String(healthResult.content?.[0]?.text || "health_check failed"));
|
|
73
105
|
const health = JSON.parse(String(healthResult.content?.[0]?.text || "{}"));
|
|
74
106
|
if (
|
|
75
|
-
health.tool_profile !==
|
|
107
|
+
health.tool_profile !== profile ||
|
|
76
108
|
health.tool_count !== expectedTools.length ||
|
|
77
|
-
!health.tool_manifest_sha256
|
|
109
|
+
!health.tool_manifest_sha256 ||
|
|
110
|
+
(profile === "chatgpt_direct" && health.direct_profile_enabled !== true)
|
|
78
111
|
) {
|
|
79
112
|
throw new Error(`health_check catalog mismatch: ${JSON.stringify(health)}`);
|
|
80
113
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
114
|
+
if (profile === "chatgpt_core") {
|
|
115
|
+
const hiddenResult = await client.callTool({ name: "get_plan", arguments: { plan_id: "stale-client-probe" } });
|
|
116
|
+
const hiddenPayload = JSON.parse(String(hiddenResult.content?.[0]?.text || "{}"));
|
|
117
|
+
if (
|
|
118
|
+
!hiddenResult.isError ||
|
|
119
|
+
hiddenPayload.reason !== "tool_catalog_mismatch" ||
|
|
120
|
+
hiddenPayload.refresh_required !== true ||
|
|
121
|
+
hiddenPayload.tool_manifest_sha256 !== health.tool_manifest_sha256
|
|
122
|
+
) {
|
|
123
|
+
throw new Error(`hidden tool mismatch guidance is incomplete: ${JSON.stringify(hiddenPayload)}`);
|
|
124
|
+
}
|
|
125
|
+
if (hiddenPayload.next_tool_call?.name !== "health_check" || hiddenPayload.next_tool_call?.arguments?.detail !== "self_diagnostic") {
|
|
126
|
+
throw new Error(`mismatch next_tool_call missing or wrong: ${JSON.stringify(hiddenPayload.next_tool_call || null)}`);
|
|
127
|
+
}
|
|
128
|
+
if (!Array.isArray(hiddenPayload.connector_refresh_steps) || hiddenPayload.connector_refresh_steps.length < 3) {
|
|
129
|
+
throw new Error(`mismatch connector_refresh_steps missing or too short: ${JSON.stringify(hiddenPayload.connector_refresh_steps || null)}`);
|
|
130
|
+
}
|
|
97
131
|
}
|
|
132
|
+
|
|
98
133
|
const output = {
|
|
99
134
|
ok: true,
|
|
100
135
|
server_version: health.server_version,
|
|
@@ -103,11 +138,18 @@ try {
|
|
|
103
138
|
tool_count: health.tool_count,
|
|
104
139
|
tool_names: health.tool_names,
|
|
105
140
|
tool_manifest_sha256: health.tool_manifest_sha256,
|
|
106
|
-
required_schema: {
|
|
141
|
+
required_schema: profile === "chatgpt_core" ? {
|
|
107
142
|
create_task: ["inline_plan", "verify_commands"],
|
|
108
143
|
wait_for_task: ["timeout_seconds", "wait_seconds"],
|
|
109
144
|
health_check: ["detail"],
|
|
110
145
|
list_tasks: ["repo_path", "active_only"],
|
|
146
|
+
get_task_summary: ["view", "max_items"],
|
|
147
|
+
} : {
|
|
148
|
+
create_direct_session: ["repo_path"],
|
|
149
|
+
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
150
|
+
run_verification: ["session_id", "command"],
|
|
151
|
+
finalize_direct_session: ["session_id"],
|
|
152
|
+
audit_session: ["session_id"],
|
|
111
153
|
},
|
|
112
154
|
};
|
|
113
155
|
console.log(jsonOnly ? JSON.stringify(output) : JSON.stringify(output, null, 2));
|
package/scripts/mcp-smoke.js
CHANGED
|
@@ -101,9 +101,13 @@ try {
|
|
|
101
101
|
const tools = await client.listTools();
|
|
102
102
|
const names = tools.tools.map((tool) => tool.name).sort();
|
|
103
103
|
const expected = [
|
|
104
|
+
"apply_patch",
|
|
105
|
+
"audit_session",
|
|
104
106
|
"audit_task",
|
|
105
107
|
"cancel_task",
|
|
108
|
+
"create_direct_session",
|
|
106
109
|
"create_task",
|
|
110
|
+
"finalize_direct_session",
|
|
107
111
|
"get_diff",
|
|
108
112
|
"get_plan",
|
|
109
113
|
"get_result",
|
|
@@ -121,7 +125,9 @@ try {
|
|
|
121
125
|
"list_workspace",
|
|
122
126
|
"read_workspace_file",
|
|
123
127
|
"retry_task",
|
|
128
|
+
"run_verification",
|
|
124
129
|
"save_plan",
|
|
130
|
+
"search_workspace",
|
|
125
131
|
"wait_for_task",
|
|
126
132
|
];
|
|
127
133
|
if (JSON.stringify(names) !== JSON.stringify(expected)) {
|
|
@@ -133,7 +139,7 @@ try {
|
|
|
133
139
|
if (!tools._meta || typeof tools._meta.tool_manifest_sha256 !== "string" || tools._meta.tool_manifest_sha256.length !== 64) {
|
|
134
140
|
throw new Error(`tools/list _meta missing manifest hash: ${JSON.stringify(tools._meta || null)}`);
|
|
135
141
|
}
|
|
136
|
-
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !==
|
|
142
|
+
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 28) {
|
|
137
143
|
throw new Error(`tools/list _meta profile/count mismatch: ${JSON.stringify(tools._meta)}`);
|
|
138
144
|
}
|
|
139
145
|
if (typeof tools._meta.schema_epoch !== "string" || typeof tools._meta.server_version !== "string") {
|
|
@@ -204,7 +210,7 @@ try {
|
|
|
204
210
|
test_command: "npm test",
|
|
205
211
|
});
|
|
206
212
|
if (
|
|
207
|
-
task.server_version !== "0.
|
|
213
|
+
task.server_version !== "0.6.0" ||
|
|
208
214
|
!/^[a-f0-9]{64}$/.test(task.tool_manifest_sha256 || "") ||
|
|
209
215
|
task.next_tool_call?.name !== "wait_for_task" ||
|
|
210
216
|
task.next_tool_call?.arguments?.timeout_seconds !== 25
|
|
@@ -298,10 +304,17 @@ try {
|
|
|
298
304
|
if (!summary.terminal || summary.acceptance_status !== "ready_for_review") {
|
|
299
305
|
throw new Error(`unexpected terminal summary: ${JSON.stringify(summary)}`);
|
|
300
306
|
}
|
|
307
|
+
const compactSummary = await parseToolJson("get_task_summary", { task_id: task.task_id, view: "compact", max_items: 2 });
|
|
308
|
+
if (compactSummary.view !== "compact" || "log_tails" in compactSummary || compactSummary.artifact_hygiene?.max_items !== 2) {
|
|
309
|
+
throw new Error(`unexpected compact summary: ${JSON.stringify(compactSummary)}`);
|
|
310
|
+
}
|
|
301
311
|
const waited = await parseToolJson("wait_for_task", { task_id: task.task_id, timeout_seconds: 1 });
|
|
302
312
|
if (!waited.terminal || waited.continuation_required || waited.next_tool_call?.name !== "audit_task") {
|
|
303
313
|
throw new Error(`wait_for_task did not return terminal acceptance: ${JSON.stringify(waited)}`);
|
|
304
314
|
}
|
|
315
|
+
if (waited.summary?.view !== "compact" || "log_tails" in (waited.summary || {})) {
|
|
316
|
+
throw new Error(`wait_for_task did not embed compact evidence: ${JSON.stringify(waited.summary)}`);
|
|
317
|
+
}
|
|
305
318
|
const legacyWaited = await parseToolJson("wait_for_task", { task_id: task.task_id, wait_seconds: 1 });
|
|
306
319
|
if (!legacyWaited.terminal) throw new Error("legacy wait_seconds alias stopped working");
|
|
307
320
|
const conflictingWait = await client.callTool({
|
|
@@ -323,6 +336,148 @@ try {
|
|
|
323
336
|
}
|
|
324
337
|
await client.close();
|
|
325
338
|
ok("runner executes a task and writes result files");
|
|
339
|
+
|
|
340
|
+
// ── Direct profile checks (lightweight) ──────────────────────────
|
|
341
|
+
|
|
342
|
+
// 1. chatgpt_direct disabled: only health_check exposed
|
|
343
|
+
const disabledConfigPath = join(tempRoot, "direct-disabled.json");
|
|
344
|
+
writeFileSync(
|
|
345
|
+
disabledConfigPath,
|
|
346
|
+
JSON.stringify({
|
|
347
|
+
workspaceRoot,
|
|
348
|
+
plansDir: ".patchwarden/plans",
|
|
349
|
+
tasksDir: ".patchwarden/tasks",
|
|
350
|
+
agents: { codex: { command: "node", args: ["-e", "console.log('agent')"] } },
|
|
351
|
+
allowedTestCommands: ["npm test"],
|
|
352
|
+
maxReadFileBytes: 200000,
|
|
353
|
+
enableDirectProfile: false,
|
|
354
|
+
}, null, 2),
|
|
355
|
+
"utf-8"
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const disabledTransport = new StdioClientTransport({
|
|
359
|
+
command: "node",
|
|
360
|
+
args: ["dist/index.js"],
|
|
361
|
+
cwd: root,
|
|
362
|
+
env: {
|
|
363
|
+
PATCHWARDEN_CONFIG: disabledConfigPath,
|
|
364
|
+
PATCHWARDEN_TOOL_PROFILE: "chatgpt_direct",
|
|
365
|
+
},
|
|
366
|
+
stderr: "pipe",
|
|
367
|
+
});
|
|
368
|
+
const disabledClient = new Client(
|
|
369
|
+
{ name: "patchwarden-direct-disabled", version: "0.1.0" },
|
|
370
|
+
{ capabilities: {} }
|
|
371
|
+
);
|
|
372
|
+
await disabledClient.connect(disabledTransport);
|
|
373
|
+
|
|
374
|
+
const disabledTools = await disabledClient.listTools();
|
|
375
|
+
const disabledNames = disabledTools.tools.map((t) => t.name).sort();
|
|
376
|
+
if (disabledNames.length !== 1 || disabledNames[0] !== "health_check") {
|
|
377
|
+
throw new Error(`chatgpt_direct disabled should expose only health_check, got: ${disabledNames.join(", ")}`);
|
|
378
|
+
}
|
|
379
|
+
if (disabledTools._meta.tool_count !== 1) {
|
|
380
|
+
throw new Error(`chatgpt_direct disabled tool_count should be 1, got ${disabledTools._meta.tool_count}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const disabledHealth = JSON.parse(
|
|
384
|
+
(await disabledClient.callTool({ name: "health_check", arguments: {} })).content?.[0]?.text || "{}"
|
|
385
|
+
);
|
|
386
|
+
if (disabledHealth.direct_profile_enabled !== false) {
|
|
387
|
+
throw new Error(`direct_profile_enabled should be false, got ${disabledHealth.direct_profile_enabled}`);
|
|
388
|
+
}
|
|
389
|
+
await disabledClient.close();
|
|
390
|
+
ok("chatgpt_direct disabled exposes only health_check with diagnostic");
|
|
391
|
+
|
|
392
|
+
// 2. chatgpt_direct enabled: 9 tools + minimal create_direct_session
|
|
393
|
+
const enabledConfigPath = join(tempRoot, "direct-enabled.json");
|
|
394
|
+
const directRepo = join(workspaceRoot, "direct-fixture");
|
|
395
|
+
mkdirSync(join(directRepo, "src"), { recursive: true });
|
|
396
|
+
writeFileSync(join(directRepo, "src", "index.ts"), "export const x = 1;\n", "utf-8");
|
|
397
|
+
writeFileSync(join(directRepo, "package.json"), JSON.stringify({
|
|
398
|
+
name: "direct-fixture",
|
|
399
|
+
private: true,
|
|
400
|
+
scripts: { test: 'node -e "console.log(\'ok\')"' },
|
|
401
|
+
}, null, 2), "utf-8");
|
|
402
|
+
|
|
403
|
+
writeFileSync(
|
|
404
|
+
enabledConfigPath,
|
|
405
|
+
JSON.stringify({
|
|
406
|
+
workspaceRoot,
|
|
407
|
+
plansDir: ".patchwarden/plans",
|
|
408
|
+
tasksDir: ".patchwarden/tasks",
|
|
409
|
+
agents: { codex: { command: "node", args: ["-e", "console.log('agent')"] } },
|
|
410
|
+
allowedTestCommands: ["npm test"],
|
|
411
|
+
maxReadFileBytes: 200000,
|
|
412
|
+
enableDirectProfile: true,
|
|
413
|
+
directAllowedCommands: ["npm test", "npm run build", "npm run lint"],
|
|
414
|
+
directSessionsDir: ".patchwarden/direct-sessions",
|
|
415
|
+
directSessionTtlSeconds: 3600,
|
|
416
|
+
directMaxPatchBytes: 200000,
|
|
417
|
+
directMaxFileBytes: 500000,
|
|
418
|
+
}, null, 2),
|
|
419
|
+
"utf-8"
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
const enabledTransport = new StdioClientTransport({
|
|
423
|
+
command: "node",
|
|
424
|
+
args: ["dist/index.js"],
|
|
425
|
+
cwd: root,
|
|
426
|
+
env: {
|
|
427
|
+
PATCHWARDEN_CONFIG: enabledConfigPath,
|
|
428
|
+
PATCHWARDEN_TOOL_PROFILE: "chatgpt_direct",
|
|
429
|
+
},
|
|
430
|
+
stderr: "pipe",
|
|
431
|
+
});
|
|
432
|
+
const enabledClient = new Client(
|
|
433
|
+
{ name: "patchwarden-direct-enabled", version: "0.1.0" },
|
|
434
|
+
{ capabilities: {} }
|
|
435
|
+
);
|
|
436
|
+
await enabledClient.connect(enabledTransport);
|
|
437
|
+
|
|
438
|
+
const enabledTools = await enabledClient.listTools();
|
|
439
|
+
const enabledNames = enabledTools.tools.map((t) => t.name).sort();
|
|
440
|
+
const expectedDirect = [
|
|
441
|
+
"apply_patch",
|
|
442
|
+
"audit_session",
|
|
443
|
+
"create_direct_session",
|
|
444
|
+
"finalize_direct_session",
|
|
445
|
+
"health_check",
|
|
446
|
+
"list_workspace",
|
|
447
|
+
"read_workspace_file",
|
|
448
|
+
"run_verification",
|
|
449
|
+
"search_workspace",
|
|
450
|
+
];
|
|
451
|
+
if (JSON.stringify(enabledNames) !== JSON.stringify(expectedDirect)) {
|
|
452
|
+
throw new Error(`chatgpt_direct enabled tools mismatch: ${enabledNames.join(", ")}`);
|
|
453
|
+
}
|
|
454
|
+
if (enabledTools._meta.tool_count !== 9) {
|
|
455
|
+
throw new Error(`chatgpt_direct enabled tool_count should be 9, got ${enabledTools._meta.tool_count}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Minimal create_direct_session
|
|
459
|
+
const sessionResult = JSON.parse(
|
|
460
|
+
(await enabledClient.callTool({
|
|
461
|
+
name: "create_direct_session",
|
|
462
|
+
arguments: { repo_path: "direct-fixture", title: "smoke test" },
|
|
463
|
+
})).content?.[0]?.text || "{}"
|
|
464
|
+
);
|
|
465
|
+
if (!sessionResult.session_id || !sessionResult.session_id.startsWith("direct_")) {
|
|
466
|
+
throw new Error(`create_direct_session failed: ${JSON.stringify(sessionResult)}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const enabledHealth = JSON.parse(
|
|
470
|
+
(await enabledClient.callTool({ name: "health_check", arguments: {} })).content?.[0]?.text || "{}"
|
|
471
|
+
);
|
|
472
|
+
if (enabledHealth.direct_profile_enabled !== true) {
|
|
473
|
+
throw new Error(`direct_profile_enabled should be true, got ${enabledHealth.direct_profile_enabled}`);
|
|
474
|
+
}
|
|
475
|
+
if (enabledHealth.direct_tool_count !== 9) {
|
|
476
|
+
throw new Error(`direct_tool_count should be 9, got ${enabledHealth.direct_tool_count}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
await enabledClient.close();
|
|
480
|
+
ok("chatgpt_direct enabled exposes 9 tools and create_direct_session works");
|
|
326
481
|
} catch (error) {
|
|
327
482
|
fail("MCP smoke test", error);
|
|
328
483
|
} finally {
|
package/scripts/pack-clean.js
CHANGED
|
@@ -22,10 +22,7 @@ const include = [
|
|
|
22
22
|
"examples",
|
|
23
23
|
"scripts",
|
|
24
24
|
"src",
|
|
25
|
-
"
|
|
26
|
-
"Restart-PatchWarden.cmd",
|
|
27
|
-
"Check-PatchWarden-Health.cmd",
|
|
28
|
-
"Reset-PatchWarden-Tunnel-Key.cmd",
|
|
25
|
+
"PatchWarden.cmd",
|
|
29
26
|
".gitignore",
|
|
30
27
|
"LICENSE",
|
|
31
28
|
"README.md",
|
|
@@ -44,6 +41,8 @@ const forbidden = [
|
|
|
44
41
|
/\.dpapi$/i,
|
|
45
42
|
/(^|[\\/])\.env$/,
|
|
46
43
|
/\.log$/,
|
|
44
|
+
/^docs[\\/]optimization-proposal\.md$/i,
|
|
45
|
+
/(^|[\\/])kill-patchwarden\.(cmd|ps1)$/i,
|
|
47
46
|
];
|
|
48
47
|
|
|
49
48
|
console.log("[pack-clean] Preparing clean release directory...");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
const isWindows = process.platform === "win32";
|
|
6
|
+
const command = isWindows ? (process.env.ComSpec || "cmd.exe") : "npm";
|
|
7
|
+
const args = isWindows
|
|
8
|
+
? ["/d", "/s", "/c", "npm.cmd pack --dry-run --json"]
|
|
9
|
+
: ["pack", "--dry-run", "--json"];
|
|
10
|
+
const result = spawnSync(command, args, {
|
|
11
|
+
encoding: "utf8",
|
|
12
|
+
shell: false,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
if (result.status !== 0) {
|
|
16
|
+
process.stderr.write(result.stderr || result.stdout || result.error?.message || "npm pack --dry-run --json failed\n");
|
|
17
|
+
process.exit(result.status || 1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let metadata;
|
|
21
|
+
try {
|
|
22
|
+
metadata = JSON.parse(result.stdout);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(`[package-manifest-check] Could not parse npm pack JSON: ${error.message}`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const files = metadata?.[0]?.files?.map((entry) => String(entry.path).replace(/\\/g, "/")) || [];
|
|
29
|
+
const forbidden = [
|
|
30
|
+
/(^|\/)\.local(\/|$)/i,
|
|
31
|
+
/\.local\.(cmd|ps1)$/i,
|
|
32
|
+
/(^|\/)patchwarden\.config\.json$/i,
|
|
33
|
+
new RegExp(`(^|/)${["safe", "bifrost"].join("-")}\\.config\\.json$`, "i"),
|
|
34
|
+
/(^|\/)\.env$/i,
|
|
35
|
+
/\.dpapi$/i,
|
|
36
|
+
/^docs\/optimization-proposal\.md$/i,
|
|
37
|
+
/(^|\/)kill-patchwarden\.(cmd|ps1)$/i,
|
|
38
|
+
];
|
|
39
|
+
const leaked = files.filter((file) => forbidden.some((pattern) => pattern.test(file)));
|
|
40
|
+
if (leaked.length > 0) {
|
|
41
|
+
console.error("[package-manifest-check] Private files would enter the npm package:");
|
|
42
|
+
for (const file of leaked) console.error(` ${file}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const required = [
|
|
47
|
+
"PatchWarden.cmd",
|
|
48
|
+
"scripts/manage-patchwarden.ps1",
|
|
49
|
+
"scripts/launchers/Start-PatchWarden-Tunnel.cmd",
|
|
50
|
+
"scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
|
|
51
|
+
];
|
|
52
|
+
const missing = required.filter((file) => !files.includes(file));
|
|
53
|
+
if (missing.length > 0) {
|
|
54
|
+
console.error("[package-manifest-check] Required control files are missing:");
|
|
55
|
+
for (const file of missing) console.error(` ${file}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const publicControlFiles = [
|
|
60
|
+
"PatchWarden.cmd",
|
|
61
|
+
"scripts/launchers/Start-PatchWarden-Tunnel.cmd",
|
|
62
|
+
"scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
|
|
63
|
+
];
|
|
64
|
+
const privateAbsolutePath = /[A-Za-z]:\\(?:Users\\[^\\\r\n]+|ai_agent)\\/i;
|
|
65
|
+
const privatePathLeaks = publicControlFiles.filter((file) => {
|
|
66
|
+
try {
|
|
67
|
+
return privateAbsolutePath.test(readFileSync(file, "utf8"));
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
if (privatePathLeaks.length > 0) {
|
|
73
|
+
console.error("[package-manifest-check] Public control files contain machine-specific absolute paths:");
|
|
74
|
+
for (const file of privatePathLeaks) console.error(` ${file}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`[package-manifest-check] OK: ${files.length} package files, no private local launchers.`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
@echo off
|
|
2
2
|
setlocal
|
|
3
3
|
set "PROJECT_ROOT=%~dp0.."
|
|
4
|
-
set "PATCHWARDEN_CONFIG=%PROJECT_ROOT%\patchwarden.config.json"
|
|
4
|
+
if not defined PATCHWARDEN_CONFIG set "PATCHWARDEN_CONFIG=%PROJECT_ROOT%\patchwarden.config.json"
|
|
5
5
|
set "PATCHWARDEN_TOOL_PROFILE=chatgpt_core"
|
|
6
6
|
cd /d "%PROJECT_ROOT%"
|
|
7
7
|
"node" "%PROJECT_ROOT%\dist\index.js"
|
|
@@ -196,7 +196,7 @@ Write-Host ""
|
|
|
196
196
|
Write-Host "[4/4] Launching PatchWarden tunnel..." -ForegroundColor Yellow
|
|
197
197
|
|
|
198
198
|
if ($WhatIf) {
|
|
199
|
-
Write-Host " WHAT-IF: Would launch
|
|
199
|
+
Write-Host " WHAT-IF: Would launch the Core compatibility launcher." -ForegroundColor Magenta
|
|
200
200
|
Write-Host ""
|
|
201
201
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
202
202
|
Write-Host " Restart plan complete (--WhatIf)." -ForegroundColor Cyan
|
|
@@ -207,8 +207,9 @@ if ($WhatIf) {
|
|
|
207
207
|
|
|
208
208
|
# Relaunch the tunnel in a new PowerShell window.
|
|
209
209
|
# Prefer Start-PatchWarden-Tunnel.local.cmd (your saved config) over the generic launcher.
|
|
210
|
-
$
|
|
211
|
-
$
|
|
210
|
+
$launcherDirectory = Join-Path $ProjectRoot "scripts\launchers"
|
|
211
|
+
$localLauncher = Join-Path $launcherDirectory "Start-PatchWarden-Tunnel.local.cmd"
|
|
212
|
+
$genericLauncher = Join-Path $launcherDirectory "Start-PatchWarden-Tunnel.cmd"
|
|
212
213
|
$launcherPath = if (Test-Path -LiteralPath $localLauncher) { $localLauncher } else { $genericLauncher }
|
|
213
214
|
|
|
214
215
|
if (-not (Test-Path -LiteralPath $launcherPath)) {
|
|
@@ -237,6 +238,6 @@ Start-Process powershell.exe -ArgumentList $args
|
|
|
237
238
|
|
|
238
239
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
239
240
|
Write-Host " Restart complete. Tunnel launcher window opened." -ForegroundColor Cyan
|
|
240
|
-
Write-Host " Use
|
|
241
|
+
Write-Host " Use PatchWarden.cmd status all to verify." -ForegroundColor Cyan
|
|
241
242
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
242
243
|
Write-Host ""
|