mewkit 1.7.0 → 1.8.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.md +76 -0
- package/dist/commands/memory.js +3 -3
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/orchviz.d.ts +12 -0
- package/dist/commands/orchviz.d.ts.map +1 -0
- package/dist/commands/orchviz.js +160 -0
- package/dist/commands/orchviz.js.map +1 -0
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +7 -5
- package/dist/commands/setup.js.map +1 -1
- package/dist/core/skills-dependencies.js +2 -2
- package/dist/core/skills-dependencies.js.map +1 -1
- package/dist/index.js +50 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate/__tests__/skill-id-utils.test.d.ts +2 -0
- package/dist/migrate/__tests__/skill-id-utils.test.d.ts.map +1 -0
- package/dist/migrate/__tests__/skill-id-utils.test.js +78 -0
- package/dist/migrate/__tests__/skill-id-utils.test.js.map +1 -0
- package/dist/migrate/discovery/skill-id-utils.d.ts +18 -0
- package/dist/migrate/discovery/skill-id-utils.d.ts.map +1 -0
- package/dist/migrate/discovery/skill-id-utils.js +45 -0
- package/dist/migrate/discovery/skill-id-utils.js.map +1 -0
- package/dist/migrate/discovery/skills-discovery.d.ts.map +1 -1
- package/dist/migrate/discovery/skills-discovery.js +5 -1
- package/dist/migrate/discovery/skills-discovery.js.map +1 -1
- package/dist/migrate/reconcile/portable-registry.d.ts +12 -12
- package/dist/migrate/types.d.ts +8 -6
- package/dist/migrate/types.d.ts.map +1 -1
- package/dist/migrate/types.js.map +1 -1
- package/dist/orchviz/constants.d.ts +53 -0
- package/dist/orchviz/constants.d.ts.map +1 -0
- package/dist/orchviz/constants.js +68 -0
- package/dist/orchviz/constants.js.map +1 -0
- package/dist/orchviz/fs-utils.d.ts +24 -0
- package/dist/orchviz/fs-utils.d.ts.map +1 -0
- package/dist/orchviz/fs-utils.js +49 -0
- package/dist/orchviz/fs-utils.js.map +1 -0
- package/dist/orchviz/index.d.ts +29 -0
- package/dist/orchviz/index.d.ts.map +1 -0
- package/dist/orchviz/index.js +28 -0
- package/dist/orchviz/index.js.map +1 -0
- package/dist/orchviz/log-persister.d.ts +22 -0
- package/dist/orchviz/log-persister.d.ts.map +1 -0
- package/dist/orchviz/log-persister.js +96 -0
- package/dist/orchviz/log-persister.js.map +1 -0
- package/dist/orchviz/logger.d.ts +13 -0
- package/dist/orchviz/logger.d.ts.map +1 -0
- package/dist/orchviz/logger.js +47 -0
- package/dist/orchviz/logger.js.map +1 -0
- package/dist/orchviz/open-url.d.ts +11 -0
- package/dist/orchviz/open-url.d.ts.map +1 -0
- package/dist/orchviz/open-url.js +28 -0
- package/dist/orchviz/open-url.js.map +1 -0
- package/dist/orchviz/overlay/collector.d.ts +29 -0
- package/dist/orchviz/overlay/collector.d.ts.map +1 -0
- package/dist/orchviz/overlay/collector.js +38 -0
- package/dist/orchviz/overlay/collector.js.map +1 -0
- package/dist/orchviz/overlay/gate-readers.d.ts +18 -0
- package/dist/orchviz/overlay/gate-readers.d.ts.map +1 -0
- package/dist/orchviz/overlay/gate-readers.js +111 -0
- package/dist/orchviz/overlay/gate-readers.js.map +1 -0
- package/dist/orchviz/overlay/session-state-readers.d.ts +18 -0
- package/dist/orchviz/overlay/session-state-readers.d.ts.map +1 -0
- package/dist/orchviz/overlay/session-state-readers.js +78 -0
- package/dist/orchviz/overlay/session-state-readers.js.map +1 -0
- package/dist/orchviz/parser/handle-progress.d.ts +9 -0
- package/dist/orchviz/parser/handle-progress.d.ts.map +1 -0
- package/dist/orchviz/parser/handle-progress.js +38 -0
- package/dist/orchviz/parser/handle-progress.js.map +1 -0
- package/dist/orchviz/parser/handle-text.d.ts +10 -0
- package/dist/orchviz/parser/handle-text.d.ts.map +1 -0
- package/dist/orchviz/parser/handle-text.js +38 -0
- package/dist/orchviz/parser/handle-text.js.map +1 -0
- package/dist/orchviz/parser/handle-thinking.d.ts +10 -0
- package/dist/orchviz/parser/handle-thinking.d.ts.map +1 -0
- package/dist/orchviz/parser/handle-thinking.js +35 -0
- package/dist/orchviz/parser/handle-thinking.js.map +1 -0
- package/dist/orchviz/parser/handle-tool-result.d.ts +10 -0
- package/dist/orchviz/parser/handle-tool-result.d.ts.map +1 -0
- package/dist/orchviz/parser/handle-tool-result.js +62 -0
- package/dist/orchviz/parser/handle-tool-result.js.map +1 -0
- package/dist/orchviz/parser/handle-tool-use.d.ts +11 -0
- package/dist/orchviz/parser/handle-tool-use.d.ts.map +1 -0
- package/dist/orchviz/parser/handle-tool-use.js +38 -0
- package/dist/orchviz/parser/handle-tool-use.js.map +1 -0
- package/dist/orchviz/parser/index.d.ts +38 -0
- package/dist/orchviz/parser/index.d.ts.map +1 -0
- package/dist/orchviz/parser/index.js +139 -0
- package/dist/orchviz/parser/index.js.map +1 -0
- package/dist/orchviz/parser/label-helpers.d.ts +9 -0
- package/dist/orchviz/parser/label-helpers.d.ts.map +1 -0
- package/dist/orchviz/parser/label-helpers.js +44 -0
- package/dist/orchviz/parser/label-helpers.js.map +1 -0
- package/dist/orchviz/parser/strip-ansi.d.ts +6 -0
- package/dist/orchviz/parser/strip-ansi.d.ts.map +1 -0
- package/dist/orchviz/parser/strip-ansi.js +14 -0
- package/dist/orchviz/parser/strip-ansi.js.map +1 -0
- package/dist/orchviz/parser/utils.d.ts +20 -0
- package/dist/orchviz/parser/utils.d.ts.map +1 -0
- package/dist/orchviz/parser/utils.js +56 -0
- package/dist/orchviz/parser/utils.js.map +1 -0
- package/dist/orchviz/permission-detection.d.ts +18 -0
- package/dist/orchviz/permission-detection.d.ts.map +1 -0
- package/dist/orchviz/permission-detection.js +50 -0
- package/dist/orchviz/permission-detection.js.map +1 -0
- package/dist/orchviz/plan/__tests__/apply-todo-toggle.test.d.ts +15 -0
- package/dist/orchviz/plan/__tests__/apply-todo-toggle.test.d.ts.map +1 -0
- package/dist/orchviz/plan/__tests__/apply-todo-toggle.test.js +165 -0
- package/dist/orchviz/plan/__tests__/apply-todo-toggle.test.js.map +1 -0
- package/dist/orchviz/plan/__tests__/atomic-write.test.d.ts +11 -0
- package/dist/orchviz/plan/__tests__/atomic-write.test.d.ts.map +1 -0
- package/dist/orchviz/plan/__tests__/atomic-write.test.js +89 -0
- package/dist/orchviz/plan/__tests__/atomic-write.test.js.map +1 -0
- package/dist/orchviz/plan/__tests__/end-to-end.test.d.ts +10 -0
- package/dist/orchviz/plan/__tests__/end-to-end.test.d.ts.map +1 -0
- package/dist/orchviz/plan/__tests__/end-to-end.test.js +132 -0
- package/dist/orchviz/plan/__tests__/end-to-end.test.js.map +1 -0
- package/dist/orchviz/plan/__tests__/etag.test.d.ts +11 -0
- package/dist/orchviz/plan/__tests__/etag.test.d.ts.map +1 -0
- package/dist/orchviz/plan/__tests__/etag.test.js +92 -0
- package/dist/orchviz/plan/__tests__/etag.test.js.map +1 -0
- package/dist/orchviz/plan/__tests__/list-plans.test.d.ts +14 -0
- package/dist/orchviz/plan/__tests__/list-plans.test.d.ts.map +1 -0
- package/dist/orchviz/plan/__tests__/list-plans.test.js +154 -0
- package/dist/orchviz/plan/__tests__/list-plans.test.js.map +1 -0
- package/dist/orchviz/plan/apply-todo-toggle.d.ts +29 -0
- package/dist/orchviz/plan/apply-todo-toggle.d.ts.map +1 -0
- package/dist/orchviz/plan/apply-todo-toggle.js +129 -0
- package/dist/orchviz/plan/apply-todo-toggle.js.map +1 -0
- package/dist/orchviz/plan/atomic-write.d.ts +25 -0
- package/dist/orchviz/plan/atomic-write.d.ts.map +1 -0
- package/dist/orchviz/plan/atomic-write.js +85 -0
- package/dist/orchviz/plan/atomic-write.js.map +1 -0
- package/dist/orchviz/plan/collector.d.ts +49 -0
- package/dist/orchviz/plan/collector.d.ts.map +1 -0
- package/dist/orchviz/plan/collector.js +173 -0
- package/dist/orchviz/plan/collector.js.map +1 -0
- package/dist/orchviz/plan/etag.d.ts +24 -0
- package/dist/orchviz/plan/etag.d.ts.map +1 -0
- package/dist/orchviz/plan/etag.js +58 -0
- package/dist/orchviz/plan/etag.js.map +1 -0
- package/dist/orchviz/plan/find-active-plan.d.ts +9 -0
- package/dist/orchviz/plan/find-active-plan.d.ts.map +1 -0
- package/dist/orchviz/plan/find-active-plan.js +90 -0
- package/dist/orchviz/plan/find-active-plan.js.map +1 -0
- package/dist/orchviz/plan/index.d.ts +23 -0
- package/dist/orchviz/plan/index.d.ts.map +1 -0
- package/dist/orchviz/plan/index.js +91 -0
- package/dist/orchviz/plan/index.js.map +1 -0
- package/dist/orchviz/plan/list-plans.d.ts +19 -0
- package/dist/orchviz/plan/list-plans.d.ts.map +1 -0
- package/dist/orchviz/plan/list-plans.js +148 -0
- package/dist/orchviz/plan/list-plans.js.map +1 -0
- package/dist/orchviz/plan/parse-phase-file.d.ts +13 -0
- package/dist/orchviz/plan/parse-phase-file.d.ts.map +1 -0
- package/dist/orchviz/plan/parse-phase-file.js +136 -0
- package/dist/orchviz/plan/parse-phase-file.js.map +1 -0
- package/dist/orchviz/plan/parse-plan-file.d.ts +19 -0
- package/dist/orchviz/plan/parse-plan-file.d.ts.map +1 -0
- package/dist/orchviz/plan/parse-plan-file.js +62 -0
- package/dist/orchviz/plan/parse-plan-file.js.map +1 -0
- package/dist/orchviz/plan/plan-constants.d.ts +17 -0
- package/dist/orchviz/plan/plan-constants.d.ts.map +1 -0
- package/dist/orchviz/plan/plan-constants.js +19 -0
- package/dist/orchviz/plan/plan-constants.js.map +1 -0
- package/dist/orchviz/plan/types.d.ts +46 -0
- package/dist/orchviz/plan/types.d.ts.map +1 -0
- package/dist/orchviz/plan/types.js +6 -0
- package/dist/orchviz/plan/types.js.map +1 -0
- package/dist/orchviz/protocol.d.ts +116 -0
- package/dist/orchviz/protocol.d.ts.map +1 -0
- package/dist/orchviz/protocol.js +23 -0
- package/dist/orchviz/protocol.js.map +1 -0
- package/dist/orchviz/redact.d.ts +10 -0
- package/dist/orchviz/redact.d.ts.map +1 -0
- package/dist/orchviz/redact.js +38 -0
- package/dist/orchviz/redact.js.map +1 -0
- package/dist/orchviz/sanitize.d.ts +11 -0
- package/dist/orchviz/sanitize.d.ts.map +1 -0
- package/dist/orchviz/sanitize.js +38 -0
- package/dist/orchviz/sanitize.js.map +1 -0
- package/dist/orchviz/server/__tests__/security-smoke.test.d.ts +13 -0
- package/dist/orchviz/server/__tests__/security-smoke.test.d.ts.map +1 -0
- package/dist/orchviz/server/__tests__/security-smoke.test.js +207 -0
- package/dist/orchviz/server/__tests__/security-smoke.test.js.map +1 -0
- package/dist/orchviz/server/__tests__/write-handlers.test.d.ts +24 -0
- package/dist/orchviz/server/__tests__/write-handlers.test.d.ts.map +1 -0
- package/dist/orchviz/server/__tests__/write-handlers.test.js +347 -0
- package/dist/orchviz/server/__tests__/write-handlers.test.js.map +1 -0
- package/dist/orchviz/server/api-handlers.d.ts +55 -0
- package/dist/orchviz/server/api-handlers.d.ts.map +1 -0
- package/dist/orchviz/server/api-handlers.js +112 -0
- package/dist/orchviz/server/api-handlers.js.map +1 -0
- package/dist/orchviz/server/index.d.ts +36 -0
- package/dist/orchviz/server/index.d.ts.map +1 -0
- package/dist/orchviz/server/index.js +147 -0
- package/dist/orchviz/server/index.js.map +1 -0
- package/dist/orchviz/server/sse-handler.d.ts +25 -0
- package/dist/orchviz/server/sse-handler.d.ts.map +1 -0
- package/dist/orchviz/server/sse-handler.js +128 -0
- package/dist/orchviz/server/sse-handler.js.map +1 -0
- package/dist/orchviz/server/static-handler.d.ts +9 -0
- package/dist/orchviz/server/static-handler.d.ts.map +1 -0
- package/dist/orchviz/server/static-handler.js +76 -0
- package/dist/orchviz/server/static-handler.js.map +1 -0
- package/dist/orchviz/server/write-handlers.d.ts +20 -0
- package/dist/orchviz/server/write-handlers.d.ts.map +1 -0
- package/dist/orchviz/server/write-handlers.js +167 -0
- package/dist/orchviz/server/write-handlers.js.map +1 -0
- package/dist/orchviz/server/write-utils.d.ts +59 -0
- package/dist/orchviz/server/write-utils.d.ts.map +1 -0
- package/dist/orchviz/server/write-utils.js +161 -0
- package/dist/orchviz/server/write-utils.js.map +1 -0
- package/dist/orchviz/session-discovery.d.ts +19 -0
- package/dist/orchviz/session-discovery.d.ts.map +1 -0
- package/dist/orchviz/session-discovery.js +104 -0
- package/dist/orchviz/session-discovery.js.map +1 -0
- package/dist/orchviz/session-manager.d.ts +17 -0
- package/dist/orchviz/session-manager.d.ts.map +1 -0
- package/dist/orchviz/session-manager.js +62 -0
- package/dist/orchviz/session-manager.js.map +1 -0
- package/dist/orchviz/session-runtime.d.ts +22 -0
- package/dist/orchviz/session-runtime.d.ts.map +1 -0
- package/dist/orchviz/session-runtime.js +135 -0
- package/dist/orchviz/session-runtime.js.map +1 -0
- package/dist/orchviz/session-watcher.d.ts +33 -0
- package/dist/orchviz/session-watcher.d.ts.map +1 -0
- package/dist/orchviz/session-watcher.js +135 -0
- package/dist/orchviz/session-watcher.js.map +1 -0
- package/dist/orchviz/subagent-meta.d.ts +8 -0
- package/dist/orchviz/subagent-meta.d.ts.map +1 -0
- package/dist/orchviz/subagent-meta.js +28 -0
- package/dist/orchviz/subagent-meta.js.map +1 -0
- package/dist/orchviz/subagent-scanner.d.ts +15 -0
- package/dist/orchviz/subagent-scanner.d.ts.map +1 -0
- package/dist/orchviz/subagent-scanner.js +27 -0
- package/dist/orchviz/subagent-scanner.js.map +1 -0
- package/dist/orchviz/subagent-watcher.d.ts +19 -0
- package/dist/orchviz/subagent-watcher.d.ts.map +1 -0
- package/dist/orchviz/subagent-watcher.js +147 -0
- package/dist/orchviz/subagent-watcher.js.map +1 -0
- package/dist/orchviz/test-server-boot.d.ts +28 -0
- package/dist/orchviz/test-server-boot.d.ts.map +1 -0
- package/dist/orchviz/test-server-boot.js +64 -0
- package/dist/orchviz/test-server-boot.js.map +1 -0
- package/dist/orchviz/token-estimator.d.ts +11 -0
- package/dist/orchviz/token-estimator.d.ts.map +1 -0
- package/dist/orchviz/token-estimator.js +38 -0
- package/dist/orchviz/token-estimator.js.map +1 -0
- package/dist/orchviz/tool-input-data.d.ts +11 -0
- package/dist/orchviz/tool-input-data.d.ts.map +1 -0
- package/dist/orchviz/tool-input-data.js +76 -0
- package/dist/orchviz/tool-input-data.js.map +1 -0
- package/dist/orchviz/tool-summarizer.d.ts +16 -0
- package/dist/orchviz/tool-summarizer.d.ts.map +1 -0
- package/dist/orchviz/tool-summarizer.js +131 -0
- package/dist/orchviz/tool-summarizer.js.map +1 -0
- package/dist/orchviz-web/index.css +1 -0
- package/dist/orchviz-web/index.html +18 -0
- package/dist/orchviz-web/index.js +51 -0
- package/package.json +18 -3
- package/dist/logger.d.ts +0 -26
- package/dist/logger.js +0 -70
- package/dist/logger.js.map +0 -1
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for write-handlers.ts — POST /api/plan/todo.
|
|
3
|
+
*
|
|
4
|
+
* Each test spins up an ephemeral-port OrchvizServer with a temp fixture dir,
|
|
5
|
+
* makes HTTP requests, and verifies responses + on-disk state.
|
|
6
|
+
*
|
|
7
|
+
* Coverage (10+ cases):
|
|
8
|
+
* 1. 200 happy path — file mutated, ok:true, changed:true, new etag
|
|
9
|
+
* 2. 200 idempotent no-op — changed:false, mtime unchanged
|
|
10
|
+
* 3. 400 missing Origin header
|
|
11
|
+
* 4. 403 disallowed Origin header
|
|
12
|
+
* 5. 405 MEOWKIT_ORCHVIZ_READONLY=1
|
|
13
|
+
* 6. 409 stale etag — file unchanged
|
|
14
|
+
* 7. 413 body > 4KB
|
|
15
|
+
* 8. 415 wrong Content-Type
|
|
16
|
+
* 9. OPTIONS 204 — allowed origin
|
|
17
|
+
* 10. OPTIONS 403 — disallowed origin (no ACAO header emitted)
|
|
18
|
+
* 11. 400 invalid JSON
|
|
19
|
+
* 12. 400 zod validation failure (bad phase number)
|
|
20
|
+
* 13. POST to /api/plan (unrelated path) still returns 405 (v1.1 regression guard)
|
|
21
|
+
* 14. Phase zero-pad: POST {phase:1} matches phase-01-*.md
|
|
22
|
+
*/
|
|
23
|
+
import { describe, expect, it, afterEach, beforeEach } from "vitest";
|
|
24
|
+
import { EventEmitter } from "node:events";
|
|
25
|
+
import * as fs from "node:fs";
|
|
26
|
+
import * as os from "node:os";
|
|
27
|
+
import * as path from "node:path";
|
|
28
|
+
import * as http from "node:http";
|
|
29
|
+
import { OrchvizServer } from "../index.js";
|
|
30
|
+
import { computePhaseFileEtag } from "../../plan/etag.js";
|
|
31
|
+
import { PlanCollector } from "../../plan/collector.js";
|
|
32
|
+
let active = null;
|
|
33
|
+
let tmpDir = null;
|
|
34
|
+
let serverUrl = "";
|
|
35
|
+
let serverPort = 0;
|
|
36
|
+
// ── Fixture helpers ─────────────────────────────────────────────────────────
|
|
37
|
+
function makeProjectRoot() {
|
|
38
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "write-handler-test-"));
|
|
39
|
+
const root = tmpDir;
|
|
40
|
+
const slug = "260501-test-plan";
|
|
41
|
+
const planDir = path.join(root, "tasks", "plans", slug);
|
|
42
|
+
fs.mkdirSync(planDir, { recursive: true });
|
|
43
|
+
const phaseContent = [
|
|
44
|
+
"# Phase 1",
|
|
45
|
+
"",
|
|
46
|
+
"## Todo List",
|
|
47
|
+
"",
|
|
48
|
+
"- [ ] Task A",
|
|
49
|
+
"- [x] Task B",
|
|
50
|
+
"- [ ] Task C",
|
|
51
|
+
"",
|
|
52
|
+
"## Next Steps",
|
|
53
|
+
"",
|
|
54
|
+
].join("\n");
|
|
55
|
+
const phaseFile = path.join(planDir, "phase-01-setup.md");
|
|
56
|
+
fs.writeFileSync(phaseFile, phaseContent, "utf-8");
|
|
57
|
+
// plan.md stub
|
|
58
|
+
fs.writeFileSync(path.join(planDir, "plan.md"), "---\ntitle: Test\n---\n# Test\n", "utf-8");
|
|
59
|
+
return { root, planDir, phaseFile };
|
|
60
|
+
}
|
|
61
|
+
async function startServer(projectRoot) {
|
|
62
|
+
const staticDir = os.tmpdir();
|
|
63
|
+
const planCollector = new PlanCollector(projectRoot);
|
|
64
|
+
const ev = new EventEmitter();
|
|
65
|
+
active = new OrchvizServer({
|
|
66
|
+
staticDir,
|
|
67
|
+
eventSource: ev,
|
|
68
|
+
projectRoot,
|
|
69
|
+
planCollector,
|
|
70
|
+
});
|
|
71
|
+
serverUrl = await active.start();
|
|
72
|
+
serverPort = active.port;
|
|
73
|
+
}
|
|
74
|
+
function rawRequest(opts) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const u = new URL(serverUrl);
|
|
77
|
+
const reqOpts = {
|
|
78
|
+
host: u.hostname,
|
|
79
|
+
port: Number(u.port),
|
|
80
|
+
path: opts.path,
|
|
81
|
+
method: opts.method,
|
|
82
|
+
headers: {
|
|
83
|
+
Host: `${u.hostname}:${u.port}`,
|
|
84
|
+
...opts.headers,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
const req = http.request(reqOpts, (res) => {
|
|
88
|
+
const resHeaders = {};
|
|
89
|
+
for (const [k, v] of Object.entries(res.headers)) {
|
|
90
|
+
if (typeof v === "string")
|
|
91
|
+
resHeaders[k] = v;
|
|
92
|
+
}
|
|
93
|
+
let body = "";
|
|
94
|
+
res.setEncoding("utf-8");
|
|
95
|
+
res.on("data", (chunk) => { body += chunk; });
|
|
96
|
+
res.on("end", () => resolve({ status: res.statusCode ?? 0, headers: resHeaders, body }));
|
|
97
|
+
res.on("error", reject);
|
|
98
|
+
});
|
|
99
|
+
req.on("error", reject);
|
|
100
|
+
if (opts.body)
|
|
101
|
+
req.write(opts.body);
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function postTodo(body, extraHeaders = {}) {
|
|
106
|
+
const bodyStr = JSON.stringify(body);
|
|
107
|
+
return rawRequest({
|
|
108
|
+
method: "POST",
|
|
109
|
+
path: "/api/plan/todo",
|
|
110
|
+
headers: {
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
"Origin": `http://127.0.0.1:${serverPort}`,
|
|
113
|
+
...extraHeaders,
|
|
114
|
+
},
|
|
115
|
+
body: bodyStr,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function validBody(phaseFile, overrides = {}) {
|
|
119
|
+
return {
|
|
120
|
+
slug: "260501-test-plan",
|
|
121
|
+
phase: 1,
|
|
122
|
+
todoIdx: 0,
|
|
123
|
+
checked: true,
|
|
124
|
+
etag: computePhaseFileEtag(phaseFile),
|
|
125
|
+
...overrides,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
// ── Setup/teardown ──────────────────────────────────────────────────────────
|
|
129
|
+
let projectRoot = "";
|
|
130
|
+
let phaseFile = "";
|
|
131
|
+
beforeEach(async () => {
|
|
132
|
+
delete process.env.MEOWKIT_ORCHVIZ_READONLY;
|
|
133
|
+
const fixture = makeProjectRoot();
|
|
134
|
+
projectRoot = fixture.root;
|
|
135
|
+
phaseFile = fixture.phaseFile;
|
|
136
|
+
await startServer(projectRoot);
|
|
137
|
+
});
|
|
138
|
+
afterEach(async () => {
|
|
139
|
+
delete process.env.MEOWKIT_ORCHVIZ_READONLY;
|
|
140
|
+
if (active) {
|
|
141
|
+
await active.stop();
|
|
142
|
+
active = null;
|
|
143
|
+
}
|
|
144
|
+
if (tmpDir) {
|
|
145
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
146
|
+
tmpDir = null;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// ── Tests ───────────────────────────────────────────────────────────────────
|
|
150
|
+
describe("POST /api/plan/todo", () => {
|
|
151
|
+
it("1. 200 happy path: file mutated, changed:true, new etag returned", async () => {
|
|
152
|
+
const beforeEtag = computePhaseFileEtag(phaseFile);
|
|
153
|
+
const r = await postTodo(validBody(phaseFile, { todoIdx: 0, checked: true }));
|
|
154
|
+
expect(r.status).toBe(200);
|
|
155
|
+
const parsed = JSON.parse(r.body);
|
|
156
|
+
expect(parsed.ok).toBe(true);
|
|
157
|
+
expect(parsed.changed).toBe(true);
|
|
158
|
+
expect(parsed.etag).toHaveLength(64);
|
|
159
|
+
expect(parsed.etag).not.toBe(beforeEtag);
|
|
160
|
+
// File on disk is mutated
|
|
161
|
+
const newContent = fs.readFileSync(phaseFile, "utf-8");
|
|
162
|
+
expect(newContent).toContain("- [x] Task A");
|
|
163
|
+
});
|
|
164
|
+
it("2. 200 idempotent: already-checked flip → changed:false, mtime unchanged", async () => {
|
|
165
|
+
// Task B (idx=1) is already [x]
|
|
166
|
+
const mtime = fs.statSync(phaseFile).mtimeMs;
|
|
167
|
+
const r = await postTodo(validBody(phaseFile, { todoIdx: 1, checked: true }));
|
|
168
|
+
expect(r.status).toBe(200);
|
|
169
|
+
const parsed = JSON.parse(r.body);
|
|
170
|
+
expect(parsed.ok).toBe(true);
|
|
171
|
+
expect(parsed.changed).toBe(false);
|
|
172
|
+
// mtime unchanged (no write occurred)
|
|
173
|
+
const newMtime = fs.statSync(phaseFile).mtimeMs;
|
|
174
|
+
expect(newMtime).toBe(mtime);
|
|
175
|
+
});
|
|
176
|
+
it("3. 403 missing Origin header", async () => {
|
|
177
|
+
const r = await rawRequest({
|
|
178
|
+
method: "POST",
|
|
179
|
+
path: "/api/plan/todo",
|
|
180
|
+
headers: {
|
|
181
|
+
"Content-Type": "application/json",
|
|
182
|
+
// No Origin header
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify(validBody(phaseFile)),
|
|
185
|
+
});
|
|
186
|
+
expect(r.status).toBe(403);
|
|
187
|
+
});
|
|
188
|
+
it("4. 403 disallowed Origin header", async () => {
|
|
189
|
+
const r = await postTodo(validBody(phaseFile), {
|
|
190
|
+
"Origin": "http://evil.example.com",
|
|
191
|
+
});
|
|
192
|
+
expect(r.status).toBe(403);
|
|
193
|
+
});
|
|
194
|
+
it("5. 405 MEOWKIT_ORCHVIZ_READONLY=1 blocks writes", async () => {
|
|
195
|
+
process.env.MEOWKIT_ORCHVIZ_READONLY = "1";
|
|
196
|
+
const r = await postTodo(validBody(phaseFile));
|
|
197
|
+
expect(r.status).toBe(405);
|
|
198
|
+
const parsed = JSON.parse(r.body);
|
|
199
|
+
expect(parsed.error).toBe("readonly");
|
|
200
|
+
});
|
|
201
|
+
it("6. 409 stale etag: file unchanged", async () => {
|
|
202
|
+
const staleEtag = "a".repeat(64); // not the real etag
|
|
203
|
+
const mtime = fs.statSync(phaseFile).mtimeMs;
|
|
204
|
+
const r = await postTodo(validBody(phaseFile, { etag: staleEtag }));
|
|
205
|
+
expect(r.status).toBe(409);
|
|
206
|
+
const parsed = JSON.parse(r.body);
|
|
207
|
+
expect(parsed.error).toBe("stale");
|
|
208
|
+
expect(parsed.currentEtag).toHaveLength(64);
|
|
209
|
+
// File unchanged
|
|
210
|
+
expect(fs.statSync(phaseFile).mtimeMs).toBe(mtime);
|
|
211
|
+
});
|
|
212
|
+
it("7. 413 body > 4KB — server destroys socket on overflow", async () => {
|
|
213
|
+
// Build a body > 4096 bytes
|
|
214
|
+
const bigPad = "x".repeat(4200);
|
|
215
|
+
const bodyStr = JSON.stringify({
|
|
216
|
+
slug: "260501-test-plan",
|
|
217
|
+
phase: 1,
|
|
218
|
+
todoIdx: 0,
|
|
219
|
+
checked: true,
|
|
220
|
+
etag: "a".repeat(64),
|
|
221
|
+
_pad: bigPad,
|
|
222
|
+
});
|
|
223
|
+
expect(bodyStr.length).toBeGreaterThan(4096);
|
|
224
|
+
// The server destroys the socket on overflow — rawRequest will either get
|
|
225
|
+
// a 413 response (if response was sent before destroy) or an ECONNRESET.
|
|
226
|
+
// Both are valid behaviors for the cap; we just verify the connection closes.
|
|
227
|
+
const status = await new Promise((resolve) => {
|
|
228
|
+
const u = new URL(serverUrl);
|
|
229
|
+
const req = http.request({
|
|
230
|
+
host: u.hostname,
|
|
231
|
+
port: Number(u.port),
|
|
232
|
+
path: "/api/plan/todo",
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: {
|
|
235
|
+
Host: `${u.hostname}:${u.port}`,
|
|
236
|
+
"Content-Type": "application/json",
|
|
237
|
+
"Origin": `http://127.0.0.1:${serverPort}`,
|
|
238
|
+
"Content-Length": String(Buffer.byteLength(bodyStr)),
|
|
239
|
+
},
|
|
240
|
+
}, (res) => {
|
|
241
|
+
// Got a response — either 413 or something else
|
|
242
|
+
resolve(res.statusCode ?? 0);
|
|
243
|
+
res.resume();
|
|
244
|
+
});
|
|
245
|
+
req.on("error", (err) => {
|
|
246
|
+
const code = err.code;
|
|
247
|
+
// ECONNRESET = server destroyed socket (expected on overflow)
|
|
248
|
+
if (code === "ECONNRESET" || code === "EPIPE") {
|
|
249
|
+
resolve(413); // treat as 413 for test assertion
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
resolve(0);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
req.write(bodyStr);
|
|
256
|
+
req.end();
|
|
257
|
+
});
|
|
258
|
+
expect(status).toBe(413);
|
|
259
|
+
});
|
|
260
|
+
it("8. 415 wrong Content-Type", async () => {
|
|
261
|
+
const r = await rawRequest({
|
|
262
|
+
method: "POST",
|
|
263
|
+
path: "/api/plan/todo",
|
|
264
|
+
headers: {
|
|
265
|
+
"Content-Type": "text/plain",
|
|
266
|
+
"Origin": `http://127.0.0.1:${serverPort}`,
|
|
267
|
+
},
|
|
268
|
+
body: JSON.stringify(validBody(phaseFile)),
|
|
269
|
+
});
|
|
270
|
+
expect(r.status).toBe(415);
|
|
271
|
+
});
|
|
272
|
+
it("11. 400 invalid JSON", async () => {
|
|
273
|
+
const r = await rawRequest({
|
|
274
|
+
method: "POST",
|
|
275
|
+
path: "/api/plan/todo",
|
|
276
|
+
headers: {
|
|
277
|
+
"Content-Type": "application/json",
|
|
278
|
+
"Origin": `http://127.0.0.1:${serverPort}`,
|
|
279
|
+
},
|
|
280
|
+
body: "not valid json {",
|
|
281
|
+
});
|
|
282
|
+
expect(r.status).toBe(400);
|
|
283
|
+
const parsed = JSON.parse(r.body);
|
|
284
|
+
expect(parsed.error).toBe("invalid-json");
|
|
285
|
+
});
|
|
286
|
+
it("12. 400 zod validation failure (phase out of range)", async () => {
|
|
287
|
+
const r = await postTodo({ ...validBody(phaseFile), phase: 200 }); // max is 99
|
|
288
|
+
expect(r.status).toBe(400);
|
|
289
|
+
const parsed = JSON.parse(r.body);
|
|
290
|
+
expect(parsed.error).toBe("validation-failed");
|
|
291
|
+
});
|
|
292
|
+
it("13. POST to /api/plan still returns 405 (v1.1 regression guard)", async () => {
|
|
293
|
+
const r = await rawRequest({
|
|
294
|
+
method: "POST",
|
|
295
|
+
path: "/api/plan",
|
|
296
|
+
headers: { "Content-Type": "application/json" },
|
|
297
|
+
body: "{}",
|
|
298
|
+
});
|
|
299
|
+
expect(r.status).toBe(405);
|
|
300
|
+
});
|
|
301
|
+
it("14. Phase zero-pad: POST {phase:1} matches phase-01-setup.md (not phase-10-*)", async () => {
|
|
302
|
+
// Verify: phase:1 unambiguously matches phase-01-setup.md.
|
|
303
|
+
// phaseFile IS phase-01-setup.md, so the etag is fresh → expect 200.
|
|
304
|
+
const body = validBody(phaseFile, { phase: 1 });
|
|
305
|
+
const r = await postTodo(body);
|
|
306
|
+
const parsed = JSON.parse(r.body);
|
|
307
|
+
// Must NOT be ambiguous-phase error (phase-01-setup.md is the only match)
|
|
308
|
+
expect(parsed.error).not.toBe("ambiguous-phase");
|
|
309
|
+
// Must succeed: phase:1 → regex /^phase-0*1-.*\.md$/i matches "phase-01-setup.md"
|
|
310
|
+
// and does NOT match "phase-10-*.md" (zero-pad correct)
|
|
311
|
+
expect(r.status).toBe(200);
|
|
312
|
+
// Verify the matched file ends with phase-01-setup.md (not phase-1-anything or phase-10-*)
|
|
313
|
+
const updatedContent = fs.readFileSync(phaseFile, "utf-8");
|
|
314
|
+
// File content changed means phase-01-setup.md was correctly targeted
|
|
315
|
+
expect(updatedContent).toContain("- [x] Task A");
|
|
316
|
+
expect(phaseFile.endsWith("phase-01-setup.md")).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
describe("OPTIONS /api/plan/todo", () => {
|
|
320
|
+
it("9. OPTIONS 204 with allowed origin — emits ACAO header", async () => {
|
|
321
|
+
const r = await rawRequest({
|
|
322
|
+
method: "OPTIONS",
|
|
323
|
+
path: "/api/plan/todo",
|
|
324
|
+
headers: {
|
|
325
|
+
"Origin": `http://127.0.0.1:${serverPort}`,
|
|
326
|
+
"Access-Control-Request-Method": "POST",
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
expect(r.status).toBe(204);
|
|
330
|
+
expect(r.headers["access-control-allow-origin"]).toBe(`http://127.0.0.1:${serverPort}`);
|
|
331
|
+
expect(r.headers["access-control-allow-methods"]).toContain("POST");
|
|
332
|
+
});
|
|
333
|
+
it("10. OPTIONS 403 with disallowed origin — NO ACAO header emitted", async () => {
|
|
334
|
+
const r = await rawRequest({
|
|
335
|
+
method: "OPTIONS",
|
|
336
|
+
path: "/api/plan/todo",
|
|
337
|
+
headers: {
|
|
338
|
+
"Origin": "http://evil.example.com",
|
|
339
|
+
"Access-Control-Request-Method": "POST",
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
expect(r.status).toBe(403);
|
|
343
|
+
// CRITICAL: no ACAO header on rejected origin
|
|
344
|
+
expect(r.headers["access-control-allow-origin"]).toBeUndefined();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
//# sourceMappingURL=write-handlers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-handlers.test.js","sourceRoot":"","sources":["../../../../src/orchviz/server/__tests__/write-handlers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/overlays + /api/plan + /api/plans — JSON endpoints backed by collectors.
|
|
3
|
+
*/
|
|
4
|
+
import type { ServerResponse } from "node:http";
|
|
5
|
+
import { PlanCollector, type PlanSnapshot } from "../plan/collector.js";
|
|
6
|
+
export interface OverlayState {
|
|
7
|
+
gate1?: {
|
|
8
|
+
name: string;
|
|
9
|
+
approved: boolean;
|
|
10
|
+
} | null;
|
|
11
|
+
gate2?: {
|
|
12
|
+
name: string;
|
|
13
|
+
verdict: string;
|
|
14
|
+
} | null;
|
|
15
|
+
model?: string | null;
|
|
16
|
+
cost?: {
|
|
17
|
+
tokens: number;
|
|
18
|
+
usd: number;
|
|
19
|
+
} | null;
|
|
20
|
+
phase?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export type OverlayProvider = () => OverlayState;
|
|
23
|
+
export declare function writeJson(res: ServerResponse, status: number, body: unknown): void;
|
|
24
|
+
export declare function handleOverlays(res: ServerResponse, provider: OverlayProvider): void;
|
|
25
|
+
export declare const PLACEHOLDER_OVERLAYS: OverlayProvider;
|
|
26
|
+
export declare function makeOverlayProvider(projectRoot: string): OverlayProvider;
|
|
27
|
+
/**
|
|
28
|
+
* PlanProvider now accepts an optional slug string.
|
|
29
|
+
* PlanCollector.snapshot(slug?) maps to this signature.
|
|
30
|
+
*/
|
|
31
|
+
export type PlanProvider = (slug?: string) => PlanSnapshot;
|
|
32
|
+
/**
|
|
33
|
+
* Handle GET /api/plan — optionally targeted by ?slug=<slug> query param.
|
|
34
|
+
*
|
|
35
|
+
* Error mapping (R2-8):
|
|
36
|
+
* snapshot.error === "invalid-slug" → 400
|
|
37
|
+
* snapshot.error === "forbidden-path" → 403
|
|
38
|
+
* snapshot.error === "not-found" → 404
|
|
39
|
+
* no error → 200
|
|
40
|
+
*/
|
|
41
|
+
export declare function handlePlan(res: ServerResponse, provider: PlanProvider, query: URLSearchParams): void;
|
|
42
|
+
export declare const PLACEHOLDER_PLAN: PlanProvider;
|
|
43
|
+
export declare function makePlanProvider(projectRoot: string): PlanProvider;
|
|
44
|
+
/**
|
|
45
|
+
* Create a standalone PlanCollector instance for injection into the write handler.
|
|
46
|
+
* The write handler calls collector.invalidate() after every successful write,
|
|
47
|
+
* invalidating the cache so the next GET returns fresh data (R2-4).
|
|
48
|
+
*/
|
|
49
|
+
export declare function makePlanCollector(projectRoot: string): PlanCollector;
|
|
50
|
+
/**
|
|
51
|
+
* Handle GET /api/plans — returns all non-archived plan summaries sorted mtime desc.
|
|
52
|
+
* HTML-tag scrub applied to all string fields (red-team H2).
|
|
53
|
+
*/
|
|
54
|
+
export declare function handlePlans(res: ServerResponse, projectRoot: string): void;
|
|
55
|
+
//# sourceMappingURL=api-handlers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-handlers.d.ts","sourceRoot":"","sources":["../../../src/orchviz/server/api-handlers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAA0B,MAAM,sBAAsB,CAAC;AAyBhG,MAAM,WAAW,YAAY;IAC5B,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,YAAY,CAAC;AAEjD,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAQlF;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAMnF;AAED,eAAO,MAAM,oBAAoB,EAAE,eAMjC,CAAC;AAEH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,eAAe,CAGxE;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,YAAY,CAAC;AAE3D;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACzB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,eAAe,GACpB,IAAI,CAaN;AAED,eAAO,MAAM,gBAAgB,EAAE,YAK7B,CAAC;AAEH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAGlE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAEpE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAQ1E"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/overlays + /api/plan + /api/plans — JSON endpoints backed by collectors.
|
|
3
|
+
*/
|
|
4
|
+
import { OverlayCollector } from "../overlay/collector.js";
|
|
5
|
+
import { PlanCollector } from "../plan/collector.js";
|
|
6
|
+
import { listPlans } from "../plan/list-plans.js";
|
|
7
|
+
const PLAN_ERROR_HTTP = {
|
|
8
|
+
"invalid-slug": 400,
|
|
9
|
+
"forbidden-path": 403,
|
|
10
|
+
"not-found": 404,
|
|
11
|
+
};
|
|
12
|
+
const HTML_TAG_RE = /<[^>]*>/g;
|
|
13
|
+
/** Recursively strip HTML tags from string fields. Defense-in-depth (red-team H2). */
|
|
14
|
+
function stripHtmlDeep(value) {
|
|
15
|
+
if (typeof value === "string")
|
|
16
|
+
return value.replace(HTML_TAG_RE, "");
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.map(stripHtmlDeep);
|
|
19
|
+
if (value && typeof value === "object") {
|
|
20
|
+
const out = {};
|
|
21
|
+
for (const [k, v] of Object.entries(value)) {
|
|
22
|
+
out[k] = stripHtmlDeep(v);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
export function writeJson(res, status, body) {
|
|
29
|
+
const payload = JSON.stringify(body);
|
|
30
|
+
res.writeHead(status, {
|
|
31
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
32
|
+
"Content-Length": String(Buffer.byteLength(payload)),
|
|
33
|
+
"Cache-Control": "no-cache",
|
|
34
|
+
});
|
|
35
|
+
res.end(payload);
|
|
36
|
+
}
|
|
37
|
+
export function handleOverlays(res, provider) {
|
|
38
|
+
try {
|
|
39
|
+
writeJson(res, 200, provider());
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
writeJson(res, 500, { error: "overlay-read-failed" });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export const PLACEHOLDER_OVERLAYS = () => ({
|
|
46
|
+
gate1: null,
|
|
47
|
+
gate2: null,
|
|
48
|
+
model: null,
|
|
49
|
+
cost: null,
|
|
50
|
+
phase: null,
|
|
51
|
+
});
|
|
52
|
+
export function makeOverlayProvider(projectRoot) {
|
|
53
|
+
const collector = new OverlayCollector(projectRoot);
|
|
54
|
+
return () => collector.snapshot();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Handle GET /api/plan — optionally targeted by ?slug=<slug> query param.
|
|
58
|
+
*
|
|
59
|
+
* Error mapping (R2-8):
|
|
60
|
+
* snapshot.error === "invalid-slug" → 400
|
|
61
|
+
* snapshot.error === "forbidden-path" → 403
|
|
62
|
+
* snapshot.error === "not-found" → 404
|
|
63
|
+
* no error → 200
|
|
64
|
+
*/
|
|
65
|
+
export function handlePlan(res, provider, query) {
|
|
66
|
+
try {
|
|
67
|
+
const slug = query.get("slug") ?? undefined;
|
|
68
|
+
const snapshot = provider(slug);
|
|
69
|
+
if (snapshot.error) {
|
|
70
|
+
writeJson(res, PLAN_ERROR_HTTP[snapshot.error], { error: snapshot.error });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const safe = stripHtmlDeep(snapshot);
|
|
74
|
+
writeJson(res, 200, safe);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
writeJson(res, 500, { error: "plan-read-failed" });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export const PLACEHOLDER_PLAN = () => ({
|
|
81
|
+
plan: null,
|
|
82
|
+
phaseEtags: null,
|
|
83
|
+
readonly: false,
|
|
84
|
+
generatedAt: new Date().toISOString(),
|
|
85
|
+
});
|
|
86
|
+
export function makePlanProvider(projectRoot) {
|
|
87
|
+
const collector = new PlanCollector(projectRoot);
|
|
88
|
+
return (slug) => collector.snapshot(slug);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a standalone PlanCollector instance for injection into the write handler.
|
|
92
|
+
* The write handler calls collector.invalidate() after every successful write,
|
|
93
|
+
* invalidating the cache so the next GET returns fresh data (R2-4).
|
|
94
|
+
*/
|
|
95
|
+
export function makePlanCollector(projectRoot) {
|
|
96
|
+
return new PlanCollector(projectRoot);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Handle GET /api/plans — returns all non-archived plan summaries sorted mtime desc.
|
|
100
|
+
* HTML-tag scrub applied to all string fields (red-team H2).
|
|
101
|
+
*/
|
|
102
|
+
export function handlePlans(res, projectRoot) {
|
|
103
|
+
try {
|
|
104
|
+
const plans = listPlans(projectRoot);
|
|
105
|
+
const safe = stripHtmlDeep(plans);
|
|
106
|
+
writeJson(res, 200, { plans: safe, generatedAt: new Date().toISOString() });
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
writeJson(res, 500, { error: "plans-read-failed" });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=api-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-handlers.js","sourceRoot":"","sources":["../../../src/orchviz/server/api-handlers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OrchvizServer — node:http server bound 127.0.0.1 with SSE relay + static UI.
|
|
3
|
+
*
|
|
4
|
+
* Architecture ported from patoles/agent-flow @ 59ccf4e (app/src/server.ts +
|
|
5
|
+
* scripts/relay.ts SSE block). License Apache-2.0 (see ../../NOTICE).
|
|
6
|
+
*/
|
|
7
|
+
import { EventEmitter } from "node:events";
|
|
8
|
+
import { type OverlayProvider, type PlanProvider } from "./api-handlers.js";
|
|
9
|
+
import type { PlanCollector } from "../plan/collector.js";
|
|
10
|
+
export interface OrchvizServerOptions {
|
|
11
|
+
port?: number;
|
|
12
|
+
staticDir: string;
|
|
13
|
+
eventSource: EventEmitter;
|
|
14
|
+
overlayProvider?: OverlayProvider;
|
|
15
|
+
planProvider?: PlanProvider;
|
|
16
|
+
/** Required for /api/plans endpoint. Falls back to no-op if absent. */
|
|
17
|
+
projectRoot?: string;
|
|
18
|
+
/** Required for POST /api/plan/todo cache invalidation (red-team M4 / R2-4). */
|
|
19
|
+
planCollector?: PlanCollector;
|
|
20
|
+
verbose?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class OrchvizServer {
|
|
23
|
+
private server;
|
|
24
|
+
private readonly relay;
|
|
25
|
+
private actualPort;
|
|
26
|
+
private readonly opts;
|
|
27
|
+
private readonly forwardEvent;
|
|
28
|
+
constructor(opts: OrchvizServerOptions);
|
|
29
|
+
get port(): number;
|
|
30
|
+
get url(): string;
|
|
31
|
+
start(onListening?: (url: string) => void): Promise<string>;
|
|
32
|
+
stop(): Promise<void>;
|
|
33
|
+
private routeRequest;
|
|
34
|
+
private isHostAllowed;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/orchviz/server/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM3C,OAAO,EAON,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,MAAM,mBAAmB,CAAC;AAM3B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,MAAM,WAAW,oBAAoB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,YAAY,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,qBAAa,aAAa;IACzB,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0B;gBAE3C,IAAI,EAAE,oBAAoB;IAOtC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,GAAG,IAAI,MAAM,CAEhB;IAEK,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAoC3D,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,CAAC,YAAY;IAqDpB,OAAO,CAAC,aAAa;CAUrB"}
|