@wopr-network/defcon 0.2.0 → 0.2.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.
- package/dist/src/execution/cli.js +0 -0
- package/package.json +3 -2
- package/dist/api/router.d.ts +0 -24
- package/dist/api/router.js +0 -44
- package/dist/api/server.d.ts +0 -13
- package/dist/api/server.js +0 -280
- package/dist/api/wire-types.d.ts +0 -46
- package/dist/api/wire-types.js +0 -5
- package/dist/config/db-path.d.ts +0 -1
- package/dist/config/db-path.js +0 -1
- package/dist/config/exporter.d.ts +0 -3
- package/dist/config/exporter.js +0 -87
- package/dist/config/index.d.ts +0 -4
- package/dist/config/index.js +0 -4
- package/dist/config/seed-loader.d.ts +0 -10
- package/dist/config/seed-loader.js +0 -108
- package/dist/config/zod-schemas.d.ts +0 -165
- package/dist/config/zod-schemas.js +0 -283
- package/dist/cors.d.ts +0 -8
- package/dist/cors.js +0 -21
- package/dist/engine/constants.d.ts +0 -1
- package/dist/engine/constants.js +0 -1
- package/dist/engine/engine.d.ts +0 -69
- package/dist/engine/engine.js +0 -485
- package/dist/engine/event-emitter.d.ts +0 -9
- package/dist/engine/event-emitter.js +0 -19
- package/dist/engine/event-types.d.ts +0 -105
- package/dist/engine/event-types.js +0 -1
- package/dist/engine/flow-spawner.d.ts +0 -8
- package/dist/engine/flow-spawner.js +0 -28
- package/dist/engine/gate-command-validator.d.ts +0 -6
- package/dist/engine/gate-command-validator.js +0 -46
- package/dist/engine/gate-evaluator.d.ts +0 -12
- package/dist/engine/gate-evaluator.js +0 -233
- package/dist/engine/handlebars.d.ts +0 -9
- package/dist/engine/handlebars.js +0 -51
- package/dist/engine/index.d.ts +0 -12
- package/dist/engine/index.js +0 -7
- package/dist/engine/invocation-builder.d.ts +0 -18
- package/dist/engine/invocation-builder.js +0 -58
- package/dist/engine/on-enter.d.ts +0 -8
- package/dist/engine/on-enter.js +0 -102
- package/dist/engine/ssrf-guard.d.ts +0 -22
- package/dist/engine/ssrf-guard.js +0 -159
- package/dist/engine/state-machine.d.ts +0 -12
- package/dist/engine/state-machine.js +0 -74
- package/dist/execution/active-runner.d.ts +0 -45
- package/dist/execution/active-runner.js +0 -165
- package/dist/execution/admin-schemas.d.ts +0 -116
- package/dist/execution/admin-schemas.js +0 -125
- package/dist/execution/cli.d.ts +0 -57
- package/dist/execution/cli.js +0 -498
- package/dist/execution/handlers/admin.d.ts +0 -67
- package/dist/execution/handlers/admin.js +0 -200
- package/dist/execution/handlers/flow.d.ts +0 -25
- package/dist/execution/handlers/flow.js +0 -289
- package/dist/execution/handlers/query.d.ts +0 -31
- package/dist/execution/handlers/query.js +0 -64
- package/dist/execution/index.d.ts +0 -4
- package/dist/execution/index.js +0 -3
- package/dist/execution/mcp-helpers.d.ts +0 -42
- package/dist/execution/mcp-helpers.js +0 -23
- package/dist/execution/mcp-server.d.ts +0 -33
- package/dist/execution/mcp-server.js +0 -1020
- package/dist/execution/provision-worktree.d.ts +0 -16
- package/dist/execution/provision-worktree.js +0 -123
- package/dist/execution/tool-schemas.d.ts +0 -40
- package/dist/execution/tool-schemas.js +0 -44
- package/dist/logger.d.ts +0 -8
- package/dist/logger.js +0 -12
- package/dist/main.d.ts +0 -14
- package/dist/main.js +0 -28
- package/dist/repositories/drizzle/entity.repo.d.ts +0 -27
- package/dist/repositories/drizzle/entity.repo.js +0 -190
- package/dist/repositories/drizzle/event.repo.d.ts +0 -12
- package/dist/repositories/drizzle/event.repo.js +0 -24
- package/dist/repositories/drizzle/flow.repo.d.ts +0 -22
- package/dist/repositories/drizzle/flow.repo.js +0 -364
- package/dist/repositories/drizzle/gate.repo.d.ts +0 -16
- package/dist/repositories/drizzle/gate.repo.js +0 -98
- package/dist/repositories/drizzle/index.d.ts +0 -6
- package/dist/repositories/drizzle/index.js +0 -7
- package/dist/repositories/drizzle/invocation.repo.d.ts +0 -23
- package/dist/repositories/drizzle/invocation.repo.js +0 -199
- package/dist/repositories/drizzle/schema.d.ts +0 -1932
- package/dist/repositories/drizzle/schema.js +0 -155
- package/dist/repositories/drizzle/transition-log.repo.d.ts +0 -11
- package/dist/repositories/drizzle/transition-log.repo.js +0 -42
- package/dist/repositories/interfaces.d.ts +0 -321
- package/dist/repositories/interfaces.js +0 -2
- package/dist/utils/redact.d.ts +0 -2
- package/dist/utils/redact.js +0 -62
- package/gates/blocking-graph.d.ts +0 -26
- package/gates/blocking-graph.js +0 -102
- package/gates/test/bad-return-gate.d.ts +0 -1
- package/gates/test/bad-return-gate.js +0 -4
- package/gates/test/passing-gate.d.ts +0 -2
- package/gates/test/passing-gate.js +0 -3
- package/gates/test/slow-gate.d.ts +0 -2
- package/gates/test/slow-gate.js +0 -5
- package/gates/test/throwing-gate.d.ts +0 -1
- package/gates/test/throwing-gate.js +0 -3
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wopr-network/defcon",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.0.0"
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"gates"
|
|
25
25
|
],
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "tsc",
|
|
27
|
+
"build": "rm -rf dist && tsc",
|
|
28
|
+
"prepublishOnly": "pnpm build",
|
|
28
29
|
"dev": "tsx src/main.ts",
|
|
29
30
|
"lint": "biome check src/",
|
|
30
31
|
"lint:fix": "biome check --fix src/",
|
package/dist/api/router.d.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export interface ParsedRequest {
|
|
2
|
-
params: Record<string, string>;
|
|
3
|
-
query: URLSearchParams;
|
|
4
|
-
body: Record<string, unknown> | null;
|
|
5
|
-
authorization?: string;
|
|
6
|
-
}
|
|
7
|
-
export interface ApiResponse {
|
|
8
|
-
status: number;
|
|
9
|
-
body: unknown;
|
|
10
|
-
}
|
|
11
|
-
type Handler = (req: ParsedRequest) => Promise<ApiResponse>;
|
|
12
|
-
interface MatchResult {
|
|
13
|
-
params: Record<string, string>;
|
|
14
|
-
handler: Handler;
|
|
15
|
-
longRunning?: boolean;
|
|
16
|
-
}
|
|
17
|
-
export declare class Router {
|
|
18
|
-
private routes;
|
|
19
|
-
add(method: string, path: string, handler: Handler, options?: {
|
|
20
|
-
longRunning?: boolean;
|
|
21
|
-
}): void;
|
|
22
|
-
match(method: string, pathname: string): MatchResult | null;
|
|
23
|
-
}
|
|
24
|
-
export {};
|
package/dist/api/router.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
export class Router {
|
|
2
|
-
routes = [];
|
|
3
|
-
add(method, path, handler, options) {
|
|
4
|
-
const paramNames = [];
|
|
5
|
-
// Split on param segments, escape literal segments, then reassemble
|
|
6
|
-
const patternStr = path
|
|
7
|
-
.split(/(:([^/]+))/g)
|
|
8
|
-
.map((segment, i) => {
|
|
9
|
-
// Every 3rd token (i % 3 === 1) is the full ":name" match — replace with capture group
|
|
10
|
-
if (i % 3 === 1) {
|
|
11
|
-
paramNames.push(segment.slice(1));
|
|
12
|
-
return "([^/]+)";
|
|
13
|
-
}
|
|
14
|
-
// Every 3rd+1 token (i % 3 === 2) is the captured name — skip (already handled above)
|
|
15
|
-
if (i % 3 === 2)
|
|
16
|
-
return "";
|
|
17
|
-
// Literal segment — escape regex metacharacters
|
|
18
|
-
return segment.replace(/[.+*?^${}()|[\]\\]/g, "\\$&");
|
|
19
|
-
})
|
|
20
|
-
.join("");
|
|
21
|
-
this.routes.push({
|
|
22
|
-
method,
|
|
23
|
-
pattern: new RegExp(`^${patternStr}$`),
|
|
24
|
-
paramNames,
|
|
25
|
-
handler,
|
|
26
|
-
longRunning: options?.longRunning,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
match(method, pathname) {
|
|
30
|
-
for (const route of this.routes) {
|
|
31
|
-
if (route.method !== method)
|
|
32
|
-
continue;
|
|
33
|
-
const m = pathname.match(route.pattern);
|
|
34
|
-
if (m) {
|
|
35
|
-
const params = {};
|
|
36
|
-
route.paramNames.forEach((name, i) => {
|
|
37
|
-
params[name] = m[i + 1];
|
|
38
|
-
});
|
|
39
|
-
return { params, handler: route.handler, longRunning: route.longRunning };
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
package/dist/api/server.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import type { Engine } from "../engine/engine.js";
|
|
3
|
-
import type { McpServerDeps } from "../execution/mcp-server.js";
|
|
4
|
-
import type { Logger } from "../logger.js";
|
|
5
|
-
export interface HttpServerDeps {
|
|
6
|
-
engine: Engine;
|
|
7
|
-
mcpDeps: McpServerDeps;
|
|
8
|
-
adminToken?: string;
|
|
9
|
-
workerToken?: string;
|
|
10
|
-
corsOrigin?: string;
|
|
11
|
-
logger?: Logger;
|
|
12
|
-
}
|
|
13
|
-
export declare function createHttpServer(deps: HttpServerDeps): http.Server;
|
package/dist/api/server.js
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import { callToolHandler } from "../execution/mcp-server.js";
|
|
4
|
-
import { consoleLogger } from "../logger.js";
|
|
5
|
-
import { Router } from "./router.js";
|
|
6
|
-
function extractBearerToken(header) {
|
|
7
|
-
if (!header)
|
|
8
|
-
return undefined;
|
|
9
|
-
const lower = header.toLowerCase();
|
|
10
|
-
if (!lower.startsWith("bearer "))
|
|
11
|
-
return undefined;
|
|
12
|
-
return header.slice(7).trim() || undefined;
|
|
13
|
-
}
|
|
14
|
-
function requireWorkerToken(deps, req) {
|
|
15
|
-
const configuredToken = deps.workerToken?.trim() || undefined; // treat "" and whitespace-only as unset
|
|
16
|
-
if (!configuredToken)
|
|
17
|
-
return null; // open mode
|
|
18
|
-
const callerToken = extractBearerToken(req.authorization);
|
|
19
|
-
if (!callerToken) {
|
|
20
|
-
return { status: 401, body: { error: "Unauthorized: worker endpoints require authentication." } };
|
|
21
|
-
}
|
|
22
|
-
const hashA = createHash("sha256").update(configuredToken.trim()).digest();
|
|
23
|
-
const hashB = createHash("sha256").update(callerToken.trim()).digest();
|
|
24
|
-
if (!timingSafeEqual(hashA, hashB)) {
|
|
25
|
-
return { status: 401, body: { error: "Unauthorized: worker endpoints require authentication." } };
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
const BODY_SIZE_LIMIT = 1024 * 1024; // 1MB
|
|
30
|
-
function readBody(req) {
|
|
31
|
-
return new Promise((resolve, reject) => {
|
|
32
|
-
const chunks = [];
|
|
33
|
-
let size = 0;
|
|
34
|
-
let tooLarge = false;
|
|
35
|
-
req.on("data", (chunk) => {
|
|
36
|
-
size += chunk.length;
|
|
37
|
-
if (size > BODY_SIZE_LIMIT) {
|
|
38
|
-
tooLarge = true;
|
|
39
|
-
req.destroy();
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
chunks.push(chunk);
|
|
43
|
-
});
|
|
44
|
-
req.on("end", () => resolve({ body: Buffer.concat(chunks).toString("utf8"), tooLarge }));
|
|
45
|
-
req.on("error", (err) => {
|
|
46
|
-
if (tooLarge)
|
|
47
|
-
resolve({ body: "", tooLarge: true });
|
|
48
|
-
else
|
|
49
|
-
reject(err);
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
/** Unwrap MCP tool result into HTTP response */
|
|
54
|
-
function mcpResultToApi(result) {
|
|
55
|
-
const text = result.content[0]?.text ?? "";
|
|
56
|
-
let body;
|
|
57
|
-
try {
|
|
58
|
-
body = JSON.parse(text);
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
body = { message: text };
|
|
62
|
-
}
|
|
63
|
-
if (result.isError) {
|
|
64
|
-
const msg = typeof body === "object" && body !== null && "message" in body ? body.message : text;
|
|
65
|
-
const msgStr = String(msg);
|
|
66
|
-
if (msgStr.includes("not found") || msgStr.includes("Not found"))
|
|
67
|
-
return { status: 404, body: { error: msgStr } };
|
|
68
|
-
if (msgStr.includes("Unauthorized"))
|
|
69
|
-
return { status: 401, body: { error: msgStr } };
|
|
70
|
-
if (msgStr.includes("Validation error"))
|
|
71
|
-
return { status: 400, body: { error: msgStr } };
|
|
72
|
-
if (msgStr.includes("No active invocation"))
|
|
73
|
-
return { status: 409, body: { error: msgStr } };
|
|
74
|
-
return { status: 500, body: { error: msgStr } };
|
|
75
|
-
}
|
|
76
|
-
if (body === null)
|
|
77
|
-
return { status: 204, body: null };
|
|
78
|
-
return { status: 200, body };
|
|
79
|
-
}
|
|
80
|
-
export function createHttpServer(deps) {
|
|
81
|
-
const router = new Router();
|
|
82
|
-
// Ensure engine is set on mcpDeps
|
|
83
|
-
deps.mcpDeps.engine = deps.engine;
|
|
84
|
-
if (deps.logger)
|
|
85
|
-
deps.mcpDeps.logger = deps.logger;
|
|
86
|
-
// --- Status ---
|
|
87
|
-
router.add("GET", "/api/status", async () => {
|
|
88
|
-
const status = await deps.engine.getStatus();
|
|
89
|
-
return { status: 200, body: status };
|
|
90
|
-
});
|
|
91
|
-
// --- Flow claim (cross-flow: no flow filter) ---
|
|
92
|
-
router.add("POST", "/api/claim", async (req) => {
|
|
93
|
-
const authErr = requireWorkerToken(deps, req);
|
|
94
|
-
if (authErr)
|
|
95
|
-
return authErr;
|
|
96
|
-
const args = { role: req.body?.role };
|
|
97
|
-
const result = await callToolHandler(deps.mcpDeps, "flow.claim", args);
|
|
98
|
-
return mcpResultToApi(result);
|
|
99
|
-
});
|
|
100
|
-
// --- Flow claim ---
|
|
101
|
-
router.add("POST", "/api/flows/:flow/claim", async (req) => {
|
|
102
|
-
const authErr = requireWorkerToken(deps, req);
|
|
103
|
-
if (authErr)
|
|
104
|
-
return authErr;
|
|
105
|
-
const args = { role: req.body?.role, flow: req.params.flow };
|
|
106
|
-
const result = await callToolHandler(deps.mcpDeps, "flow.claim", args);
|
|
107
|
-
return mcpResultToApi(result);
|
|
108
|
-
});
|
|
109
|
-
// --- Entity report ---
|
|
110
|
-
// longRunning: true — flow.report blocks for the duration of gate evaluation
|
|
111
|
-
// (potentially many minutes). The server handler calls req.setTimeout(0) for
|
|
112
|
-
// this route specifically so only this connection bypasses the global 30s timeout.
|
|
113
|
-
router.add("POST", "/api/entities/:id/report", async (req) => {
|
|
114
|
-
const authErr = requireWorkerToken(deps, req);
|
|
115
|
-
if (authErr)
|
|
116
|
-
return authErr;
|
|
117
|
-
const args = {
|
|
118
|
-
entity_id: req.params.id,
|
|
119
|
-
signal: req.body?.signal,
|
|
120
|
-
};
|
|
121
|
-
if (req.body?.worker_id)
|
|
122
|
-
args.worker_id = req.body.worker_id;
|
|
123
|
-
if (req.body?.artifacts)
|
|
124
|
-
args.artifacts = req.body.artifacts;
|
|
125
|
-
const result = await callToolHandler(deps.mcpDeps, "flow.report", args);
|
|
126
|
-
return mcpResultToApi(result);
|
|
127
|
-
}, { longRunning: true });
|
|
128
|
-
// --- Entity fail ---
|
|
129
|
-
router.add("POST", "/api/entities/:id/fail", async (req) => {
|
|
130
|
-
const authErr = requireWorkerToken(deps, req);
|
|
131
|
-
if (authErr)
|
|
132
|
-
return authErr;
|
|
133
|
-
const args = { entity_id: req.params.id, error: req.body?.error };
|
|
134
|
-
const result = await callToolHandler(deps.mcpDeps, "flow.fail", args);
|
|
135
|
-
return mcpResultToApi(result);
|
|
136
|
-
});
|
|
137
|
-
// --- Entity CRUD ---
|
|
138
|
-
router.add("POST", "/api/entities", async (req) => {
|
|
139
|
-
const flowName = req.body?.flow;
|
|
140
|
-
const refs = req.body?.refs;
|
|
141
|
-
if (!flowName)
|
|
142
|
-
return { status: 400, body: { error: "Missing required field: flow" } };
|
|
143
|
-
try {
|
|
144
|
-
const entity = await deps.engine.createEntity(flowName, refs);
|
|
145
|
-
return { status: 201, body: entity };
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
-
if (msg.includes("not found"))
|
|
150
|
-
return { status: 404, body: { error: msg } };
|
|
151
|
-
return { status: 500, body: { error: msg } };
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
router.add("GET", "/api/entities/:id", async (req) => {
|
|
155
|
-
const result = await callToolHandler(deps.mcpDeps, "query.entity", { id: req.params.id });
|
|
156
|
-
return mcpResultToApi(result);
|
|
157
|
-
});
|
|
158
|
-
router.add("GET", "/api/entities", async (req) => {
|
|
159
|
-
const flow = req.query.get("flow");
|
|
160
|
-
const state = req.query.get("state");
|
|
161
|
-
if (!flow || !state)
|
|
162
|
-
return { status: 400, body: { error: "Required query params: flow, state" } };
|
|
163
|
-
const limitStr = req.query.get("limit");
|
|
164
|
-
const args = { flow, state };
|
|
165
|
-
if (limitStr) {
|
|
166
|
-
const limit = parseInt(limitStr, 10);
|
|
167
|
-
if (!Number.isNaN(limit) && limit > 0)
|
|
168
|
-
args.limit = limit;
|
|
169
|
-
}
|
|
170
|
-
const result = await callToolHandler(deps.mcpDeps, "query.entities", args);
|
|
171
|
-
return mcpResultToApi(result);
|
|
172
|
-
});
|
|
173
|
-
// --- Flow definition CRUD ---
|
|
174
|
-
router.add("GET", "/api/flows", async () => {
|
|
175
|
-
const flows = await deps.mcpDeps.flows.listAll();
|
|
176
|
-
return { status: 200, body: flows };
|
|
177
|
-
});
|
|
178
|
-
router.add("GET", "/api/flows/:id", async (req) => {
|
|
179
|
-
const result = await callToolHandler(deps.mcpDeps, "query.flow", { name: req.params.id });
|
|
180
|
-
return mcpResultToApi(result);
|
|
181
|
-
});
|
|
182
|
-
router.add("PUT", "/api/flows/:id", async (req) => {
|
|
183
|
-
const existing = await deps.mcpDeps.flows.getByName(req.params.id);
|
|
184
|
-
// Only pick known fields from body — never spread req.body to prevent param injection
|
|
185
|
-
const definition = req.body?.definition;
|
|
186
|
-
const description = req.body?.description;
|
|
187
|
-
const callerToken = extractBearerToken(req.authorization);
|
|
188
|
-
if (existing) {
|
|
189
|
-
const result = await callToolHandler(deps.mcpDeps, "admin.flow.update", { flow_name: req.params.id, definition, description }, { adminToken: deps.adminToken, callerToken });
|
|
190
|
-
return mcpResultToApi(result);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
const result = await callToolHandler(deps.mcpDeps, "admin.flow.create", { name: req.params.id, definition, description }, { adminToken: deps.adminToken, callerToken });
|
|
194
|
-
return mcpResultToApi(result);
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
|
-
router.add("DELETE", "/api/flows/:id", async () => {
|
|
198
|
-
return { status: 501, body: { error: "Flow deletion not implemented" } };
|
|
199
|
-
});
|
|
200
|
-
// --- HTTP server ---
|
|
201
|
-
const server = http.createServer(async (req, res) => {
|
|
202
|
-
// CORS
|
|
203
|
-
const origin = req.headers.origin;
|
|
204
|
-
if (origin) {
|
|
205
|
-
const isLoopbackOrigin = /^https?:\/\/localhost(:\d+)?$/.test(origin) ||
|
|
206
|
-
/^https?:\/\/127\.0\.0\.1(:\d+)?$/.test(origin) ||
|
|
207
|
-
/^https?:\/\/\[::1\](:\d+)?$/.test(origin);
|
|
208
|
-
const corsAllowed = deps.corsOrigin
|
|
209
|
-
? origin === deps.corsOrigin // explicit origin: exact match only
|
|
210
|
-
: isLoopbackOrigin; // loopback mode: only reflect loopback origins
|
|
211
|
-
if (corsAllowed) {
|
|
212
|
-
res.setHeader("Vary", "Origin");
|
|
213
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
214
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
215
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
if (req.method === "OPTIONS") {
|
|
219
|
-
res.writeHead(204).end();
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
223
|
-
const match = router.match(req.method ?? "GET", url.pathname);
|
|
224
|
-
if (!match) {
|
|
225
|
-
res.writeHead(404, { "Content-Type": "application/json" });
|
|
226
|
-
res.end(JSON.stringify({ error: "Not found" }));
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
// flow.report blocks until gate evaluation completes (potentially many
|
|
230
|
-
// minutes). Extend timeout per-request for this route only; all other
|
|
231
|
-
// routes keep the global 30s limit.
|
|
232
|
-
if (match.longRunning) {
|
|
233
|
-
req.setTimeout(0);
|
|
234
|
-
}
|
|
235
|
-
let body = null;
|
|
236
|
-
if (req.method === "POST" || req.method === "PUT") {
|
|
237
|
-
try {
|
|
238
|
-
const { body: raw, tooLarge } = await readBody(req);
|
|
239
|
-
if (tooLarge) {
|
|
240
|
-
res.writeHead(413, { "Content-Type": "application/json" });
|
|
241
|
-
res.end(JSON.stringify({ error: "Request body too large" }));
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
body = raw ? JSON.parse(raw) : null;
|
|
245
|
-
}
|
|
246
|
-
catch {
|
|
247
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
248
|
-
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
const parsed = {
|
|
253
|
-
params: match.params,
|
|
254
|
-
query: url.searchParams,
|
|
255
|
-
body,
|
|
256
|
-
authorization: req.headers.authorization,
|
|
257
|
-
};
|
|
258
|
-
try {
|
|
259
|
-
const apiRes = await match.handler(parsed);
|
|
260
|
-
if (apiRes.status === 204) {
|
|
261
|
-
res.writeHead(204).end();
|
|
262
|
-
}
|
|
263
|
-
else {
|
|
264
|
-
res.writeHead(apiRes.status, { "Content-Type": "application/json" });
|
|
265
|
-
res.end(JSON.stringify(apiRes.body));
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
catch (err) {
|
|
269
|
-
(deps.logger ?? consoleLogger).error("Request error:", err);
|
|
270
|
-
res.writeHead(500, { "Content-Type": "application/json" });
|
|
271
|
-
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
// Sensible defaults protect all routes from Slowloris/slow-header DoS.
|
|
275
|
-
// The entity report route calls req.setTimeout(0) per-request to bypass this
|
|
276
|
-
// limit only for connections that need long-running gate evaluation.
|
|
277
|
-
server.requestTimeout = 30000;
|
|
278
|
-
server.headersTimeout = 10000;
|
|
279
|
-
return server;
|
|
280
|
-
}
|
package/dist/api/wire-types.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire types for defcon's REST and MCP APIs.
|
|
3
|
-
* Exported for consumers (e.g. norad) to import instead of duplicating.
|
|
4
|
-
*/
|
|
5
|
-
export type ClaimResponse = {
|
|
6
|
-
next_action: "check_back";
|
|
7
|
-
retry_after_ms: number;
|
|
8
|
-
message: string;
|
|
9
|
-
} | {
|
|
10
|
-
worker_id?: string;
|
|
11
|
-
entity_id: string;
|
|
12
|
-
invocation_id: string;
|
|
13
|
-
flow: string | null;
|
|
14
|
-
stage: string;
|
|
15
|
-
prompt: string;
|
|
16
|
-
context: Record<string, unknown> | null;
|
|
17
|
-
worker_notice?: string;
|
|
18
|
-
};
|
|
19
|
-
export type ReportResponse = {
|
|
20
|
-
next_action: "continue";
|
|
21
|
-
new_state: string;
|
|
22
|
-
gates_passed?: string[];
|
|
23
|
-
prompt: string | null;
|
|
24
|
-
context: Record<string, unknown> | null;
|
|
25
|
-
} | {
|
|
26
|
-
next_action: "waiting";
|
|
27
|
-
new_state: null;
|
|
28
|
-
gated: true;
|
|
29
|
-
gate_output: string;
|
|
30
|
-
gateName: string;
|
|
31
|
-
failure_prompt: string | null;
|
|
32
|
-
} | {
|
|
33
|
-
next_action: "check_back";
|
|
34
|
-
message: string;
|
|
35
|
-
retry_after_ms: number;
|
|
36
|
-
timeout_prompt?: string;
|
|
37
|
-
} | {
|
|
38
|
-
next_action: "completed";
|
|
39
|
-
new_state: string;
|
|
40
|
-
gates_passed?: string[];
|
|
41
|
-
prompt: null;
|
|
42
|
-
context: null;
|
|
43
|
-
};
|
|
44
|
-
export interface CreateEntityResponse {
|
|
45
|
-
id: string;
|
|
46
|
-
}
|
package/dist/api/wire-types.js
DELETED
package/dist/config/db-path.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const DB_PATH = "./agentic-flow.db";
|
package/dist/config/db-path.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const DB_PATH = "./agentic-flow.db";
|
package/dist/config/exporter.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export async function exportSeed(flowRepo, gateRepo) {
|
|
2
|
-
const flows = await flowRepo.listAll();
|
|
3
|
-
// Fetch all gates once, build ID->name map (no N+1)
|
|
4
|
-
const allGates = await gateRepo.listAll();
|
|
5
|
-
const gateIdToName = new Map(allGates.map((g) => [g.id, g.name]));
|
|
6
|
-
const gateById = new Map(allGates.map((g) => [g.id, g]));
|
|
7
|
-
const exportedFlows = flows.filter((f) => f.discipline !== null);
|
|
8
|
-
// Collect gate IDs referenced by transitions of exported flows only
|
|
9
|
-
const referencedGateIds = new Set();
|
|
10
|
-
for (const flow of exportedFlows) {
|
|
11
|
-
for (const t of flow.transitions) {
|
|
12
|
-
if (t.gateId)
|
|
13
|
-
referencedGateIds.add(t.gateId);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
// Build gates array from referenced gates (already loaded)
|
|
17
|
-
const gateEntries = [];
|
|
18
|
-
for (const gateId of referencedGateIds) {
|
|
19
|
-
const gate = gateById.get(gateId);
|
|
20
|
-
if (!gate)
|
|
21
|
-
continue;
|
|
22
|
-
if (gate.type === "command" && gate.command) {
|
|
23
|
-
gateEntries.push({
|
|
24
|
-
name: gate.name,
|
|
25
|
-
type: "command",
|
|
26
|
-
command: gate.command,
|
|
27
|
-
timeoutMs: gate.timeoutMs ?? undefined,
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
else if (gate.type === "function" && gate.functionRef) {
|
|
31
|
-
gateEntries.push({
|
|
32
|
-
name: gate.name,
|
|
33
|
-
type: "function",
|
|
34
|
-
functionRef: gate.functionRef,
|
|
35
|
-
timeoutMs: gate.timeoutMs ?? undefined,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
else if (gate.type === "api" && gate.apiConfig) {
|
|
39
|
-
gateEntries.push({
|
|
40
|
-
name: gate.name,
|
|
41
|
-
type: "api",
|
|
42
|
-
apiConfig: gate.apiConfig,
|
|
43
|
-
timeoutMs: gate.timeoutMs ?? undefined,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const seedFlows = exportedFlows.map((f) => ({
|
|
48
|
-
name: f.name,
|
|
49
|
-
description: f.description ?? undefined,
|
|
50
|
-
entitySchema: f.entitySchema ?? undefined,
|
|
51
|
-
initialState: f.initialState,
|
|
52
|
-
maxConcurrent: f.maxConcurrent,
|
|
53
|
-
maxConcurrentPerRepo: f.maxConcurrentPerRepo,
|
|
54
|
-
affinityWindowMs: f.affinityWindowMs,
|
|
55
|
-
gateTimeoutMs: f.gateTimeoutMs ?? undefined,
|
|
56
|
-
version: f.version,
|
|
57
|
-
createdBy: f.createdBy ?? undefined,
|
|
58
|
-
discipline: f.discipline,
|
|
59
|
-
defaultModelTier: f.defaultModelTier ?? undefined,
|
|
60
|
-
timeoutPrompt: f.timeoutPrompt ?? undefined,
|
|
61
|
-
}));
|
|
62
|
-
const seedStates = exportedFlows.flatMap((f) => f.states.map((s) => ({
|
|
63
|
-
name: s.name,
|
|
64
|
-
flowName: f.name,
|
|
65
|
-
modelTier: s.modelTier ?? undefined,
|
|
66
|
-
mode: s.mode,
|
|
67
|
-
promptTemplate: s.promptTemplate ?? undefined,
|
|
68
|
-
constraints: s.constraints ?? undefined,
|
|
69
|
-
})));
|
|
70
|
-
const seedTransitions = exportedFlows.flatMap((f) => f.transitions.map((t) => ({
|
|
71
|
-
flowName: f.name,
|
|
72
|
-
fromState: t.fromState,
|
|
73
|
-
toState: t.toState,
|
|
74
|
-
trigger: t.trigger,
|
|
75
|
-
gateName: t.gateId ? gateIdToName.get(t.gateId) : undefined,
|
|
76
|
-
condition: t.condition ?? undefined,
|
|
77
|
-
priority: t.priority,
|
|
78
|
-
spawnFlow: t.spawnFlow ?? undefined,
|
|
79
|
-
spawnTemplate: t.spawnTemplate ?? undefined,
|
|
80
|
-
})));
|
|
81
|
-
return {
|
|
82
|
-
flows: seedFlows,
|
|
83
|
-
states: seedStates,
|
|
84
|
-
gates: gateEntries,
|
|
85
|
-
transitions: seedTransitions,
|
|
86
|
-
};
|
|
87
|
-
}
|
package/dist/config/index.d.ts
DELETED
package/dist/config/index.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type Database from "better-sqlite3";
|
|
2
|
-
import type { IFlowRepository, IGateRepository } from "../repositories/interfaces.js";
|
|
3
|
-
export interface LoadSeedOptions {
|
|
4
|
-
allowedRoot?: string;
|
|
5
|
-
}
|
|
6
|
-
export interface LoadSeedResult {
|
|
7
|
-
flows: number;
|
|
8
|
-
gates: number;
|
|
9
|
-
}
|
|
10
|
-
export declare function loadSeed(seedPath: string, flowRepo: IFlowRepository, gateRepo: IGateRepository, sqlite: InstanceType<typeof Database>, options?: LoadSeedOptions): Promise<LoadSeedResult>;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { readFileSync, realpathSync } from "node:fs";
|
|
2
|
-
import { relative, resolve, sep } from "node:path";
|
|
3
|
-
import { SeedFileSchema } from "./zod-schemas.js";
|
|
4
|
-
export async function loadSeed(seedPath, flowRepo, gateRepo, sqlite, options) {
|
|
5
|
-
const allowedRoot = options?.allowedRoot ?? process.cwd();
|
|
6
|
-
const resolvedRoot = resolve(allowedRoot);
|
|
7
|
-
const resolvedSeed = resolve(seedPath);
|
|
8
|
-
const lexicalRel = relative(resolvedRoot, resolvedSeed);
|
|
9
|
-
if (lexicalRel === ".." || lexicalRel.startsWith(`..${sep}`)) {
|
|
10
|
-
throw new Error(`Seed path escapes allowed root: ${resolvedSeed} is not under ${resolvedRoot}`);
|
|
11
|
-
}
|
|
12
|
-
const realSeed = realpathSync(resolvedSeed);
|
|
13
|
-
let realRoot;
|
|
14
|
-
try {
|
|
15
|
-
realRoot = realpathSync(resolvedRoot);
|
|
16
|
-
}
|
|
17
|
-
catch {
|
|
18
|
-
realRoot = resolvedRoot;
|
|
19
|
-
}
|
|
20
|
-
const realRel = relative(realRoot, realSeed);
|
|
21
|
-
if (realRel === ".." || realRel.startsWith(`..${sep}`)) {
|
|
22
|
-
throw new Error(`Seed path escapes allowed root: resolved symlink ${realSeed} is not under ${realRoot}`);
|
|
23
|
-
}
|
|
24
|
-
const raw = readFileSync(realSeed, "utf-8");
|
|
25
|
-
return parseSeedAndLoad(parseJson(raw, realSeed), flowRepo, gateRepo, sqlite);
|
|
26
|
-
}
|
|
27
|
-
function parseJson(raw, path) {
|
|
28
|
-
try {
|
|
29
|
-
return JSON.parse(raw);
|
|
30
|
-
}
|
|
31
|
-
catch (e) {
|
|
32
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
33
|
-
throw new Error(`Invalid JSON in seed file: ${path}: ${msg}`, { cause: e });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
async function parseSeedAndLoad(json, flowRepo, gateRepo, sqlite) {
|
|
37
|
-
const parsed = SeedFileSchema.parse(json);
|
|
38
|
-
sqlite.exec("BEGIN");
|
|
39
|
-
try {
|
|
40
|
-
// 1. Create gates first (transitions reference them by name)
|
|
41
|
-
const gateNameToId = new Map();
|
|
42
|
-
for (const g of parsed.gates) {
|
|
43
|
-
const gate = await gateRepo.create({
|
|
44
|
-
name: g.name,
|
|
45
|
-
type: g.type,
|
|
46
|
-
command: "command" in g ? g.command : undefined,
|
|
47
|
-
functionRef: "functionRef" in g ? g.functionRef : undefined,
|
|
48
|
-
apiConfig: "apiConfig" in g ? g.apiConfig : undefined,
|
|
49
|
-
timeoutMs: g.timeoutMs,
|
|
50
|
-
failurePrompt: g.failurePrompt,
|
|
51
|
-
timeoutPrompt: g.timeoutPrompt,
|
|
52
|
-
});
|
|
53
|
-
gateNameToId.set(g.name, gate.id);
|
|
54
|
-
}
|
|
55
|
-
// 2. Create flows, states, and transitions
|
|
56
|
-
for (const f of parsed.flows) {
|
|
57
|
-
const flow = await flowRepo.create({
|
|
58
|
-
name: f.name,
|
|
59
|
-
description: f.description,
|
|
60
|
-
entitySchema: f.entitySchema,
|
|
61
|
-
initialState: f.initialState,
|
|
62
|
-
maxConcurrent: f.maxConcurrent,
|
|
63
|
-
maxConcurrentPerRepo: f.maxConcurrentPerRepo,
|
|
64
|
-
gateTimeoutMs: f.gateTimeoutMs,
|
|
65
|
-
createdBy: f.createdBy,
|
|
66
|
-
discipline: f.discipline,
|
|
67
|
-
defaultModelTier: f.defaultModelTier,
|
|
68
|
-
timeoutPrompt: f.timeoutPrompt,
|
|
69
|
-
});
|
|
70
|
-
const flowStates = parsed.states.filter((s) => s.flowName === f.name);
|
|
71
|
-
for (const s of flowStates) {
|
|
72
|
-
await flowRepo.addState(flow.id, {
|
|
73
|
-
name: s.name,
|
|
74
|
-
modelTier: s.modelTier,
|
|
75
|
-
mode: s.mode,
|
|
76
|
-
promptTemplate: s.promptTemplate,
|
|
77
|
-
constraints: s.constraints,
|
|
78
|
-
onEnter: s.onEnter,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
const flowTransitions = parsed.transitions.filter((t) => t.flowName === f.name);
|
|
82
|
-
for (const t of flowTransitions) {
|
|
83
|
-
if (t.gateName && !gateNameToId.has(t.gateName)) {
|
|
84
|
-
throw new Error(`Transition from "${t.fromState}" to "${t.toState}" in flow "${f.name}" references unknown gate "${t.gateName}"`);
|
|
85
|
-
}
|
|
86
|
-
await flowRepo.addTransition(flow.id, {
|
|
87
|
-
fromState: t.fromState,
|
|
88
|
-
toState: t.toState,
|
|
89
|
-
trigger: t.trigger,
|
|
90
|
-
gateId: t.gateName ? gateNameToId.get(t.gateName) : undefined,
|
|
91
|
-
condition: t.condition,
|
|
92
|
-
priority: t.priority,
|
|
93
|
-
spawnFlow: t.spawnFlow,
|
|
94
|
-
spawnTemplate: t.spawnTemplate,
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
sqlite.exec("COMMIT");
|
|
99
|
-
}
|
|
100
|
-
catch (err) {
|
|
101
|
-
sqlite.exec("ROLLBACK");
|
|
102
|
-
throw err;
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
flows: parsed.flows.length,
|
|
106
|
-
gates: parsed.gates.length,
|
|
107
|
-
};
|
|
108
|
-
}
|