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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, statSync, existsSync } from "node:fs";
|
|
2
|
+
import { relative, join, extname } from "node:path";
|
|
3
|
+
import { getConfig } from "../config.js";
|
|
4
|
+
import { PatchWardenError } from "../errors.js";
|
|
5
|
+
import { isSensitivePath } from "../security/sensitiveGuard.js";
|
|
6
|
+
import { validateDirectSessionFreshness, } from "../direct/directSessionStore.js";
|
|
7
|
+
import { guardDirectSessionActive } from "../direct/directGuards.js";
|
|
8
|
+
// ── Constants ──────────────────────────────────────────────────────
|
|
9
|
+
const SKIP_DIRECTORIES = new Set([
|
|
10
|
+
".git",
|
|
11
|
+
".patchwarden",
|
|
12
|
+
"node_modules",
|
|
13
|
+
"dist",
|
|
14
|
+
"release",
|
|
15
|
+
"coverage",
|
|
16
|
+
".next",
|
|
17
|
+
".turbo",
|
|
18
|
+
]);
|
|
19
|
+
const BINARY_EXTENSIONS = new Set([
|
|
20
|
+
".exe",
|
|
21
|
+
".dll",
|
|
22
|
+
".so",
|
|
23
|
+
".png",
|
|
24
|
+
".jpg",
|
|
25
|
+
".jpeg",
|
|
26
|
+
".gif",
|
|
27
|
+
".bmp",
|
|
28
|
+
".ico",
|
|
29
|
+
".webp",
|
|
30
|
+
".pdf",
|
|
31
|
+
".mp3",
|
|
32
|
+
".mp4",
|
|
33
|
+
".zip",
|
|
34
|
+
".gz",
|
|
35
|
+
".tar",
|
|
36
|
+
".bin",
|
|
37
|
+
".dat",
|
|
38
|
+
".class",
|
|
39
|
+
".jar",
|
|
40
|
+
".wasm",
|
|
41
|
+
".node",
|
|
42
|
+
]);
|
|
43
|
+
// Safety valve: stop scanning after this many total matches to prevent
|
|
44
|
+
// runaway searches in large repositories with very common terms.
|
|
45
|
+
const MAX_TOTAL_MATCHES = 10_000;
|
|
46
|
+
// ── Tool implementation ────────────────────────────────────────────
|
|
47
|
+
export function searchWorkspace(input) {
|
|
48
|
+
const config = getConfig();
|
|
49
|
+
// ── Validate session freshness ───────────────────────────────────
|
|
50
|
+
const validation = validateDirectSessionFreshness(input.session_id);
|
|
51
|
+
if (!validation.valid || !validation.session) {
|
|
52
|
+
throw new PatchWardenError(validation.failure_reason || "session_invalid", `Direct session "${input.session_id}" is not valid: ${validation.failure_reason}.`, "Create a new direct session with create_direct_session.", true, {
|
|
53
|
+
session_id: input.session_id,
|
|
54
|
+
failure_reason: validation.failure_reason,
|
|
55
|
+
operation: "search_workspace",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const session = validation.session;
|
|
59
|
+
guardDirectSessionActive(session);
|
|
60
|
+
// ── Validate query ───────────────────────────────────────────────
|
|
61
|
+
if (!input.query || input.query.trim() === "") {
|
|
62
|
+
throw new PatchWardenError("invalid_input", "query is required and must be a non-empty string.", "Provide a search query to look for in file contents.", true, { operation: "search_workspace", session_id: input.session_id });
|
|
63
|
+
}
|
|
64
|
+
// ── Resolve parameters ───────────────────────────────────────────
|
|
65
|
+
const maxResults = input.max_results ?? 20;
|
|
66
|
+
const caseSensitive = input.case_sensitive ?? false;
|
|
67
|
+
const maxPreviewChars = input.max_preview_chars ?? 200;
|
|
68
|
+
const includeGlobs = input.include_globs;
|
|
69
|
+
const query = caseSensitive ? input.query : input.query.toLowerCase();
|
|
70
|
+
const repoPath = session.resolved_repo_path;
|
|
71
|
+
if (!existsSync(repoPath)) {
|
|
72
|
+
throw new PatchWardenError("repo_not_found", `Session repo path "${repoPath}" no longer exists.`, "Create a new direct session with create_direct_session.", true, {
|
|
73
|
+
session_id: input.session_id,
|
|
74
|
+
resolved_repo_path: repoPath,
|
|
75
|
+
operation: "search_workspace",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
// ── Recursive search ─────────────────────────────────────────────
|
|
79
|
+
const results = [];
|
|
80
|
+
let totalMatches = 0;
|
|
81
|
+
let truncated = false;
|
|
82
|
+
const visit = (directory) => {
|
|
83
|
+
if (truncated && totalMatches >= MAX_TOTAL_MATCHES)
|
|
84
|
+
return;
|
|
85
|
+
let entries;
|
|
86
|
+
try {
|
|
87
|
+
entries = readdirSync(directory, { withFileTypes: true });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
if (totalMatches >= MAX_TOTAL_MATCHES) {
|
|
94
|
+
truncated = true;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Skip blacklisted directories
|
|
98
|
+
if (entry.isDirectory() && SKIP_DIRECTORIES.has(entry.name)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const fullPath = join(directory, entry.name);
|
|
102
|
+
const relPath = relative(repoPath, fullPath).replace(/\\/g, "/");
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
visit(fullPath);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (!entry.isFile())
|
|
108
|
+
continue;
|
|
109
|
+
// Skip sensitive files
|
|
110
|
+
if (isSensitivePath(relPath))
|
|
111
|
+
continue;
|
|
112
|
+
// Skip binary files by extension
|
|
113
|
+
const ext = extname(entry.name).toLowerCase();
|
|
114
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
115
|
+
continue;
|
|
116
|
+
// Apply include_globs filter
|
|
117
|
+
if (includeGlobs && includeGlobs.length > 0) {
|
|
118
|
+
if (!matchesAnyGlob(relPath, includeGlobs))
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// Read file content and search line by line
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
const fileStat = statSync(fullPath);
|
|
125
|
+
if (fileStat.size > config.directMaxFileBytes)
|
|
126
|
+
continue;
|
|
127
|
+
content = readFileSync(fullPath, "utf-8");
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const lines = content.split(/\r?\n/);
|
|
133
|
+
for (let i = 0; i < lines.length; i++) {
|
|
134
|
+
if (totalMatches >= MAX_TOTAL_MATCHES) {
|
|
135
|
+
truncated = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
const line = lines[i];
|
|
139
|
+
const haystack = caseSensitive ? line : line.toLowerCase();
|
|
140
|
+
if (haystack.includes(query)) {
|
|
141
|
+
totalMatches++;
|
|
142
|
+
if (results.length < maxResults) {
|
|
143
|
+
const preview = line.length > maxPreviewChars
|
|
144
|
+
? line.slice(0, maxPreviewChars)
|
|
145
|
+
: line;
|
|
146
|
+
results.push({
|
|
147
|
+
path: relPath,
|
|
148
|
+
line: i + 1,
|
|
149
|
+
preview,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
truncated = true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
visit(repoPath);
|
|
160
|
+
return {
|
|
161
|
+
results,
|
|
162
|
+
total_matches: totalMatches,
|
|
163
|
+
truncated,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// ── Glob matching ──────────────────────────────────────────────────
|
|
167
|
+
function matchesAnyGlob(filePath, globs) {
|
|
168
|
+
for (const pattern of globs) {
|
|
169
|
+
if (matchGlob(filePath, pattern))
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Simple glob matching: "*" matches any sequence of characters (including
|
|
176
|
+
* path separators), "?" matches a single character.
|
|
177
|
+
*
|
|
178
|
+
* Examples:
|
|
179
|
+
* "*.ts" matches "foo.ts", "src/bar.ts"
|
|
180
|
+
* "src/*.ts" matches "src/foo.ts", "src/sub/bar.ts"
|
|
181
|
+
* "*.test.ts" matches "foo.test.ts", "src/bar.test.ts"
|
|
182
|
+
*/
|
|
183
|
+
function matchGlob(filePath, pattern) {
|
|
184
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
185
|
+
const regex = globToRegex(normalizedPattern);
|
|
186
|
+
return regex.test(filePath);
|
|
187
|
+
}
|
|
188
|
+
function globToRegex(pattern) {
|
|
189
|
+
let regexStr = "";
|
|
190
|
+
for (const char of pattern) {
|
|
191
|
+
if (char === "*") {
|
|
192
|
+
regexStr += ".*";
|
|
193
|
+
}
|
|
194
|
+
else if (char === "?") {
|
|
195
|
+
regexStr += ".";
|
|
196
|
+
}
|
|
197
|
+
else if (/[.+^${}()|[\]\\]/.test(char)) {
|
|
198
|
+
regexStr += "\\" + char;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
regexStr += char;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return new RegExp("^" + regexStr + "$");
|
|
205
|
+
}
|
|
@@ -19,7 +19,8 @@ export function expandTaskTemplate(input) {
|
|
|
19
19
|
`## Goal\n${goal}`,
|
|
20
20
|
"## Safety boundaries",
|
|
21
21
|
"- Work only inside the resolved repository path.",
|
|
22
|
-
"-
|
|
22
|
+
"- Leave repository changes uncommitted for review; remote operations are outside this task.",
|
|
23
|
+
"- Keep credentials private and do not modify unrelated files.",
|
|
23
24
|
"- Report exact files inspected or changed and any remaining uncertainty.",
|
|
24
25
|
];
|
|
25
26
|
switch (input.template) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type ToolProfile = "full" | "chatgpt_core";
|
|
1
|
+
export type ToolProfile = "full" | "chatgpt_core" | "chatgpt_direct";
|
|
2
2
|
export interface CatalogTool {
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
@@ -13,7 +13,8 @@ export interface ToolCatalogSnapshot {
|
|
|
13
13
|
tool_manifest_sha256: string;
|
|
14
14
|
}
|
|
15
15
|
export declare const CHATGPT_CORE_TOOL_NAMES: readonly ["health_check", "list_agents", "list_workspace", "read_workspace_file", "save_plan", "create_task", "wait_for_task", "get_task_summary", "get_diff", "get_result", "get_result_json", "get_test_log", "get_task_status", "list_tasks", "cancel_task", "audit_task"];
|
|
16
|
+
export declare const CHATGPT_DIRECT_TOOL_NAMES: readonly ["health_check", "list_workspace", "create_direct_session", "search_workspace", "read_workspace_file", "apply_patch", "run_verification", "finalize_direct_session", "audit_session"];
|
|
16
17
|
export declare function resolveToolProfile(configProfile?: string): ToolProfile;
|
|
17
|
-
export declare function selectToolsForProfile<T extends CatalogTool>(tools: T[], profile: ToolProfile): T[];
|
|
18
|
+
export declare function selectToolsForProfile<T extends CatalogTool>(tools: T[], profile: ToolProfile, enableDirectProfile?: boolean): T[];
|
|
18
19
|
export declare function buildToolCatalogSnapshot(tools: CatalogTool[], profile: ToolProfile): ToolCatalogSnapshot;
|
|
19
20
|
export declare function getLastToolCatalogSnapshot(): ToolCatalogSnapshot | null;
|
|
@@ -18,22 +18,49 @@ export const CHATGPT_CORE_TOOL_NAMES = [
|
|
|
18
18
|
"cancel_task",
|
|
19
19
|
"audit_task",
|
|
20
20
|
];
|
|
21
|
+
export const CHATGPT_DIRECT_TOOL_NAMES = [
|
|
22
|
+
"health_check",
|
|
23
|
+
"list_workspace",
|
|
24
|
+
"create_direct_session",
|
|
25
|
+
"search_workspace",
|
|
26
|
+
"read_workspace_file",
|
|
27
|
+
"apply_patch",
|
|
28
|
+
"run_verification",
|
|
29
|
+
"finalize_direct_session",
|
|
30
|
+
"audit_session",
|
|
31
|
+
];
|
|
21
32
|
let lastSnapshot = null;
|
|
22
33
|
export function resolveToolProfile(configProfile) {
|
|
23
34
|
const raw = (process.env.PATCHWARDEN_TOOL_PROFILE || configProfile || "full").trim();
|
|
24
|
-
if (raw !== "full" && raw !== "chatgpt_core") {
|
|
25
|
-
throw new Error(`Invalid tool profile "${raw}". Expected "full" or "
|
|
35
|
+
if (raw !== "full" && raw !== "chatgpt_core" && raw !== "chatgpt_direct") {
|
|
36
|
+
throw new Error(`Invalid tool profile "${raw}". Expected "full", "chatgpt_core", or "chatgpt_direct".`);
|
|
26
37
|
}
|
|
27
38
|
return raw;
|
|
28
39
|
}
|
|
29
|
-
export function selectToolsForProfile(tools, profile) {
|
|
40
|
+
export function selectToolsForProfile(tools, profile, enableDirectProfile) {
|
|
30
41
|
if (profile === "full")
|
|
31
42
|
return tools;
|
|
32
43
|
const byName = new Map(tools.map((tool) => [tool.name, tool]));
|
|
33
|
-
|
|
44
|
+
if (profile === "chatgpt_core") {
|
|
45
|
+
return CHATGPT_CORE_TOOL_NAMES.map((name) => {
|
|
46
|
+
const tool = byName.get(name);
|
|
47
|
+
if (!tool)
|
|
48
|
+
throw new Error(`chatgpt_core tool profile requires missing tool "${name}".`);
|
|
49
|
+
return tool;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// chatgpt_direct
|
|
53
|
+
if (!enableDirectProfile) {
|
|
54
|
+
// Degraded mode: only expose health_check for diagnostics
|
|
55
|
+
const tool = byName.get("health_check");
|
|
56
|
+
if (!tool)
|
|
57
|
+
throw new Error(`chatgpt_direct degraded mode requires missing tool "health_check".`);
|
|
58
|
+
return [tool];
|
|
59
|
+
}
|
|
60
|
+
return CHATGPT_DIRECT_TOOL_NAMES.map((name) => {
|
|
34
61
|
const tool = byName.get(name);
|
|
35
62
|
if (!tool)
|
|
36
|
-
throw new Error(`
|
|
63
|
+
throw new Error(`chatgpt_direct tool profile requires missing tool "${name}".`);
|
|
37
64
|
return tool;
|
|
38
65
|
});
|
|
39
66
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type TaskSummaryResult } from "./getTaskSummary.js";
|
|
2
2
|
export interface WaitForTaskProgressSummary {
|
|
3
3
|
phase: string;
|
|
4
4
|
heartbeat_at: string;
|
|
@@ -19,7 +19,7 @@ export interface WaitForTaskOutput {
|
|
|
19
19
|
name: "wait_for_task" | "audit_task" | "health_check";
|
|
20
20
|
arguments: Record<string, unknown>;
|
|
21
21
|
};
|
|
22
|
-
summary?:
|
|
22
|
+
summary?: TaskSummaryResult;
|
|
23
23
|
progress_summary?: WaitForTaskProgressSummary;
|
|
24
24
|
}
|
|
25
25
|
export declare function waitForTask(taskId: string, waitSeconds?: number): Promise<WaitForTaskOutput>;
|
|
@@ -54,6 +54,6 @@ export async function waitForTask(taskId, waitSeconds = 25) {
|
|
|
54
54
|
: executionBlocked
|
|
55
55
|
? { name: "health_check", arguments: {} }
|
|
56
56
|
: { name: "wait_for_task", arguments: { task_id: taskId, timeout_seconds: waitSeconds } },
|
|
57
|
-
...(terminal ? { summary: getTaskSummary(taskId) } : { progress_summary: progressSummary }),
|
|
57
|
+
...(terminal ? { summary: getTaskSummary(taskId, { view: "compact" }) } : { progress_summary: progressSummary }),
|
|
58
58
|
};
|
|
59
59
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const PATCHWARDEN_VERSION = "0.
|
|
2
|
-
export declare const TOOL_SCHEMA_EPOCH = "2026-06-
|
|
1
|
+
export declare const PATCHWARDEN_VERSION = "0.6.0";
|
|
2
|
+
export declare const TOOL_SCHEMA_EPOCH = "2026-06-22-v6";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PATCHWARDEN_VERSION = "0.
|
|
2
|
-
export const TOOL_SCHEMA_EPOCH = "2026-06-
|
|
1
|
+
export const PATCHWARDEN_VERSION = "0.6.0";
|
|
2
|
+
export const TOOL_SCHEMA_EPOCH = "2026-06-22-v6";
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ChatGPT 调用规范 / ChatGPT usage guide
|
|
2
|
+
|
|
3
|
+
## 中文
|
|
4
|
+
|
|
5
|
+
这套约定用于降低长参数、长轮询、构建产物和完整日志触发连接器误拦截的概率,同时保留 PatchWarden 的本地安全边界和完整证据。
|
|
6
|
+
|
|
7
|
+
1. 一个任务只处理一个目标;功能实现、耗时构建和 Git/发布收尾分开执行。
|
|
8
|
+
2. 计划标题使用短英文 slug,详细说明放在目标或计划正文中。
|
|
9
|
+
3. ChatGPT 优先使用守护模板:只读诊断用 `inspect_only`,小范围修改用 `feature_small`,修复已知失败用 `fix_tests`。模板不够表达目标时才使用 `inline_plan` 或保存的长计划。
|
|
10
|
+
4. 创建任务时优先采用 assess → execute 两步流程。execute 必须直接使用 assess 返回的 `next_tool_call`,不要重复发送 goal、plan、repo、agent 或验证命令。
|
|
11
|
+
5. 短任务可以调用 `wait_for_task(timeout_seconds: 25)`;长任务使用 `list_tasks(repo_path=..., active_only=true)` 和 `get_task_status`。
|
|
12
|
+
6. 终态先读取 `get_task_summary(view: "compact")` 和 `audit_task`;证据不足时才读取 standard 摘要、完整 diff 或日志。
|
|
13
|
+
7. 构建验证与源码任务分开。`artifact_hygiene` 会区分源码、已跟踪构建物、忽略产物、运行态文件和可疑变更。
|
|
14
|
+
8. PatchWarden 保留未提交改动供人工审核;提交、推送和发布不属于普通任务范围。
|
|
15
|
+
|
|
16
|
+
### assess → execute
|
|
17
|
+
|
|
18
|
+
第一步发送完整目标:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"tool": "create_task",
|
|
23
|
+
"execution_mode": "assess_only",
|
|
24
|
+
"template": "feature_small",
|
|
25
|
+
"goal": "Add a bounded activity timeline UI",
|
|
26
|
+
"agent": "opencode",
|
|
27
|
+
"repo_path": "my-project",
|
|
28
|
+
"verify_commands": ["npm test"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
当 `decision` 为 `allow` 时,响应会包含结构化的最小调用:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"next_tool_call": {
|
|
37
|
+
"name": "create_task",
|
|
38
|
+
"arguments": {
|
|
39
|
+
"execution_mode": "execute",
|
|
40
|
+
"assessment_id": "assessment_20260622_143000_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
请原样调用 `next_tool_call`。完整的 128-bit `assessment_id` 用于执行;`assessment_short_id` 仅用于展示。工作区、计划、策略或工具清单变化后会返回 `assessment_stale_*`,此时必须重新 assess。
|
|
47
|
+
|
|
48
|
+
当 `decision` 为 `needs_confirm` 时,用户需要在使用同一份本地配置的终端运行:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
patchwarden-confirm assessment_20260622_143000_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
确认入口不属于 MCP 工具,远程调用者不能自行确认。确认后再原样调用返回的 `next_tool_call`;`blocked` 票据不能确认或执行。
|
|
55
|
+
|
|
56
|
+
### audit_task 结果解释
|
|
57
|
+
|
|
58
|
+
- `confirmed_failures`:证据已经明确的失败,例如非零测试退出码、越界变更或失败状态。
|
|
59
|
+
- `possible_false_positives`:启发式规则可能产生的误报,例如文档属于其他包、构建物是有意跟踪的发布资产。
|
|
60
|
+
- `manual_verification_required` / `manual_verification_items`:需要人工或远程权威来源核实的事项。
|
|
61
|
+
|
|
62
|
+
因此 `verdict: "warn"` 不等于任务一定有错。应先核实上述字段,再决定是否接受结果。
|
|
63
|
+
|
|
64
|
+
仓库专属验证命令只能配置在本机可信的 `patchwarden.config.json` 中:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"repoAllowedTestCommands": {
|
|
69
|
+
"desktop-app": ["npm run release:check"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
路径和命令都采用精确匹配,不支持通配符,也不会读取目标仓库的 `package.json` 自动授权。
|
|
75
|
+
|
|
76
|
+
## English
|
|
77
|
+
|
|
78
|
+
These conventions reduce connector false positives while preserving local
|
|
79
|
+
safety boundaries and complete evidence.
|
|
80
|
+
|
|
81
|
+
1. Keep one goal per task; separate feature work, expensive builds, and
|
|
82
|
+
Git/release handoff.
|
|
83
|
+
2. Use a short English slug for the title.
|
|
84
|
+
3. Prefer guarded templates: `inspect_only` for diagnosis, `feature_small` for
|
|
85
|
+
a scoped change, and `fix_tests` for a known failure. Use a long plan only
|
|
86
|
+
when a template cannot express the goal.
|
|
87
|
+
4. Prefer assess → execute. Invoke the returned `next_tool_call` unchanged and
|
|
88
|
+
do not resend goal, plan, repository, agent, or verification arguments.
|
|
89
|
+
5. Poll long tasks with `list_tasks` and `get_task_status`; inspect
|
|
90
|
+
`get_task_summary(view: "compact")` before full logs or diffs.
|
|
91
|
+
6. Run build/package validation separately from source implementation.
|
|
92
|
+
7. Changes remain uncommitted for review; commit, push, and publish are outside
|
|
93
|
+
the ordinary task scope.
|
|
94
|
+
|
|
95
|
+
`needs_confirm` assessments must be confirmed locally with
|
|
96
|
+
`patchwarden-confirm <full_assessment_id>`. The confirmation command is not an
|
|
97
|
+
MCP tool. A `blocked` assessment cannot be confirmed.
|
|
98
|
+
|
|
99
|
+
In `audit_task`, evidence-backed failures appear in `confirmed_failures`,
|
|
100
|
+
heuristic warnings in `possible_false_positives`, and unresolved checks in
|
|
101
|
+
`manual_verification_items`. A warning is not automatically a confirmed error.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Manual release checklist
|
|
2
|
+
|
|
3
|
+
PatchWarden releases remain confirmation-gated. Creating a tag or GitHub Release does not publish npm automatically.
|
|
4
|
+
|
|
5
|
+
1. Work on a release branch and update `package.json`, changelog/release notes, README, examples, and tool manifests together.
|
|
6
|
+
2. Run the complete local quality gates from `AGENTS.md`.
|
|
7
|
+
3. Open a pull request, wait for `CI gate`, review the diff and package contents, then merge.
|
|
8
|
+
4. Create the version tag from the verified merge commit.
|
|
9
|
+
5. Create the GitHub Release and attach only reviewed release artifacts and checksums.
|
|
10
|
+
6. Publish `patchwarden` to npm using process-scoped authentication; never store the raw token in the repository.
|
|
11
|
+
7. Verify `gh release view`, the remote tag, `npm.cmd view patchwarden version`, and `dist-tags.latest`.
|
|
12
|
+
8. Update or close the associated issue only after remote verification succeeds.
|
|
13
|
+
|
|
14
|
+
Do not publish new versions under the frozen pre-rename package name.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# PatchWarden v0.6.0
|
|
2
|
+
|
|
3
|
+
PatchWarden v0.6.0 在保持 `chatgpt_core` 固定 16 工具兼容的同时,加入默认关闭的
|
|
4
|
+
`chatgpt_direct` 安全直接开发模式、本地 assess → confirm → execute 闭环,以及更可靠的
|
|
5
|
+
Windows Core/Direct 双通道运行管理。
|
|
6
|
+
|
|
7
|
+
## 主要更新
|
|
8
|
+
|
|
9
|
+
### ChatGPT Core 安全体验
|
|
10
|
+
|
|
11
|
+
- `assess_only` 返回最小化的结构化 `next_tool_call`;execute 只需
|
|
12
|
+
`execution_mode` 和完整 `assessment_id`,不再重复传输目标、计划和仓库参数。
|
|
13
|
+
- 中风险票据通过本地 `patchwarden-confirm <full_assessment_id>` 确认。该入口不是 MCP
|
|
14
|
+
工具,远程调用者不能自行放行。
|
|
15
|
+
- 确认和执行会重新校验票据时效、工作区、计划、策略和工具 Manifest。
|
|
16
|
+
- `audit_task` 将已确认失败、可能误报和人工核查项分开呈现。
|
|
17
|
+
- `get_task_summary(view: "compact")` 和终态 `wait_for_task` 返回有界验收证据,避免长任务
|
|
18
|
+
结果淹没客户端上下文。
|
|
19
|
+
|
|
20
|
+
### ChatGPT Direct 模式
|
|
21
|
+
|
|
22
|
+
- 默认关闭;启用需设置 `enableDirectProfile: true` 并使用 `chatgpt_direct` Profile。
|
|
23
|
+
- 固定 9 工具:健康检查、工作区列表、会话创建、搜索、文件读取、JSON 补丁、白名单
|
|
24
|
+
验证、会话完成和独立审计。
|
|
25
|
+
- 所有写操作绑定 Direct session 和内容哈希;不支持任意 shell、删除/重命名、Git 推送、
|
|
26
|
+
npm 发布或远程部署。
|
|
27
|
+
|
|
28
|
+
### Windows Core/Direct 控制与可观测性
|
|
29
|
+
|
|
30
|
+
- 根目录统一使用 `PatchWarden.cmd`,支持 Core、Direct 或两者的
|
|
31
|
+
`start`、`stop`、`restart`、`status`、`health` 和受限 `kill`。
|
|
32
|
+
- Core 和 Direct 分别使用 `127.0.0.1:8080` 与 `127.0.0.1:8081`,并自动校正各自
|
|
33
|
+
tunnel profile 的 health 地址。
|
|
34
|
+
- supervisor 将 stdout/stderr 写入各自 runtime 目录;非零退出时显示 stderr 最后 30 行,
|
|
35
|
+
并把退出码、脱敏 tail 和日志路径写入 `tunnel-status.json`。
|
|
36
|
+
- 修复 Core watcher 的 `XDG_CONFIG_HOME` 泄漏到 tunnel-client,导致 Core 错误读取
|
|
37
|
+
`%LOCALAPPDATA%\patchwarden\opencode-config\tunnel-client\patchwarden.yaml` 并反复退出的问题。
|
|
38
|
+
- `stop` / `restart` 可清理 Profile 精确匹配的遗留 tunnel-client;无关端口占用只报告、不误杀。
|
|
39
|
+
- `status` 支持 health endpoint fallback,陈旧 JSON 不再把实际 ready 的实例误报为 stopped。
|
|
40
|
+
- 旧的单用途入口迁入 `scripts/launchers/`;个人 `.local/launchers/`、运行时配置、凭据和日志
|
|
41
|
+
不进入 Git、npm 包或 GitHub 发布资产。
|
|
42
|
+
|
|
43
|
+
## 兼容性
|
|
44
|
+
|
|
45
|
+
- `chatgpt_core` 仍为固定 16 工具;`chatgpt_direct` 为固定 9 工具。
|
|
46
|
+
- schema epoch 为 `2026-06-22-v6`。
|
|
47
|
+
- `assess_only`、`execute`、`assessment_id`、`plan_ref` 和既有任务记录保持兼容。
|
|
48
|
+
- npm 包名保持 `patchwarden`;旧的 `safe-bifrost` 包继续冻结在 v0.3.0。
|
|
49
|
+
|
|
50
|
+
## 验证
|
|
51
|
+
|
|
52
|
+
发布候选通过以下门禁:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
npm.cmd run build
|
|
56
|
+
npm.cmd test
|
|
57
|
+
npm.cmd run check:tool-manifest
|
|
58
|
+
npm.cmd run check:direct-tool-manifest
|
|
59
|
+
npm.cmd run test:mcp
|
|
60
|
+
npm.cmd run test:http-mcp
|
|
61
|
+
npm.cmd run doctor:ci
|
|
62
|
+
npm.cmd run pack:clean
|
|
63
|
+
npm.cmd run verify:package
|
|
64
|
+
git diff --check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
本机双通道验收确认 Core 16 工具和 Direct 9 工具均为 `running`、`ready=true`,8080/8081
|
|
68
|
+
由对应 `tunnel-client.exe` 监听,两个 `/readyz` 均返回 `ready`。
|
|
69
|
+
|
|
70
|
+
GitHub Release 与 npm Registry 是独立发布面。创建 Tag/Release 不会自动发布 npm;npm 发布后
|
|
71
|
+
仍需单独核对 `patchwarden@latest`、版本时间、dist-tag 和包完整性。
|
|
@@ -17,21 +17,28 @@ or local private paths.
|
|
|
17
17
|
2. Run `npm.cmd run build`.
|
|
18
18
|
3. Use `scripts/patchwarden-mcp-stdio.cmd` as the tunnel MCP command.
|
|
19
19
|
4. Start `npm.cmd run watch` in a separate terminal.
|
|
20
|
-
5. Start `tunnel-client run` or use `
|
|
20
|
+
5. Start `tunnel-client run` or use `PatchWarden.cmd start core`.
|
|
21
21
|
6. Create a ChatGPT Connector using the tunnel channel.
|
|
22
22
|
7. After a tunnel/schema refresh, reconnect the Connector and validate from a
|
|
23
23
|
new ChatGPT conversation; an already-open conversation may retain its older
|
|
24
24
|
tool catalog.
|
|
25
25
|
|
|
26
|
+
For ChatGPT Direct editing, set `enableDirectProfile: true` in the trusted
|
|
27
|
+
local config and run `PatchWarden.cmd start direct`. It uses
|
|
28
|
+
`scripts/patchwarden-mcp-direct.cmd`, the separate `patchwarden-direct` Tunnel
|
|
29
|
+
Client profile, no Watcher, and an isolated `runtime-direct` status directory.
|
|
30
|
+
Use a separate Direct Connector/Tunnel ID so the fixed 16-tool Core catalog and
|
|
31
|
+
the 9-tool Direct catalog never overwrite each other's cached schema.
|
|
32
|
+
|
|
26
33
|
The Windows launcher prompts for the runtime API key once and stores only a
|
|
27
34
|
Windows DPAPI-encrypted value under `%APPDATA%\patchwarden`. Use
|
|
28
|
-
`
|
|
35
|
+
`PatchWarden.cmd reset-key` to remove the saved credential.
|
|
29
36
|
|
|
30
37
|
Before the launcher starts the tunnel it performs a real MCP stdio handshake
|
|
31
38
|
and requires the exact `chatgpt_core` manifest. Run
|
|
32
|
-
`
|
|
39
|
+
`PatchWarden.cmd health` to see the version, profile, tool names, schema
|
|
33
40
|
hash, process sources, and any mixed-version warnings. The check is read-only.
|
|
34
|
-
The v0.4.
|
|
41
|
+
The v0.4.1 core manifest contains 16 tools. A different count or schema hash
|
|
35
42
|
requires a Connector refresh and validation from a new ChatGPT conversation.
|
|
36
43
|
|
|
37
44
|
## Architecture
|
|
@@ -9,7 +9,7 @@ Use the patchwarden connector tools to verify the workflow.
|
|
|
9
9
|
Step 1:
|
|
10
10
|
Call health_check and list_agents. Continue only if the watcher and selected
|
|
11
11
|
agent are available. Then call list_workspace for the configured workspace.
|
|
12
|
-
Confirm schema_epoch is 2026-06-
|
|
12
|
+
Confirm schema_epoch is 2026-06-22-v6, tool_profile is chatgpt_core, and
|
|
13
13
|
tool_count is 16. If the watcher is stale or missing, stop the task flow and
|
|
14
14
|
report the health_check recovery instruction.
|
|
15
15
|
|
|
@@ -17,30 +17,33 @@ Step 2:
|
|
|
17
17
|
Call read_workspace_file for README.md.
|
|
18
18
|
|
|
19
19
|
Step 3:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
title: Add Usage Section
|
|
23
|
-
|
|
24
|
-
content:
|
|
20
|
+
Prefer the feature_small template for this bounded change. Use goal:
|
|
25
21
|
Add a "## Usage" section to the end of README.md. The section should include
|
|
26
22
|
one sentence: "This repository was updated through PatchWarden."
|
|
27
23
|
Do not modify other files. After the change, run the configured test command.
|
|
28
24
|
|
|
29
25
|
Step 4:
|
|
30
|
-
Call create_task with
|
|
31
|
-
|
|
32
|
-
timeout_seconds
|
|
26
|
+
Call create_task with execution_mode "assess_only", template "feature_small",
|
|
27
|
+
the goal above, agent "opencode", an explicit repo_path, verify_commands
|
|
28
|
+
["npm test"], and timeout_seconds 600. If the decision is allow, invoke the
|
|
29
|
+
returned next_tool_call exactly as provided. Do not resend the goal, plan,
|
|
30
|
+
repository, agent, or verification arguments. If needs_confirm, stop and ask
|
|
31
|
+
the user to run the returned local patchwarden-confirm command. Never execute
|
|
32
|
+
a blocked assessment.
|
|
33
33
|
|
|
34
34
|
Step 5:
|
|
35
|
-
|
|
36
|
-
continuation_required: true, call
|
|
37
|
-
|
|
35
|
+
For this short task, call wait_for_task with timeout_seconds 25. If it returns
|
|
36
|
+
continuation_required: true, call it again in this same assistant turn. For a
|
|
37
|
+
long task, prefer list_tasks(repo_path=..., active_only=true) and
|
|
38
|
+
get_task_status instead of an extended wait loop.
|
|
38
39
|
If execution_blocked is true or next_tool_call is health_check, do not keep
|
|
39
40
|
polling; report the watcher recovery requirement.
|
|
40
41
|
|
|
41
42
|
Step 6:
|
|
42
|
-
When terminal is true, review the included summary, call
|
|
43
|
-
|
|
43
|
+
When terminal is true, review the included compact summary, call
|
|
44
|
+
get_task_summary with view "compact", and call audit_task. Request the
|
|
45
|
+
standard summary, diff, test log, or result JSON only when compact evidence is
|
|
46
|
+
insufficient.
|
|
44
47
|
|
|
45
48
|
Step 7:
|
|
46
49
|
Summarize:
|
|
@@ -49,5 +52,6 @@ Summarize:
|
|
|
49
52
|
- whether tests passed
|
|
50
53
|
- whether any out-of-scope changes were detected
|
|
51
54
|
- acceptance_status and audit verdict
|
|
55
|
+
- confirmed_failures, possible_false_positives, and manual verification items
|
|
52
56
|
- whether the diff is acceptable
|
|
53
57
|
```
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
# --tunnel-id tunnel_xxx \
|
|
15
15
|
# --mcp-command "C:/path/to/patchwarden/scripts/patchwarden-mcp-stdio.cmd"
|
|
16
16
|
|
|
17
|
+
# Direct mode uses a separate profile and command:
|
|
18
|
+
#
|
|
19
|
+
# tunnel-client init \
|
|
20
|
+
# --sample sample_mcp_stdio_local \
|
|
21
|
+
# --profile patchwarden-direct \
|
|
22
|
+
# --tunnel-id tunnel_xxx \
|
|
23
|
+
# --mcp-command "C:/path/to/patchwarden/scripts/patchwarden-mcp-direct.cmd"
|
|
24
|
+
|
|
17
25
|
mcp:
|
|
18
26
|
commands:
|
|
19
27
|
- channel: main
|