@sapiom/harness 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +44 -43
- package/dist/cli/auth.d.ts +26 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +39 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +163 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/consent.d.ts +15 -0
- package/dist/cli/consent.d.ts.map +1 -0
- package/dist/cli/consent.js +81 -0
- package/dist/cli/consent.js.map +1 -0
- package/dist/cli/doctor.d.ts +27 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +74 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/machine-id.d.ts +19 -0
- package/dist/cli/machine-id.d.ts.map +1 -0
- package/dist/cli/machine-id.js +75 -0
- package/dist/cli/machine-id.js.map +1 -0
- package/dist/cli/paths.d.ts +3 -0
- package/dist/cli/paths.d.ts.map +1 -0
- package/dist/cli/paths.js +11 -0
- package/dist/cli/paths.js.map +1 -0
- package/dist/cli/settings.d.ts +26 -0
- package/dist/cli/settings.d.ts.map +1 -0
- package/dist/cli/settings.js +123 -0
- package/dist/cli/settings.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +41 -0
- package/dist/core/adapters/claude-code.d.ts.map +1 -0
- package/dist/core/adapters/claude-code.js +238 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/codex.d.ts +59 -0
- package/dist/core/adapters/codex.d.ts.map +1 -0
- package/dist/core/adapters/codex.js +319 -0
- package/dist/core/adapters/codex.js.map +1 -0
- package/dist/core/canvas-body.d.ts +45 -0
- package/dist/core/canvas-body.d.ts.map +1 -0
- package/dist/core/canvas-body.js +94 -0
- package/dist/core/canvas-body.js.map +1 -0
- package/dist/core/canvas-cache.d.ts +23 -0
- package/dist/core/canvas-cache.d.ts.map +1 -0
- package/dist/core/canvas-cache.js +64 -0
- package/dist/core/canvas-cache.js.map +1 -0
- package/dist/core/canvas-enrich.d.ts +106 -0
- package/dist/core/canvas-enrich.d.ts.map +1 -0
- package/dist/core/canvas-enrich.js +178 -0
- package/dist/core/canvas-enrich.js.map +1 -0
- package/dist/core/canvas-enrichment.d.ts +144 -0
- package/dist/core/canvas-enrichment.d.ts.map +1 -0
- package/dist/core/canvas-enrichment.js +192 -0
- package/dist/core/canvas-enrichment.js.map +1 -0
- package/dist/core/canvas-graph.d.ts +71 -0
- package/dist/core/canvas-graph.d.ts.map +1 -0
- package/dist/core/canvas-graph.js +111 -0
- package/dist/core/canvas-graph.js.map +1 -0
- package/dist/core/canvas-index-classify.d.ts +41 -0
- package/dist/core/canvas-index-classify.d.ts.map +1 -0
- package/dist/core/canvas-index-classify.js +63 -0
- package/dist/core/canvas-index-classify.js.map +1 -0
- package/dist/core/canvas-interconnections.d.ts +24 -0
- package/dist/core/canvas-interconnections.d.ts.map +1 -0
- package/dist/core/canvas-interconnections.js +96 -0
- package/dist/core/canvas-interconnections.js.map +1 -0
- package/dist/core/canvas-manifest-check.d.ts +15 -0
- package/dist/core/canvas-manifest-check.d.ts.map +1 -0
- package/dist/core/canvas-manifest-check.js +90 -0
- package/dist/core/canvas-manifest-check.js.map +1 -0
- package/dist/core/canvas-render.d.ts +73 -0
- package/dist/core/canvas-render.d.ts.map +1 -0
- package/dist/core/canvas-render.js +145 -0
- package/dist/core/canvas-render.js.map +1 -0
- package/dist/core/canvas-svg.d.ts +50 -0
- package/dist/core/canvas-svg.d.ts.map +1 -0
- package/dist/core/canvas-svg.js +240 -0
- package/dist/core/canvas-svg.js.map +1 -0
- package/dist/core/canvas-template.d.ts +43 -0
- package/dist/core/canvas-template.d.ts.map +1 -0
- package/dist/core/canvas-template.js +365 -0
- package/dist/core/canvas-template.js.map +1 -0
- package/dist/core/canvas-watcher.d.ts +25 -0
- package/dist/core/canvas-watcher.d.ts.map +1 -0
- package/dist/core/canvas-watcher.js +144 -0
- package/dist/core/canvas-watcher.js.map +1 -0
- package/dist/core/collector/batcher.d.ts +57 -0
- package/dist/core/collector/batcher.d.ts.map +1 -0
- package/dist/core/collector/batcher.js +143 -0
- package/dist/core/collector/batcher.js.map +1 -0
- package/dist/core/collector/codex-tailer.d.ts +85 -0
- package/dist/core/collector/codex-tailer.d.ts.map +1 -0
- package/dist/core/collector/codex-tailer.js +270 -0
- package/dist/core/collector/codex-tailer.js.map +1 -0
- package/dist/core/collector/normalizer.d.ts +33 -0
- package/dist/core/collector/normalizer.d.ts.map +1 -0
- package/dist/core/collector/normalizer.js +94 -0
- package/dist/core/collector/normalizer.js.map +1 -0
- package/dist/core/collector/seq.d.ts +14 -0
- package/dist/core/collector/seq.d.ts.map +1 -0
- package/dist/core/collector/seq.js +20 -0
- package/dist/core/collector/seq.js.map +1 -0
- package/dist/core/collector/store.d.ts +16 -0
- package/dist/core/collector/store.d.ts.map +1 -0
- package/dist/core/collector/store.js +30 -0
- package/dist/core/collector/store.js.map +1 -0
- package/dist/core/collector/transcript.d.ts +38 -0
- package/dist/core/collector/transcript.d.ts.map +1 -0
- package/dist/core/collector/transcript.js +110 -0
- package/dist/core/collector/transcript.js.map +1 -0
- package/dist/core/event-bus.d.ts +9 -0
- package/dist/core/event-bus.d.ts.map +1 -0
- package/dist/core/event-bus.js +25 -0
- package/dist/core/event-bus.js.map +1 -0
- package/dist/core/example-seed.d.ts +34 -0
- package/dist/core/example-seed.d.ts.map +1 -0
- package/dist/core/example-seed.js +277 -0
- package/dist/core/example-seed.js.map +1 -0
- package/dist/core/inject/claude-settings.d.ts +34 -0
- package/dist/core/inject/claude-settings.d.ts.map +1 -0
- package/dist/core/inject/claude-settings.js +152 -0
- package/dist/core/inject/claude-settings.js.map +1 -0
- package/dist/core/inject/mcp-config.d.ts +26 -0
- package/dist/core/inject/mcp-config.d.ts.map +1 -0
- package/dist/core/inject/mcp-config.js +39 -0
- package/dist/core/inject/mcp-config.js.map +1 -0
- package/dist/core/inject/retention.d.ts +66 -0
- package/dist/core/inject/retention.d.ts.map +1 -0
- package/dist/core/inject/retention.js +119 -0
- package/dist/core/inject/retention.js.map +1 -0
- package/dist/core/inject/system-prompt.d.ts +11 -0
- package/dist/core/inject/system-prompt.d.ts.map +1 -0
- package/dist/core/inject/system-prompt.js +22 -0
- package/dist/core/inject/system-prompt.js.map +1 -0
- package/dist/core/macro-runner.d.ts +36 -0
- package/dist/core/macro-runner.d.ts.map +1 -0
- package/dist/core/macro-runner.js +66 -0
- package/dist/core/macro-runner.js.map +1 -0
- package/dist/core/macros.d.ts +10 -0
- package/dist/core/macros.d.ts.map +1 -0
- package/dist/core/macros.js +59 -0
- package/dist/core/macros.js.map +1 -0
- package/dist/core/path-safety.d.ts +33 -0
- package/dist/core/path-safety.d.ts.map +1 -0
- package/dist/core/path-safety.js +63 -0
- package/dist/core/path-safety.js.map +1 -0
- package/dist/core/paths.d.ts +27 -0
- package/dist/core/paths.d.ts.map +1 -0
- package/dist/core/paths.js +43 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/port-detector.d.ts +69 -0
- package/dist/core/port-detector.d.ts.map +1 -0
- package/dist/core/port-detector.js +121 -0
- package/dist/core/port-detector.js.map +1 -0
- package/dist/core/session-manager.d.ts +275 -0
- package/dist/core/session-manager.d.ts.map +1 -0
- package/dist/core/session-manager.js +729 -0
- package/dist/core/session-manager.js.map +1 -0
- package/dist/core/task-manager.d.ts +138 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +286 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/task-stream.d.ts +32 -0
- package/dist/core/task-stream.d.ts.map +1 -0
- package/dist/core/task-stream.js +87 -0
- package/dist/core/task-stream.js.map +1 -0
- package/dist/core/workflow-registry.d.ts +36 -0
- package/dist/core/workflow-registry.d.ts.map +1 -0
- package/dist/core/workflow-registry.js +220 -0
- package/dist/core/workflow-registry.js.map +1 -0
- package/dist/core/workspace-context.d.ts +43 -0
- package/dist/core/workspace-context.d.ts.map +1 -0
- package/dist/core/workspace-context.js +108 -0
- package/dist/core/workspace-context.js.map +1 -0
- package/dist/core/workspace-watcher.d.ts +26 -0
- package/dist/core/workspace-watcher.d.ts.map +1 -0
- package/dist/core/workspace-watcher.js +174 -0
- package/dist/core/workspace-watcher.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/profiles/canvas-guidelines.d.ts +20 -0
- package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
- package/dist/profiles/canvas-guidelines.js +48 -0
- package/dist/profiles/canvas-guidelines.js.map +1 -0
- package/dist/profiles/default.d.ts +10 -0
- package/dist/profiles/default.d.ts.map +1 -0
- package/dist/profiles/default.js +70 -0
- package/dist/profiles/default.js.map +1 -0
- package/dist/server/auth.d.ts +9 -0
- package/dist/server/auth.d.ts.map +1 -0
- package/dist/server/auth.js +28 -0
- package/dist/server/auth.js.map +1 -0
- package/dist/server/canvas-render.d.ts +18 -0
- package/dist/server/canvas-render.d.ts.map +1 -0
- package/dist/server/canvas-render.js +24 -0
- package/dist/server/canvas-render.js.map +1 -0
- package/dist/server/canvas.d.ts +12 -0
- package/dist/server/canvas.d.ts.map +1 -0
- package/dist/server/canvas.js +174 -0
- package/dist/server/canvas.js.map +1 -0
- package/dist/server/events-ws.d.ts +11 -0
- package/dist/server/events-ws.d.ts.map +1 -0
- package/dist/server/events-ws.js +24 -0
- package/dist/server/events-ws.js.map +1 -0
- package/dist/server/fs.d.ts +4 -0
- package/dist/server/fs.d.ts.map +1 -0
- package/dist/server/fs.js +88 -0
- package/dist/server/fs.js.map +1 -0
- package/dist/server/index.d.ts +83 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +0 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/ingest.d.ts +74 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +74 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/macros.d.ts +32 -0
- package/dist/server/macros.d.ts.map +1 -0
- package/dist/server/macros.js +76 -0
- package/dist/server/macros.js.map +1 -0
- package/dist/server/rest.d.ts +75 -0
- package/dist/server/rest.d.ts.map +1 -0
- package/dist/server/rest.js +235 -0
- package/dist/server/rest.js.map +1 -0
- package/dist/server/static.d.ts +8 -0
- package/dist/server/static.d.ts.map +1 -0
- package/dist/server/static.js +36 -0
- package/dist/server/static.js.map +1 -0
- package/dist/server/terminal-ws.d.ts +9 -0
- package/dist/server/terminal-ws.d.ts.map +1 -0
- package/dist/server/terminal-ws.js +59 -0
- package/dist/server/terminal-ws.js.map +1 -0
- package/dist/server/ws-router.d.ts +16 -0
- package/dist/server/ws-router.d.ts.map +1 -0
- package/dist/server/ws-router.js +26 -0
- package/dist/server/ws-router.js.map +1 -0
- package/dist/shared/types.d.ts +485 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +80 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/web/assets/index-B9R4TzH7.css +32 -0
- package/dist/web/assets/index-Dh97QAgq.js +192 -0
- package/dist/web/index.html +27 -0
- package/package.json +84 -7
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI enrichment runner: spawns ONE bounded, headless agent task per
|
|
3
|
+
* workflow (via TaskManager) that reads the workflow's step bodies and
|
|
4
|
+
* returns the JSON `CanvasEnrichment` contract — never HTML, never a file
|
|
5
|
+
* write. On success the validated enrichment is persisted to the workflow's
|
|
6
|
+
* cache file and the render file is rebuilt with it merged in; on any
|
|
7
|
+
* parse/validation failure the enrichment is discarded whole and the base
|
|
8
|
+
* render stands (core/canvas-enrichment.ts's contract). Task-process
|
|
9
|
+
* failures surface through the task record itself (the pane's error state +
|
|
10
|
+
* Retry), never as HTML on disk.
|
|
11
|
+
*
|
|
12
|
+
* Triggering (see server/index.ts):
|
|
13
|
+
* - bind / session create → `ensureFresh`: spawn only when no cache entry
|
|
14
|
+
* matches the current source fingerprint; every skip reason is silent.
|
|
15
|
+
* - the visualize macro → `forceRefresh`: drop the extraction + enrichment
|
|
16
|
+
* caches, re-render the base immediately, re-enrich; refusals propagate
|
|
17
|
+
* (an already-running enrichment for this workflow → 409 via the macros
|
|
18
|
+
* router, exactly like any other double-fired background macro).
|
|
19
|
+
*
|
|
20
|
+
* Dedupe is per WORKFLOW, not per session: TaskManager refuses a second
|
|
21
|
+
* running task with the same macroId + workflowPath regardless of which
|
|
22
|
+
* session asked (see TaskAlreadyRunningError) — two panes bound to the same
|
|
23
|
+
* workflow can never race its cache/render files.
|
|
24
|
+
*/
|
|
25
|
+
import type { BackgroundTask, HarnessKind } from "../shared/types.js";
|
|
26
|
+
import { type CachedExtraction } from "./canvas-cache.js";
|
|
27
|
+
import type { CanvasGraph } from "./canvas-graph.js";
|
|
28
|
+
import { type RenderableWorkflow } from "./canvas-render.js";
|
|
29
|
+
import type { RunTaskRequest } from "./task-manager.js";
|
|
30
|
+
/** The macro identity enrichment tasks run under — the pane's Retry re-fires
|
|
31
|
+
* this macro, which routes back through forceRefresh. */
|
|
32
|
+
export declare const ENRICHMENT_MACRO_ID = "visualize";
|
|
33
|
+
/** Model the enrichment task runs on — overridable per install. */
|
|
34
|
+
export declare function enrichmentModel(env?: NodeJS.ProcessEnv): string;
|
|
35
|
+
/**
|
|
36
|
+
* Builds the one-shot enrichment prompt: the extracted graph inline (the AI
|
|
37
|
+
* annotates THIS structure — it never invents nodes), pointed at the step
|
|
38
|
+
* sources for semantics, with the JSON contract and its hard limits stated
|
|
39
|
+
* verbatim so a well-behaved run needs no retry.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildEnrichmentPrompt(graph: CanvasGraph, workflowDir: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Pulls the JSON object out of a task's final result text — either fenced
|
|
44
|
+
* (\`\`\`json ... \`\`\`) or raw, tolerating prose around it by falling back to
|
|
45
|
+
* the outermost {...} span. Null when nothing parses.
|
|
46
|
+
*/
|
|
47
|
+
export declare function extractEnrichmentJson(text: string): unknown | null;
|
|
48
|
+
/** The slice of TaskManager the runner uses — injectable for tests. */
|
|
49
|
+
export interface EnrichmentTaskRunner {
|
|
50
|
+
run(req: RunTaskRequest): Promise<BackgroundTask>;
|
|
51
|
+
onStatusChange(listener: (task: BackgroundTask) => void): () => void;
|
|
52
|
+
}
|
|
53
|
+
/** The session shape the coordinator needs — satisfied by HarnessSession. */
|
|
54
|
+
export interface EnrichmentSession {
|
|
55
|
+
id: string;
|
|
56
|
+
cwd: string;
|
|
57
|
+
harness: HarnessKind;
|
|
58
|
+
boundWorkflowPath: string | null;
|
|
59
|
+
}
|
|
60
|
+
export interface EnrichmentCoordinatorOptions {
|
|
61
|
+
tasks: EnrichmentTaskRunner;
|
|
62
|
+
/** Rebuilds the workflow's render file (with the just-persisted enrichment
|
|
63
|
+
* merged) after a successful run — the write alone hot-reloads any open
|
|
64
|
+
* pane via the canvas watcher. */
|
|
65
|
+
rerender?: (cwd: string, workflow: RenderableWorkflow) => Promise<void>;
|
|
66
|
+
/** Injectable for tests — defaults to the real cached extraction. */
|
|
67
|
+
extractCached?: (dir: string) => Promise<CachedExtraction>;
|
|
68
|
+
env?: NodeJS.ProcessEnv;
|
|
69
|
+
now?: () => string;
|
|
70
|
+
onError?: (message: string) => void;
|
|
71
|
+
}
|
|
72
|
+
export declare class CanvasEnrichmentCoordinator {
|
|
73
|
+
private readonly tasks;
|
|
74
|
+
private readonly rerender;
|
|
75
|
+
private readonly extractCached;
|
|
76
|
+
private readonly env;
|
|
77
|
+
private readonly now;
|
|
78
|
+
private readonly onError;
|
|
79
|
+
constructor(options: EnrichmentCoordinatorOptions);
|
|
80
|
+
/**
|
|
81
|
+
* The bind/create trigger: spawn an enrichment task unless a cached
|
|
82
|
+
* enrichment already matches the workflow's current sources. Silent on
|
|
83
|
+
* every expected refusal — unbound session, failed extraction, fresh
|
|
84
|
+
* cache, an enrichment already in flight for this workflow, a harness
|
|
85
|
+
* with no headless mode, even a spawn failure (logged) — because nothing
|
|
86
|
+
* here was user-initiated and a bind must never fail on enrichment's
|
|
87
|
+
* account.
|
|
88
|
+
*/
|
|
89
|
+
ensureFresh(session: EnrichmentSession, workflows: readonly RenderableWorkflow[]): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* The visualize macro: invalidate both caches, re-render the base diagram
|
|
92
|
+
* immediately (instant feedback, honest content — no leftover enrichment
|
|
93
|
+
* from sources that may have changed), then re-enrich. Refusals propagate
|
|
94
|
+
* to the macros router (TaskAlreadyRunningError → 409,
|
|
95
|
+
* TaskNotSupportedError → 400). Unbound session: just a no-op, matching
|
|
96
|
+
* the deterministic render's own unbound contract.
|
|
97
|
+
*/
|
|
98
|
+
forceRefresh(session: EnrichmentSession, workflows: readonly RenderableWorkflow[]): Promise<void>;
|
|
99
|
+
private resolveBound;
|
|
100
|
+
private spawn;
|
|
101
|
+
/** Completed task → parse → validate → cache file → re-render. Any parse
|
|
102
|
+
* or validation failure discards the enrichment whole; the base render
|
|
103
|
+
* already on disk stands untouched. */
|
|
104
|
+
private persist;
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=canvas-enrich.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-enrich.d.ts","sourceRoot":"","sources":["../../src/core/canvas-enrich.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,EAAyD,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACjH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AASrD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD;0DAC0D;AAC1D,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAO/C,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAuBrF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAgBlE;AAED,uEAAuE;AACvE,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IAClD,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CACtE;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,WAAW,CAAC;IACrB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,oBAAoB,CAAC;IAC5B;;uCAEmC;IACnC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxE,qEAAqE;IACrE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,qBAAa,2BAA2B;IACtC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+D;IACxF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6C;IAC3E,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;gBAExC,OAAO,EAAE,4BAA4B;IASjD;;;;;;;;OAQG;IACG,WAAW,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAetG;;;;;;;OAOG;IACG,YAAY,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,SAAS,kBAAkB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvG,OAAO,CAAC,YAAY;YAON,KAAK;IA+BnB;;4CAEwC;YAC1B,OAAO;CAqBtB"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { extractWorkflowGraphCached, invalidateExtractionCache } from "./canvas-cache.js";
|
|
2
|
+
import { ENRICHMENT_LIMITS, normalizeCanvasEnrichmentCandidate, parseCanvasEnrichment, readEnrichmentCacheFile, removeEnrichmentCacheFile, writeEnrichmentCacheFile, } from "./canvas-enrichment.js";
|
|
3
|
+
import { enrichmentCacheFileFor, renderWorkflowRenderFile, } from "./canvas-render.js";
|
|
4
|
+
import { TaskAlreadyRunningError, TaskNotSupportedError } from "./task-manager.js";
|
|
5
|
+
/** The macro identity enrichment tasks run under — the pane's Retry re-fires
|
|
6
|
+
* this macro, which routes back through forceRefresh. */
|
|
7
|
+
export const ENRICHMENT_MACRO_ID = "visualize";
|
|
8
|
+
const ENRICHMENT_TASK_LABEL = "Visualize";
|
|
9
|
+
/** Hard turn cap: read a few step files, answer. A run that needs more than
|
|
10
|
+
* this is off the rails, not thorough. */
|
|
11
|
+
const ENRICHMENT_MAX_TURNS = 8;
|
|
12
|
+
const DEFAULT_ENRICHMENT_MODEL = "sonnet";
|
|
13
|
+
/** Model the enrichment task runs on — overridable per install. */
|
|
14
|
+
export function enrichmentModel(env = process.env) {
|
|
15
|
+
return env.SAPIOM_HARNESS_VISUALIZE_MODEL || DEFAULT_ENRICHMENT_MODEL;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Builds the one-shot enrichment prompt: the extracted graph inline (the AI
|
|
19
|
+
* annotates THIS structure — it never invents nodes), pointed at the step
|
|
20
|
+
* sources for semantics, with the JSON contract and its hard limits stated
|
|
21
|
+
* verbatim so a well-behaved run needs no retry.
|
|
22
|
+
*/
|
|
23
|
+
export function buildEnrichmentPrompt(graph, workflowDir) {
|
|
24
|
+
const L = ENRICHMENT_LIMITS;
|
|
25
|
+
return `You are annotating an already-rendered workflow diagram. The diagram's structure below is fixed — you only supply short text annotations, as one JSON object.
|
|
26
|
+
|
|
27
|
+
Extracted workflow graph:
|
|
28
|
+
${JSON.stringify(graph, null, 2)}
|
|
29
|
+
|
|
30
|
+
Read the step run() bodies in ${workflowDir} to understand what each step actually does (what it calls, what it decides, why it branches), then RETURN ONLY a JSON object matching this schema — no prose before or after, no markdown required, and DO NOT write or modify any files:
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
"summary": "what this workflow does, one line (max ${L.summary} chars)",
|
|
34
|
+
"nodeDetails": { "<nodeId>": { "sublabel": "short annotation shown in the node (max ${L.sublabel} chars)", "description": "one sentence, shown on hover (max ${L.description} chars)" } },
|
|
35
|
+
"edgeLabels": { "<fromNodeId>-><toNodeId>": "intent/condition name (max ${L.edgeLabel} chars)" },
|
|
36
|
+
"notes": ["up to ${L.noteCount} facts worth knowing, each max ${L.note} chars"],
|
|
37
|
+
"layoutHints": { "groups": [{ "label": "group name (max ${L.groupLabel} chars)", "nodeIds": ["..."] }], "laneOrder": { "<layerIndex>": ["nodeId", "..."] } },
|
|
38
|
+
"crossWorkflow": "how this workflow ties into the project's other workflows, if it does (max ${L.crossWorkflow} chars)"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Rules:
|
|
42
|
+
- Every field is optional — omit what you have nothing useful for (omit, don't write null). Empty strings are worse than omissions.
|
|
43
|
+
- Use ONLY node ids that appear in the graph above.
|
|
44
|
+
- Length limits are hard caps and oversize strings get truncated mid-sentence — aim comfortably under each limit.
|
|
45
|
+
- Your final message must be exactly the JSON object.`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pulls the JSON object out of a task's final result text — either fenced
|
|
49
|
+
* (\`\`\`json ... \`\`\`) or raw, tolerating prose around it by falling back to
|
|
50
|
+
* the outermost {...} span. Null when nothing parses.
|
|
51
|
+
*/
|
|
52
|
+
export function extractEnrichmentJson(text) {
|
|
53
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
|
|
54
|
+
const candidates = [fenced?.[1], text];
|
|
55
|
+
const first = text.indexOf("{");
|
|
56
|
+
const last = text.lastIndexOf("}");
|
|
57
|
+
if (first !== -1 && last > first)
|
|
58
|
+
candidates.push(text.slice(first, last + 1));
|
|
59
|
+
for (const candidate of candidates) {
|
|
60
|
+
if (!candidate)
|
|
61
|
+
continue;
|
|
62
|
+
try {
|
|
63
|
+
const parsed = JSON.parse(candidate.trim());
|
|
64
|
+
if (typeof parsed === "object" && parsed !== null)
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Try the next candidate shape.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
export class CanvasEnrichmentCoordinator {
|
|
74
|
+
constructor(options) {
|
|
75
|
+
this.tasks = options.tasks;
|
|
76
|
+
this.rerender = options.rerender ?? (async (cwd, workflow) => void (await renderWorkflowRenderFile(cwd, workflow)));
|
|
77
|
+
this.extractCached = options.extractCached ?? extractWorkflowGraphCached;
|
|
78
|
+
this.env = options.env ?? process.env;
|
|
79
|
+
this.now = options.now ?? (() => new Date().toISOString());
|
|
80
|
+
this.onError = options.onError ?? ((message) => console.error(`[harness] ${message}`));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The bind/create trigger: spawn an enrichment task unless a cached
|
|
84
|
+
* enrichment already matches the workflow's current sources. Silent on
|
|
85
|
+
* every expected refusal — unbound session, failed extraction, fresh
|
|
86
|
+
* cache, an enrichment already in flight for this workflow, a harness
|
|
87
|
+
* with no headless mode, even a spawn failure (logged) — because nothing
|
|
88
|
+
* here was user-initiated and a bind must never fail on enrichment's
|
|
89
|
+
* account.
|
|
90
|
+
*/
|
|
91
|
+
async ensureFresh(session, workflows) {
|
|
92
|
+
const bound = this.resolveBound(session, workflows);
|
|
93
|
+
if (!bound)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
const { result, fingerprint } = await this.extractCached(bound.path);
|
|
97
|
+
if (!result.ok)
|
|
98
|
+
return;
|
|
99
|
+
const entry = await readEnrichmentCacheFile(enrichmentCacheFileFor(session.cwd, bound.path));
|
|
100
|
+
if (entry && entry.sourceFingerprint === fingerprint)
|
|
101
|
+
return;
|
|
102
|
+
await this.spawn(session, bound, result.graph, fingerprint);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err instanceof TaskAlreadyRunningError || err instanceof TaskNotSupportedError)
|
|
106
|
+
return;
|
|
107
|
+
this.onError(`canvas enrichment (auto) failed to start: ${err.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The visualize macro: invalidate both caches, re-render the base diagram
|
|
112
|
+
* immediately (instant feedback, honest content — no leftover enrichment
|
|
113
|
+
* from sources that may have changed), then re-enrich. Refusals propagate
|
|
114
|
+
* to the macros router (TaskAlreadyRunningError → 409,
|
|
115
|
+
* TaskNotSupportedError → 400). Unbound session: just a no-op, matching
|
|
116
|
+
* the deterministic render's own unbound contract.
|
|
117
|
+
*/
|
|
118
|
+
async forceRefresh(session, workflows) {
|
|
119
|
+
const bound = this.resolveBound(session, workflows);
|
|
120
|
+
if (!bound)
|
|
121
|
+
return;
|
|
122
|
+
invalidateExtractionCache(bound.path);
|
|
123
|
+
await removeEnrichmentCacheFile(enrichmentCacheFileFor(session.cwd, bound.path));
|
|
124
|
+
await this.rerender(session.cwd, bound);
|
|
125
|
+
const { result, fingerprint } = await this.extractCached(bound.path);
|
|
126
|
+
if (!result.ok)
|
|
127
|
+
return;
|
|
128
|
+
await this.spawn(session, bound, result.graph, fingerprint);
|
|
129
|
+
}
|
|
130
|
+
resolveBound(session, workflows) {
|
|
131
|
+
return session.boundWorkflowPath ? workflows.find((w) => w.path === session.boundWorkflowPath) : undefined;
|
|
132
|
+
}
|
|
133
|
+
async spawn(session, workflow, graph, fingerprint) {
|
|
134
|
+
const task = await this.tasks.run({
|
|
135
|
+
macroId: ENRICHMENT_MACRO_ID,
|
|
136
|
+
label: ENRICHMENT_TASK_LABEL,
|
|
137
|
+
harnessSessionId: session.id,
|
|
138
|
+
harness: session.harness,
|
|
139
|
+
cwd: session.cwd,
|
|
140
|
+
prompt: buildEnrichmentPrompt(graph, workflow.path),
|
|
141
|
+
workflowPath: workflow.path,
|
|
142
|
+
model: enrichmentModel(this.env),
|
|
143
|
+
maxTurns: ENRICHMENT_MAX_TURNS,
|
|
144
|
+
});
|
|
145
|
+
// Subscribed synchronously right after run() resolves — the process's
|
|
146
|
+
// exit can only arrive via a later event-loop turn, so the terminal
|
|
147
|
+
// status can't be missed.
|
|
148
|
+
const unsubscribe = this.tasks.onStatusChange((update) => {
|
|
149
|
+
if (update.id !== task.id || update.status === "running")
|
|
150
|
+
return;
|
|
151
|
+
unsubscribe();
|
|
152
|
+
if (update.status !== "completed")
|
|
153
|
+
return; // pane shows the failure; nothing to persist
|
|
154
|
+
void this.persist(session.cwd, workflow, graph, fingerprint, update.resultText).catch((err) => {
|
|
155
|
+
this.onError(`canvas enrichment persist failed: ${err.message}`);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
/** Completed task → parse → validate → cache file → re-render. Any parse
|
|
160
|
+
* or validation failure discards the enrichment whole; the base render
|
|
161
|
+
* already on disk stands untouched. */
|
|
162
|
+
async persist(cwd, workflow, graph, fingerprint, resultText) {
|
|
163
|
+
const candidate = resultText ? extractEnrichmentJson(resultText) : null;
|
|
164
|
+
const enrichment = candidate ? parseCanvasEnrichment(normalizeCanvasEnrichmentCandidate(candidate)) : null;
|
|
165
|
+
if (!enrichment) {
|
|
166
|
+
this.onError(`canvas enrichment for ${workflow.path} returned invalid output — discarded, base render stands`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
await writeEnrichmentCacheFile(enrichmentCacheFileFor(cwd, workflow.path), {
|
|
170
|
+
graph,
|
|
171
|
+
enrichment,
|
|
172
|
+
sourceFingerprint: fingerprint,
|
|
173
|
+
enrichedAt: this.now(),
|
|
174
|
+
});
|
|
175
|
+
await this.rerender(cwd, workflow);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=canvas-enrich.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-enrich.js","sourceRoot":"","sources":["../../src/core/canvas-enrich.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { CanvasGraph } from "./canvas-graph.js";
|
|
3
|
+
export declare const ENRICHMENT_LIMITS: {
|
|
4
|
+
readonly summary: 160;
|
|
5
|
+
readonly sublabel: 48;
|
|
6
|
+
readonly description: 120;
|
|
7
|
+
readonly edgeLabel: 32;
|
|
8
|
+
readonly noteCount: 3;
|
|
9
|
+
readonly note: 140;
|
|
10
|
+
readonly groupLabel: 48;
|
|
11
|
+
readonly crossWorkflow: 160;
|
|
12
|
+
};
|
|
13
|
+
export declare const canvasEnrichmentSchema: z.ZodObject<{
|
|
14
|
+
/** One-liner under the panel header. */
|
|
15
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
16
|
+
/** Per-node annotations, keyed by node id. */
|
|
17
|
+
nodeDetails: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
18
|
+
/** Second text line inside the node box. */
|
|
19
|
+
sublabel: z.ZodOptional<z.ZodString>;
|
|
20
|
+
/** Hover tooltip (SVG <title>) — room for a full sentence. */
|
|
21
|
+
description: z.ZodOptional<z.ZodString>;
|
|
22
|
+
}, "strip", z.ZodTypeAny, {
|
|
23
|
+
description?: string | undefined;
|
|
24
|
+
sublabel?: string | undefined;
|
|
25
|
+
}, {
|
|
26
|
+
description?: string | undefined;
|
|
27
|
+
sublabel?: string | undefined;
|
|
28
|
+
}>>>;
|
|
29
|
+
/** Edge annotations (intent/condition names), keyed `"<from>-><to>"`. */
|
|
30
|
+
edgeLabels: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
31
|
+
/** Footer facts worth knowing that don't fit the diagram itself. */
|
|
32
|
+
notes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
33
|
+
layoutHints: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
/** Named clusters rendered as background bands behind member nodes. */
|
|
35
|
+
groups: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
36
|
+
label: z.ZodString;
|
|
37
|
+
nodeIds: z.ZodArray<z.ZodString, "many">;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
label: string;
|
|
40
|
+
nodeIds: string[];
|
|
41
|
+
}, {
|
|
42
|
+
label: string;
|
|
43
|
+
nodeIds: string[];
|
|
44
|
+
}>, "many">>;
|
|
45
|
+
/** Preferred left-to-right node order per layout layer, keyed by the
|
|
46
|
+
* layer index as a string. Reorders WITHIN the computed layer only —
|
|
47
|
+
* it can never move a node between layers. */
|
|
48
|
+
laneOrder: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString, "many">>>;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
groups?: {
|
|
51
|
+
label: string;
|
|
52
|
+
nodeIds: string[];
|
|
53
|
+
}[] | undefined;
|
|
54
|
+
laneOrder?: Record<string, string[]> | undefined;
|
|
55
|
+
}, {
|
|
56
|
+
groups?: {
|
|
57
|
+
label: string;
|
|
58
|
+
nodeIds: string[];
|
|
59
|
+
}[] | undefined;
|
|
60
|
+
laneOrder?: Record<string, string[]> | undefined;
|
|
61
|
+
}>>;
|
|
62
|
+
/** How this workflow ties into the workspace's other workflows. */
|
|
63
|
+
crossWorkflow: z.ZodOptional<z.ZodString>;
|
|
64
|
+
}, "strip", z.ZodTypeAny, {
|
|
65
|
+
summary?: string | undefined;
|
|
66
|
+
nodeDetails?: Record<string, {
|
|
67
|
+
description?: string | undefined;
|
|
68
|
+
sublabel?: string | undefined;
|
|
69
|
+
}> | undefined;
|
|
70
|
+
edgeLabels?: Record<string, string> | undefined;
|
|
71
|
+
notes?: string[] | undefined;
|
|
72
|
+
layoutHints?: {
|
|
73
|
+
groups?: {
|
|
74
|
+
label: string;
|
|
75
|
+
nodeIds: string[];
|
|
76
|
+
}[] | undefined;
|
|
77
|
+
laneOrder?: Record<string, string[]> | undefined;
|
|
78
|
+
} | undefined;
|
|
79
|
+
crossWorkflow?: string | undefined;
|
|
80
|
+
}, {
|
|
81
|
+
summary?: string | undefined;
|
|
82
|
+
nodeDetails?: Record<string, {
|
|
83
|
+
description?: string | undefined;
|
|
84
|
+
sublabel?: string | undefined;
|
|
85
|
+
}> | undefined;
|
|
86
|
+
edgeLabels?: Record<string, string> | undefined;
|
|
87
|
+
notes?: string[] | undefined;
|
|
88
|
+
layoutHints?: {
|
|
89
|
+
groups?: {
|
|
90
|
+
label: string;
|
|
91
|
+
nodeIds: string[];
|
|
92
|
+
}[] | undefined;
|
|
93
|
+
laneOrder?: Record<string, string[]> | undefined;
|
|
94
|
+
} | undefined;
|
|
95
|
+
crossWorkflow?: string | undefined;
|
|
96
|
+
}>;
|
|
97
|
+
export type CanvasEnrichment = z.infer<typeof canvasEnrichmentSchema>;
|
|
98
|
+
export type CanvasLayoutHints = NonNullable<CanvasEnrichment["layoutHints"]>;
|
|
99
|
+
/**
|
|
100
|
+
* Validates a candidate enrichment object. Returns null on ANY schema
|
|
101
|
+
* violation (oversize string, wrong shape, too many notes) — partial
|
|
102
|
+
* salvage is deliberately not attempted, so the contract stays a hard line
|
|
103
|
+
* the prompt can state truthfully: "invalid output is thrown away whole."
|
|
104
|
+
*/
|
|
105
|
+
export declare function parseCanvasEnrichment(value: unknown): CanvasEnrichment | null;
|
|
106
|
+
/**
|
|
107
|
+
* Deterministic repair of the two ways an otherwise-good answer most often
|
|
108
|
+
* misses the contract, applied BEFORE strict validation:
|
|
109
|
+
* - `null` where the schema means "omit the field"
|
|
110
|
+
* - strings a few words over their cap (measured live: sonnet writes
|
|
111
|
+
* excellent content but doesn't count characters — a single 160-char note
|
|
112
|
+
* against the 140 cap discarded 3 of 4 real enrichments whole), truncated
|
|
113
|
+
* to the cap with an ellipsis; extra notes beyond the count cap dropped
|
|
114
|
+
*
|
|
115
|
+
* The bound the caps exist for — the AI can never blow up the layout with
|
|
116
|
+
* unbounded text — is still enforced, just deterministically here instead
|
|
117
|
+
* of by rejection. Everything else (wrong shapes, wrong types, unknown
|
|
118
|
+
* structure) still fails `parseCanvasEnrichment` and discards the
|
|
119
|
+
* enrichment whole.
|
|
120
|
+
*/
|
|
121
|
+
export declare function normalizeCanvasEnrichmentCandidate(value: unknown): unknown;
|
|
122
|
+
/**
|
|
123
|
+
* What one cache file holds. `sourceFingerprint` is canvas-cache.ts's cheap
|
|
124
|
+
* source fingerprint at the moment the enrichment task was SPAWNED — when it
|
|
125
|
+
* no longer matches the workflow's current sources, the base diagram
|
|
126
|
+
* re-renders immediately from fresh extraction while this enrichment stays
|
|
127
|
+
* displayed with a "stale" chip until a re-run replaces it.
|
|
128
|
+
*/
|
|
129
|
+
export interface EnrichmentCacheEntry {
|
|
130
|
+
/** The graph the enrichment was generated against (diagnostic context —
|
|
131
|
+
* renders always use the freshly extracted graph, never this copy). */
|
|
132
|
+
graph: CanvasGraph;
|
|
133
|
+
enrichment: CanvasEnrichment;
|
|
134
|
+
sourceFingerprint: string;
|
|
135
|
+
enrichedAt: string;
|
|
136
|
+
}
|
|
137
|
+
/** Reads + validates one enrichment cache file. Null for missing, unparsable
|
|
138
|
+
* or schema-invalid content — a bad cache degrades to "no enrichment". */
|
|
139
|
+
export declare function readEnrichmentCacheFile(filePath: string): Promise<EnrichmentCacheEntry | null>;
|
|
140
|
+
export declare function writeEnrichmentCacheFile(filePath: string, entry: EnrichmentCacheEntry): Promise<void>;
|
|
141
|
+
/** Deletes one enrichment cache file (the visualize macro's force refresh).
|
|
142
|
+
* Missing file is fine — the goal state is "no cache". */
|
|
143
|
+
export declare function removeEnrichmentCacheFile(filePath: string): Promise<void>;
|
|
144
|
+
//# sourceMappingURL=canvas-enrichment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-enrichment.d.ts","sourceRoot":"","sources":["../../src/core/canvas-enrichment.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,iBAAiB;;;;;;;;;CASpB,CAAC;AAIX,eAAO,MAAM,sBAAsB;IAE/B,wCAAwC;;IAExC,8CAA8C;;QAKtC,4CAA4C;;QAE5C,8DAA8D;;;;;;;;;IAMtE,yEAAyE;;IAEzE,oEAAoE;;;QAIhE,uEAAuE;;;;;;;;;;;QAWvE;;uDAE+C;;;;;;;;;;;;;;;IAKnD,mEAAmE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAG7D,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACtE,MAAM,MAAM,iBAAiB,GAAG,WAAW,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC;AAE7E;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAG7E;AAkBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kCAAkC,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CA6C1E;AAMD;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC;4EACwE;IACxE,KAAK,EAAE,WAAW,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;CACpB;AAmBD;2EAC2E;AAC3E,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAepG;AAED,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3G;AAED;2DAC2D;AAC3D,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/E"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The AI enrichment contract: everything the enrichment task is allowed to
|
|
3
|
+
* add to a deterministic canvas render, as a zod schema. The AI never writes
|
|
4
|
+
* HTML — it returns one JSON object of BOUNDED plain strings (every limit
|
|
5
|
+
* below is a hard cap, not advice) and the renderer decides where they go.
|
|
6
|
+
* Unknown keys are stripped; any parse failure means the whole enrichment is
|
|
7
|
+
* discarded and the base render stands — a malformed answer can degrade
|
|
8
|
+
* nothing but itself.
|
|
9
|
+
*
|
|
10
|
+
* Layout hints are the one structural concession: named groups (rendered as
|
|
11
|
+
* subtle background bands) and per-layer ordering. The renderer applies a
|
|
12
|
+
* hint only when every node id it references actually exists in the graph
|
|
13
|
+
* (see canvas-svg.ts) — ids are validated at render time, not here, because
|
|
14
|
+
* an enrichment can outlive the graph it was written against (the "stale"
|
|
15
|
+
* cache state).
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
export const ENRICHMENT_LIMITS = {
|
|
21
|
+
summary: 160,
|
|
22
|
+
sublabel: 48,
|
|
23
|
+
description: 120,
|
|
24
|
+
edgeLabel: 32,
|
|
25
|
+
noteCount: 3,
|
|
26
|
+
note: 140,
|
|
27
|
+
groupLabel: 48,
|
|
28
|
+
crossWorkflow: 160,
|
|
29
|
+
};
|
|
30
|
+
const bounded = (max) => z.string().max(max);
|
|
31
|
+
export const canvasEnrichmentSchema = z
|
|
32
|
+
.object({
|
|
33
|
+
/** One-liner under the panel header. */
|
|
34
|
+
summary: bounded(ENRICHMENT_LIMITS.summary).optional(),
|
|
35
|
+
/** Per-node annotations, keyed by node id. */
|
|
36
|
+
nodeDetails: z
|
|
37
|
+
.record(z
|
|
38
|
+
.object({
|
|
39
|
+
/** Second text line inside the node box. */
|
|
40
|
+
sublabel: bounded(ENRICHMENT_LIMITS.sublabel).optional(),
|
|
41
|
+
/** Hover tooltip (SVG <title>) — room for a full sentence. */
|
|
42
|
+
description: bounded(ENRICHMENT_LIMITS.description).optional(),
|
|
43
|
+
})
|
|
44
|
+
.strip())
|
|
45
|
+
.optional(),
|
|
46
|
+
/** Edge annotations (intent/condition names), keyed `"<from>-><to>"`. */
|
|
47
|
+
edgeLabels: z.record(bounded(ENRICHMENT_LIMITS.edgeLabel)).optional(),
|
|
48
|
+
/** Footer facts worth knowing that don't fit the diagram itself. */
|
|
49
|
+
notes: z.array(bounded(ENRICHMENT_LIMITS.note)).max(ENRICHMENT_LIMITS.noteCount).optional(),
|
|
50
|
+
layoutHints: z
|
|
51
|
+
.object({
|
|
52
|
+
/** Named clusters rendered as background bands behind member nodes. */
|
|
53
|
+
groups: z
|
|
54
|
+
.array(z
|
|
55
|
+
.object({
|
|
56
|
+
label: bounded(ENRICHMENT_LIMITS.groupLabel),
|
|
57
|
+
nodeIds: z.array(z.string()).min(1),
|
|
58
|
+
})
|
|
59
|
+
.strip())
|
|
60
|
+
.optional(),
|
|
61
|
+
/** Preferred left-to-right node order per layout layer, keyed by the
|
|
62
|
+
* layer index as a string. Reorders WITHIN the computed layer only —
|
|
63
|
+
* it can never move a node between layers. */
|
|
64
|
+
laneOrder: z.record(z.array(z.string()).min(1)).optional(),
|
|
65
|
+
})
|
|
66
|
+
.strip()
|
|
67
|
+
.optional(),
|
|
68
|
+
/** How this workflow ties into the workspace's other workflows. */
|
|
69
|
+
crossWorkflow: bounded(ENRICHMENT_LIMITS.crossWorkflow).optional(),
|
|
70
|
+
})
|
|
71
|
+
.strip();
|
|
72
|
+
/**
|
|
73
|
+
* Validates a candidate enrichment object. Returns null on ANY schema
|
|
74
|
+
* violation (oversize string, wrong shape, too many notes) — partial
|
|
75
|
+
* salvage is deliberately not attempted, so the contract stays a hard line
|
|
76
|
+
* the prompt can state truthfully: "invalid output is thrown away whole."
|
|
77
|
+
*/
|
|
78
|
+
export function parseCanvasEnrichment(value) {
|
|
79
|
+
const parsed = canvasEnrichmentSchema.safeParse(value);
|
|
80
|
+
return parsed.success ? parsed.data : null;
|
|
81
|
+
}
|
|
82
|
+
function clampString(value, max) {
|
|
83
|
+
if (typeof value !== "string" || value.length <= max)
|
|
84
|
+
return value;
|
|
85
|
+
return `${value.slice(0, max - 1)}…`;
|
|
86
|
+
}
|
|
87
|
+
function isRecord(value) {
|
|
88
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
89
|
+
}
|
|
90
|
+
/** Removes null/undefined-valued properties (models write `"field": null`
|
|
91
|
+
* to mean "omitted"; the schema means it as `undefined`). Shallow — called
|
|
92
|
+
* per level below where object shapes are known. */
|
|
93
|
+
function dropNulls(record) {
|
|
94
|
+
return Object.fromEntries(Object.entries(record).filter(([, v]) => v != null));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Deterministic repair of the two ways an otherwise-good answer most often
|
|
98
|
+
* misses the contract, applied BEFORE strict validation:
|
|
99
|
+
* - `null` where the schema means "omit the field"
|
|
100
|
+
* - strings a few words over their cap (measured live: sonnet writes
|
|
101
|
+
* excellent content but doesn't count characters — a single 160-char note
|
|
102
|
+
* against the 140 cap discarded 3 of 4 real enrichments whole), truncated
|
|
103
|
+
* to the cap with an ellipsis; extra notes beyond the count cap dropped
|
|
104
|
+
*
|
|
105
|
+
* The bound the caps exist for — the AI can never blow up the layout with
|
|
106
|
+
* unbounded text — is still enforced, just deterministically here instead
|
|
107
|
+
* of by rejection. Everything else (wrong shapes, wrong types, unknown
|
|
108
|
+
* structure) still fails `parseCanvasEnrichment` and discards the
|
|
109
|
+
* enrichment whole.
|
|
110
|
+
*/
|
|
111
|
+
export function normalizeCanvasEnrichmentCandidate(value) {
|
|
112
|
+
if (!isRecord(value))
|
|
113
|
+
return value;
|
|
114
|
+
const L = ENRICHMENT_LIMITS;
|
|
115
|
+
const out = { ...value };
|
|
116
|
+
out.summary = clampString(out.summary, L.summary);
|
|
117
|
+
out.crossWorkflow = clampString(out.crossWorkflow, L.crossWorkflow);
|
|
118
|
+
if (isRecord(out.nodeDetails)) {
|
|
119
|
+
out.nodeDetails = Object.fromEntries(Object.entries(out.nodeDetails).map(([id, details]) => {
|
|
120
|
+
if (!isRecord(details))
|
|
121
|
+
return [id, details];
|
|
122
|
+
return [
|
|
123
|
+
id,
|
|
124
|
+
dropNulls({
|
|
125
|
+
...details,
|
|
126
|
+
sublabel: clampString(details.sublabel, L.sublabel),
|
|
127
|
+
description: clampString(details.description, L.description),
|
|
128
|
+
}),
|
|
129
|
+
];
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
if (isRecord(out.edgeLabels)) {
|
|
133
|
+
out.edgeLabels = Object.fromEntries(Object.entries(out.edgeLabels).map(([key, label]) => [key, clampString(label, L.edgeLabel)]));
|
|
134
|
+
}
|
|
135
|
+
if (Array.isArray(out.notes)) {
|
|
136
|
+
out.notes = out.notes.slice(0, L.noteCount).map((note) => clampString(note, L.note));
|
|
137
|
+
}
|
|
138
|
+
if (isRecord(out.layoutHints)) {
|
|
139
|
+
const hints = dropNulls({ ...out.layoutHints });
|
|
140
|
+
if (Array.isArray(hints.groups)) {
|
|
141
|
+
hints.groups = hints.groups.map((group) => isRecord(group) ? dropNulls({ ...group, label: clampString(group.label, L.groupLabel) }) : group);
|
|
142
|
+
}
|
|
143
|
+
out.layoutHints = hints;
|
|
144
|
+
}
|
|
145
|
+
return dropNulls(out);
|
|
146
|
+
}
|
|
147
|
+
/** Envelope validation is deliberately shallow for `graph` (it's our own
|
|
148
|
+
* writer's serialization, not AI output) and strict for `enrichment` (it IS
|
|
149
|
+
* AI output — re-validated on every read, so a hand-edited or corrupted
|
|
150
|
+
* cache file can never smuggle unbounded strings into a render). */
|
|
151
|
+
const cacheEntrySchema = z.object({
|
|
152
|
+
graph: z.object({
|
|
153
|
+
manifestName: z.string(),
|
|
154
|
+
entry: z.string(),
|
|
155
|
+
nodes: z.array(z.record(z.unknown())),
|
|
156
|
+
edges: z.array(z.record(z.unknown())),
|
|
157
|
+
warnings: z.array(z.string()),
|
|
158
|
+
}),
|
|
159
|
+
enrichment: canvasEnrichmentSchema,
|
|
160
|
+
sourceFingerprint: z.string(),
|
|
161
|
+
enrichedAt: z.string(),
|
|
162
|
+
});
|
|
163
|
+
/** Reads + validates one enrichment cache file. Null for missing, unparsable
|
|
164
|
+
* or schema-invalid content — a bad cache degrades to "no enrichment". */
|
|
165
|
+
export async function readEnrichmentCacheFile(filePath) {
|
|
166
|
+
let raw;
|
|
167
|
+
try {
|
|
168
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const parsed = cacheEntrySchema.safeParse(JSON.parse(raw));
|
|
175
|
+
// The shallow node/edge validation above means this cast, not a full
|
|
176
|
+
// structural proof — see the schema's own comment for why that's enough.
|
|
177
|
+
return parsed.success ? parsed.data : null;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export async function writeEnrichmentCacheFile(filePath, entry) {
|
|
184
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
185
|
+
await fs.writeFile(filePath, JSON.stringify(entry, null, 2), "utf8");
|
|
186
|
+
}
|
|
187
|
+
/** Deletes one enrichment cache file (the visualize macro's force refresh).
|
|
188
|
+
* Missing file is fine — the goal state is "no cache". */
|
|
189
|
+
export async function removeEnrichmentCacheFile(filePath) {
|
|
190
|
+
await fs.rm(filePath, { force: true });
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=canvas-enrichment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canvas-enrichment.js","sourceRoot":"","sources":["../../src/core/canvas-enrichment.ts"],"names":[],"mappings":"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"}
|