pmx-canvas 0.3.0 → 0.3.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/CHANGELOG.md +38 -0
- package/dist/canvas/index.js +2 -2
- package/dist/types/cli/daemon.d.ts +74 -0
- package/dist/types/cli/watch.d.ts +2 -2
- package/dist/types/client/canvas/CanvasViewport.d.ts +1 -1
- package/dist/types/client/canvas/CommandPalette.d.ts +1 -1
- package/dist/types/client/canvas/Minimap.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +1 -1
- package/dist/types/client/nodes/ImageNode.d.ts +1 -1
- package/dist/types/client/nodes/InlineFormatBar.d.ts +1 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +1 -1
- package/dist/types/client/nodes/PromptNode.d.ts +1 -1
- package/dist/types/client/nodes/ResponseNode.d.ts +1 -1
- package/dist/types/server/canvas-schema.d.ts +1 -1
- package/dist/types/server/html-primitives.d.ts +1 -1
- package/dist/types/server/index.d.ts +4 -4
- package/dist/types/server/operations/index.d.ts +1 -1
- package/dist/types/server/operations/ops/ax-read.d.ts +2 -0
- package/dist/types/server/operations/ops/canvas-wire.d.ts +2 -0
- package/dist/types/server/operations/ops/ext-app.d.ts +2 -0
- package/docs/http-api.md +28 -0
- package/docs/plans/plan-009-tech-debt-backlog.md +5 -5
- package/docs/tech-debt-assessment-2026-07.md +2 -2
- package/package.json +5 -2
- package/skills/pmx-canvas/SKILL.md +3 -1
- package/skills/pmx-canvas/references/full-reference.md +10 -3
- package/src/cli/agent.ts +1861 -1548
- package/src/cli/daemon.ts +460 -0
- package/src/cli/index.ts +63 -326
- package/src/cli/watch.ts +2 -10
- package/src/client/App.tsx +48 -46
- package/src/client/canvas/AttentionHistory.tsx +11 -1
- package/src/client/canvas/CanvasNode.tsx +41 -29
- package/src/client/canvas/CanvasViewport.tsx +101 -66
- package/src/client/canvas/CommandPalette.tsx +61 -27
- package/src/client/canvas/ContextMenu.tsx +13 -20
- package/src/client/canvas/ContextPinBar.tsx +1 -5
- package/src/client/canvas/ContextPinHud.tsx +1 -6
- package/src/client/canvas/DockedNode.tsx +4 -4
- package/src/client/canvas/EdgeLayer.tsx +37 -36
- package/src/client/canvas/ExpandedNodeOverlay.tsx +69 -45
- package/src/client/canvas/FocusFieldLayer.tsx +20 -22
- package/src/client/canvas/IntentLayer.tsx +31 -14
- package/src/client/canvas/Minimap.tsx +11 -16
- package/src/client/canvas/SelectionBar.tsx +4 -11
- package/src/client/canvas/ShortcutOverlay.tsx +3 -1
- package/src/client/canvas/SnapshotPanel.tsx +77 -95
- package/src/client/canvas/auto-fit.ts +15 -14
- package/src/client/canvas/snap-guides.ts +12 -12
- package/src/client/canvas/use-node-resize.ts +1 -5
- package/src/client/canvas/use-pan-zoom.ts +25 -26
- package/src/client/ext-app/bridge.ts +3 -12
- package/src/client/icons.tsx +63 -20
- package/src/client/nodes/ContextNode.tsx +14 -25
- package/src/client/nodes/ExtAppFrame.tsx +60 -39
- package/src/client/nodes/FileNode.tsx +74 -62
- package/src/client/nodes/GroupNode.tsx +4 -6
- package/src/client/nodes/HtmlNode.tsx +76 -46
- package/src/client/nodes/ImageNode.tsx +18 -27
- package/src/client/nodes/InlineFormatBar.tsx +4 -21
- package/src/client/nodes/InlineMarkdownEditor.tsx +0 -1
- package/src/client/nodes/LedgerNode.tsx +10 -4
- package/src/client/nodes/MarkdownNode.tsx +3 -10
- package/src/client/nodes/McpAppNode.tsx +26 -22
- package/src/client/nodes/MdFormatBar.tsx +10 -7
- package/src/client/nodes/PromptNode.tsx +23 -51
- package/src/client/nodes/ResponseNode.tsx +3 -13
- package/src/client/nodes/StatusNode.tsx +5 -9
- package/src/client/nodes/StatusSummary.tsx +2 -8
- package/src/client/nodes/WebpageNode.tsx +20 -14
- package/src/client/nodes/iframe-document-url.ts +25 -16
- package/src/client/nodes/image-warnings.ts +1 -7
- package/src/client/nodes/md-format.ts +20 -5
- package/src/client/state/attention-bridge.ts +4 -9
- package/src/client/state/attention-store.ts +1 -7
- package/src/client/state/canvas-store.ts +52 -36
- package/src/client/state/intent-bridge.ts +176 -112
- package/src/client/state/intent-store.ts +4 -1
- package/src/client/state/sse-bridge.ts +53 -70
- package/src/json-render/catalog.ts +12 -16
- package/src/json-render/charts/components.tsx +16 -20
- package/src/json-render/charts/extra-components.tsx +8 -16
- package/src/json-render/charts/extra-definitions.ts +1 -2
- package/src/json-render/charts/tufte-components.tsx +37 -20
- package/src/json-render/charts/tufte-definitions.ts +8 -2
- package/src/json-render/renderer/index.tsx +42 -22
- package/src/json-render/schema.ts +6 -3
- package/src/json-render/server.ts +33 -39
- package/src/mcp/canvas-access.ts +35 -21
- package/src/mcp/server.ts +132 -70
- package/src/server/agent-context.ts +63 -36
- package/src/server/ax-context.ts +7 -5
- package/src/server/ax-interaction.ts +176 -43
- package/src/server/ax-state-manager.ts +182 -39
- package/src/server/ax-state.ts +142 -47
- package/src/server/canvas-db.ts +213 -95
- package/src/server/canvas-operations.ts +177 -120
- package/src/server/canvas-provenance.ts +1 -4
- package/src/server/canvas-schema.ts +454 -73
- package/src/server/canvas-serialization.ts +27 -35
- package/src/server/canvas-state.ts +150 -58
- package/src/server/chart-template.ts +4 -5
- package/src/server/code-graph.ts +19 -6
- package/src/server/diagram-presets.ts +28 -29
- package/src/server/ext-app-lookup.ts +3 -12
- package/src/server/html-node-summary.ts +19 -10
- package/src/server/html-primitives.ts +326 -97
- package/src/server/html-surface.ts +6 -9
- package/src/server/image-source.ts +6 -4
- package/src/server/index.ts +320 -217
- package/src/server/intent-registry.ts +2 -5
- package/src/server/mcp-app-candidate.ts +5 -10
- package/src/server/mcp-app-host.ts +14 -38
- package/src/server/mcp-app-runtime.ts +12 -20
- package/src/server/mutation-history.ts +15 -5
- package/src/server/operations/composites.ts +1 -3
- package/src/server/operations/http.ts +2 -3
- package/src/server/operations/index.ts +7 -1
- package/src/server/operations/invoker.ts +4 -3
- package/src/server/operations/mcp.ts +22 -30
- package/src/server/operations/ops/annotation.ts +122 -10
- package/src/server/operations/ops/app.ts +98 -73
- package/src/server/operations/ops/ax-await.ts +17 -10
- package/src/server/operations/ops/ax-read.ts +347 -0
- package/src/server/operations/ops/ax-shared.ts +2 -7
- package/src/server/operations/ops/ax-state.ts +32 -14
- package/src/server/operations/ops/ax-timeline.ts +32 -19
- package/src/server/operations/ops/ax-work.ts +54 -37
- package/src/server/operations/ops/batch.ts +39 -14
- package/src/server/operations/ops/canvas-wire.ts +91 -0
- package/src/server/operations/ops/edges.ts +37 -25
- package/src/server/operations/ops/ext-app.ts +346 -0
- package/src/server/operations/ops/groups.ts +49 -20
- package/src/server/operations/ops/intent.ts +18 -12
- package/src/server/operations/ops/json-render.ts +239 -98
- package/src/server/operations/ops/nodes.ts +298 -109
- package/src/server/operations/ops/query.ts +46 -28
- package/src/server/operations/ops/snapshots.ts +35 -26
- package/src/server/operations/ops/validate.ts +2 -1
- package/src/server/operations/ops/viewport.ts +60 -16
- package/src/server/operations/ops/webview.ts +44 -18
- package/src/server/operations/registry.ts +2 -3
- package/src/server/operations/types.ts +7 -5
- package/src/server/operations/webview-runner.ts +1 -3
- package/src/server/placement.ts +8 -18
- package/src/server/server.ts +108 -1027
- package/src/server/spatial-analysis.ts +39 -25
- package/src/server/trace-manager.ts +3 -8
- package/src/server/web-artifacts.ts +23 -27
- package/src/server/webpage-node.ts +5 -13
- package/src/shared/auto-arrange.ts +12 -5
- package/src/shared/content-height-reporter.ts +8 -6
- package/src/shared/ext-app-tool-result.ts +2 -6
- package/src/shared/placement.ts +1 -4
- package/src/shared/semantic-attention.ts +39 -37
- package/src/shared/surface.ts +8 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export declare function outputJson(data: unknown): void;
|
|
2
|
+
export declare function readPidFile(path: string): number | null;
|
|
3
|
+
export declare function isProcessRunning(pid: number): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Whether pid's command line contains `needle`. Returns null when `ps` cannot
|
|
6
|
+
* answer (no such process, unsupported platform) — callers fall back to the
|
|
7
|
+
* plain liveness signal.
|
|
8
|
+
*/
|
|
9
|
+
export declare function processCommandMatches(pid: number, needle: string): boolean | null;
|
|
10
|
+
/**
|
|
11
|
+
* Liveness with a PID-recycling guard: the pid must be alive AND its command
|
|
12
|
+
* line must still look like our daemon (unless `ps` is unavailable, in which
|
|
13
|
+
* case the plain liveness signal wins).
|
|
14
|
+
*/
|
|
15
|
+
export declare function isOwnDaemonProcess(pid: number, entryNeedle: string): boolean;
|
|
16
|
+
export declare function removePidFile(path: string): void;
|
|
17
|
+
export interface HealthStatus {
|
|
18
|
+
responsive: boolean;
|
|
19
|
+
workspace: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function readHealthStatus(url: string): Promise<HealthStatus>;
|
|
22
|
+
export declare function isHealthy(url: string): Promise<boolean>;
|
|
23
|
+
export declare function readLogTail(path: string, maxLines?: number): string | null;
|
|
24
|
+
export declare function waitForHealth(healthUrl: string, timeoutMs: number, getExitMessage: () => string | null): Promise<{
|
|
25
|
+
ok: true;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: string;
|
|
29
|
+
}>;
|
|
30
|
+
export declare function waitForShutdown(healthUrl: string, timeoutMs: number, isAlive: () => boolean): Promise<boolean>;
|
|
31
|
+
/** Workspace the daemon child will bind — mirrors startCanvasServer's resolution. */
|
|
32
|
+
export declare function resolveExpectedWorkspace(): string;
|
|
33
|
+
export type PrecheckVerdict = 'not-running' | 'already-running' | 'foreign-port-owner';
|
|
34
|
+
/** Pure decision for the pre-spawn health probe. */
|
|
35
|
+
export declare function classifyPrecheck(health: HealthStatus, expectedWorkspace: string): PrecheckVerdict;
|
|
36
|
+
export type LockResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
holderPid: number | null;
|
|
41
|
+
reason: 'running' | 'starting';
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Exclusive pid-file creation IS the spawn lock. A held file with a live
|
|
45
|
+
* daemon pid means 'running'; a fresh empty file means another starter is
|
|
46
|
+
* mid-spawn ('starting'); anything stale is reclaimed once.
|
|
47
|
+
*/
|
|
48
|
+
export declare function acquireDaemonLock(pidFile: string, entryNeedle: string): LockResult;
|
|
49
|
+
/**
|
|
50
|
+
* True when the pid file is a concurrent starter's spawn lock: an EMPTY file
|
|
51
|
+
* (acquireDaemonLock creates it before writing the child pid) younger than
|
|
52
|
+
* LOCK_FRESH_MS. `serve status` must NOT delete such a file — clearing it
|
|
53
|
+
* mid-spawn would defeat the spawn lock and let a racing starter double-spawn.
|
|
54
|
+
* Mirrors the fresh-lock protection acquireDaemonLock applies. A stat failure
|
|
55
|
+
* biases toward preserving the lock.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isFreshEmptyLock(pidFile: string): boolean;
|
|
58
|
+
export interface DaemonPaths {
|
|
59
|
+
port: number;
|
|
60
|
+
logFile: string;
|
|
61
|
+
pidFile: string;
|
|
62
|
+
}
|
|
63
|
+
export declare function startDaemonMode(options: DaemonPaths & {
|
|
64
|
+
baseArgs: string[];
|
|
65
|
+
waitMs: number;
|
|
66
|
+
entry: string;
|
|
67
|
+
}): Promise<void>;
|
|
68
|
+
export declare function showServeStatus(options: DaemonPaths & {
|
|
69
|
+
entry: string;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
export declare function stopServeDaemon(options: DaemonPaths & {
|
|
72
|
+
waitMs: number;
|
|
73
|
+
entry: string;
|
|
74
|
+
}): Promise<void>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer, type SemanticWatchEvent, type SemanticWatchEventType, type SseMessage } from '../shared/semantic-attention.js';
|
|
2
|
-
export { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer
|
|
3
|
-
export type { SemanticWatchEvent, SemanticWatchEventType, SseMessage
|
|
2
|
+
export { ALL_SEMANTIC_WATCH_EVENT_TYPES, formatCompactWatchEvent, SemanticWatchReducer };
|
|
3
|
+
export type { SemanticWatchEvent, SemanticWatchEventType, SseMessage };
|
|
4
4
|
export declare function parseSemanticEventFilter(raw: string | undefined): Set<SemanticWatchEventType>;
|
|
5
5
|
export declare function parseSseStream(stream: ReadableStream<Uint8Array>): AsyncGenerator<SseMessage>;
|
|
@@ -7,5 +7,5 @@ interface CanvasViewportProps {
|
|
|
7
7
|
annotationTool?: AnnotationTool;
|
|
8
8
|
}
|
|
9
9
|
export declare function getRenderableWorldNodes(allNodes: Iterable<CanvasNodeState>, focusedNodeId: string | null): CanvasNodeState[];
|
|
10
|
-
export declare function CanvasViewport({ onNodeContextMenu, onCanvasContextMenu, annotationMode, annotationTool }: CanvasViewportProps): import("preact/src").JSX.Element;
|
|
10
|
+
export declare function CanvasViewport({ onNodeContextMenu, onCanvasContextMenu, annotationMode, annotationTool, }: CanvasViewportProps): import("preact/src").JSX.Element;
|
|
11
11
|
export {};
|
|
@@ -19,5 +19,5 @@ interface MinimapProps {
|
|
|
19
19
|
containerWidth: number;
|
|
20
20
|
containerHeight: number;
|
|
21
21
|
}
|
|
22
|
-
export declare function Minimap({ viewport, nodes, edges, onNavigate, containerWidth, containerHeight
|
|
22
|
+
export declare function Minimap({ viewport, nodes, edges, onNavigate, containerWidth, containerHeight }: MinimapProps): import("preact/src").JSX.Element;
|
|
23
23
|
export {};
|
|
@@ -4,7 +4,7 @@ import type { CanvasNodeState } from '../types';
|
|
|
4
4
|
* Supports: file paths (served via /api/canvas/image/:nodeId), data URIs, and URLs.
|
|
5
5
|
* Features: fit-to-container, zoom in/out within node, pan when zoomed.
|
|
6
6
|
*/
|
|
7
|
-
export declare function ImageNode({ node, expanded
|
|
7
|
+
export declare function ImageNode({ node, expanded }: {
|
|
8
8
|
node: CanvasNodeState;
|
|
9
9
|
expanded?: boolean;
|
|
10
10
|
}): import("preact/src").JSX.Element;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { RefObject } from 'preact';
|
|
2
2
|
/** Floating selection toolbar for a contentEditable host. Mounts once; while
|
|
3
3
|
* visible, positions itself above the current selection's viewport rect. */
|
|
4
|
-
export declare function InlineFormatBar({ hostRef, onChange
|
|
4
|
+
export declare function InlineFormatBar({ hostRef, onChange }: {
|
|
5
5
|
hostRef: RefObject<HTMLElement>;
|
|
6
6
|
onChange: () => void;
|
|
7
7
|
}): import("preact").JSX.Element | null;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { JSX } from 'preact';
|
|
2
2
|
import type { CanvasNodeState } from '../types';
|
|
3
|
-
export declare function MarkdownNode({ node, expanded
|
|
3
|
+
export declare function MarkdownNode({ node, expanded }: {
|
|
4
4
|
node: CanvasNodeState;
|
|
5
5
|
expanded?: boolean;
|
|
6
6
|
}): JSX.Element;
|
|
@@ -34,7 +34,7 @@ export interface StructuredValidationResult {
|
|
|
34
34
|
summary: Record<string, unknown>;
|
|
35
35
|
}
|
|
36
36
|
declare const CANONICAL_GRAPH_TYPES: readonly ["line", "bar", "pie", "area", "scatter", "radar", "stacked-bar", "composed", "sparkline", "dot-plot", "bullet", "slopegraph"];
|
|
37
|
-
type CanvasGraphType = typeof CANONICAL_GRAPH_TYPES[number];
|
|
37
|
+
type CanvasGraphType = (typeof CANONICAL_GRAPH_TYPES)[number];
|
|
38
38
|
export declare function describeCanvasSchema(): {
|
|
39
39
|
ok: true;
|
|
40
40
|
source: 'running-server';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const HTML_PRIMITIVE_KINDS: readonly ["choice-grid", "plan-timeline", "review-sheet", "pr-writeup", "system-map", "code-walkthrough", "design-sheet", "component-gallery", "interaction-prototype", "flowchart", "deck", "presentation", "illustration-set", "explainer", "status-report", "incident-report", "triage-board", "config-editor", "prompt-tuner"];
|
|
2
|
-
export type HtmlPrimitiveKind = typeof HTML_PRIMITIVE_KINDS[number];
|
|
2
|
+
export type HtmlPrimitiveKind = (typeof HTML_PRIMITIVE_KINDS)[number];
|
|
3
3
|
export interface HtmlPrimitiveDescriptor {
|
|
4
4
|
kind: HtmlPrimitiveKind;
|
|
5
5
|
title: string;
|
|
@@ -13,7 +13,7 @@ import { type SerializedCanvasNode } from './canvas-serialization.js';
|
|
|
13
13
|
import type { HtmlPrimitiveKind } from './html-primitives.js';
|
|
14
14
|
import { type WebArtifactBuildInput, type WebArtifactCanvasBuildResult } from './web-artifacts.js';
|
|
15
15
|
import { type ExternalMcpTransportConfig } from './mcp-app-runtime.js';
|
|
16
|
-
import {
|
|
16
|
+
import type { DiagramPresetOpenInput } from './diagram-presets.js';
|
|
17
17
|
import { type GraphNodeInput, type JsonRenderNodeInput, type JsonRenderSpec } from '../json-render/server.js';
|
|
18
18
|
import type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus } from './server.js';
|
|
19
19
|
/**
|
|
@@ -557,21 +557,21 @@ export type { CanvasNodeState, CanvasEdge, CanvasLayout, ViewportState } from '.
|
|
|
557
557
|
export type { CanvasAutomationWebViewOptions, CanvasAutomationWebViewStatus, PrimaryWorkbenchCanvasPromptRequest, PrimaryWorkbenchIntent, } from './server.js';
|
|
558
558
|
export { emitPrimaryWorkbenchEvent, consumePrimaryWorkbenchIntents, setPrimaryWorkbenchAutoOpenEnabled, setPrimaryWorkbenchCanvasPromptHandler, startCanvasServer, stopCanvasServer, getCanvasServerPort, openUrlInExternalBrowser, getCanvasAutomationWebViewStatus, startCanvasAutomationWebView, stopCanvasAutomationWebView, evaluateCanvasAutomationWebView, resizeCanvasAutomationWebView, screenshotCanvasAutomationWebView, } from './server.js';
|
|
559
559
|
export { canvasState } from './canvas-state.js';
|
|
560
|
-
export type { CanvasAnnotation, CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions } from './canvas-state.js';
|
|
560
|
+
export type { CanvasAnnotation, CanvasSnapshot, CanvasSnapshotGcResult, CanvasSnapshotListOptions, } from './canvas-state.js';
|
|
561
561
|
export { findOpenCanvasPosition } from './placement.js';
|
|
562
562
|
export { searchNodes, buildSpatialContext, detectClusters, findNeighborhoods } from './spatial-analysis.js';
|
|
563
563
|
export type { SpatialCluster, SpatialContext, SpatialNeighbor, NodeSpatialInfo } from './spatial-analysis.js';
|
|
564
564
|
export { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
|
|
565
565
|
export { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
|
|
566
566
|
export { describeCanvasSchema, validateStructuredCanvasPayload } from './canvas-schema.js';
|
|
567
|
-
export { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors } from './html-primitives.js';
|
|
567
|
+
export { buildHtmlPrimitive, getHtmlPrimitiveSemanticMetadata, isHtmlPrimitiveKind, listHtmlPrimitiveDescriptors, } from './html-primitives.js';
|
|
568
568
|
export { buildWebArtifactOnCanvas, executeWebArtifactBuild, openWebArtifactInCanvas, resolveWebArtifactScriptPath, resolveWorkspacePath, } from './web-artifacts.js';
|
|
569
569
|
export { buildGraphSpec, buildJsonRenderViewerHtml, createJsonRenderNodeData, GRAPH_NODE_SIZE, JSON_RENDER_NODE_SIZE, normalizeAndValidateJsonRenderSpec, } from '../json-render/server.js';
|
|
570
570
|
export type { CodeGraphSummary, CodeGraphEdge } from './code-graph.js';
|
|
571
571
|
export type { MutationEntry, MutationSummary, SnapshotDiffResult } from './mutation-history.js';
|
|
572
572
|
export type { WebArtifactBuildInput, WebArtifactBuildOutput, WebArtifactCanvasBuildResult, WebArtifactCanvasOpenResult, } from './web-artifacts.js';
|
|
573
573
|
export type { GraphNodeInput, JsonRenderNodeInput, JsonRenderSpec } from '../json-render/server.js';
|
|
574
|
-
export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult } from './html-primitives.js';
|
|
574
|
+
export type { HtmlPrimitiveKind, HtmlPrimitiveDescriptor, HtmlPrimitiveInput, HtmlPrimitiveBuildResult, } from './html-primitives.js';
|
|
575
575
|
export { traceManager } from './trace-manager.js';
|
|
576
576
|
export type { PmxAxApprovalGate, PmxAxApprovalStatus, PmxAxCommandDescriptor, PmxAxContext, PmxAxEvent, PmxAxElicitation, PmxAxElicitationStatus, PmxAxEventKind, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxModeRequestStatus, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxTimelineSummary, PmxAxWorkItem, PmxAxWorkItemStatus, } from './ax-state.js';
|
|
577
577
|
export type { AxTimelineQuery } from './canvas-db.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { executeOperation, getOperation, listOperations, registerOperation, setOperationEventEmitter, } from './registry.js';
|
|
2
2
|
export { dispatchOperationRoute } from './http.js';
|
|
3
3
|
export { runCanvasBatchOperation, type BatchEnvelope } from './ops/batch.js';
|
|
4
|
-
export {
|
|
4
|
+
export type { OpenMcpAppCoreResult } from './ops/app.js';
|
|
5
5
|
export { LocalOperationInvoker, HttpOperationInvoker, type OperationInvoker } from './invoker.js';
|
|
6
6
|
export { registerOperationTools, registerCompositeTools, type OperationToolHost } from './mcp.js';
|
|
7
7
|
export { compositeToolDefinitions, type CompositeToolDefinition } from './composites.js';
|
package/docs/http-api.md
CHANGED
|
@@ -276,6 +276,34 @@ and `/ax/mode/:id/resolve` return 404 for unknown IDs; `/ax/activity` requires a
|
|
|
276
276
|
valid `kind` + `title` (400 otherwise); the single-item gate GETs return 404 for
|
|
277
277
|
unknown IDs and clamp `?waitMs` to ≤120000.
|
|
278
278
|
|
|
279
|
+
## Ghost Cursor intents (ephemeral)
|
|
280
|
+
|
|
281
|
+
Intents are verb-routed over HTTP — there is **no `action` field**. That
|
|
282
|
+
discriminator exists only on the MCP `canvas_intent` composite; `POST` always
|
|
283
|
+
signals a *new* intent, so a body like `{"action":"clear"}` is rejected by the
|
|
284
|
+
registry's kind validation. Pick the operation by method + path:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Signal a ghost — returns { ok, intent } with the id. Auto-expires after ttlMs
|
|
288
|
+
# (default 8000, max 60000), so cleanup is optional.
|
|
289
|
+
curl -X POST http://localhost:4313/api/canvas/ax/intent \
|
|
290
|
+
-H "Content-Type: application/json" \
|
|
291
|
+
-d '{"kind":"create","position":{"x":400,"y":300},"nodeType":"markdown","label":"Add evidence","reason":"collecting run logs"}'
|
|
292
|
+
|
|
293
|
+
# Update the ghost (position/label/reason/confidence/ttlMs; vetoed:true dissolves
|
|
294
|
+
# it AND poisons the id so a later linked settle is rejected)
|
|
295
|
+
curl -X PATCH http://localhost:4313/api/canvas/ax/intent/<id> \
|
|
296
|
+
-H "Content-Type: application/json" \
|
|
297
|
+
-d '{"position":{"x":520,"y":300},"reason":"moved next to the trace"}'
|
|
298
|
+
|
|
299
|
+
# Clear the ghost: plain DELETE dissolves it; settle it into the real node it
|
|
300
|
+
# became via settledNodeId, or veto it. Fields are accepted as JSON body or
|
|
301
|
+
# query params (?vetoed=true/false is coerced; other values are rejected 400).
|
|
302
|
+
curl -X DELETE http://localhost:4313/api/canvas/ax/intent/<id>
|
|
303
|
+
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?settledNodeId=node-42"
|
|
304
|
+
curl -X DELETE "http://localhost:4313/api/canvas/ax/intent/<id>?vetoed=true"
|
|
305
|
+
```
|
|
306
|
+
|
|
279
307
|
## Diagrams (Excalidraw preset)
|
|
280
308
|
|
|
281
309
|
```bash
|
|
@@ -10,9 +10,9 @@ Each item is tagged by shape: **project** (multi-day, own plan/PR), **decision**
|
|
|
10
10
|
|
|
11
11
|
| Item | Source | What's open | Shape |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| Complete the registry migration | C1 | Fold the
|
|
13
|
+
| Complete the registry migration | C1 | Fold the remaining hand-written routes in `server.ts` into operations; collapse the 76-method `PmxCanvas` onto `executeOperation`; migrate the CLI's 60 raw `fetch` calls onto the operation invoker. **Slice 1 done post-0.3.0:** the 7 `/api/ext-app/*` routes moved to `ops/ext-app.ts` (~290 lines out of `server.ts`, the 5 clone handlers — old M5 — collapsed to one parametrized definition; wire envelopes byte-identical, no new MCP tools). **Slice 2 done:** the 15 ax wire routes (8 GET lists + `ax/context` + `ax/surface-snapshot` + `pinned-context` + `code-graph` + `ax/activity` + `ax/interaction` + legacy `PATCH /api/canvas/ax`) moved to `ops/ax-read.ts` (~370 more lines out of `server.ts`); `open-external` stays hand-written (coupled to the live server port + system browser opener). **Slice 3 done:** theme GET/POST, canvas update, viewport, annotation POST, and node `:id/refresh` moved to the registry (`ops/canvas-wire.ts`, plus additions to `ops/viewport.ts`, `ops/annotation.ts`, `ops/nodes.ts`); the two dispatch gates unified into one `/api/` gate; `server.ts` is at ~2,980 lines (from 3,873 at the audit). **Remaining routes (~20)** are the deliberate poor fits (SSE, binary, HTML/static, file bodies) plus four server-coupled handlers: `prompt` + workbench `state`/`open`/`intent` (module-local `primaryWorkbench*` session state), `render` (Excalidraw URL-normalizer chain), `open-external` (live port + system browser). Those need state injection like the webview runner — a deliberate follow-up, not mechanical. After routes, the bigger halves remain: `PmxCanvas` (76 methods) onto `executeOperation`, and the CLI's raw fetches onto the invoker. | project (in progress) |
|
|
14
14
|
| One HTTP envelope | M4 residual | Registry ops return bare bodies, legacy ext-app handlers `{ok:true,result}`, 404s plaintext. Unify (naturally falls out of C1). Also: surface persistence save failures via a `/health` degraded flag — `canvas-state.ts` has 63 catch→warn→continue sites. | project (with C1) |
|
|
15
|
-
| `dist/` commit policy | C3 residual |
|
|
15
|
+
| `dist/` commit policy | C3 residual | **Decided + done post-0.3.0:** `dist/` stays committed; `test.yml` now has a `dist/types` staleness gate (rebuild + fail on drift). The `dist/canvas`/`dist/json-render` bundles are deliberately not byte-checked — bundler output varies across bun versions; their staleness story folds into M1. | done |
|
|
16
16
|
| Skill-mirror validator | C2 residual | `validate:agent-skills` exits 0 when the (local-only, gitignored) trees are absent. Docs are honest about this now; decide whether to repoint, gate, or delete the check. | decision |
|
|
17
17
|
| Generate CLAUDE.md/AGENTS.md from one source | M9 residual | The twins are byte-identical again but still hand-maintained; generate or symlink to prevent the next drift. | small |
|
|
18
18
|
|
|
@@ -20,10 +20,10 @@ Each item is tagged by shape: **project** (multi-day, own plan/PR), **decision**
|
|
|
20
20
|
|
|
21
21
|
| Item | Source | What's open | Shape |
|
|
22
22
|
|---|---|---|---|
|
|
23
|
-
| Lint/format tooling | H6 |
|
|
23
|
+
| Lint/format tooling | H6 | **Done post-0.3.0:** Biome 2.5 adopted — `bun run lint` / `lint:fix`, wired into `release:check` (making RELEASE.md's "lints" claim true) and CI. First-run format sweep landed as its own mechanical commit; 7 lint errors hand-fixed (incl. a real conditional-hooks bug in `SelectionBar.tsx`). Deliberately disabled rules to revisit: a11y group, `useExhaustiveDependencies` (misfires on Preact signals), `noNonNullAssertion` (143 deliberate uses), `noArrayIndexKey` (13 sites). ~77 warnings (unused imports/vars, optional-chain) left as a burn-down list — warnings don't fail the gate. | done |
|
|
24
24
|
| Client + server unit coverage | H7 | Zero component render tests (no DOM test lib in repo); untested big modules: `ax-state-manager.ts` (826 lines), `mcp-app-host.ts` (814), `canvas-schema.ts` (644), `shared/semantic-attention.ts` (600, in zero test files). Coverage is collected but gates nothing; tests aren't typechecked. Add `happy-dom` + `@testing-library/preact`, a tests tsconfig, and a coverage threshold. | project |
|
|
25
|
-
| Daemon port/lock lifecycle | H4 |
|
|
26
|
-
| Test-suite structure | M8 | Integration monoliths under `tests/unit/` (`server-api` 5,213 lines, `cli-node` 2,903); `getAvailablePort()` copy-pasted in 5 files; `waitForPersistence(650ms)` fixed sleeps in 9 files + ~19 magic `Bun.sleep`s; Playwright lacks `retries`/`forbidOnly`; `test:e2e-cli` (517-line script) runs in no CI workflow. | project |
|
|
25
|
+
| Daemon port/lock lifecycle | H4 | **Fixed post-0.3.0** (`src/cli/daemon.ts` + `tests/unit/cli-daemon.test.ts`). Note the original finding was partly wrong: the daemon child was already strict-port (`PmxCanvas.start` pins `allowPortFallback: false`), so fallback-blind polling could not occur. The real defects — fixed — were: the pre-check accepted any responsive `/health` as "already running" (even a foreign workspace's daemon), a TOCTOU double-spawn window, the pid recorded only after health passed (slow start → unkillable orphan), and no PID-recycling guard. The flaky `pmx-canvas-sdk.test.ts` port collision is NOT this — it's M8's copy-pasted `getAvailablePort()` racing. | done |
|
|
26
|
+
| Test-suite structure | M8 | Integration monoliths under `tests/unit/` (`server-api` 5,213 lines, `cli-node` 2,903); `getAvailablePort()` copy-pasted in 5 files; `waitForPersistence(650ms)` fixed sleeps in 9 files + ~19 magic `Bun.sleep`s; Playwright lacks `retries`/`forbidOnly`; `test:e2e-cli` (517-line script) runs in no CI workflow. *Post-0.3.0:* `operation-parity` now binds port 0 and derives the port from the base URL (removes one reserve-then-rebind race); the shared-helper dedup and the rest remain. Also noted: `ax-parity.test.ts` is a string-matching source-analysis test whose needles broke when Biome re-wrapped `cmd(` calls — its CLI needles are now layout-independent, but any future needle should avoid depending on line layout. **Also noted (2026-07-07):** the `server-api` Excalidraw diagram tests do real network round-trips to the hosted `mcp.excalidraw.com` MCP server and intermittently time out (30s) under full parallel runs — they should be mocked or moved to a network-tagged tier. | project |
|
|
27
27
|
| `noUncheckedIndexedAccess` | L7 residual | Enable in tsconfig; will surface many index-access errors — treat as its own sweep. | small (noisy) |
|
|
28
28
|
|
|
29
29
|
## Phase 3 — client consolidation
|
|
@@ -67,8 +67,8 @@ The 3 fully parallel implementations (`canvas_add_html_node`, `canvas_add_html_p
|
|
|
67
67
|
#### H3. `src/cli/agent.ts` is a 3,337-line monolith with 83 hand-registered commands
|
|
68
68
|
83 `cmd(...)` registrations in one file, routed by a manual three-then-two-then-one word lookup (`agent.ts:3287-3301`); 81 copy-pasted `if (flags.help || flags.h) return showCommandHelp(...)` preambles; a hand-rolled flag parser with a ~40-entry `BOOL_FLAGS` allowlist (`agent.ts:184-193`) that must be updated for every new boolean flag — while `src/cli/index.ts:43-87` independently implements a *second* parser with different rules. **Remediation:** per-domain command modules + a `defineCommand` wrapper + one shared arg parser (and see C1 for the transport).
|
|
69
69
|
|
|
70
|
-
#### H4. Daemon lifecycle
|
|
71
|
-
|
|
70
|
+
#### H4. Daemon lifecycle: races, orphans, and a dishonest pre-check — FIXED post-0.3.0 (finding partly corrected)
|
|
71
|
+
*Correction (2026-07-07):* the original claim that a fallen-back child leaves the parent polling the wrong port was wrong — the daemon child was already strict-port (`PmxCanvas.start` pins `allowPortFallback: false`, `index.ts:176`; only the MCP auto-start path opts into fallback). The real defects were: the pre-check treated **any** responsive `/health` on the port as "already running" (including a different workspace's daemon — the same wrong-workspace class as v0.2.7 Finding I); a TOCTOU window between health check and spawn; the pid file written only after health passed, so a slow-starting daemon outlived its `--wait-ms` budget as an unkillable orphan; and `isProcessRunning` treating `EPERM` as alive with no PID-recycling guard. *Fix (post-0.3.0):* daemon lifecycle extracted to `src/cli/daemon.ts` — the pid file is created exclusively as the spawn lock and records the pid immediately, failed startups kill the child, the pre-check is workspace-aware, and liveness is cross-checked against the process command line (`tests/unit/cli-daemon.test.ts`, 19 tests, plus a live start/double-start/foreign-workspace/status/stop smoke). The intermittent `pmx-canvas-sdk.test.ts` port flake is unrelated — it is M8's copy-pasted `getAvailablePort()` racing.
|
|
72
72
|
|
|
73
73
|
#### H5. sse-bridge maintains a second, divergent copy of node construction and thread state
|
|
74
74
|
`src/client/state/sse-bridge.ts` (1,066 lines, 34 handlers) has its own `DEFAULT_POSITIONS`/`makeNode` node factory (`:75-114`) fully separate from `canvas-store`'s `addNode`/`parseCanvasNode` — two factories with divergent defaults. Thread/turn reconciliation hand-mutates a `turns[]` array plus a module-level `responseToThreadMap` whose own comment admits it is *"Not cleaned on SSE reconnect"* (`:43`), with heuristic dedup (`:650-654`). Server-side, `syncEventToCanvasState` (`server.ts:3046`, ~330 lines) mirrors this as a giant if/else chain, with node geometry defaults duplicated against `ensureDefaultDockedNodes` under a comment saying *"keep geometry/dock defaults in sync if you change them"* (`:3006`). This is the exact drift class that Canvas Architecture Rule 2 exists to prevent. **Remediation:** one shared node factory; treat `canvas-layout-update` as the single source of truth for thread nodes; table-drive the event sync.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
"build": "bun run build:client && bun run build:json-render && bun run build:types",
|
|
39
39
|
"start": "bun run src/cli/index.ts --no-open",
|
|
40
40
|
"pack:dry-run": "bun pm pack --dry-run",
|
|
41
|
-
"release:check": "bun run build && bun run typecheck && bun run test:all",
|
|
41
|
+
"release:check": "bun run build && bun run typecheck && bun run lint && bun run test:all",
|
|
42
42
|
"release:smoke": "bash scripts/release-smoke.sh",
|
|
43
43
|
"typecheck": "tsc --noEmit",
|
|
44
|
+
"lint": "biome check .",
|
|
45
|
+
"lint:fix": "biome check --write .",
|
|
44
46
|
"test": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
45
47
|
"test:unit": "PMX_CANVAS_DISABLE_BROWSER_OPEN=1 bun test tests/unit",
|
|
46
48
|
"test:coverage": "bun test tests/unit --coverage --coverage-reporter=text --coverage-reporter=lcov --coverage-dir coverage",
|
|
@@ -76,6 +78,7 @@
|
|
|
76
78
|
"zod": "^4.3.6"
|
|
77
79
|
},
|
|
78
80
|
"devDependencies": {
|
|
81
|
+
"@biomejs/biome": "^2.5.2",
|
|
79
82
|
"@playwright/test": "^1.58.2",
|
|
80
83
|
"@tailwindcss/cli": "^4.1.18",
|
|
81
84
|
"@types/bun": "latest",
|
|
@@ -55,7 +55,9 @@ Both surfaces report `workspace`. It must match the intended workspace root.
|
|
|
55
55
|
|
|
56
56
|
- If `responsive: true` but `pidRunning: false`, treat the listener as potentially stale.
|
|
57
57
|
- On mismatch, do not mutate. Start the intended workspace on an explicit free port:
|
|
58
|
-
`pmx-canvas serve --daemon --no-open --port=<free-port>`.
|
|
58
|
+
`pmx-canvas serve --daemon --no-open --port=<free-port>`. (`serve --daemon` enforces this
|
|
59
|
+
itself: pointed at a port owned by another workspace, it refuses with the owner named instead
|
|
60
|
+
of reporting "already running".)
|
|
59
61
|
- Target that port and re-check `/health`.
|
|
60
62
|
- `PMX_CANVAS_PORT` is the agent CLI target; the server's startup port is controlled by `--port`
|
|
61
63
|
or `PMX_WEB_CANVAS_PORT`.
|
|
@@ -151,8 +151,13 @@ pmx-canvas serve stop # Stop the daemon for thi
|
|
|
151
151
|
|
|
152
152
|
`serve --daemon` writes a pid file (`.pmx-canvas/daemon-<port>.pid`) and a log file
|
|
153
153
|
(`.pmx-canvas/daemon-<port>.log`); the wait flag blocks until `/health` returns OK so a script
|
|
154
|
-
can rely on the server being responsive when the command returns.
|
|
155
|
-
|
|
154
|
+
can rely on the server being responsive when the command returns. The pid file doubles as the
|
|
155
|
+
spawn lock and records the pid immediately, so a starting-but-not-yet-healthy daemon is already
|
|
156
|
+
addressable by `serve stop`, and a failed startup kills the child — no orphans. If the requested
|
|
157
|
+
port already serves a *different* workspace (or a non-canvas app), `serve --daemon` refuses with
|
|
158
|
+
the owner named and a hint to pick another port; it never reports a foreign daemon as "already
|
|
159
|
+
running". `serve stop` reads the pid file, verifies the pid still looks like a canvas daemon
|
|
160
|
+
(recycled pids read as stale), sends SIGTERM, and cleans up on exit.
|
|
156
161
|
|
|
157
162
|
### Verify workspace identity BEFORE mutating (required)
|
|
158
163
|
|
|
@@ -170,7 +175,9 @@ file, sends SIGTERM, and cleans up on exit.
|
|
|
170
175
|
3. **On mismatch, isolate**: start the intended workspace on an explicit free port —
|
|
171
176
|
`pmx-canvas serve --daemon --no-open --port=<free-port>` — and target that port. (`PMX_CANVAS_PORT`
|
|
172
177
|
alone may still attach to an existing `4313` listener; prefer an explicit `--port`.) Then
|
|
173
|
-
**re-read `/health`** to confirm the workspace now matches.
|
|
178
|
+
**re-read `/health`** to confirm the workspace now matches. `serve --daemon` also enforces
|
|
179
|
+
this itself: pointed at a port owned by another workspace, it exits with an error naming the
|
|
180
|
+
owner instead of attaching to it.
|
|
174
181
|
4. **MCP transport exception:** `pmx-canvas --mcp` launched from an incidental host dir may attach to
|
|
175
182
|
the healthy daemon already on the preferred port when no explicit workspace root is set, so writes
|
|
176
183
|
land in the visible workbench instead of a hidden fallback workspace. Host adapters should set
|