patchwarden 1.1.0 → 1.5.1

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.
Files changed (258) hide show
  1. package/README.en.md +83 -8
  2. package/README.md +78 -10
  3. package/dist/assessments/agentAssessor.d.ts +1 -1
  4. package/dist/assessments/agentAssessor.js +3 -3
  5. package/dist/assessments/assessmentStore.d.ts +1 -1
  6. package/dist/assessments/assessmentStore.js +2 -2
  7. package/dist/assessments/confirmCli.js +5 -4
  8. package/dist/config.d.ts +7 -0
  9. package/dist/config.js +34 -0
  10. package/dist/control/middleware/auth.d.ts +10 -0
  11. package/dist/control/middleware/auth.js +8 -0
  12. package/dist/control/middleware/static.d.ts +3 -0
  13. package/dist/control/middleware/static.js +65 -0
  14. package/dist/control/routes/audit.d.ts +15 -0
  15. package/dist/control/routes/audit.js +277 -0
  16. package/dist/control/routes/evidence.d.ts +4 -0
  17. package/dist/control/routes/evidence.js +96 -0
  18. package/dist/control/routes/lineage.d.ts +3 -0
  19. package/dist/control/routes/lineage.js +71 -0
  20. package/dist/control/routes/policy.d.ts +3 -0
  21. package/dist/control/routes/policy.js +81 -0
  22. package/dist/control/routes/process.d.ts +5 -0
  23. package/dist/control/routes/process.js +200 -0
  24. package/dist/control/routes/sessions.d.ts +22 -0
  25. package/dist/control/routes/sessions.js +224 -0
  26. package/dist/control/routes/status.d.ts +6 -0
  27. package/dist/control/routes/status.js +250 -0
  28. package/dist/control/routes/taskActions.d.ts +21 -0
  29. package/dist/control/routes/taskActions.js +233 -0
  30. package/dist/control/routes/tasks.d.ts +20 -0
  31. package/dist/control/routes/tasks.js +310 -0
  32. package/dist/control/routes/workspace.d.ts +15 -0
  33. package/dist/control/routes/workspace.js +193 -0
  34. package/dist/control/runtime.d.ts +91 -0
  35. package/dist/control/runtime.js +392 -0
  36. package/dist/control/server.d.ts +13 -0
  37. package/dist/control/server.js +479 -0
  38. package/dist/control/shared.d.ts +35 -0
  39. package/dist/control/shared.js +288 -0
  40. package/dist/controlCenter.d.ts +6 -0
  41. package/dist/controlCenter.js +7 -2001
  42. package/dist/direct/directGuards.js +30 -8
  43. package/dist/direct/directSessionStore.d.ts +2 -0
  44. package/dist/direct/directVerification.js +7 -0
  45. package/dist/doctor.d.ts +18 -1
  46. package/dist/doctor.js +579 -348
  47. package/dist/goal/goalReport.d.ts +54 -0
  48. package/dist/goal/goalReport.js +204 -0
  49. package/dist/goal/goalStatus.d.ts +6 -0
  50. package/dist/goal/specKitImport.d.ts +63 -0
  51. package/dist/goal/specKitImport.js +220 -0
  52. package/dist/goal/subgoalSync.js +2 -1
  53. package/dist/httpServer.js +15 -12
  54. package/dist/index.js +7 -4
  55. package/dist/logging.d.ts +7 -1
  56. package/dist/logging.js +8 -0
  57. package/dist/policy/projectPolicy.d.ts +55 -0
  58. package/dist/policy/projectPolicy.js +286 -0
  59. package/dist/runner/changeCapture.d.ts +3 -3
  60. package/dist/runner/changeCapture.js +63 -39
  61. package/dist/runner/cli.js +7 -6
  62. package/dist/runner/postTaskCleanup.js +26 -2
  63. package/dist/runner/runTask.js +245 -221
  64. package/dist/runner/simpleProcess.js +4 -4
  65. package/dist/runner/watch.js +17 -14
  66. package/dist/security/contentRedaction.d.ts +6 -0
  67. package/dist/security/contentRedaction.js +22 -0
  68. package/dist/smoke-test.js +261 -255
  69. package/dist/test/unit/apply-patch.test.d.ts +1 -0
  70. package/dist/test/unit/apply-patch.test.js +225 -0
  71. package/dist/test/unit/create-task.test.d.ts +1 -0
  72. package/dist/test/unit/create-task.test.js +197 -0
  73. package/dist/test/unit/direct-guards.test.js +124 -9
  74. package/dist/test/unit/evidence-pack.test.d.ts +1 -0
  75. package/dist/test/unit/evidence-pack.test.js +224 -0
  76. package/dist/test/unit/get-task-status.test.d.ts +1 -0
  77. package/dist/test/unit/get-task-status.test.js +174 -0
  78. package/dist/test/unit/get-task-summary.test.d.ts +1 -0
  79. package/dist/test/unit/get-task-summary.test.js +146 -0
  80. package/dist/test/unit/goal-report.test.d.ts +1 -0
  81. package/dist/test/unit/goal-report.test.js +159 -0
  82. package/dist/test/unit/goal-subgoal-task.test.js +6 -6
  83. package/dist/test/unit/goal-tools-registry.test.js +6 -4
  84. package/dist/test/unit/path-guard.test.js +24 -0
  85. package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
  86. package/dist/test/unit/project-policy-release-mode.test.js +125 -0
  87. package/dist/test/unit/run-task-loop.test.d.ts +1 -0
  88. package/dist/test/unit/run-task-loop.test.js +380 -0
  89. package/dist/test/unit/schema-drift-check.test.js +10 -9
  90. package/dist/test/unit/spec-kit-import.test.d.ts +1 -0
  91. package/dist/test/unit/spec-kit-import.test.js +341 -0
  92. package/dist/test/unit/wait-for-task.test.d.ts +1 -0
  93. package/dist/test/unit/wait-for-task.test.js +144 -0
  94. package/dist/tools/auditTask.d.ts +8 -63
  95. package/dist/tools/auditTask.js +12 -8
  96. package/dist/tools/createDirectSession.d.ts +1 -1
  97. package/dist/tools/createDirectSession.js +2 -2
  98. package/dist/tools/createTask.d.ts +2 -2
  99. package/dist/tools/createTask.js +4 -4
  100. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  101. package/dist/tools/dispatch/coreDispatch.js +282 -0
  102. package/dist/tools/dispatch/diagnosticDispatch.d.ts +14 -0
  103. package/dist/tools/dispatch/diagnosticDispatch.js +78 -0
  104. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  105. package/dist/tools/dispatch/directDispatch.js +115 -0
  106. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  107. package/dist/tools/dispatch/goalDispatch.js +91 -0
  108. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  109. package/dist/tools/dispatch/releaseDispatch.js +45 -0
  110. package/dist/tools/dispatch/types.d.ts +23 -0
  111. package/dist/tools/dispatch/types.js +15 -0
  112. package/dist/tools/evidencePack.d.ts +45 -0
  113. package/dist/tools/evidencePack.js +375 -0
  114. package/dist/tools/finalizeDirectSession.d.ts +1 -1
  115. package/dist/tools/finalizeDirectSession.js +3 -3
  116. package/dist/tools/goalSubgoalTask.d.ts +1 -1
  117. package/dist/tools/goalSubgoalTask.js +2 -2
  118. package/dist/tools/healthCheck.js +3 -3
  119. package/dist/tools/recommendAgentForTask.d.ts +19 -0
  120. package/dist/tools/recommendAgentForTask.js +56 -0
  121. package/dist/tools/registry.d.ts +3 -3
  122. package/dist/tools/registry.js +333 -402
  123. package/dist/tools/releaseMode.d.ts +50 -0
  124. package/dist/tools/releaseMode.js +370 -0
  125. package/dist/tools/retryTask.d.ts +2 -2
  126. package/dist/tools/retryTask.js +2 -2
  127. package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
  128. package/dist/tools/runDirectVerificationBundle.js +64 -0
  129. package/dist/tools/runTaskLoop.d.ts +57 -0
  130. package/dist/tools/runTaskLoop.js +417 -0
  131. package/dist/tools/runVerification.d.ts +4 -0
  132. package/dist/tools/runVerification.js +4 -0
  133. package/dist/tools/safeViews.d.ts +8 -2
  134. package/dist/tools/safeViews.js +4 -2
  135. package/dist/tools/taskLineage.d.ts +91 -0
  136. package/dist/tools/taskLineage.js +175 -0
  137. package/dist/tools/toolCatalog.d.ts +2 -2
  138. package/dist/tools/toolCatalog.js +6 -0
  139. package/dist/tools/toolRegistry.js +132 -0
  140. package/dist/version.d.ts +2 -2
  141. package/dist/version.js +2 -2
  142. package/docs/CODE_WIKI.md +983 -0
  143. package/docs/agentseal-integration.md +150 -0
  144. package/docs/architecture.md +63 -0
  145. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  146. package/docs/chatgpt-usage.md +31 -0
  147. package/docs/control-center/README.md +9 -0
  148. package/docs/dashboard-overview.md +86 -0
  149. package/docs/demo.md +8 -0
  150. package/docs/direct-session-workflow.md +98 -0
  151. package/docs/evidence-pack-schema.md +215 -0
  152. package/docs/execution-plan-2026-07-09.md +315 -0
  153. package/docs/lineage-evidence-pack-workflow.md +127 -0
  154. package/docs/mcp-inspector-testing.md +200 -0
  155. package/docs/open-source-application.md +162 -0
  156. package/docs/opencode-worker.md +151 -0
  157. package/docs/openhands-worker.md +181 -0
  158. package/docs/release-evidence.md +72 -0
  159. package/docs/roadmap-execution-and-acceptance.md +365 -0
  160. package/docs/spec-kit-integration.md +131 -0
  161. package/docs/task-safe-review-workflow.md +98 -0
  162. package/docs/threat-model.md +79 -0
  163. package/docs/user-feedback.md +40 -0
  164. package/docs/why-patchwarden.md +110 -0
  165. package/package.json +3 -3
  166. package/scripts/checks/control-center-smoke.js +443 -0
  167. package/scripts/checks/control-smoke.js +2 -2
  168. package/scripts/checks/lifecycle-smoke.js +29 -23
  169. package/scripts/checks/mcp-manifest-check.js +12 -0
  170. package/scripts/checks/mcp-smoke.js +33 -7
  171. package/scripts/checks/watcher-supervisor-smoke.js +1 -1
  172. package/scripts/generate-demo-gif.py +320 -0
  173. package/src/assessments/agentAssessor.ts +3 -3
  174. package/src/assessments/assessmentStore.ts +2 -2
  175. package/src/assessments/confirmCli.ts +5 -4
  176. package/src/config.ts +37 -0
  177. package/src/control/middleware/auth.ts +17 -0
  178. package/src/control/middleware/static.ts +71 -0
  179. package/src/control/routes/audit.ts +321 -0
  180. package/src/control/routes/evidence.ts +107 -0
  181. package/src/control/routes/lineage.ts +92 -0
  182. package/src/control/routes/policy.ts +81 -0
  183. package/src/control/routes/process.ts +204 -0
  184. package/src/control/routes/sessions.ts +251 -0
  185. package/src/control/routes/status.ts +325 -0
  186. package/src/control/routes/taskActions.ts +248 -0
  187. package/src/control/routes/tasks.ts +323 -0
  188. package/src/control/routes/workspace.ts +203 -0
  189. package/src/control/runtime.ts +472 -0
  190. package/src/control/server.ts +471 -0
  191. package/src/control/shared.ts +294 -0
  192. package/src/controlCenter.ts +7 -2150
  193. package/src/direct/directGuards.ts +28 -7
  194. package/src/direct/directSessionStore.ts +2 -0
  195. package/src/direct/directVerification.ts +7 -0
  196. package/src/doctor.ts +741 -481
  197. package/src/goal/goalReport.ts +271 -0
  198. package/src/goal/goalStatus.ts +6 -0
  199. package/src/goal/specKitImport.ts +355 -0
  200. package/src/goal/subgoalSync.ts +4 -2
  201. package/src/httpServer.ts +17 -14
  202. package/src/index.ts +7 -4
  203. package/src/logging.ts +10 -1
  204. package/src/policy/projectPolicy.ts +344 -0
  205. package/src/runner/changeCapture.ts +70 -42
  206. package/src/runner/cli.ts +7 -6
  207. package/src/runner/postTaskCleanup.ts +26 -2
  208. package/src/runner/runTask.ts +325 -223
  209. package/src/runner/simpleProcess.ts +4 -4
  210. package/src/runner/watch.ts +17 -14
  211. package/src/security/contentRedaction.ts +29 -0
  212. package/src/smoke-test.ts +254 -252
  213. package/src/test/unit/apply-patch.test.ts +293 -0
  214. package/src/test/unit/create-task.test.ts +255 -0
  215. package/src/test/unit/direct-guards.test.ts +178 -8
  216. package/src/test/unit/evidence-pack.test.ts +251 -0
  217. package/src/test/unit/get-task-status.test.ts +203 -0
  218. package/src/test/unit/get-task-summary.test.ts +173 -0
  219. package/src/test/unit/goal-report.test.ts +189 -0
  220. package/src/test/unit/goal-subgoal-task.test.ts +6 -6
  221. package/src/test/unit/goal-tools-registry.test.ts +7 -5
  222. package/src/test/unit/path-guard.test.ts +30 -0
  223. package/src/test/unit/project-policy-release-mode.test.ts +156 -0
  224. package/src/test/unit/run-task-loop.test.ts +425 -0
  225. package/src/test/unit/schema-drift-check.test.ts +11 -9
  226. package/src/test/unit/spec-kit-import.test.ts +429 -0
  227. package/src/test/unit/wait-for-task.test.ts +176 -0
  228. package/src/tools/auditTask.ts +99 -59
  229. package/src/tools/createDirectSession.ts +3 -3
  230. package/src/tools/createTask.ts +7 -7
  231. package/src/tools/dispatch/coreDispatch.ts +374 -0
  232. package/src/tools/dispatch/diagnosticDispatch.ts +101 -0
  233. package/src/tools/dispatch/directDispatch.ts +167 -0
  234. package/src/tools/dispatch/goalDispatch.ts +127 -0
  235. package/src/tools/dispatch/releaseDispatch.ts +65 -0
  236. package/src/tools/dispatch/types.ts +24 -0
  237. package/src/tools/evidencePack.ts +490 -0
  238. package/src/tools/finalizeDirectSession.ts +4 -4
  239. package/src/tools/goalSubgoalTask.ts +2 -2
  240. package/src/tools/healthCheck.ts +3 -3
  241. package/src/tools/listWorkspace.ts +71 -71
  242. package/src/tools/recommendAgentForTask.ts +79 -0
  243. package/src/tools/registry.ts +352 -509
  244. package/src/tools/releaseMode.ts +450 -0
  245. package/src/tools/retryTask.ts +2 -2
  246. package/src/tools/runDirectVerificationBundle.ts +98 -0
  247. package/src/tools/runTaskLoop.ts +526 -0
  248. package/src/tools/runVerification.ts +8 -0
  249. package/src/tools/safeViews.ts +4 -2
  250. package/src/tools/taskLineage.ts +300 -0
  251. package/src/tools/toolCatalog.ts +6 -0
  252. package/src/tools/toolRegistry.ts +132 -0
  253. package/src/version.ts +2 -2
  254. package/ui/pages/audit.html +192 -3
  255. package/ui/pages/dashboard.html +1070 -14
  256. package/ui/pages/direct-sessions.html +505 -53
  257. package/ui/pages/task-detail.html +456 -438
  258. package/ui/pages/tasks.html +598 -61
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Control Center routes — process lifecycle proxy (/api/start-all,
3
+ * /api/stop-all, /api/restart-all, /api/core/*, /api/direct/*).
4
+ *
5
+ * Proxies start/stop/restart actions to scripts/control/manage-patchwarden.ps1
6
+ * and exposes open-logs-folder. Start/restart actions run a preflight that
7
+ * verifies the tunnel-client executable and launcher files exist before
8
+ * launching, so the non-interactive web UI never deadlocks on a missing
9
+ * dependency. All endpoints are POST routes gated by the control token.
10
+ */
11
+ import { existsSync } from "node:fs";
12
+ import { delimiter, extname, join } from "node:path";
13
+ import { homedir } from "node:os";
14
+ import { spawn } from "node:child_process";
15
+ import { recordEvent } from "../runtime.js";
16
+ import { DEFAULT_TUNNEL_CLIENT_EXE, errorMessage, getRuntimeRoot, manageScriptPath, projectRoot, sendJson } from "../shared.js";
17
+ function runManageAction(action, mode) {
18
+ return new Promise((resolveP, rejectP) => {
19
+ let child;
20
+ try {
21
+ child = spawn("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", manageScriptPath, action, mode, "-Background"], { cwd: projectRoot, windowsHide: true });
22
+ }
23
+ catch (err) {
24
+ rejectP(err);
25
+ return;
26
+ }
27
+ let stdout = "";
28
+ let stderr = "";
29
+ let settled = false;
30
+ const timer = setTimeout(() => {
31
+ if (settled)
32
+ return;
33
+ settled = true;
34
+ try {
35
+ child.kill();
36
+ }
37
+ catch { /* ignore */ }
38
+ rejectP(new Error(`manage-patchwarden.ps1 timed out after 60s (action=${action}, mode=${mode})`));
39
+ }, 60_000);
40
+ child.stdout.on("data", (d) => {
41
+ stdout += d.toString("utf-8");
42
+ });
43
+ child.stderr.on("data", (d) => {
44
+ stderr += d.toString("utf-8");
45
+ });
46
+ child.on("error", (err) => {
47
+ if (settled)
48
+ return;
49
+ settled = true;
50
+ clearTimeout(timer);
51
+ rejectP(err);
52
+ });
53
+ child.on("close", (code) => {
54
+ if (settled)
55
+ return;
56
+ settled = true;
57
+ clearTimeout(timer);
58
+ resolveP({ exitCode: code ?? -1, stdout, stderr });
59
+ });
60
+ });
61
+ }
62
+ function selectedControlModes(mode) {
63
+ if (mode === "core" || mode === "direct")
64
+ return [mode];
65
+ return ["core", "direct"];
66
+ }
67
+ function launcherPathForMode(mode) {
68
+ const launcherName = mode === "direct" ? "Start-PatchWarden-Direct-Tunnel.cmd" : "Start-PatchWarden-Tunnel.cmd";
69
+ return join(projectRoot, "scripts", "launchers", launcherName);
70
+ }
71
+ function findExecutableOnPath(fileName) {
72
+ const pathValue = process.env.PATH || "";
73
+ const extensions = process.platform === "win32"
74
+ ? (process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)
75
+ : [""];
76
+ for (const entry of pathValue.split(delimiter)) {
77
+ if (!entry)
78
+ continue;
79
+ const direct = join(entry, fileName);
80
+ if (existsSync(direct))
81
+ return direct;
82
+ if (process.platform === "win32" && !extname(fileName)) {
83
+ for (const ext of extensions) {
84
+ const candidate = join(entry, fileName + ext.toLowerCase());
85
+ if (existsSync(candidate))
86
+ return candidate;
87
+ }
88
+ }
89
+ }
90
+ return null;
91
+ }
92
+ function findTunnelClientExecutable() {
93
+ if (process.env.PATCHWARDEN_CONTROL_FORCE_MISSING_TUNNEL_CLIENT === "1")
94
+ return null;
95
+ const explicit = process.env.TUNNEL_CLIENT_EXE || process.env.PATCHWARDEN_TUNNEL_CLIENT_EXE;
96
+ if (explicit && existsSync(explicit))
97
+ return explicit;
98
+ const fromPath = findExecutableOnPath("tunnel-client.exe") ?? findExecutableOnPath("tunnel-client");
99
+ if (fromPath)
100
+ return fromPath;
101
+ const candidates = [
102
+ DEFAULT_TUNNEL_CLIENT_EXE,
103
+ process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, "patchwarden", "tunnel-client.exe") : null,
104
+ process.env.APPDATA ? join(process.env.APPDATA, "tunnel-client", "tunnel-client.exe") : null,
105
+ join(homedir(), "tunnel-client", "tunnel-client.exe"),
106
+ ].filter((v) => typeof v === "string" && v.length > 0);
107
+ for (const candidate of candidates) {
108
+ if (existsSync(candidate))
109
+ return candidate;
110
+ }
111
+ return null;
112
+ }
113
+ // Re-exported so the status route can surface tunnel-client availability in
114
+ // /api/status without duplicating the discovery logic.
115
+ export { findTunnelClientExecutable };
116
+ function preflightManageAction(action, mode) {
117
+ if (action !== "start" && action !== "restart")
118
+ return null;
119
+ const missingLaunchers = selectedControlModes(mode)
120
+ .map((m) => ({ mode: m, path: launcherPathForMode(m) }))
121
+ .filter((entry) => !existsSync(entry.path));
122
+ const tunnelClient = findTunnelClientExecutable();
123
+ const missing = [];
124
+ if (!tunnelClient)
125
+ missing.push("tunnel-client.exe");
126
+ for (const entry of missingLaunchers)
127
+ missing.push(`${entry.mode} launcher`);
128
+ if (missing.length === 0)
129
+ return null;
130
+ return {
131
+ status: 409,
132
+ body: {
133
+ ok: false,
134
+ action,
135
+ mode,
136
+ error: "Control Center preflight failed. Start/restart from the Web UI is non-interactive, so required runtime dependencies must be available before launching.",
137
+ missing,
138
+ next_steps: [
139
+ "Install tunnel-client.exe or set TUNNEL_CLIENT_EXE / PATCHWARDEN_TUNNEL_CLIENT_EXE to its full path.",
140
+ `Default checked path: ${DEFAULT_TUNNEL_CLIENT_EXE}`,
141
+ "Verify the launcher files under scripts/launchers are present.",
142
+ "Then retry from PatchWarden Control Center or PatchWarden-Control-Tray.cmd.",
143
+ ],
144
+ },
145
+ };
146
+ }
147
+ export async function handleManageAction(res, action, mode) {
148
+ try {
149
+ const preflight = preflightManageAction(action, mode);
150
+ if (preflight) {
151
+ recordEvent("manage." + mode + "." + action + ".preflight_failed", {
152
+ missing: preflight.body.missing,
153
+ });
154
+ sendJson(res, preflight.status, preflight.body);
155
+ return;
156
+ }
157
+ const result = await runManageAction(action, mode);
158
+ recordEvent("manage." + mode + "." + action, {
159
+ exit_code: result.exitCode,
160
+ ok: result.exitCode === 0,
161
+ });
162
+ sendJson(res, 200, {
163
+ ok: result.exitCode === 0,
164
+ exitCode: result.exitCode,
165
+ stdout: result.stdout,
166
+ stderr: result.stderr,
167
+ });
168
+ }
169
+ catch (err) {
170
+ recordEvent("manage." + mode + "." + action + ".failed", { error: errorMessage(err) });
171
+ sendJson(res, 500, { error: errorMessage(err) });
172
+ }
173
+ }
174
+ export function handleOpenLogsFolder(res) {
175
+ try {
176
+ const target = getRuntimeRoot(false);
177
+ let cmd;
178
+ if (process.platform === "win32") {
179
+ cmd = "explorer.exe";
180
+ }
181
+ else if (process.platform === "darwin") {
182
+ cmd = "open";
183
+ }
184
+ else {
185
+ cmd = "xdg-open";
186
+ }
187
+ try {
188
+ const child = spawn(cmd, [target], { detached: true, stdio: "ignore" });
189
+ child.on("error", () => { });
190
+ child.unref();
191
+ }
192
+ catch {
193
+ /* ignore */
194
+ }
195
+ sendJson(res, 200, { ok: true, path: target });
196
+ }
197
+ catch (err) {
198
+ sendJson(res, 500, { error: errorMessage(err) });
199
+ }
200
+ }
@@ -0,0 +1,22 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export declare function handleDirectSessions(res: ServerResponse): void;
3
+ export declare function handleDirectSessionDetail(res: ServerResponse, sessionId: string): void;
4
+ export declare function handleDirectSessionSafeSummary(res: ServerResponse, sessionId: string): void;
5
+ /**
6
+ * Finalize a direct session via safeFinalizeDirectSession. Returns the safe
7
+ * summary on success, or { error } on failure with HTTP 200 (fault tolerance:
8
+ * the UI always gets a JSON body it can render).
9
+ */
10
+ export declare function handleDirectSessionFinalize(res: ServerResponse, sessionId: string): Promise<void>;
11
+ /**
12
+ * Audit a direct session via safeAuditDirectSession. Returns the safe audit
13
+ * result on success, or { error } on failure (HTTP 200 for fault tolerance).
14
+ */
15
+ export declare function handleDirectSessionAudit(res: ServerResponse, sessionId: string): void;
16
+ /**
17
+ * Hide a direct session from the dashboard list. The session itself is NOT
18
+ * deleted or modified — only the control-center's local
19
+ * hidden-direct-session-ids.json state file is updated. Requires control token
20
+ * (enforced by the POST router).
21
+ */
22
+ export declare function handleDirectSessionHide(res: ServerResponse, sessionId: string): void;
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Control Center routes — Direct sessions (/api/direct-sessions/*).
3
+ *
4
+ * Lists, inspects, finalizes, audits, and hides Direct editing sessions. The
5
+ * list/detail endpoints are read-only and fault-tolerant (missing dir returns
6
+ * an empty list, never 500). The finalize/audit/hide endpoints are POST routes
7
+ * gated by the control token in the server router.
8
+ */
9
+ import { existsSync, readdirSync, statSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { getDirectSessionsDir } from "../../config.js";
12
+ import { safeAuditDirectSession, safeDirectSummary, safeFinalizeDirectSession } from "../../tools/safeViews.js";
13
+ import { fileMtimeIso, isValidDirectSessionId, readHiddenDirectSessionIds, recordEvent, writeHiddenDirectSessionIds, } from "../runtime.js";
14
+ import { config, errorMessage, readJsonFileSafe, readTextFileSafe, sendJson } from "../shared.js";
15
+ function readDirectSessionSummary(sessionDir, sessionId) {
16
+ const sessionFile = join(sessionDir, "session.json");
17
+ if (!existsSync(sessionFile))
18
+ return null;
19
+ const data = readJsonFileSafe(sessionFile);
20
+ if (!data)
21
+ return null;
22
+ // summary.json holds the finalized change summary (changed_files_total, etc.)
23
+ const summaryFile = join(sessionDir, "summary.json");
24
+ const summary = readJsonFileSafe(summaryFile);
25
+ const changedFilesTotal = summary
26
+ ? typeof summary.changed_files_total === "number"
27
+ ? summary.changed_files_total
28
+ : null
29
+ : null;
30
+ // audit.json (written by audit_session) holds the audit decision
31
+ const auditFile = join(sessionDir, "audit.json");
32
+ const audit = readJsonFileSafe(auditFile);
33
+ const auditDecision = audit
34
+ ? typeof audit.decision === "string"
35
+ ? audit.decision
36
+ : typeof audit.verdict === "string"
37
+ ? audit.verdict
38
+ : null
39
+ : null;
40
+ const auditCheckedAt = audit
41
+ ? typeof audit.checked_at === "string"
42
+ ? audit.checked_at
43
+ : fileMtimeIso(auditFile)
44
+ : null;
45
+ // verification summary: read from session.json verification_runs (last run)
46
+ let verificationSummary = null;
47
+ if (Array.isArray(data.verification_runs) && data.verification_runs.length > 0) {
48
+ const runs = data.verification_runs;
49
+ verificationSummary = runs[runs.length - 1];
50
+ }
51
+ return {
52
+ session_id: sessionId,
53
+ repo_path: typeof data.repo_path === "string" ? data.repo_path : "",
54
+ resolved_repo_path: typeof data.resolved_repo_path === "string" ? data.resolved_repo_path : "",
55
+ created_at: typeof data.created_at === "string" ? data.created_at : "",
56
+ expires_at: typeof data.expires_at === "string" ? data.expires_at : "",
57
+ finalized: Boolean(data.finalized),
58
+ finalized_at: typeof data.finalized_at === "string" ? data.finalized_at : null,
59
+ audited: Boolean(data.audited),
60
+ changed_files_total: changedFilesTotal,
61
+ verification_summary: verificationSummary,
62
+ audit_decision: auditDecision,
63
+ audit_checked_at: auditCheckedAt,
64
+ title: typeof data.title === "string" ? data.title : "",
65
+ };
66
+ }
67
+ export function handleDirectSessions(res) {
68
+ try {
69
+ const sessionsDir = getDirectSessionsDir(config);
70
+ if (!existsSync(sessionsDir)) {
71
+ // Directory missing -> empty list, never 500.
72
+ sendJson(res, 200, { sessions: [], total: 0, reason: null });
73
+ return;
74
+ }
75
+ let entries = [];
76
+ try {
77
+ entries = readdirSync(sessionsDir, { withFileTypes: true }).filter((e) => e.isDirectory());
78
+ }
79
+ catch (err) {
80
+ sendJson(res, 200, { sessions: [], total: 0, reason: errorMessage(err) });
81
+ return;
82
+ }
83
+ // Filter out sessions hidden from the dashboard via POST .../hide.
84
+ const hiddenIds = new Set(readHiddenDirectSessionIds());
85
+ const summaries = [];
86
+ for (const entry of entries) {
87
+ if (hiddenIds.has(entry.name))
88
+ continue;
89
+ const summary = readDirectSessionSummary(join(sessionsDir, entry.name), entry.name);
90
+ if (summary)
91
+ summaries.push(summary);
92
+ }
93
+ // Sort by created_at descending.
94
+ summaries.sort((a, b) => b.created_at.localeCompare(a.created_at));
95
+ sendJson(res, 200, { sessions: summaries, total: summaries.length, reason: null });
96
+ }
97
+ catch (err) {
98
+ sendJson(res, 200, { sessions: [], total: 0, reason: errorMessage(err) });
99
+ }
100
+ }
101
+ export function handleDirectSessionDetail(res, sessionId) {
102
+ try {
103
+ if (sessionId === "." ||
104
+ sessionId === ".." ||
105
+ sessionId.includes("/") ||
106
+ sessionId.includes("\\") ||
107
+ sessionId.includes("\0")) {
108
+ sendJson(res, 400, { error: "Invalid session id" });
109
+ return;
110
+ }
111
+ const sessionsDir = getDirectSessionsDir(config);
112
+ const sessionDir = join(sessionsDir, sessionId);
113
+ if (!existsSync(sessionDir) || !statSync(sessionDir).isDirectory()) {
114
+ sendJson(res, 404, { error: "Direct session not found" });
115
+ return;
116
+ }
117
+ const summary = readDirectSessionSummary(sessionDir, sessionId);
118
+ sendJson(res, 200, {
119
+ session_id: sessionId,
120
+ summary,
121
+ session: readJsonFileSafe(join(sessionDir, "session.json")),
122
+ summary_md: readTextFileSafe(join(sessionDir, "summary.md")),
123
+ diff_patch: readTextFileSafe(join(sessionDir, "diff.patch")),
124
+ audit_json: readJsonFileSafe(join(sessionDir, "audit.json")),
125
+ audit_md: readTextFileSafe(join(sessionDir, "audit.md")),
126
+ changed_files: readJsonFileSafe(join(sessionDir, "changed-files.json")),
127
+ });
128
+ }
129
+ catch (err) {
130
+ sendJson(res, 200, { session_id: sessionId, error: errorMessage(err) });
131
+ }
132
+ }
133
+ export function handleDirectSessionSafeSummary(res, sessionId) {
134
+ try {
135
+ if (sessionId === "." ||
136
+ sessionId === ".." ||
137
+ sessionId.includes("/") ||
138
+ sessionId.includes("\\") ||
139
+ sessionId.includes("\0")) {
140
+ sendJson(res, 400, { error: "Invalid session id" });
141
+ return;
142
+ }
143
+ sendJson(res, 200, safeDirectSummary(sessionId, { max_items: 12 }));
144
+ }
145
+ catch (err) {
146
+ sendJson(res, 200, {
147
+ session_id: sessionId,
148
+ error: errorMessage(err),
149
+ large_logs_omitted: true,
150
+ diff_omitted: true,
151
+ });
152
+ }
153
+ }
154
+ /**
155
+ * Finalize a direct session via safeFinalizeDirectSession. Returns the safe
156
+ * summary on success, or { error } on failure with HTTP 200 (fault tolerance:
157
+ * the UI always gets a JSON body it can render).
158
+ */
159
+ export async function handleDirectSessionFinalize(res, sessionId) {
160
+ try {
161
+ if (!isValidDirectSessionId(sessionId)) {
162
+ sendJson(res, 400, { error: "Invalid session id" });
163
+ return;
164
+ }
165
+ const result = await safeFinalizeDirectSession(sessionId, { max_items: 12 });
166
+ recordEvent("direct_session.finalized", { session_id: sessionId });
167
+ sendJson(res, 200, result);
168
+ }
169
+ catch (err) {
170
+ sendJson(res, 200, {
171
+ session_id: sessionId,
172
+ error: errorMessage(err),
173
+ large_logs_omitted: true,
174
+ diff_omitted: true,
175
+ });
176
+ }
177
+ }
178
+ /**
179
+ * Audit a direct session via safeAuditDirectSession. Returns the safe audit
180
+ * result on success, or { error } on failure (HTTP 200 for fault tolerance).
181
+ */
182
+ export function handleDirectSessionAudit(res, sessionId) {
183
+ try {
184
+ if (!isValidDirectSessionId(sessionId)) {
185
+ sendJson(res, 400, { error: "Invalid session id" });
186
+ return;
187
+ }
188
+ const result = safeAuditDirectSession(sessionId, { max_items: 12 });
189
+ recordEvent("direct_session.audited", { session_id: sessionId });
190
+ sendJson(res, 200, result);
191
+ }
192
+ catch (err) {
193
+ sendJson(res, 200, {
194
+ session_id: sessionId,
195
+ error: errorMessage(err),
196
+ large_logs_omitted: true,
197
+ diff_omitted: true,
198
+ });
199
+ }
200
+ }
201
+ /**
202
+ * Hide a direct session from the dashboard list. The session itself is NOT
203
+ * deleted or modified — only the control-center's local
204
+ * hidden-direct-session-ids.json state file is updated. Requires control token
205
+ * (enforced by the POST router).
206
+ */
207
+ export function handleDirectSessionHide(res, sessionId) {
208
+ try {
209
+ if (!isValidDirectSessionId(sessionId)) {
210
+ sendJson(res, 400, { error: "Invalid session id" });
211
+ return;
212
+ }
213
+ const ids = readHiddenDirectSessionIds();
214
+ if (!ids.includes(sessionId)) {
215
+ ids.push(sessionId);
216
+ writeHiddenDirectSessionIds(ids);
217
+ }
218
+ recordEvent("direct_session.hidden", { session_id: sessionId });
219
+ sendJson(res, 200, { ok: true, hidden: sessionId });
220
+ }
221
+ catch (err) {
222
+ sendJson(res, 500, { error: errorMessage(err) });
223
+ }
224
+ }
@@ -0,0 +1,6 @@
1
+ import { type ServerResponse } from "node:http";
2
+ export declare function handleStatus(res: ServerResponse): Promise<void>;
3
+ export declare function handleControlCenterStatus(res: ServerResponse): void;
4
+ export declare function handleEvents(res: ServerResponse, limit: number): void;
5
+ export declare function handleTunnelUiUrl(res: ServerResponse): void;
6
+ export declare function handleDiagnostics(res: ServerResponse): void;