@kontextmind/kxm 0.6.0 → 0.7.4
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/.claude-plugin/marketplace.json +1 -1
- package/.kxm/agents/coordinator.yaml +9 -0
- package/.kxm/agents/critic-arch.yaml +13 -0
- package/.kxm/agents/critic-cli.yaml +13 -0
- package/.kxm/agents/implementer.yaml +13 -0
- package/.kxm/gates.yaml +8 -0
- package/.kxm/producers.yaml +22 -0
- package/.kxm/project.yaml +15 -0
- package/.kxm/roles/writer.yaml +7 -0
- package/.kxm/workflows/default.yaml +47 -0
- package/CHANGELOG.md +39 -7
- package/README.md +1 -0
- package/docs/README.md +4 -0
- package/docs/agent-skills.md +121 -0
- package/docs/architecture.md +1 -1
- package/docs/assignment-runner.md +21 -8
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +21 -0
- package/docs/kxm-handbook.md +3 -3
- package/docs/operations.md +24 -0
- package/docs/operator-pi-packages.md +63 -0
- package/docs/skills/repo-work-delivery.md +107 -0
- package/docs/skills.md +2 -0
- package/docs/test-matrix.md +4 -3
- package/docs/troubleshooting.md +41 -1
- package/docs/vnext/validation.md +9 -0
- package/docs/webhook-workflows.md +2 -2
- package/examples/README.md +1 -1
- package/package.json +16 -17
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +1 -1
- package/plugins/kxm/dist/cli.js +8955 -3934
- package/plugins/kxm/dist/core.js +271 -34
- package/plugins/kxm/dist/extension.js +7759 -86
- package/plugins/kxm/dist/mcp-server.js +75 -21
- package/plugins/kxm/dist/runtime.js +5637 -1125
- package/plugins/kxm/dist/server.js +3125 -2260
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +5808 -565
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/SUITE.md +5 -0
- package/plugins/kxm/skills/hints.json +73 -0
- package/plugins/kxm/skills/kxm/SKILL.md +30 -83
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
- package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
- package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
- package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
- package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
- package/plugins/kxm/src/autocomplete.ts +9 -3
- package/plugins/kxm/src/cli.ts +1637 -73
- package/plugins/kxm/src/commands.ts +150 -8
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/config.ts +7 -4
- package/plugins/kxm/src/context-packet.ts +172 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/extension.ts +36 -1
- package/plugins/kxm/src/external-effects.ts +357 -8
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/hub.ts +2 -4
- package/plugins/kxm/src/improve.ts +72 -0
- package/plugins/kxm/src/init-guide-setup.ts +547 -0
- package/plugins/kxm/src/local-snapshot.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-inventory.ts +127 -0
- package/plugins/kxm/src/policy-draft.d.mts +55 -0
- package/plugins/kxm/src/policy-draft.mjs +565 -0
- package/plugins/kxm/src/price-calc.ts +17 -18
- package/plugins/kxm/src/prices.ts +32 -16
- package/plugins/kxm/src/producers.ts +71 -0
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
- package/plugins/kxm/src/restricted-yaml.mjs +145 -0
- package/plugins/kxm/src/role.ts +710 -0
- package/plugins/kxm/src/routing.ts +99 -1
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/session-work.ts +9 -2
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/studio-layout.ts +660 -17
- package/plugins/kxm/src/suggest.ts +7 -13
- package/plugins/kxm/src/telemetry.ts +82 -0
- package/plugins/kxm/src/tui.ts +140 -0
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +53 -111
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +214 -62
- package/plugins/kxm/src/vnext-harness.ts +267 -83
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +85 -0
- package/plugins/kxm/src/vnext-oneshot-process.ts +166 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +179 -224
- package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +120 -5
- package/plugins/kxm/src/vnext-runtime.ts +14 -0
- package/plugins/kxm/src/workflow-manager.ts +392 -0
- package/plugins/kxm/src/workflow-tui.ts +255 -0
- package/plugins/kxm/src/workflow.ts +144 -0
- package/schemas/policy-draft/README.md +17 -0
- package/schemas/policy-draft/model.v2.schema.json +140 -0
- package/schemas/policy-draft/role.v2.schema.json +91 -0
- package/schemas/vnext/role.schema.json +76 -0
- package/schemas/vnext/run-event.schema.json +1 -0
- package/scripts/assignment-run.d.mts +1 -1
- package/scripts/assignment-run.mjs +44 -35
- package/scripts/check-generated.mjs +33 -9
- package/scripts/emit-codex-artifacts.mjs +255 -11
- package/scripts/harness-run.d.mts +12 -4
- package/scripts/harness-run.mjs +65 -17
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +150 -2
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
- package/scripts/native-critic.d.mts +5 -0
- package/scripts/native-critic.mjs +60 -0
- package/.kxm/config/README.md +0 -5
- package/.kxm/config/agents.json +0 -43
- package/.kxm/config/env.example +0 -56
- package/.kxm/config/update.example.yaml +0 -9
- package/.kxm/config/workflows/fix.json +0 -160
- package/.kxm/config/workflows/jira-development.json +0 -116
- package/.kxm/config/workflows/provenance-quorum.json +0 -150
- package/.kxm/config/workflows/v04-dogfood.json +0 -72
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* and Temporal swimlane activity timelines from compiled plans and run states.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { createServer, type IncomingMessage, type ServerResponse, type Server } from "node:http";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
7
9
|
import type { VnextCompiledPlan, VnextCompiledStep } from "./vnext-engine-compile.ts";
|
|
8
10
|
import type { VnextRunState } from "./vnext-engine-fold.ts";
|
|
9
11
|
|
|
@@ -90,9 +92,13 @@ export function generateStudioLayout(
|
|
|
90
92
|
|
|
91
93
|
for (const [sourceId, step] of Object.entries(plan.steps)) {
|
|
92
94
|
for (const transition of Object.values(step.transitions)) {
|
|
93
|
-
if (transition.to
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
if (transition.to === "step") {
|
|
96
|
+
if (transition.edge === "back") continue; // Back-edges are cycles (retries), ignore for DAG forward rank
|
|
97
|
+
const targetStepId = transition.target;
|
|
98
|
+
if (plan.steps[targetStepId]) {
|
|
99
|
+
adj[sourceId]!.push(targetStepId);
|
|
100
|
+
inDegree[targetStepId] = (inDegree[targetStepId] ?? 0) + 1;
|
|
101
|
+
}
|
|
96
102
|
}
|
|
97
103
|
}
|
|
98
104
|
}
|
|
@@ -113,13 +119,17 @@ export function generateStudioLayout(
|
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
|
|
122
|
+
let iterations = 0;
|
|
123
|
+
const maxIterations = stepIds.length * 4;
|
|
124
|
+
while (queue.length > 0 && iterations++ < maxIterations) {
|
|
117
125
|
const current = queue.shift()!;
|
|
118
126
|
const curLevel = levels[current] ?? 0;
|
|
119
127
|
for (const neighbor of adj[current] ?? []) {
|
|
120
128
|
const nextLevel = Math.max(levels[neighbor] ?? 0, curLevel + 1);
|
|
121
|
-
levels[neighbor]
|
|
122
|
-
|
|
129
|
+
if (levels[neighbor] === undefined || nextLevel > levels[neighbor]!) {
|
|
130
|
+
levels[neighbor] = nextLevel;
|
|
131
|
+
queue.push(neighbor);
|
|
132
|
+
}
|
|
123
133
|
}
|
|
124
134
|
}
|
|
125
135
|
|
|
@@ -196,17 +206,20 @@ export function generateStudioLayout(
|
|
|
196
206
|
|
|
197
207
|
// Build edges
|
|
198
208
|
for (const [outcome, transition] of Object.entries(step.transitions)) {
|
|
199
|
-
if (transition.to
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
if (transition.to === "step") {
|
|
210
|
+
const targetStepId = transition.target;
|
|
211
|
+
if (plan.steps[targetStepId]) {
|
|
212
|
+
edges.push({
|
|
213
|
+
id: `e_${stepId}_to_${targetStepId}_${outcome}`,
|
|
214
|
+
source: stepId,
|
|
215
|
+
target: targetStepId,
|
|
216
|
+
label: outcome !== "passed" && outcome !== "completed" ? outcome : undefined,
|
|
217
|
+
animated: status === "running",
|
|
218
|
+
style: {
|
|
219
|
+
stroke: outcome === "failed" ? "#ef4444" : outcome === "warning" ? "#f59e0b" : "#64748b",
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
}
|
|
210
223
|
}
|
|
211
224
|
}
|
|
212
225
|
});
|
|
@@ -266,3 +279,633 @@ export function generateStudioLayout(
|
|
|
266
279
|
temporalSwimlanes,
|
|
267
280
|
};
|
|
268
281
|
}
|
|
282
|
+
|
|
283
|
+
export const DEFAULT_STUDIO_PORT = 4242; // Decision Q8 & D14
|
|
284
|
+
|
|
285
|
+
export interface StudioServerOptions {
|
|
286
|
+
port?: number | undefined;
|
|
287
|
+
host?: string | undefined;
|
|
288
|
+
projectRoot?: string | undefined;
|
|
289
|
+
sessionToken?: string | undefined;
|
|
290
|
+
planProvider?: (() => VnextCompiledPlan | undefined) | undefined;
|
|
291
|
+
stateProvider?: (() => VnextRunState | undefined) | undefined;
|
|
292
|
+
onMutation?: ((command: string, args: Record<string, unknown>) => Promise<{ ok: boolean; result?: unknown; error?: string }> | { ok: boolean; result?: unknown; error?: string }) | undefined;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface StudioServerHandle {
|
|
296
|
+
server: Server;
|
|
297
|
+
port: number;
|
|
298
|
+
listen: () => Promise<number>;
|
|
299
|
+
close: () => Promise<void>;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Embedded Web Studio Local Server (Decision Q8 & D14).
|
|
304
|
+
* Serves the visual DAG, form stepper, and Temporal swimlane dashboards on http://localhost:4242.
|
|
305
|
+
* Enforces strict audit parity: web mutations authenticate via SessionToken and map 1:1 to CLI commands.
|
|
306
|
+
*/
|
|
307
|
+
export function createStudioServer(options: StudioServerOptions = {}): StudioServerHandle {
|
|
308
|
+
const targetPort = options.port ?? DEFAULT_STUDIO_PORT;
|
|
309
|
+
const host = options.host ?? "127.0.0.1";
|
|
310
|
+
|
|
311
|
+
const server = createServer(async (req: IncomingMessage, res: ServerResponse) => {
|
|
312
|
+
const url = new URL(req.url ?? "/", `http://${req.headers.host || "localhost"}`);
|
|
313
|
+
|
|
314
|
+
// CORS headers
|
|
315
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
316
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
317
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
318
|
+
|
|
319
|
+
if (req.method === "OPTIONS") {
|
|
320
|
+
res.writeHead(204);
|
|
321
|
+
res.end();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (url.pathname === "/health" || url.pathname === "/api/health") {
|
|
326
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
327
|
+
res.end(JSON.stringify({ ok: true, studio: true, port: targetPort }));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (url.pathname === "/api/layout" && req.method === "GET") {
|
|
332
|
+
const plan = options.planProvider?.();
|
|
333
|
+
if (!plan) {
|
|
334
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
335
|
+
res.end(JSON.stringify({ ok: false, error: "no_active_workflow_plan" }));
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const state = options.stateProvider?.();
|
|
339
|
+
const layout = generateStudioLayout(plan, state);
|
|
340
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
341
|
+
res.end(JSON.stringify({ ok: true, layout }));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (url.pathname === "/api/mutate" && req.method === "POST") {
|
|
346
|
+
// 1. Strict Authentication via SessionToken
|
|
347
|
+
if (options.sessionToken) {
|
|
348
|
+
const auth = req.headers.authorization;
|
|
349
|
+
const expected = `Bearer ${options.sessionToken}`;
|
|
350
|
+
if (!auth || auth !== expected) {
|
|
351
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
352
|
+
res.end(JSON.stringify({
|
|
353
|
+
ok: false,
|
|
354
|
+
error: "unauthorized",
|
|
355
|
+
message: "Valid SessionToken required for Web Studio mutations",
|
|
356
|
+
}));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 2. Read request body
|
|
362
|
+
const chunks: Buffer[] = [];
|
|
363
|
+
for await (const chunk of req) {
|
|
364
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
365
|
+
}
|
|
366
|
+
const bodyStr = Buffer.concat(chunks).toString("utf8");
|
|
367
|
+
let body: { command?: string; args?: Record<string, unknown> };
|
|
368
|
+
try {
|
|
369
|
+
body = JSON.parse(bodyStr);
|
|
370
|
+
} catch {
|
|
371
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
372
|
+
res.end(JSON.stringify({ ok: false, error: "malformed_json" }));
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const command = body.command;
|
|
377
|
+
const args = body.args ?? {};
|
|
378
|
+
if (!command || typeof command !== "string") {
|
|
379
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
380
|
+
res.end(JSON.stringify({ ok: false, error: "missing_command" }));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 3. Strict Audit Parity: verify command maps 1:1 to underlying CLI commands
|
|
385
|
+
const ALLOWED_CLI_MUTATIONS = [
|
|
386
|
+
"workflow.signal",
|
|
387
|
+
"workflow.checkpoint",
|
|
388
|
+
"workflow.wait",
|
|
389
|
+
"workflow.start",
|
|
390
|
+
"run.cancel",
|
|
391
|
+
"config.set",
|
|
392
|
+
"task.update",
|
|
393
|
+
"task.sync",
|
|
394
|
+
"peer.send",
|
|
395
|
+
"peer.reply",
|
|
396
|
+
"gate.signal",
|
|
397
|
+
"gate.degrade",
|
|
398
|
+
];
|
|
399
|
+
|
|
400
|
+
if (!ALLOWED_CLI_MUTATIONS.includes(command)) {
|
|
401
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
402
|
+
res.end(JSON.stringify({
|
|
403
|
+
ok: false,
|
|
404
|
+
error: "unsupported_web_mutation",
|
|
405
|
+
message: `Command '${command}' does not have a 1:1 underlying CLI audit mapping`,
|
|
406
|
+
}));
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const mutationId = `mut_${randomUUID().replaceAll("-", "").slice(0, 12)}`;
|
|
411
|
+
if (options.onMutation) {
|
|
412
|
+
try {
|
|
413
|
+
const outcome = await options.onMutation(command, args);
|
|
414
|
+
res.writeHead(outcome.ok ? 200 : 400, { "Content-Type": "application/json" });
|
|
415
|
+
res.end(JSON.stringify({ mutationId, ...outcome }));
|
|
416
|
+
} catch (err: unknown) {
|
|
417
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
418
|
+
res.end(JSON.stringify({ mutationId, ok: false, error: (err as Error).message }));
|
|
419
|
+
}
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
424
|
+
res.end(JSON.stringify({
|
|
425
|
+
ok: true,
|
|
426
|
+
mutationId,
|
|
427
|
+
command,
|
|
428
|
+
mappedToCli: true,
|
|
429
|
+
executedAt: new Date().toISOString(),
|
|
430
|
+
}));
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (url.pathname === "/" || url.pathname === "/index.html") {
|
|
435
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
436
|
+
res.end(`<!DOCTYPE html>
|
|
437
|
+
<html lang="en">
|
|
438
|
+
<head>
|
|
439
|
+
<meta charset="UTF-8">
|
|
440
|
+
<title>KXM Web Studio</title>
|
|
441
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
442
|
+
<style>
|
|
443
|
+
:root {
|
|
444
|
+
--bg: #0b0f19;
|
|
445
|
+
--panel-bg: #111827;
|
|
446
|
+
--card-bg: #1f2937;
|
|
447
|
+
--border: #374151;
|
|
448
|
+
--text: #f9fafb;
|
|
449
|
+
--text-muted: #9ca3af;
|
|
450
|
+
--primary: #38bdf8;
|
|
451
|
+
--success: #10b981;
|
|
452
|
+
--warning: #f59e0b;
|
|
453
|
+
--danger: #ef4444;
|
|
454
|
+
}
|
|
455
|
+
* { box-sizing: border-box; }
|
|
456
|
+
body {
|
|
457
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
458
|
+
background: var(--bg);
|
|
459
|
+
color: var(--text);
|
|
460
|
+
margin: 0;
|
|
461
|
+
padding: 0;
|
|
462
|
+
display: flex;
|
|
463
|
+
flex-direction: column;
|
|
464
|
+
height: 100vh;
|
|
465
|
+
overflow: hidden;
|
|
466
|
+
}
|
|
467
|
+
header {
|
|
468
|
+
background: var(--panel-bg);
|
|
469
|
+
border-bottom: 1px solid var(--border);
|
|
470
|
+
padding: 12px 24px;
|
|
471
|
+
display: flex;
|
|
472
|
+
align-items: center;
|
|
473
|
+
justify-content: space-between;
|
|
474
|
+
}
|
|
475
|
+
.brand { display: flex; align-items: center; gap: 12px; }
|
|
476
|
+
h1 { font-size: 18px; font-weight: 700; color: var(--primary); margin: 0; }
|
|
477
|
+
.badge {
|
|
478
|
+
display: inline-flex;
|
|
479
|
+
align-items: center;
|
|
480
|
+
gap: 6px;
|
|
481
|
+
padding: 3px 8px;
|
|
482
|
+
border-radius: 9999px;
|
|
483
|
+
background: #1e293b;
|
|
484
|
+
color: var(--text-muted);
|
|
485
|
+
font-size: 12px;
|
|
486
|
+
font-weight: 500;
|
|
487
|
+
}
|
|
488
|
+
.dot-live { width: 8px; height: 8px; border-radius: 50%; background: var(--success); }
|
|
489
|
+
.telemetry-bar {
|
|
490
|
+
display: flex;
|
|
491
|
+
gap: 16px;
|
|
492
|
+
font-size: 13px;
|
|
493
|
+
color: var(--text-muted);
|
|
494
|
+
font-family: ui-monospace, monospace;
|
|
495
|
+
}
|
|
496
|
+
.telemetry-val { color: var(--text); font-weight: 600; }
|
|
497
|
+
main {
|
|
498
|
+
flex: 1;
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-direction: column;
|
|
501
|
+
overflow: hidden;
|
|
502
|
+
padding: 16px 24px;
|
|
503
|
+
gap: 16px;
|
|
504
|
+
}
|
|
505
|
+
.panel {
|
|
506
|
+
background: var(--panel-bg);
|
|
507
|
+
border: 1px solid var(--border);
|
|
508
|
+
border-radius: 8px;
|
|
509
|
+
padding: 16px;
|
|
510
|
+
}
|
|
511
|
+
.panel-title {
|
|
512
|
+
font-size: 13px;
|
|
513
|
+
text-transform: uppercase;
|
|
514
|
+
letter-spacing: 0.05em;
|
|
515
|
+
color: var(--text-muted);
|
|
516
|
+
margin: 0 0 12px 0;
|
|
517
|
+
font-weight: 600;
|
|
518
|
+
}
|
|
519
|
+
/* Stepper */
|
|
520
|
+
.stepper-container {
|
|
521
|
+
display: flex;
|
|
522
|
+
align-items: center;
|
|
523
|
+
gap: 8px;
|
|
524
|
+
overflow-x: auto;
|
|
525
|
+
padding: 4px 0;
|
|
526
|
+
}
|
|
527
|
+
.step-badge {
|
|
528
|
+
display: flex;
|
|
529
|
+
align-items: center;
|
|
530
|
+
gap: 8px;
|
|
531
|
+
padding: 8px 14px;
|
|
532
|
+
border-radius: 6px;
|
|
533
|
+
background: var(--card-bg);
|
|
534
|
+
border: 1px solid var(--border);
|
|
535
|
+
font-size: 13px;
|
|
536
|
+
font-weight: 500;
|
|
537
|
+
white-space: nowrap;
|
|
538
|
+
}
|
|
539
|
+
.step-badge.active {
|
|
540
|
+
border-color: var(--primary);
|
|
541
|
+
box-shadow: 0 0 0 1px var(--primary);
|
|
542
|
+
color: var(--primary);
|
|
543
|
+
}
|
|
544
|
+
.step-badge.completed { border-color: var(--success); color: var(--success); }
|
|
545
|
+
.step-badge.failed { border-color: var(--danger); color: var(--danger); }
|
|
546
|
+
.stepper-arrow { color: var(--text-muted); font-size: 14px; }
|
|
547
|
+
/* Middle row */
|
|
548
|
+
.middle-row {
|
|
549
|
+
flex: 1;
|
|
550
|
+
display: flex;
|
|
551
|
+
gap: 16px;
|
|
552
|
+
overflow: hidden;
|
|
553
|
+
}
|
|
554
|
+
/* DAG Canvas */
|
|
555
|
+
.dag-panel {
|
|
556
|
+
flex: 3;
|
|
557
|
+
position: relative;
|
|
558
|
+
overflow: auto;
|
|
559
|
+
background: #0d1321;
|
|
560
|
+
border-radius: 8px;
|
|
561
|
+
border: 1px solid var(--border);
|
|
562
|
+
}
|
|
563
|
+
#dagCanvas {
|
|
564
|
+
position: relative;
|
|
565
|
+
min-width: 100%;
|
|
566
|
+
min-height: 100%;
|
|
567
|
+
padding: 32px;
|
|
568
|
+
}
|
|
569
|
+
.dag-node {
|
|
570
|
+
position: absolute;
|
|
571
|
+
width: 220px;
|
|
572
|
+
background: var(--card-bg);
|
|
573
|
+
border: 1px solid var(--border);
|
|
574
|
+
border-radius: 8px;
|
|
575
|
+
padding: 12px;
|
|
576
|
+
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.3);
|
|
577
|
+
cursor: pointer;
|
|
578
|
+
transition: border-color 0.2s, transform 0.2s;
|
|
579
|
+
}
|
|
580
|
+
.dag-node:hover {
|
|
581
|
+
border-color: var(--primary);
|
|
582
|
+
transform: translateY(-2px);
|
|
583
|
+
}
|
|
584
|
+
.dag-node.active { border-color: var(--primary); }
|
|
585
|
+
.dag-node.passed { border-color: var(--success); }
|
|
586
|
+
.dag-node.failed { border-color: var(--danger); }
|
|
587
|
+
.dag-node-header {
|
|
588
|
+
display: flex;
|
|
589
|
+
justify-content: space-between;
|
|
590
|
+
align-items: center;
|
|
591
|
+
margin-bottom: 6px;
|
|
592
|
+
}
|
|
593
|
+
.dag-node-title { font-size: 14px; font-weight: 600; color: var(--text); }
|
|
594
|
+
.dag-node-kind {
|
|
595
|
+
font-size: 10px;
|
|
596
|
+
text-transform: uppercase;
|
|
597
|
+
padding: 2px 6px;
|
|
598
|
+
background: #374151;
|
|
599
|
+
border-radius: 4px;
|
|
600
|
+
color: #94a3b8;
|
|
601
|
+
}
|
|
602
|
+
.dag-node-body { font-size: 12px; color: var(--text-muted); display: flex; flex-direction: column; gap: 4px; }
|
|
603
|
+
/* Side Panel */
|
|
604
|
+
.side-panel {
|
|
605
|
+
flex: 2;
|
|
606
|
+
display: flex;
|
|
607
|
+
flex-direction: column;
|
|
608
|
+
gap: 16px;
|
|
609
|
+
overflow-y: auto;
|
|
610
|
+
}
|
|
611
|
+
/* Swimlanes */
|
|
612
|
+
.timeline-track {
|
|
613
|
+
margin-bottom: 8px;
|
|
614
|
+
}
|
|
615
|
+
.track-label { font-size: 12px; color: var(--text-muted); margin-bottom: 4px; }
|
|
616
|
+
.track-bar-container {
|
|
617
|
+
background: var(--card-bg);
|
|
618
|
+
height: 20px;
|
|
619
|
+
border-radius: 4px;
|
|
620
|
+
position: relative;
|
|
621
|
+
overflow: hidden;
|
|
622
|
+
}
|
|
623
|
+
.track-bar {
|
|
624
|
+
position: absolute;
|
|
625
|
+
top: 2px;
|
|
626
|
+
bottom: 2px;
|
|
627
|
+
background: var(--primary);
|
|
628
|
+
border-radius: 3px;
|
|
629
|
+
opacity: 0.85;
|
|
630
|
+
}
|
|
631
|
+
/* Mutation Actions */
|
|
632
|
+
.mutation-form {
|
|
633
|
+
display: flex;
|
|
634
|
+
flex-direction: column;
|
|
635
|
+
gap: 10px;
|
|
636
|
+
}
|
|
637
|
+
.form-group { display: flex; flex-direction: column; gap: 4px; }
|
|
638
|
+
label { font-size: 12px; color: var(--text-muted); }
|
|
639
|
+
input, select, textarea {
|
|
640
|
+
background: var(--card-bg);
|
|
641
|
+
border: 1px solid var(--border);
|
|
642
|
+
border-radius: 4px;
|
|
643
|
+
color: var(--text);
|
|
644
|
+
padding: 8px;
|
|
645
|
+
font-size: 12px;
|
|
646
|
+
font-family: inherit;
|
|
647
|
+
}
|
|
648
|
+
button.btn {
|
|
649
|
+
background: #0284c7;
|
|
650
|
+
color: white;
|
|
651
|
+
border: none;
|
|
652
|
+
border-radius: 4px;
|
|
653
|
+
padding: 8px 12px;
|
|
654
|
+
font-size: 13px;
|
|
655
|
+
font-weight: 600;
|
|
656
|
+
cursor: pointer;
|
|
657
|
+
transition: background 0.2s;
|
|
658
|
+
}
|
|
659
|
+
button.btn:hover { background: #0369a1; }
|
|
660
|
+
.status-msg {
|
|
661
|
+
font-size: 12px;
|
|
662
|
+
padding: 8px;
|
|
663
|
+
border-radius: 4px;
|
|
664
|
+
font-family: monospace;
|
|
665
|
+
}
|
|
666
|
+
.status-ok { background: #064e3b; color: #a7f3d0; }
|
|
667
|
+
.status-err { background: #7f1d1d; color: #fecaca; }
|
|
668
|
+
</style>
|
|
669
|
+
</head>
|
|
670
|
+
<body>
|
|
671
|
+
<header>
|
|
672
|
+
<div class="brand">
|
|
673
|
+
<h1>KXM Web Studio</h1>
|
|
674
|
+
<span class="badge"><span class="dot-live"></span> Port ${targetPort} · Embedded Hub Host</span>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="telemetry-bar">
|
|
677
|
+
<div>Run: <span id="runIdVal" class="telemetry-val">-</span></div>
|
|
678
|
+
<div>Workflow: <span id="wfIdVal" class="telemetry-val">default</span></div>
|
|
679
|
+
<div>Status: <span id="statusVal" class="telemetry-val">READY</span></div>
|
|
680
|
+
</div>
|
|
681
|
+
</header>
|
|
682
|
+
|
|
683
|
+
<main>
|
|
684
|
+
<!-- Stepper Section -->
|
|
685
|
+
<div class="panel">
|
|
686
|
+
<div class="panel-title">Workflow Stage Progress Stepper (Decision D14)</div>
|
|
687
|
+
<div class="stepper-container" id="stepperContainer">
|
|
688
|
+
<div class="step-badge">[▶ implement]</div>
|
|
689
|
+
<div class="stepper-arrow">──></div>
|
|
690
|
+
<div class="step-badge">[⧗ review-arch]</div>
|
|
691
|
+
<div class="stepper-arrow">──></div>
|
|
692
|
+
<div class="step-badge">[○ review-cli]</div>
|
|
693
|
+
<div class="stepper-arrow">──></div>
|
|
694
|
+
<div class="step-badge">[○ verify]</div>
|
|
695
|
+
</div>
|
|
696
|
+
</div>
|
|
697
|
+
|
|
698
|
+
<!-- Middle: DAG Canvas + Side Swimlanes/Mutations -->
|
|
699
|
+
<div class="middle-row">
|
|
700
|
+
<!-- DAG Panel -->
|
|
701
|
+
<div class="dag-panel">
|
|
702
|
+
<svg id="svgEdges" style="position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:1;"></svg>
|
|
703
|
+
<div id="dagCanvas">
|
|
704
|
+
<!-- Dynamically injected nodes -->
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
|
|
708
|
+
<!-- Side Panel: Swimlanes & Mutation Actions -->
|
|
709
|
+
<div class="side-panel">
|
|
710
|
+
<div class="panel">
|
|
711
|
+
<div class="panel-title">Temporal Activity Swimlanes</div>
|
|
712
|
+
<div id="swimlanesContainer">
|
|
713
|
+
<div class="timeline-track">
|
|
714
|
+
<div class="track-label">implement (Grok)</div>
|
|
715
|
+
<div class="track-bar-container"><div class="track-bar" style="left:5%; width:40%;"></div></div>
|
|
716
|
+
</div>
|
|
717
|
+
<div class="timeline-track">
|
|
718
|
+
<div class="track-label">review-arch (Claude Fable)</div>
|
|
719
|
+
<div class="track-bar-container"><div class="track-bar" style="left:48%; width:30%; background: #a855f7;"></div></div>
|
|
720
|
+
</div>
|
|
721
|
+
<div class="timeline-track">
|
|
722
|
+
<div class="track-label">verify (verify-gate)</div>
|
|
723
|
+
<div class="track-bar-container"><div class="track-bar" style="left:80%; width:15%; background: #10b981;"></div></div>
|
|
724
|
+
</div>
|
|
725
|
+
</div>
|
|
726
|
+
</div>
|
|
727
|
+
|
|
728
|
+
<div class="panel">
|
|
729
|
+
<div class="panel-title">Audited CLI Mutations (1:1 Audit Parity)</div>
|
|
730
|
+
<form class="mutation-form" id="mutationForm" onsubmit="handleMutate(event)">
|
|
731
|
+
<div class="form-group">
|
|
732
|
+
<label>Command (1:1 CLI audit mapping)</label>
|
|
733
|
+
<select id="mutateCmd">
|
|
734
|
+
<option value="workflow.signal">workflow.signal</option>
|
|
735
|
+
<option value="workflow.checkpoint">workflow.checkpoint</option>
|
|
736
|
+
<option value="workflow.wait">workflow.wait</option>
|
|
737
|
+
<option value="gate.signal">gate.signal</option>
|
|
738
|
+
<option value="gate.degrade">gate.degrade</option>
|
|
739
|
+
</select>
|
|
740
|
+
</div>
|
|
741
|
+
<div class="form-group">
|
|
742
|
+
<label>Arguments (JSON)</label>
|
|
743
|
+
<textarea id="mutateArgs" rows="3" style="font-family: monospace;">{"signalKey": "approval", "status": "passed"}</textarea>
|
|
744
|
+
</div>
|
|
745
|
+
<div class="form-group">
|
|
746
|
+
<label>Session Token (Authorization)</label>
|
|
747
|
+
<input type="password" id="sessionTokenInput" placeholder="Optional if not authenticated">
|
|
748
|
+
</div>
|
|
749
|
+
<button type="submit" class="btn">Execute Audited Mutation</button>
|
|
750
|
+
<div id="mutateResult" style="display:none;" class="status-msg"></div>
|
|
751
|
+
</form>
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
</main>
|
|
756
|
+
|
|
757
|
+
<script>
|
|
758
|
+
async function loadLayout() {
|
|
759
|
+
try {
|
|
760
|
+
const res = await fetch('/api/layout');
|
|
761
|
+
if (!res.ok) return;
|
|
762
|
+
const data = await res.json();
|
|
763
|
+
if (!data.ok || !data.layout) return;
|
|
764
|
+
const layout = data.layout;
|
|
765
|
+
|
|
766
|
+
document.getElementById('wfIdVal').textContent = layout.workflowId || 'default';
|
|
767
|
+
if (layout.runId) document.getElementById('runIdVal').textContent = layout.runId;
|
|
768
|
+
|
|
769
|
+
// Render Stepper
|
|
770
|
+
if (layout.stepper && layout.stepper.length > 0) {
|
|
771
|
+
const container = document.getElementById('stepperContainer');
|
|
772
|
+
container.innerHTML = '';
|
|
773
|
+
layout.stepper.forEach((step, idx) => {
|
|
774
|
+
const el = document.createElement('div');
|
|
775
|
+
el.className = 'step-badge ' + (step.status || 'pending');
|
|
776
|
+
let icon = '○';
|
|
777
|
+
if (step.status === 'completed') icon = '✔';
|
|
778
|
+
else if (step.status === 'active') icon = '▶';
|
|
779
|
+
else if (step.status === 'failed') icon = '✖';
|
|
780
|
+
el.textContent = icon + ' ' + (step.label || step.id);
|
|
781
|
+
container.appendChild(el);
|
|
782
|
+
|
|
783
|
+
if (idx < layout.stepper.length - 1) {
|
|
784
|
+
const arrow = document.createElement('div');
|
|
785
|
+
arrow.className = 'stepper-arrow';
|
|
786
|
+
arrow.textContent = '──>';
|
|
787
|
+
container.appendChild(arrow);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Render DAG Nodes
|
|
793
|
+
if (layout.dag && layout.dag.nodes) {
|
|
794
|
+
const canvas = document.getElementById('dagCanvas');
|
|
795
|
+
canvas.innerHTML = '';
|
|
796
|
+
layout.dag.nodes.forEach(node => {
|
|
797
|
+
const div = document.createElement('div');
|
|
798
|
+
div.className = 'dag-node ' + (node.data.status || 'pending');
|
|
799
|
+
div.style.left = (node.position.x + 32) + 'px';
|
|
800
|
+
div.style.top = (node.position.y + 32) + 'px';
|
|
801
|
+
div.innerHTML = '<div class="dag-node-header">' +
|
|
802
|
+
'<span class="dag-node-title">' + (node.data.label || node.id) + '</span>' +
|
|
803
|
+
'<span class="dag-node-kind">' + node.data.kind + '</span>' +
|
|
804
|
+
'</div>' +
|
|
805
|
+
'<div class="dag-node-body">' +
|
|
806
|
+
(node.data.role ? '<div>Role: <b>' + node.data.role + '</b></div>' : '') +
|
|
807
|
+
(node.data.gate ? '<div>Gate: <b>' + node.data.gate + '</b></div>' : '') +
|
|
808
|
+
'<div>Status: ' + node.data.status + '</div>' +
|
|
809
|
+
'</div>';
|
|
810
|
+
canvas.appendChild(div);
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Render Swimlanes
|
|
815
|
+
if (layout.temporalSwimlanes && layout.temporalSwimlanes.length > 0) {
|
|
816
|
+
const container = document.getElementById('swimlanesContainer');
|
|
817
|
+
container.innerHTML = '';
|
|
818
|
+
layout.temporalSwimlanes.forEach(lane => {
|
|
819
|
+
const div = document.createElement('div');
|
|
820
|
+
div.className = 'timeline-track';
|
|
821
|
+
div.innerHTML = '<div class="track-label">' + lane.laneId + (lane.role ? ' (' + lane.role + ')' : '') + '</div>' +
|
|
822
|
+
'<div class="track-bar-container"><div class="track-bar" style="left:10%; width:70%;"></div></div>';
|
|
823
|
+
container.appendChild(div);
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
} catch (err) {
|
|
827
|
+
console.error("Layout polling error:", err);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async function handleMutate(e) {
|
|
832
|
+
e.preventDefault();
|
|
833
|
+
const cmd = document.getElementById('mutateCmd').value;
|
|
834
|
+
const argsRaw = document.getElementById('mutateArgs').value;
|
|
835
|
+
const token = document.getElementById('sessionTokenInput').value;
|
|
836
|
+
const resDiv = document.getElementById('mutateResult');
|
|
837
|
+
|
|
838
|
+
let args = {};
|
|
839
|
+
try {
|
|
840
|
+
args = JSON.parse(argsRaw);
|
|
841
|
+
} catch {
|
|
842
|
+
resDiv.style.display = 'block';
|
|
843
|
+
resDiv.className = 'status-msg status-err';
|
|
844
|
+
resDiv.textContent = 'Malformed JSON arguments';
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
try {
|
|
849
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
850
|
+
if (token) headers['Authorization'] = 'Bearer ' + token;
|
|
851
|
+
const res = await fetch('/api/mutate', {
|
|
852
|
+
method: 'POST',
|
|
853
|
+
headers,
|
|
854
|
+
body: JSON.stringify({ command: cmd, args })
|
|
855
|
+
});
|
|
856
|
+
const out = await res.json();
|
|
857
|
+
resDiv.style.display = 'block';
|
|
858
|
+
if (res.ok && out.ok) {
|
|
859
|
+
resDiv.className = 'status-msg status-ok';
|
|
860
|
+
resDiv.textContent = 'Mutation applied (Audit ID: ' + out.mutationId + ')';
|
|
861
|
+
loadLayout();
|
|
862
|
+
} else {
|
|
863
|
+
resDiv.className = 'status-msg status-err';
|
|
864
|
+
resDiv.textContent = 'Mutation failed: ' + (out.message || out.error || res.status);
|
|
865
|
+
}
|
|
866
|
+
} catch (err) {
|
|
867
|
+
resDiv.style.display = 'block';
|
|
868
|
+
resDiv.className = 'status-msg status-err';
|
|
869
|
+
resDiv.textContent = 'Network error: ' + err.message;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
loadLayout();
|
|
874
|
+
setInterval(loadLayout, 3000);
|
|
875
|
+
</script>
|
|
876
|
+
</body>
|
|
877
|
+
</html>`);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
882
|
+
res.end(JSON.stringify({ ok: false, error: "not_found" }));
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
return {
|
|
886
|
+
server,
|
|
887
|
+
port: targetPort,
|
|
888
|
+
listen: () =>
|
|
889
|
+
new Promise((resolveListen, rejectListen) => {
|
|
890
|
+
server.once("error", rejectListen);
|
|
891
|
+
server.listen(targetPort, host, () => {
|
|
892
|
+
server.removeListener("error", rejectListen);
|
|
893
|
+
const addr = server.address();
|
|
894
|
+
const actualPort = typeof addr === "object" && addr ? addr.port : targetPort;
|
|
895
|
+
resolveListen(actualPort);
|
|
896
|
+
});
|
|
897
|
+
}),
|
|
898
|
+
close: () =>
|
|
899
|
+
new Promise((resolveClose, rejectClose) => {
|
|
900
|
+
try {
|
|
901
|
+
server.closeAllConnections?.();
|
|
902
|
+
} catch {
|
|
903
|
+
// ignore if not supported
|
|
904
|
+
}
|
|
905
|
+
server.close((err) => {
|
|
906
|
+
if (err) rejectClose(err);
|
|
907
|
+
else resolveClose();
|
|
908
|
+
});
|
|
909
|
+
}),
|
|
910
|
+
};
|
|
911
|
+
}
|