patchwarden 1.1.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +41 -7
- package/README.md +36 -9
- package/dist/controlCenter.js +197 -1
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +1 -1
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/smoke-test.js +8 -8
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/schema-drift-check.test.js +10 -9
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/registry.js +376 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeViews.d.ts +6 -0
- package/dist/tools/safeViews.js +2 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +2 -2
- package/dist/tools/toolCatalog.js +6 -0
- package/dist/tools/toolRegistry.js +110 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +2 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/mcp-manifest-check.js +12 -0
- package/scripts/checks/mcp-smoke.js +31 -7
- package/scripts/checks/watcher-supervisor-smoke.js +1 -1
- package/src/controlCenter.ts +198 -1
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +1 -1
- package/src/policy/projectPolicy.ts +344 -0
- package/src/smoke-test.ts +5 -5
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/schema-drift-check.test.ts +11 -9
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/registry.ts +405 -2
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeViews.ts +2 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +6 -0
- package/src/tools/toolRegistry.ts +110 -0
- package/src/version.ts +2 -2
- package/ui/pages/dashboard.html +143 -2
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { getConfig } from "../config.js";
|
|
5
|
+
import { guardReadPath } from "../security/pathGuard.js";
|
|
6
|
+
import { redactSensitiveValue } from "../security/contentRedaction.js";
|
|
7
|
+
import { PatchWardenError } from "../errors.js";
|
|
8
|
+
|
|
9
|
+
export type TaskLoopStopReason =
|
|
10
|
+
| "success"
|
|
11
|
+
| "max_iterations_reached"
|
|
12
|
+
| "verification_failed"
|
|
13
|
+
| "high_risk_blocked"
|
|
14
|
+
| "user_confirmation_required"
|
|
15
|
+
| "agent_timeout"
|
|
16
|
+
| "policy_blocked"
|
|
17
|
+
| "watcher_blocked"
|
|
18
|
+
| "direct_profile_disabled"
|
|
19
|
+
| "direct_verification_failed"
|
|
20
|
+
| "direct_audit_failed";
|
|
21
|
+
|
|
22
|
+
export interface TaskLineageDirectSession {
|
|
23
|
+
session_id: string;
|
|
24
|
+
status?: "passed" | "failed" | "skipped";
|
|
25
|
+
command_count?: number;
|
|
26
|
+
passed_commands?: number;
|
|
27
|
+
failed_commands?: number;
|
|
28
|
+
timed_out_commands?: number;
|
|
29
|
+
audit_decision?: "pass" | "warn" | "fail" | "not_run";
|
|
30
|
+
changed_files_total?: number;
|
|
31
|
+
next_action?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TaskLineageWorktree {
|
|
35
|
+
isolation_mode: "current_repo" | "worktree";
|
|
36
|
+
worktree_id?: string;
|
|
37
|
+
worktree_path?: string;
|
|
38
|
+
branch?: string;
|
|
39
|
+
requested_base_branch?: string;
|
|
40
|
+
cleanup: "keep" | "archive" | "delete_ignored_only";
|
|
41
|
+
status: "not_used" | "active" | "failed";
|
|
42
|
+
next_action: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface TaskLineageAgentRouting {
|
|
46
|
+
requested_agent: string | null;
|
|
47
|
+
selected_agent: string;
|
|
48
|
+
reason: string;
|
|
49
|
+
fallback: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface TaskLineageRound {
|
|
53
|
+
iteration: number;
|
|
54
|
+
task_id: string;
|
|
55
|
+
role: "main" | "fix_tests" | "cleanup";
|
|
56
|
+
status: string;
|
|
57
|
+
terminal: boolean;
|
|
58
|
+
verification_status: string;
|
|
59
|
+
audit_verdict: string;
|
|
60
|
+
fail_checks: string[];
|
|
61
|
+
warn_checks: string[];
|
|
62
|
+
next_action: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface TaskLineageRecord {
|
|
66
|
+
lineage_id: string;
|
|
67
|
+
goal: string;
|
|
68
|
+
repo_path: string;
|
|
69
|
+
created_at: string;
|
|
70
|
+
updated_at: string;
|
|
71
|
+
final_status: "accepted" | "needs_fix" | "blocked" | "failed";
|
|
72
|
+
stop_reason: TaskLoopStopReason;
|
|
73
|
+
next_action: string;
|
|
74
|
+
main_task: string | null;
|
|
75
|
+
fix_tasks: string[];
|
|
76
|
+
cleanup_tasks: string[];
|
|
77
|
+
direct_sessions: Array<string | TaskLineageDirectSession>;
|
|
78
|
+
rounds: TaskLineageRound[];
|
|
79
|
+
warnings: string[];
|
|
80
|
+
errors: string[];
|
|
81
|
+
worktree?: TaskLineageWorktree;
|
|
82
|
+
agent_routing?: TaskLineageAgentRouting;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface SafeTaskLineage {
|
|
86
|
+
lineage_id: string;
|
|
87
|
+
goal: string;
|
|
88
|
+
repo_path: string;
|
|
89
|
+
created_at: string;
|
|
90
|
+
updated_at: string;
|
|
91
|
+
final_status: TaskLineageRecord["final_status"];
|
|
92
|
+
stop_reason: TaskLoopStopReason;
|
|
93
|
+
next_action: string;
|
|
94
|
+
tasks: {
|
|
95
|
+
main: string | null;
|
|
96
|
+
fix: string[];
|
|
97
|
+
cleanup: string[];
|
|
98
|
+
direct_sessions: TaskLineageDirectSession[];
|
|
99
|
+
};
|
|
100
|
+
worktree: TaskLineageWorktree;
|
|
101
|
+
agent_routing: TaskLineageAgentRouting | null;
|
|
102
|
+
verification: {
|
|
103
|
+
latest_status: string;
|
|
104
|
+
passed: boolean;
|
|
105
|
+
};
|
|
106
|
+
rounds: TaskLineageRound[];
|
|
107
|
+
warnings: string[];
|
|
108
|
+
errors: string[];
|
|
109
|
+
truncated: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createLineageId(now = new Date()): string {
|
|
113
|
+
const stamp = now.toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
114
|
+
return `lineage_${stamp}_${randomBytes(4).toString("hex")}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function writeTaskLineage(record: TaskLineageRecord): SafeTaskLineage {
|
|
118
|
+
const config = getConfig();
|
|
119
|
+
const lineageDir = resolve(config.workspaceRoot, ".patchwarden", "lineages", record.lineage_id);
|
|
120
|
+
mkdirSync(lineageDir, { recursive: true });
|
|
121
|
+
const safeRecord = redactSensitiveValue(record).value as TaskLineageRecord;
|
|
122
|
+
writeFileSync(join(lineageDir, "lineage.json"), JSON.stringify(safeRecord, null, 2) + "\n", "utf-8");
|
|
123
|
+
writeFileSync(join(lineageDir, "SUMMARY.md"), buildSummaryMarkdown(safeRecord), "utf-8");
|
|
124
|
+
return toSafeTaskLineage(safeRecord);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getTaskLineage(lineageId: string, options: { max_items?: number } = {}): SafeTaskLineage {
|
|
128
|
+
const maxItems = normalizeMaxItems(options.max_items);
|
|
129
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
130
|
+
throw new PatchWardenError(
|
|
131
|
+
"invalid_lineage_id",
|
|
132
|
+
"lineage_id may contain only letters, numbers, underscores, and hyphens.",
|
|
133
|
+
"Pass a lineage_id returned by run_task_loop.",
|
|
134
|
+
true,
|
|
135
|
+
{ lineage_id: lineageId }
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
const config = getConfig();
|
|
139
|
+
const lineageFile = resolve(config.workspaceRoot, ".patchwarden", "lineages", lineageId, "lineage.json");
|
|
140
|
+
guardReadPath(lineageFile, config.workspaceRoot, ".patchwarden/lineages");
|
|
141
|
+
if (!existsSync(lineageFile)) {
|
|
142
|
+
throw new PatchWardenError(
|
|
143
|
+
"lineage_not_found",
|
|
144
|
+
`Task lineage not found: "${lineageId}".`,
|
|
145
|
+
"Pass a lineage_id returned by run_task_loop.",
|
|
146
|
+
true,
|
|
147
|
+
{ lineage_id: lineageId }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const raw = readFileSync(lineageFile, "utf-8").replace(/^\uFEFF/, "");
|
|
151
|
+
const record = JSON.parse(raw) as TaskLineageRecord;
|
|
152
|
+
return toSafeTaskLineage(redactSensitiveValue(record).value as TaskLineageRecord, maxItems);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function toSafeTaskLineage(record: TaskLineageRecord, maxItems = 8): SafeTaskLineage {
|
|
156
|
+
const rounds = record.rounds.slice(0, maxItems);
|
|
157
|
+
const latest = record.rounds[record.rounds.length - 1];
|
|
158
|
+
const directSessions = normalizeDirectSessions(record.direct_sessions);
|
|
159
|
+
return {
|
|
160
|
+
lineage_id: record.lineage_id,
|
|
161
|
+
goal: record.goal,
|
|
162
|
+
repo_path: record.repo_path,
|
|
163
|
+
created_at: record.created_at,
|
|
164
|
+
updated_at: record.updated_at,
|
|
165
|
+
final_status: record.final_status,
|
|
166
|
+
stop_reason: record.stop_reason,
|
|
167
|
+
next_action: record.next_action,
|
|
168
|
+
tasks: {
|
|
169
|
+
main: record.main_task,
|
|
170
|
+
fix: record.fix_tasks.slice(0, maxItems),
|
|
171
|
+
cleanup: record.cleanup_tasks.slice(0, maxItems),
|
|
172
|
+
direct_sessions: directSessions.slice(0, maxItems),
|
|
173
|
+
},
|
|
174
|
+
worktree: normalizeWorktree(record.worktree),
|
|
175
|
+
agent_routing: record.agent_routing ? {
|
|
176
|
+
requested_agent: record.agent_routing.requested_agent,
|
|
177
|
+
selected_agent: truncate(String(record.agent_routing.selected_agent), 120),
|
|
178
|
+
reason: truncate(String(record.agent_routing.reason), 240),
|
|
179
|
+
fallback: Boolean(record.agent_routing.fallback),
|
|
180
|
+
} : null,
|
|
181
|
+
verification: {
|
|
182
|
+
latest_status: latest?.verification_status || "not_available",
|
|
183
|
+
passed: latest?.verification_status === "passed",
|
|
184
|
+
},
|
|
185
|
+
rounds,
|
|
186
|
+
warnings: record.warnings.slice(0, maxItems).map((value) => truncate(value, 240)),
|
|
187
|
+
errors: record.errors.slice(0, maxItems).map((value) => truncate(value, 240)),
|
|
188
|
+
truncated:
|
|
189
|
+
record.rounds.length > maxItems ||
|
|
190
|
+
record.fix_tasks.length > maxItems ||
|
|
191
|
+
record.cleanup_tasks.length > maxItems ||
|
|
192
|
+
directSessions.length > maxItems ||
|
|
193
|
+
record.warnings.length > maxItems ||
|
|
194
|
+
record.errors.length > maxItems,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildSummaryMarkdown(record: TaskLineageRecord): string {
|
|
199
|
+
const rounds = record.rounds.map((round) =>
|
|
200
|
+
`- ${round.role} ${round.task_id}: ${round.status}, verification=${round.verification_status}, audit=${round.audit_verdict}`
|
|
201
|
+
);
|
|
202
|
+
return [
|
|
203
|
+
"# PatchWarden Task Lineage",
|
|
204
|
+
"",
|
|
205
|
+
`- Lineage: ${record.lineage_id}`,
|
|
206
|
+
`- Goal: ${record.goal}`,
|
|
207
|
+
`- Repo: ${record.repo_path}`,
|
|
208
|
+
`- Final status: ${record.final_status}`,
|
|
209
|
+
`- Stop reason: ${record.stop_reason}`,
|
|
210
|
+
`- Next action: ${record.next_action}`,
|
|
211
|
+
`- Isolation: ${normalizeWorktree(record.worktree).isolation_mode}`,
|
|
212
|
+
`- Worktree: ${formatWorktree(record.worktree)}`,
|
|
213
|
+
`- Agent routing: ${formatAgentRouting(record.agent_routing)}`,
|
|
214
|
+
"",
|
|
215
|
+
"## Tasks",
|
|
216
|
+
`- Main: ${record.main_task || "none"}`,
|
|
217
|
+
`- Fix tasks: ${record.fix_tasks.length > 0 ? record.fix_tasks.join(", ") : "none"}`,
|
|
218
|
+
`- Cleanup tasks: ${record.cleanup_tasks.length > 0 ? record.cleanup_tasks.join(", ") : "none"}`,
|
|
219
|
+
`- Direct sessions: ${formatDirectSessions(record.direct_sessions)}`,
|
|
220
|
+
"",
|
|
221
|
+
"## Rounds",
|
|
222
|
+
...(rounds.length > 0 ? rounds : ["- None."]),
|
|
223
|
+
"",
|
|
224
|
+
].join("\n");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function normalizeWorktree(value: TaskLineageWorktree | undefined): TaskLineageWorktree {
|
|
228
|
+
if (!value) {
|
|
229
|
+
return {
|
|
230
|
+
isolation_mode: "current_repo",
|
|
231
|
+
cleanup: "keep",
|
|
232
|
+
status: "not_used",
|
|
233
|
+
next_action: "none",
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
isolation_mode: value.isolation_mode === "worktree" ? "worktree" : "current_repo",
|
|
238
|
+
worktree_id: value.worktree_id ? truncate(String(value.worktree_id), 120) : undefined,
|
|
239
|
+
worktree_path: value.worktree_path ? truncate(String(value.worktree_path), 260) : undefined,
|
|
240
|
+
branch: value.branch ? truncate(String(value.branch), 160) : undefined,
|
|
241
|
+
requested_base_branch: value.requested_base_branch ? truncate(String(value.requested_base_branch), 160) : undefined,
|
|
242
|
+
cleanup: value.cleanup,
|
|
243
|
+
status: value.status,
|
|
244
|
+
next_action: truncate(String(value.next_action || "review_worktree"), 240),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function formatWorktree(value: TaskLineageWorktree | undefined): string {
|
|
249
|
+
const worktree = normalizeWorktree(value);
|
|
250
|
+
if (worktree.isolation_mode !== "worktree") return "not used";
|
|
251
|
+
const id = worktree.worktree_id || "unknown";
|
|
252
|
+
const status = worktree.status || "unknown";
|
|
253
|
+
const branch = worktree.branch ? ` branch=${worktree.branch}` : "";
|
|
254
|
+
return `${id} status=${status}${branch}`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function formatAgentRouting(value: TaskLineageAgentRouting | undefined): string {
|
|
258
|
+
if (!value) return "not recorded";
|
|
259
|
+
const requested = value.requested_agent ? ` requested=${value.requested_agent}` : "";
|
|
260
|
+
return `${value.selected_agent}${requested} reason=${truncate(value.reason, 160)}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function normalizeDirectSessions(value: Array<string | TaskLineageDirectSession>): TaskLineageDirectSession[] {
|
|
264
|
+
return value.map((entry) => {
|
|
265
|
+
if (typeof entry === "string") return { session_id: entry };
|
|
266
|
+
return {
|
|
267
|
+
session_id: String(entry.session_id || ""),
|
|
268
|
+
status: entry.status,
|
|
269
|
+
command_count: entry.command_count,
|
|
270
|
+
passed_commands: entry.passed_commands,
|
|
271
|
+
failed_commands: entry.failed_commands,
|
|
272
|
+
timed_out_commands: entry.timed_out_commands,
|
|
273
|
+
audit_decision: entry.audit_decision,
|
|
274
|
+
changed_files_total: entry.changed_files_total,
|
|
275
|
+
next_action: entry.next_action ? truncate(String(entry.next_action), 240) : undefined,
|
|
276
|
+
};
|
|
277
|
+
}).filter((entry) => entry.session_id !== "");
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function formatDirectSessions(value: Array<string | TaskLineageDirectSession>): string {
|
|
281
|
+
const sessions = normalizeDirectSessions(value);
|
|
282
|
+
if (sessions.length === 0) return "none";
|
|
283
|
+
return sessions.map((entry) => {
|
|
284
|
+
const status = entry.status ? ` status=${entry.status}` : "";
|
|
285
|
+
const audit = entry.audit_decision ? ` audit=${entry.audit_decision}` : "";
|
|
286
|
+
return `${entry.session_id}${status}${audit}`;
|
|
287
|
+
}).join(", ");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function normalizeMaxItems(value: number | undefined): number {
|
|
291
|
+
if (value === undefined) return 8;
|
|
292
|
+
if (!Number.isInteger(value) || value < 1 || value > 50) {
|
|
293
|
+
throw new Error("max_items must be an integer from 1 to 50.");
|
|
294
|
+
}
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function truncate(value: string, maxChars: number): string {
|
|
299
|
+
return value.length <= maxChars ? value : `${value.slice(0, maxChars)}...`;
|
|
300
|
+
}
|
package/src/tools/toolCatalog.ts
CHANGED
|
@@ -25,6 +25,11 @@ export const CHATGPT_CORE_TOOL_NAMES = [
|
|
|
25
25
|
"read_workspace_file",
|
|
26
26
|
"save_plan",
|
|
27
27
|
"create_task",
|
|
28
|
+
"run_task_loop",
|
|
29
|
+
"recommend_agent_for_task",
|
|
30
|
+
"get_task_lineage",
|
|
31
|
+
"export_task_evidence_pack",
|
|
32
|
+
"get_project_policy",
|
|
28
33
|
"wait_for_task",
|
|
29
34
|
"get_task_summary",
|
|
30
35
|
"get_diff",
|
|
@@ -50,6 +55,7 @@ export const CHATGPT_DIRECT_TOOL_NAMES = [
|
|
|
50
55
|
"read_workspace_file",
|
|
51
56
|
"apply_patch",
|
|
52
57
|
"run_verification",
|
|
58
|
+
"run_direct_verification_bundle",
|
|
53
59
|
"finalize_direct_session",
|
|
54
60
|
"audit_session",
|
|
55
61
|
"safe_direct_summary",
|
|
@@ -405,6 +405,61 @@ const STATIC_TOOL_META: Record<string, StaticToolMeta> = {
|
|
|
405
405
|
requiresConfirmation: false,
|
|
406
406
|
relatedTools: ["save_plan", "wait_for_task", "get_task_status"],
|
|
407
407
|
},
|
|
408
|
+
run_task_loop: {
|
|
409
|
+
title: "Run Task Loop",
|
|
410
|
+
summary: "运行受保护的任务循环:创建任务、等待、验证、审计,并在低风险测试失败时创建 fix_tests 后续任务",
|
|
411
|
+
profiles: ["full", "chatgpt_core"],
|
|
412
|
+
modes: ["delegate", "audit"],
|
|
413
|
+
tags: ["loop", "task", "lineage", "verify", "audit", "fix"],
|
|
414
|
+
aliases: ["task_loop", "run_loop", "guarded_loop"],
|
|
415
|
+
risk: "workspace_write",
|
|
416
|
+
requiresConfirmation: false,
|
|
417
|
+
relatedTools: ["create_task", "wait_for_task", "safe_result", "safe_audit", "get_task_lineage"],
|
|
418
|
+
},
|
|
419
|
+
recommend_agent_for_task: {
|
|
420
|
+
title: "Recommend Agent For Task",
|
|
421
|
+
summary: "Return bounded agent routing guidance without starting an agent or creating a task",
|
|
422
|
+
profiles: ["full", "chatgpt_core"],
|
|
423
|
+
modes: ["delegate", "diagnostic"],
|
|
424
|
+
tags: ["agent", "routing", "recommend", "task", "safe"],
|
|
425
|
+
aliases: ["recommend_agent", "route_agent", "agent_routing"],
|
|
426
|
+
risk: "readonly",
|
|
427
|
+
requiresConfirmation: false,
|
|
428
|
+
relatedTools: ["list_agents", "create_task", "run_task_loop"],
|
|
429
|
+
},
|
|
430
|
+
get_task_lineage: {
|
|
431
|
+
title: "Get Task Lineage",
|
|
432
|
+
summary: "读取 run_task_loop 生成的任务链路安全摘要,不返回完整日志或 diff",
|
|
433
|
+
profiles: ["full", "chatgpt_core"],
|
|
434
|
+
modes: ["delegate", "audit", "diagnostic"],
|
|
435
|
+
tags: ["lineage", "loop", "summary", "task", "safe"],
|
|
436
|
+
aliases: ["lineage", "task_lineage"],
|
|
437
|
+
risk: "readonly",
|
|
438
|
+
requiresConfirmation: false,
|
|
439
|
+
relatedTools: ["run_task_loop", "safe_result", "safe_audit"],
|
|
440
|
+
},
|
|
441
|
+
export_task_evidence_pack: {
|
|
442
|
+
title: "Export Task Evidence Pack",
|
|
443
|
+
summary: "Export bounded lineage, verification, Direct, policy, and catalog evidence without logs or diffs",
|
|
444
|
+
profiles: ["full", "chatgpt_core"],
|
|
445
|
+
modes: ["delegate", "audit", "diagnostic"],
|
|
446
|
+
tags: ["evidence", "lineage", "summary", "release", "safe"],
|
|
447
|
+
aliases: ["evidence_pack", "export_evidence", "task_evidence"],
|
|
448
|
+
risk: "workspace_write",
|
|
449
|
+
requiresConfirmation: false,
|
|
450
|
+
relatedTools: ["get_task_lineage", "run_task_loop", "get_project_policy"],
|
|
451
|
+
},
|
|
452
|
+
get_project_policy: {
|
|
453
|
+
title: "Get Project Policy",
|
|
454
|
+
summary: "Read bounded project policy and release readiness without exposing secrets or expanding command permissions",
|
|
455
|
+
profiles: ["full", "chatgpt_core"],
|
|
456
|
+
modes: ["diagnostic", "release"],
|
|
457
|
+
tags: ["policy", "project", "release", "readiness", "safe"],
|
|
458
|
+
aliases: ["project_policy", "policy", "get_policy"],
|
|
459
|
+
risk: "readonly",
|
|
460
|
+
requiresConfirmation: false,
|
|
461
|
+
relatedTools: ["release_check", "release_prepare", "release_cleanup"],
|
|
462
|
+
},
|
|
408
463
|
sync_file: {
|
|
409
464
|
title: "Sync File",
|
|
410
465
|
summary: "在 Direct 会话中同步文件(带 sha256 校验)",
|
|
@@ -517,6 +572,17 @@ const STATIC_TOOL_META: Record<string, StaticToolMeta> = {
|
|
|
517
572
|
requiresConfirmation: false,
|
|
518
573
|
relatedTools: ["finalize_direct_session", "audit_session"],
|
|
519
574
|
},
|
|
575
|
+
run_direct_verification_bundle: {
|
|
576
|
+
title: "Run Direct Verification Bundle",
|
|
577
|
+
summary: "Run multiple Direct allowlisted verification commands and return bounded status without stdout or stderr tails",
|
|
578
|
+
profiles: ["full", "chatgpt_direct"],
|
|
579
|
+
modes: ["direct", "audit"],
|
|
580
|
+
tags: ["direct", "verify", "verification", "bundle", "safe"],
|
|
581
|
+
aliases: ["direct_verify_bundle", "verification_bundle"],
|
|
582
|
+
risk: "command",
|
|
583
|
+
requiresConfirmation: false,
|
|
584
|
+
relatedTools: ["run_verification", "safe_finalize_direct_session", "safe_audit_direct_session"],
|
|
585
|
+
},
|
|
520
586
|
safe_direct_summary: {
|
|
521
587
|
title: "Safe Direct Summary",
|
|
522
588
|
summary: "Direct 会话安全摘要(不含完整 diff 或验证 stdout/stderr)",
|
|
@@ -696,6 +762,50 @@ const STATIC_TOOL_META: Record<string, StaticToolMeta> = {
|
|
|
696
762
|
requiresConfirmation: true,
|
|
697
763
|
relatedTools: ["audit_task", "safe_status"],
|
|
698
764
|
},
|
|
765
|
+
release_check: {
|
|
766
|
+
title: "Release Check",
|
|
767
|
+
summary: "Run bounded release readiness checks through the existing release gate without remote writes",
|
|
768
|
+
profiles: ["full"],
|
|
769
|
+
modes: ["release", "diagnostic"],
|
|
770
|
+
tags: ["release", "check", "gate", "readiness", "policy"],
|
|
771
|
+
aliases: ["release_check", "check_release"],
|
|
772
|
+
risk: "release",
|
|
773
|
+
requiresConfirmation: true,
|
|
774
|
+
relatedTools: ["check_release_gate", "get_project_policy", "release_prepare", "release_verify"],
|
|
775
|
+
},
|
|
776
|
+
release_prepare: {
|
|
777
|
+
title: "Release Prepare",
|
|
778
|
+
summary: "Run policy-approved local release preparation commands through the existing command guard",
|
|
779
|
+
profiles: ["full"],
|
|
780
|
+
modes: ["release"],
|
|
781
|
+
tags: ["release", "prepare", "build", "test", "policy"],
|
|
782
|
+
aliases: ["prepare_release", "release_ready"],
|
|
783
|
+
risk: "release",
|
|
784
|
+
requiresConfirmation: true,
|
|
785
|
+
relatedTools: ["get_project_policy", "release_check", "release_verify"],
|
|
786
|
+
},
|
|
787
|
+
release_verify: {
|
|
788
|
+
title: "Release Verify",
|
|
789
|
+
summary: "Verify npm, GitHub release, and CI facts with read-only remote checks",
|
|
790
|
+
profiles: ["full"],
|
|
791
|
+
modes: ["release", "diagnostic"],
|
|
792
|
+
tags: ["release", "verify", "npm", "github", "ci"],
|
|
793
|
+
aliases: ["verify_release", "release_remote_verify"],
|
|
794
|
+
risk: "release",
|
|
795
|
+
requiresConfirmation: true,
|
|
796
|
+
relatedTools: ["release_check", "check_release_gate"],
|
|
797
|
+
},
|
|
798
|
+
release_cleanup: {
|
|
799
|
+
title: "Release Cleanup",
|
|
800
|
+
summary: "Dry-run-first cleanup for low-risk release artifacts under project policy",
|
|
801
|
+
profiles: ["full"],
|
|
802
|
+
modes: ["release"],
|
|
803
|
+
tags: ["release", "cleanup", "artifacts", "policy"],
|
|
804
|
+
aliases: ["cleanup_release", "release_artifact_cleanup"],
|
|
805
|
+
risk: "workspace_write",
|
|
806
|
+
requiresConfirmation: true,
|
|
807
|
+
relatedTools: ["get_project_policy", "release_prepare"],
|
|
808
|
+
},
|
|
699
809
|
merge_worktree: {
|
|
700
810
|
title: "Merge Worktree",
|
|
701
811
|
summary: "v1.0.0: 合并隔离 worktree 变更回主工作区",
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PATCHWARDEN_VERSION = "1.
|
|
2
|
-
export const TOOL_SCHEMA_EPOCH = "2026-
|
|
1
|
+
export const PATCHWARDEN_VERSION = "1.5.0";
|
|
2
|
+
export const TOOL_SCHEMA_EPOCH = "2026-07-05-v13";
|
package/ui/pages/dashboard.html
CHANGED
|
@@ -304,6 +304,38 @@ html.dark {
|
|
|
304
304
|
<div id="suggestions-body" class="px-4 py-2"></div>
|
|
305
305
|
</div>
|
|
306
306
|
|
|
307
|
+
<!-- ===== V1.5 RELEASE / POLICY / LINEAGE / EVIDENCE OVERVIEW ===== -->
|
|
308
|
+
<div class="grid grid-cols-1 lg:grid-cols-4 gap-4 mb-6">
|
|
309
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
310
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
311
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">Lineage</h2>
|
|
312
|
+
<span id="lineage-count" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
313
|
+
</div>
|
|
314
|
+
<div id="lineage-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">Loading</div>
|
|
315
|
+
</div>
|
|
316
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
317
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
318
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">Project Policy</h2>
|
|
319
|
+
<span id="policy-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
320
|
+
</div>
|
|
321
|
+
<div id="policy-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">Loading</div>
|
|
322
|
+
</div>
|
|
323
|
+
<div class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
324
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
325
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">Release</h2>
|
|
326
|
+
<span id="release-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
327
|
+
</div>
|
|
328
|
+
<div id="release-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">Loading</div>
|
|
329
|
+
</div>
|
|
330
|
+
<div id="evidence-pack-card" class="rounded-lg" style="background-color: var(--pw-bg-surface); border: 1px solid var(--pw-border);">
|
|
331
|
+
<div class="flex items-center justify-between px-4 py-3" style="border-bottom: 1px solid var(--pw-border);">
|
|
332
|
+
<h2 class="text-sm font-semibold truncate" style="color: var(--pw-text-primary);">Evidence Pack</h2>
|
|
333
|
+
<span id="evidence-pack-status" class="text-xs whitespace-nowrap" style="color: var(--pw-text-tertiary);">...</span>
|
|
334
|
+
</div>
|
|
335
|
+
<div id="evidence-pack-overview-body" class="px-4 py-3 text-sm" style="color: var(--pw-text-tertiary);">Loading</div>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
307
339
|
<!-- ===== TWO-COLUMN LAYOUT ===== -->
|
|
308
340
|
<div class="grid grid-cols-1 lg:grid-cols-5 gap-4 mb-6">
|
|
309
341
|
|
|
@@ -1027,6 +1059,115 @@ html.dark {
|
|
|
1027
1059
|
+ '</details>';
|
|
1028
1060
|
}
|
|
1029
1061
|
|
|
1062
|
+
// ---------- V1.5 panels ----------
|
|
1063
|
+
async function refreshLineages() {
|
|
1064
|
+
const body = $('lineage-overview-body');
|
|
1065
|
+
const count = $('lineage-count');
|
|
1066
|
+
if (!body) return;
|
|
1067
|
+
try {
|
|
1068
|
+
const data = await fetchJson('/api/lineages');
|
|
1069
|
+
const lineages = Array.isArray(data.lineages) ? data.lineages : [];
|
|
1070
|
+
if (count) count.textContent = String(data.total || lineages.length);
|
|
1071
|
+
if (!lineages.length) {
|
|
1072
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">No loop lineage yet</div>';
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1075
|
+
const top = lineages[0];
|
|
1076
|
+
const directSessions = top.tasks && Array.isArray(top.tasks.direct_sessions) ? top.tasks.direct_sessions : [];
|
|
1077
|
+
const direct = directSessions.length ? directSessions[0] : null;
|
|
1078
|
+
const worktree = top.worktree || {};
|
|
1079
|
+
const directStatus = direct
|
|
1080
|
+
? ((direct.status || 'unknown') + ' / audit ' + (direct.audit_decision || 'not_run'))
|
|
1081
|
+
: 'not run';
|
|
1082
|
+
body.innerHTML =
|
|
1083
|
+
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1084
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(top.lineage_id || '') + '">' + escapeHtml(shortId(top.lineage_id)) + '</span>'
|
|
1085
|
+
+ badgeHtml(top.stop_reason || 'unknown', statusKind(top.final_status))
|
|
1086
|
+
+ '</div>'
|
|
1087
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-secondary);" title="' + escapeHtml(top.goal || '') + '">' + escapeHtml(top.goal || 'No goal') + '</div>'
|
|
1088
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);">Isolation: ' + escapeHtml(worktree.isolation_mode || 'current_repo') + '</div>'
|
|
1089
|
+
+ '<div class="text-xs truncate mb-1" style="color: var(--pw-text-tertiary);" title="' + escapeHtml(direct && direct.session_id ? direct.session_id : '') + '">Direct: ' + escapeHtml(directStatus) + '</div>'
|
|
1090
|
+
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);">Next: ' + escapeHtml(top.next_action || 'none') + '</div>';
|
|
1091
|
+
} catch (err) {
|
|
1092
|
+
if (count) count.textContent = 'error';
|
|
1093
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
async function refreshProjectPolicy() {
|
|
1098
|
+
const body = $('policy-overview-body');
|
|
1099
|
+
const status = $('policy-status');
|
|
1100
|
+
if (!body) return;
|
|
1101
|
+
try {
|
|
1102
|
+
const data = await fetchJson('/api/project-policy?repo_path=.');
|
|
1103
|
+
const valid = data && data.valid !== false;
|
|
1104
|
+
if (status) status.textContent = valid ? 'valid' : 'issues';
|
|
1105
|
+
const issueCount = Array.isArray(data.issues) ? data.issues.length : 0;
|
|
1106
|
+
const policy = data.effective_policy || {};
|
|
1107
|
+
const release = (policy.release_mode || {});
|
|
1108
|
+
body.innerHTML =
|
|
1109
|
+
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1110
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">policy file</span>'
|
|
1111
|
+
+ badgeHtml(data.exists ? 'present' : 'defaults', data.exists ? 'success' : 'warning')
|
|
1112
|
+
+ '</div>'
|
|
1113
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">issues: ' + issueCount + '</div>'
|
|
1114
|
+
+ '<div class="text-xs truncate" style="color: var(--pw-text-tertiary);" title="' + escapeHtml(release.version_source || '') + '">version: ' + escapeHtml(release.version_source || 'package.json') + '</div>';
|
|
1115
|
+
} catch (err) {
|
|
1116
|
+
if (status) status.textContent = 'error';
|
|
1117
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
async function refreshReleaseStatus() {
|
|
1122
|
+
const body = $('release-overview-body');
|
|
1123
|
+
const status = $('release-status');
|
|
1124
|
+
if (!body) return;
|
|
1125
|
+
try {
|
|
1126
|
+
const data = await fetchJson('/api/release/status?repo_path=.');
|
|
1127
|
+
const ready = data && data.policy_valid && data.release_readiness && data.release_readiness.version_consistent !== false;
|
|
1128
|
+
if (status) status.textContent = ready ? 'ready' : 'blocked';
|
|
1129
|
+
const readiness = data.release_readiness || {};
|
|
1130
|
+
const commands = Array.isArray(readiness.required_commands) ? readiness.required_commands : [];
|
|
1131
|
+
const blocked = commands.filter(function (entry) { return !entry.allowed; }).length;
|
|
1132
|
+
body.innerHTML =
|
|
1133
|
+
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1134
|
+
+ '<span class="text-xs" style="color: var(--pw-text-secondary);">version</span>'
|
|
1135
|
+
+ '<span class="text-xs" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);">' + escapeHtml(readiness.version || 'unknown') + '</span>'
|
|
1136
|
+
+ '</div>'
|
|
1137
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">commands blocked: ' + blocked + ' / ' + commands.length + '</div>'
|
|
1138
|
+
+ '<div class="text-xs" style="color: var(--pw-text-tertiary);">' + escapeHtml(data.next_action || '') + '</div>';
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
if (status) status.textContent = 'error';
|
|
1141
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
async function refreshEvidencePacks() {
|
|
1146
|
+
const body = $('evidence-pack-overview-body');
|
|
1147
|
+
const status = $('evidence-pack-status');
|
|
1148
|
+
if (!body) return;
|
|
1149
|
+
try {
|
|
1150
|
+
const data = await fetchJson('/api/evidence-packs');
|
|
1151
|
+
const packs = Array.isArray(data.evidence_packs) ? data.evidence_packs : [];
|
|
1152
|
+
if (status) status.textContent = String(data.total || packs.length);
|
|
1153
|
+
if (!packs.length) {
|
|
1154
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-text-tertiary);">No exported evidence pack yet</div>';
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
const top = packs[0];
|
|
1158
|
+
body.innerHTML =
|
|
1159
|
+
'<div class="flex items-center justify-between gap-2 mb-2">'
|
|
1160
|
+
+ '<span class="text-xs truncate" style="color: var(--pw-text-primary); font-family: var(--pw-font-mono);" title="' + escapeHtml(top.lineage_id || '') + '">' + escapeHtml(shortId(top.lineage_id)) + '</span>'
|
|
1161
|
+
+ badgeHtml(top.lineage && top.lineage.stop_reason ? top.lineage.stop_reason : 'unknown', statusKind(top.lineage && top.lineage.final_status))
|
|
1162
|
+
+ '</div>'
|
|
1163
|
+
+ '<div class="text-xs mb-1" style="color: var(--pw-text-tertiary);">policy issues: ' + escapeHtml(String(top.policy ? top.policy.issue_count : 0)) + '</div>'
|
|
1164
|
+
+ '<div class="text-xs truncate" style="color: var(--pw-text-tertiary);" title="' + escapeHtml(top.next_action || '') + '">Next: ' + escapeHtml(top.next_action || 'review') + '</div>';
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
if (status) status.textContent = 'error';
|
|
1167
|
+
body.innerHTML = '<div class="text-xs" style="color: var(--pw-state-error);">' + escapeHtml(err.message) + '</div>';
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1030
1171
|
// ---------- Refresh orchestrators ----------
|
|
1031
1172
|
async function refreshStatus() {
|
|
1032
1173
|
try {
|
|
@@ -1067,14 +1208,14 @@ html.dark {
|
|
|
1067
1208
|
}
|
|
1068
1209
|
|
|
1069
1210
|
async function refreshAll() {
|
|
1070
|
-
await Promise.all([refreshStatus(), refreshTasks(), refreshLogs(), refreshEvents()]);
|
|
1211
|
+
await Promise.all([refreshStatus(), refreshTasks(), refreshLogs(), refreshEvents(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks()]);
|
|
1071
1212
|
}
|
|
1072
1213
|
|
|
1073
1214
|
// ---------- Actions ----------
|
|
1074
1215
|
async function handleAction(action, btnEl) {
|
|
1075
1216
|
try {
|
|
1076
1217
|
if (action === 'refresh') {
|
|
1077
|
-
await Promise.all([refreshStatus(), refreshTasks()]);
|
|
1218
|
+
await Promise.all([refreshStatus(), refreshTasks(), refreshLineages(), refreshProjectPolicy(), refreshReleaseStatus(), refreshEvidencePacks()]);
|
|
1078
1219
|
return;
|
|
1079
1220
|
}
|
|
1080
1221
|
|