pmx-canvas 0.4.3 → 0.4.5
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 +79 -0
- package/Readme.md +40 -8
- package/dist/canvas/global.css +91 -0
- package/dist/canvas/index.js +53 -53
- package/dist/canvas/surface-theme.css +32 -0
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/skills.d.ts +41 -0
- package/dist/types/client/state/intent-store.d.ts +6 -0
- package/dist/types/client/state/sse-bridge.d.ts +1 -0
- package/dist/types/server/ax-state-manager.d.ts +5 -0
- package/dist/types/server/ax-state.d.ts +9 -3
- package/dist/types/server/canvas-db.d.ts +3 -2
- package/dist/types/server/canvas-state.d.ts +12 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +4 -1
- package/dist/types/server/operations/registry.d.ts +9 -1
- package/dist/types/shared/ax-intent.d.ts +6 -0
- package/dist/types/shared/themes.d.ts +1 -1
- package/docs/RELEASE.md +14 -6
- package/docs/bun-webview-integration.md +2 -2
- package/docs/cli.md +24 -1
- package/docs/environment.md +3 -2
- package/docs/http-api.md +6 -3
- package/docs/plans/plan-009-tech-debt-backlog.md +4 -3
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/amp-orb-portal.png +0 -0
- package/docs/screenshots/claude-code-desktop.png +0 -0
- package/docs/screenshots/codex-app.png +0 -0
- package/docs/screenshots/github-copilot-app.png +0 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +25 -5
- package/skills/pmx-canvas/references/full-reference.md +1 -1
- package/skills/pmx-canvas-orchestration/SKILL.md +82 -0
- package/skills/pmx-canvas-testing/SKILL.md +4 -0
- package/src/cli/agent.ts +1 -0
- package/src/cli/commands/skills.ts +183 -0
- package/src/cli/index.ts +17 -3
- package/src/cli/shared.ts +1 -0
- package/src/client/canvas/CanvasNode.tsx +12 -1
- package/src/client/canvas/IntentLayer.tsx +8 -6
- package/src/client/state/intent-bridge.ts +12 -3
- package/src/client/state/intent-store.ts +47 -2
- package/src/client/state/sse-bridge.ts +14 -3
- package/src/client/theme/global.css +91 -0
- package/src/client/theme/surface-theme.css +32 -0
- package/src/mcp/server.ts +3 -5
- package/src/server/ax-state-manager.ts +27 -6
- package/src/server/ax-state.ts +36 -3
- package/src/server/canvas-db.ts +83 -60
- package/src/server/canvas-state.ts +52 -6
- package/src/server/intent-registry.ts +2 -0
- package/src/server/operations/http.ts +5 -1
- package/src/server/operations/ops/ax-shared.ts +20 -1
- package/src/server/operations/ops/ax-state.ts +4 -7
- package/src/server/operations/ops/ax-timeline.ts +26 -3
- package/src/server/operations/ops/ax-work.ts +27 -1
- package/src/server/operations/ops/batch.ts +3 -1
- package/src/server/operations/ops/canvas-wire.ts +1 -1
- package/src/server/operations/ops/nodes.ts +15 -1
- package/src/server/operations/registry.ts +122 -1
- package/src/server/server.ts +23 -4
- package/src/shared/ax-intent.ts +6 -0
- package/src/shared/themes.ts +2 -0
|
@@ -281,6 +281,38 @@
|
|
|
281
281
|
--color-danger: #E57373;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
:root[data-theme="volt"] {
|
|
285
|
+
/* ── Core palette (volt · green-tinted charcoal + coral) ── */
|
|
286
|
+
--c-bg: #0B0D0B;
|
|
287
|
+
--c-panel: #171A17;
|
|
288
|
+
--c-panel-soft: #111311;
|
|
289
|
+
--c-line: #272A27;
|
|
290
|
+
--c-text: #F6FFF5;
|
|
291
|
+
--c-text-soft: #CBD3CA;
|
|
292
|
+
--c-muted: #9CA49C;
|
|
293
|
+
--c-dim: #626962;
|
|
294
|
+
--c-accent: #DFDFC1;
|
|
295
|
+
--c-ok: #52C880;
|
|
296
|
+
--c-warn: #E4C465;
|
|
297
|
+
--c-warn-alt: #FFB300;
|
|
298
|
+
--c-danger: #D67670;
|
|
299
|
+
--c-purple: #BC8FB4;
|
|
300
|
+
|
|
301
|
+
--color-bg: #0B0D0B;
|
|
302
|
+
--color-panel: #171A17;
|
|
303
|
+
--color-surface: #111311;
|
|
304
|
+
--color-border: #272A27;
|
|
305
|
+
--color-text: #F6FFF5;
|
|
306
|
+
--color-text-primary: #F6FFF5;
|
|
307
|
+
--color-text-secondary: #CBD3CA;
|
|
308
|
+
--color-text-muted: #9CA49C;
|
|
309
|
+
--color-text-dim: #626962;
|
|
310
|
+
--color-accent: #DFDFC1;
|
|
311
|
+
--color-success: #52C880;
|
|
312
|
+
--color-warning: #E4C465;
|
|
313
|
+
--color-danger: #D67670;
|
|
314
|
+
}
|
|
315
|
+
|
|
284
316
|
html,
|
|
285
317
|
body {
|
|
286
318
|
margin: 0;
|
|
@@ -22,5 +22,6 @@ import './commands/ax.js';
|
|
|
22
22
|
import './commands/webview.js';
|
|
23
23
|
import './commands/apps.js';
|
|
24
24
|
import './commands/copilot.js';
|
|
25
|
+
import './commands/skills.js';
|
|
25
26
|
export { extractGlobalTargetFlags };
|
|
26
27
|
export declare function runAgentCli(rawArgs: string[]): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export declare const BUNDLED_SKILLS: readonly ["pmx-canvas", "pmx-canvas-testing", "pmx-canvas-orchestration"];
|
|
2
|
+
export interface SkillTreeComparison {
|
|
3
|
+
inSync: boolean;
|
|
4
|
+
/** Files present in the package tree but missing or different in the mirror. */
|
|
5
|
+
missingOrDifferent: string[];
|
|
6
|
+
/** Files present in the mirror but not in the package tree. */
|
|
7
|
+
extra: string[];
|
|
8
|
+
}
|
|
9
|
+
export declare function compareSkillTrees(sourceRoot: string, targetRoot: string): SkillTreeComparison;
|
|
10
|
+
export interface InstalledSkillMirror {
|
|
11
|
+
/** The directory that owns the skills/ folder (e.g. ".codex", ".github", or any agent dir). */
|
|
12
|
+
host: string;
|
|
13
|
+
skill: string;
|
|
14
|
+
dir: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Find the bundled skills' installed copies in this workspace: any immediate
|
|
18
|
+
* child directory holding `skills/<skill>/SKILL.md`, regardless of which agent
|
|
19
|
+
* that directory belongs to. The package's own skills source is excluded so
|
|
20
|
+
* running inside the pmx-canvas checkout never compares a tree to itself.
|
|
21
|
+
*/
|
|
22
|
+
export declare function discoverInstalledSkillMirrors(workspaceRoot: string, packageSkillsRoot: string): InstalledSkillMirror[];
|
|
23
|
+
export interface SkillMirrorResult {
|
|
24
|
+
host: string;
|
|
25
|
+
skill: string;
|
|
26
|
+
dir: string;
|
|
27
|
+
status: 'in-sync' | 'synced' | 'drifted' | 'skipped-not-a-package-copy';
|
|
28
|
+
missingOrDifferent?: string[];
|
|
29
|
+
extra?: string[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A mirror is only replaceable when its SKILL.md frontmatter `name:` matches
|
|
33
|
+
* the bundled skill it sits under. A user's own skill living in a same-named
|
|
34
|
+
* directory (different frontmatter identity) must never be deleted — these
|
|
35
|
+
* trees are typically gitignored, so a wrong replace is unrecoverable.
|
|
36
|
+
*/
|
|
37
|
+
export declare function mirrorIdentityMatches(mirrorDir: string, skill: string): boolean;
|
|
38
|
+
/** Sync (or, with check=true, only compare) every INSTALLED mirror against the package trees. */
|
|
39
|
+
export declare function syncInstalledSkillMirrors(packageSkillsRoot: string, workspaceRoot: string, opts: {
|
|
40
|
+
check: boolean;
|
|
41
|
+
}): SkillMirrorResult[];
|
|
@@ -15,6 +15,12 @@ export interface ClientIntent extends PmxAxIntent {
|
|
|
15
15
|
export declare const intents: import("@preact/signals-core").Signal<Map<string, ClientIntent>>;
|
|
16
16
|
/** The ghost currently hovered — drives Esc-to-veto. */
|
|
17
17
|
export declare const hoveredIntentId: import("@preact/signals-core").Signal<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Minimum time a ghost stays in its forming state before a settle/dissolve
|
|
20
|
+
* plays. Fast agents (and every server auto-ghost) signal and settle within
|
|
21
|
+
* milliseconds — without this floor, correct ghost usage is imperceptible.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MIN_FORMING_MS = 650;
|
|
18
24
|
/** A live `ax-intent` frame: (re)place the ghost in its forming state. */
|
|
19
25
|
export declare function upsertIntent(intent: PmxAxIntent): void;
|
|
20
26
|
export declare function removeIntent(id: string): void;
|
|
@@ -77,6 +77,7 @@ export declare class AxStateManager {
|
|
|
77
77
|
status?: PmxAxWorkItemStatus;
|
|
78
78
|
detail?: string | null;
|
|
79
79
|
nodeIds?: string[];
|
|
80
|
+
agentId?: string | null;
|
|
80
81
|
}, options?: {
|
|
81
82
|
source?: PmxAxSource;
|
|
82
83
|
}): PmxAxWorkItem;
|
|
@@ -85,6 +86,7 @@ export declare class AxStateManager {
|
|
|
85
86
|
status?: PmxAxWorkItemStatus;
|
|
86
87
|
detail?: string | null;
|
|
87
88
|
nodeIds?: string[];
|
|
89
|
+
agentId?: string | null;
|
|
88
90
|
}, options?: {
|
|
89
91
|
source?: PmxAxSource;
|
|
90
92
|
}): PmxAxWorkItem | null;
|
|
@@ -173,6 +175,7 @@ export declare class AxStateManager {
|
|
|
173
175
|
data?: Record<string, unknown> | null;
|
|
174
176
|
}, options?: {
|
|
175
177
|
source?: PmxAxSource;
|
|
178
|
+
agentId?: string | null;
|
|
176
179
|
}): PmxAxEvent;
|
|
177
180
|
addEvidence(input: {
|
|
178
181
|
kind: PmxAxEvidenceKind;
|
|
@@ -186,6 +189,8 @@ export declare class AxStateManager {
|
|
|
186
189
|
}): PmxAxEvidence;
|
|
187
190
|
recordSteeringMessage(message: string, options?: {
|
|
188
191
|
source?: PmxAxSource;
|
|
192
|
+
agentId?: string | null;
|
|
193
|
+
target?: string | null;
|
|
189
194
|
}): PmxAxSteeringMessage;
|
|
190
195
|
markSteeringDelivered(id: string): boolean;
|
|
191
196
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CanvasLayout, CanvasNodeState } from './canvas-state.js';
|
|
2
2
|
import type { AgentContextNode } from './agent-context.js';
|
|
3
|
-
export type PmxAxSource = 'agent' | 'api' | 'browser' | 'cli' | 'codex' | 'copilot' | 'mcp' | 'sdk' | 'system';
|
|
3
|
+
export type PmxAxSource = 'agent' | 'amp' | 'api' | 'browser' | 'cli' | 'codex' | 'copilot' | 'mcp' | 'sdk' | 'system';
|
|
4
4
|
export interface PmxAxFocusState {
|
|
5
5
|
nodeIds: string[];
|
|
6
6
|
primaryNodeId: string | null;
|
|
@@ -24,6 +24,7 @@ export interface PmxAxWorkItem {
|
|
|
24
24
|
createdAt: string;
|
|
25
25
|
updatedAt: string;
|
|
26
26
|
source: PmxAxSource | null;
|
|
27
|
+
agentId: string | null;
|
|
27
28
|
}
|
|
28
29
|
export interface PmxAxApprovalGate {
|
|
29
30
|
id: string;
|
|
@@ -67,6 +68,7 @@ export interface PmxAxEvent {
|
|
|
67
68
|
data: Record<string, unknown> | null;
|
|
68
69
|
createdAt: string;
|
|
69
70
|
source: PmxAxSource | null;
|
|
71
|
+
agentId: string | null;
|
|
70
72
|
}
|
|
71
73
|
export interface PmxAxEvidence {
|
|
72
74
|
id: string;
|
|
@@ -87,6 +89,9 @@ export interface PmxAxSteeringMessage {
|
|
|
87
89
|
delivered: boolean;
|
|
88
90
|
createdAt: string;
|
|
89
91
|
source: PmxAxSource | null;
|
|
92
|
+
agentId: string | null;
|
|
93
|
+
/** The agentId/consumer this steer is addressed to; null = broadcast to all consumers. */
|
|
94
|
+
target: string | null;
|
|
90
95
|
}
|
|
91
96
|
export interface PmxAxHostCapability {
|
|
92
97
|
host: string | null;
|
|
@@ -257,6 +262,7 @@ export declare function createAxWorkItem(input: {
|
|
|
257
262
|
status?: PmxAxWorkItemStatus;
|
|
258
263
|
detail?: string | null;
|
|
259
264
|
nodeIds?: string[];
|
|
265
|
+
agentId?: string | null;
|
|
260
266
|
}, source: PmxAxSource | null, validNodeIds?: Set<string>): PmxAxWorkItem;
|
|
261
267
|
export declare function createAxApprovalGate(input: {
|
|
262
268
|
title: string;
|
|
@@ -280,7 +286,7 @@ export declare function createAxEvent(input: {
|
|
|
280
286
|
detail?: string | null;
|
|
281
287
|
nodeIds?: string[];
|
|
282
288
|
data?: Record<string, unknown> | null;
|
|
283
|
-
}, source: PmxAxSource | null): Omit<PmxAxEvent, 'seq'>;
|
|
289
|
+
}, source: PmxAxSource | null, agentId?: string | null): Omit<PmxAxEvent, 'seq'>;
|
|
284
290
|
export declare function createAxEvidence(input: {
|
|
285
291
|
kind: PmxAxEvidenceKind;
|
|
286
292
|
title: string;
|
|
@@ -289,7 +295,7 @@ export declare function createAxEvidence(input: {
|
|
|
289
295
|
nodeIds?: string[];
|
|
290
296
|
data?: Record<string, unknown> | null;
|
|
291
297
|
}, source: PmxAxSource | null): Omit<PmxAxEvidence, 'seq'>;
|
|
292
|
-
export declare function createAxSteeringMessage(message: string, source: PmxAxSource | null): Omit<PmxAxSteeringMessage, 'seq'>;
|
|
298
|
+
export declare function createAxSteeringMessage(message: string, source: PmxAxSource | null, agentId?: string | null, target?: string | null): Omit<PmxAxSteeringMessage, 'seq'>;
|
|
293
299
|
export declare function normalizeAxState(input: unknown, validNodeIds?: Set<string>): PmxAxState;
|
|
294
300
|
export declare function buildAxContext(input: {
|
|
295
301
|
layout: CanvasLayout;
|
|
@@ -56,7 +56,8 @@ export declare function loadPendingAxSteeringFromDB(db: Database, options?: {
|
|
|
56
56
|
/**
|
|
57
57
|
* NEWEST undelivered steering first (report #57) for the compact AX context lead
|
|
58
58
|
* block — so a fresh steer is visible even behind a long backlog. Loop-safe: excludes
|
|
59
|
-
* the consumer's own steering
|
|
59
|
+
* the consumer's own steering and steering targeted at a different consumer in SQL
|
|
60
|
+
* so the LIMIT applies after both filters.
|
|
60
61
|
* Distinct from loadPendingAxSteeringFromDB (FIFO oldest-first) which the claim/ack
|
|
61
62
|
* delivery queue uses for ordered processing.
|
|
62
63
|
*/
|
|
@@ -64,7 +65,7 @@ export declare function loadNewestPendingAxSteeringFromDB(db: Database, options?
|
|
|
64
65
|
consumer?: string;
|
|
65
66
|
limit?: number;
|
|
66
67
|
}): PmxAxSteeringMessage[];
|
|
67
|
-
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own). */
|
|
68
|
+
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own; target-scoped). */
|
|
68
69
|
export declare function countPendingAxSteeringFromDB(db: Database, consumer?: string): number;
|
|
69
70
|
export declare function loadAxTimelineSummaryFromDB(db: Database): PmxAxTimelineSummary;
|
|
70
71
|
export declare function upsertAxHostCapabilityToDB(db: Database, cap: PmxAxHostCapability): void;
|
|
@@ -280,6 +280,7 @@ declare class CanvasStateManager {
|
|
|
280
280
|
status?: PmxAxWorkItemStatus;
|
|
281
281
|
detail?: string | null;
|
|
282
282
|
nodeIds?: string[];
|
|
283
|
+
agentId?: string | null;
|
|
283
284
|
}, options?: {
|
|
284
285
|
source?: PmxAxSource;
|
|
285
286
|
}): PmxAxWorkItem;
|
|
@@ -288,9 +289,17 @@ declare class CanvasStateManager {
|
|
|
288
289
|
status?: PmxAxWorkItemStatus;
|
|
289
290
|
detail?: string | null;
|
|
290
291
|
nodeIds?: string[];
|
|
292
|
+
agentId?: string | null;
|
|
291
293
|
}, options?: {
|
|
292
294
|
source?: PmxAxSource;
|
|
293
295
|
}): PmxAxWorkItem | null;
|
|
296
|
+
/**
|
|
297
|
+
* Mirror a work item's status onto every linked canvas node's `data.axWorkStatus`,
|
|
298
|
+
* through the standard node-update path (persistence + undo/redo, same as any other
|
|
299
|
+
* node data change). Nodes unlinked by an updateWorkItem nodeIds change have the
|
|
300
|
+
* mirrored status cleared.
|
|
301
|
+
*/
|
|
302
|
+
private mirrorAxWorkStatusToNodes;
|
|
294
303
|
getApprovalGates(): PmxAxApprovalGate[];
|
|
295
304
|
requestApproval(input: {
|
|
296
305
|
title: string;
|
|
@@ -376,6 +385,7 @@ declare class CanvasStateManager {
|
|
|
376
385
|
data?: Record<string, unknown> | null;
|
|
377
386
|
}, options?: {
|
|
378
387
|
source?: PmxAxSource;
|
|
388
|
+
agentId?: string | null;
|
|
379
389
|
}): PmxAxEvent;
|
|
380
390
|
addEvidence(input: {
|
|
381
391
|
kind: PmxAxEvidenceKind;
|
|
@@ -389,6 +399,8 @@ declare class CanvasStateManager {
|
|
|
389
399
|
}): PmxAxEvidence;
|
|
390
400
|
recordSteeringMessage(message: string, options?: {
|
|
391
401
|
source?: PmxAxSource;
|
|
402
|
+
agentId?: string | null;
|
|
403
|
+
target?: string | null;
|
|
392
404
|
}): PmxAxSteeringMessage;
|
|
393
405
|
markSteeringDelivered(id: string): boolean;
|
|
394
406
|
ingestActivity(input: {
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
import { z } from 'zod';
|
|
12
12
|
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
13
13
|
import type { PmxAxSource } from '../../ax-state.js';
|
|
14
|
-
export declare const AX_SOURCES: readonly ["agent", "api", "browser", "cli", "codex", "copilot", "mcp", "sdk", "system"];
|
|
14
|
+
export declare const AX_SOURCES: readonly ["agent", "amp", "api", "browser", "cli", "codex", "copilot", "mcp", "sdk", "system"];
|
|
15
15
|
/** Zod schema for the optional `source` field shared by AX MCP tool shapes. */
|
|
16
16
|
export declare const AX_SOURCE_SHAPE: z.ZodOptional<z.ZodEnum<{
|
|
17
|
+
amp: "amp";
|
|
17
18
|
agent: "agent";
|
|
18
19
|
api: "api";
|
|
19
20
|
browser: "browser";
|
|
@@ -24,6 +25,8 @@ export declare const AX_SOURCE_SHAPE: z.ZodOptional<z.ZodEnum<{
|
|
|
24
25
|
sdk: "sdk";
|
|
25
26
|
system: "system";
|
|
26
27
|
}>>;
|
|
28
|
+
/** Zod schema for the optional `agentId` field shared by AX MCP tool shapes. */
|
|
29
|
+
export declare const AX_AGENT_ID_SHAPE: z.ZodOptional<z.ZodString>;
|
|
27
30
|
/** An absent or unrecognized source falls back to the per-surface default. */
|
|
28
31
|
export declare function normalizeAxSource(value: unknown, fallback: PmxAxSource): PmxAxSource;
|
|
29
32
|
export declare function normalizeAxNodeIds(value: unknown): string[];
|
|
@@ -11,5 +11,13 @@ export declare function setOperationEventEmitter(emitter: OperationEventEmitter
|
|
|
11
11
|
export declare function isEmitSuppressed(): boolean;
|
|
12
12
|
/** Run `fn` with all operation SSE emits suppressed; restores depth on finally. */
|
|
13
13
|
export declare function runWithSuppressedEmits<T>(fn: () => Promise<T>): Promise<T>;
|
|
14
|
-
export
|
|
14
|
+
export interface ExecuteOperationMeta {
|
|
15
|
+
/**
|
|
16
|
+
* Skip the synthesized auto-ghost: set by the workbench's own HTTP calls
|
|
17
|
+
* (a human dragging a node is not agent activity) and by canvas.batch for
|
|
18
|
+
* its inner dispatches (batch churn is exempt, matching the skill contract).
|
|
19
|
+
*/
|
|
20
|
+
suppressAutoGhost?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function executeOperation(name: string, rawInput: unknown, meta?: ExecuteOperationMeta): Promise<unknown>;
|
|
15
23
|
export {};
|
|
@@ -43,6 +43,12 @@ export interface PmxAxIntent {
|
|
|
43
43
|
seq?: number;
|
|
44
44
|
/** Source label of the surface that signalled the intent (mcp/api/sdk/...). */
|
|
45
45
|
source?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Server-synthesized ghost for an agent mutation that arrived WITHOUT an
|
|
48
|
+
* explicit canvas_intent signal (auto-ghost). Rendered visually lighter than
|
|
49
|
+
* a deliberate signal, and not vetoable — it settles immediately.
|
|
50
|
+
*/
|
|
51
|
+
auto?: boolean;
|
|
46
52
|
/** Epoch ms when the intent was first signalled. */
|
|
47
53
|
createdAt: number;
|
|
48
54
|
/** Epoch ms when the intent auto-dissolves if not settled/cleared first. */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* theme picker, server validation, and surface documents pick it up from this
|
|
10
10
|
* list.
|
|
11
11
|
*/
|
|
12
|
-
export declare const CANVAS_THEMES: readonly ["dark", "light", "high-contrast", "midnight", "sepia", "arctic", "ember", "forest"];
|
|
12
|
+
export declare const CANVAS_THEMES: readonly ["dark", "light", "high-contrast", "midnight", "sepia", "arctic", "ember", "forest", "volt"];
|
|
13
13
|
export type CanvasThemeName = (typeof CANVAS_THEMES)[number];
|
|
14
14
|
export declare function isCanvasTheme(value: unknown): value is CanvasThemeName;
|
|
15
15
|
export declare function normalizeCanvasThemeName(value: unknown, fallback?: CanvasThemeName): CanvasThemeName;
|
package/docs/RELEASE.md
CHANGED
|
@@ -134,12 +134,20 @@ publishing, update the installed package in the consumer workspace and re-sync
|
|
|
134
134
|
its four mirrors from `<global npm root>/pmx-canvas/skills/` so the first test
|
|
135
135
|
pass runs against the released skills, not the previous version's guidance.
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
This is now one command in the consumer workspace (added post-0.4.3):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pmx-canvas skills sync --yes # refreshes every INSTALLED skill copy from the package
|
|
141
|
+
pmx-canvas skills sync --check # drift detection only (exit 1 when stale)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
It discovers the skill copies already installed in the workspace (whatever
|
|
145
|
+
agent layout owns them — no host list is assumed) and compares/replaces
|
|
146
|
+
**whole trees** — `references/`, `evals/`, fixtures — not `SKILL.md` hashes
|
|
147
|
+
alone (the 0.3.x–0.4.x cycles repeatedly hit drift where the hash matched but
|
|
148
|
+
supporting files were stale). Also re-run
|
|
149
|
+
`pmx-canvas copilot install-extension --yes` in repos that use the Copilot
|
|
150
|
+
adapter so the panel picks up new open-URL behavior.
|
|
143
151
|
|
|
144
152
|
## GitHub release
|
|
145
153
|
|
|
@@ -12,7 +12,7 @@ This document tracks:
|
|
|
12
12
|
|
|
13
13
|
**Implemented** for Phase 1.
|
|
14
14
|
|
|
15
|
-
The repo now targets Bun `>=1.3.
|
|
15
|
+
The repo now targets Bun `>=1.3.14`, and PMX Canvas ships an opt-in, server-owned WebView automation session that can:
|
|
16
16
|
|
|
17
17
|
- start a headless browser session for `/workbench`
|
|
18
18
|
- report WebView runtime status
|
|
@@ -192,7 +192,7 @@ If `chromePath` or `chromeArgv` is provided, PMX Canvas forces a Chrome-backed s
|
|
|
192
192
|
|
|
193
193
|
The repo now requires:
|
|
194
194
|
|
|
195
|
-
- Bun `>=1.3.
|
|
195
|
+
- Bun `>=1.3.14`
|
|
196
196
|
|
|
197
197
|
That change is reflected in:
|
|
198
198
|
|
package/docs/cli.md
CHANGED
|
@@ -20,7 +20,7 @@ pmx-canvas # Start canvas, open browser
|
|
|
20
20
|
pmx-canvas --demo # Start with the saved dashboard demo board
|
|
21
21
|
pmx-canvas --port=8080 # Custom port
|
|
22
22
|
pmx-canvas --no-open # Headless (for agents/CI)
|
|
23
|
-
pmx-canvas --theme=light # dark | light | high-contrast | midnight | sepia | arctic | ember | forest
|
|
23
|
+
pmx-canvas --theme=light # dark | light | high-contrast | midnight | sepia | arctic | ember | forest | volt
|
|
24
24
|
pmx-canvas --mcp # Run as MCP server (stdio)
|
|
25
25
|
pmx-canvas --webview-automation # Start headless Bun.WebView session
|
|
26
26
|
pmx-canvas open # Open the current workbench in a browser
|
|
@@ -211,6 +211,29 @@ pmx-canvas copilot install-extension --dry-run # Preview target, writes nothin
|
|
|
211
211
|
pmx-canvas copilot install-extension --yes # Install/overwrite into .github/extensions/pmx-canvas/
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
+
## Skill sync
|
|
215
|
+
|
|
216
|
+
Refresh the pmx-canvas skill copies **already installed** in this workspace.
|
|
217
|
+
The command discovers them (any `<agent-dir>/skills/pmx-canvas[-testing]`,
|
|
218
|
+
whichever agent layout the workspace uses) and compares/replaces whole trees —
|
|
219
|
+
references, evals, and fixtures included — so a copy can no longer drift while
|
|
220
|
+
its `SKILL.md` hash matches. It never creates a copy where none is installed
|
|
221
|
+
and makes no assumptions about which agents you use.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
pmx-canvas skills sync --yes # Refresh every installed copy from the package
|
|
225
|
+
pmx-canvas skills sync --check # Report drift only (exit 1 when stale), change nothing
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Replacing a tree is destructive, so the sync requires `--yes`; without it the
|
|
229
|
+
command only reports drift (like `--check`) and hints at the flag. A directory
|
|
230
|
+
whose `SKILL.md` identity doesn't match the bundled skill is skipped, never
|
|
231
|
+
replaced.
|
|
232
|
+
|
|
233
|
+
Run `skills sync --yes` (and `copilot install-extension --yes` if the repo uses the
|
|
234
|
+
Copilot adapter) after every `pmx-canvas` upgrade so agents operate on the
|
|
235
|
+
released guidance.
|
|
236
|
+
|
|
214
237
|
## WebView automation
|
|
215
238
|
|
|
216
239
|
Drive a headless Bun.WebView (Chromium or WebKit) pointed at the workbench:
|
package/docs/environment.md
CHANGED
|
@@ -25,8 +25,9 @@ optional; defaults are listed.
|
|
|
25
25
|
|
|
26
26
|
| Variable | Read by | Effect |
|
|
27
27
|
|---|---|---|
|
|
28
|
-
| `PMX_CANVAS_THEME` | server | Startup theme: `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`. |
|
|
29
|
-
| `AMP_ORB` | server | Set by Amp orb services. When present, the served canvas page tells the browser it is running in an orb, which skips the iframe embed probe
|
|
28
|
+
| `PMX_CANVAS_THEME` | server | Startup theme: `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, `volt`. |
|
|
29
|
+
| `AMP_ORB` | server | Set by Amp orb services. When present, the served canvas page tells the browser it is running in an orb, which skips the iframe embed probe, forces `srcdoc` surface rendering when embedded (the orb portal blocks `src`-URL child iframes and the probe is unreliable there), and defaults the event transport to polling instead of SSE (the portal proxy buffers streams; `?transport=sse` overrides for diagnosis). |
|
|
30
|
+
| `PORT` | server | Portal-assigned service port, honored ONLY when `AMP_ORB` is also set (Amp orbs) — after `--port`, `PMX_WEB_CANVAS_PORT`, and `PMX_CANVAS_PORT`, before the 4313 default. A stray `PORT` in normal shells never changes the port. |
|
|
30
31
|
| `PMX_CANVAS_DISABLE_BROWSER_OPEN` | server | `1` suppresses auto-opening the browser (used by tests/CI). |
|
|
31
32
|
| `PMX_CANVAS_DIST` | server | Explicit client bundle directory to serve instead of the packaged `dist/canvas`. |
|
|
32
33
|
| `PMX_CANVAS_WEBVIEW_TIMEOUT_MS` | server | Startup timeout for the Bun.WebView automation session. |
|
package/docs/http-api.md
CHANGED
|
@@ -341,9 +341,12 @@ Responses are `{ ok, transport: "poll", snapshot, seq, events }` — poll again
|
|
|
341
341
|
with `since=<seq>` every couple of seconds. A `since` from a previous server
|
|
342
342
|
run or one that fell off the bounded event ring recovers with a fresh
|
|
343
343
|
snapshot. The browser client uses this automatically: if the SSE stream
|
|
344
|
-
produces no event within 3 seconds of connecting, it switches to polling
|
|
345
|
-
|
|
346
|
-
|
|
344
|
+
produces no event within 3 seconds of connecting, it switches to polling (and
|
|
345
|
+
tells the boot screen to wait for the fallback instead of alarming). Pages
|
|
346
|
+
served by an Amp orb (`AMP_ORB` set — the same stamp that drives srcdoc mode)
|
|
347
|
+
skip SSE entirely and default straight to polling. Force a transport with
|
|
348
|
+
`/workbench?transport=poll` (or `transport=sse` to disable the fallback /
|
|
349
|
+
override the orb default).
|
|
347
350
|
|
|
348
351
|
## Session theme override (host-default theming)
|
|
349
352
|
|
|
@@ -66,7 +66,7 @@ The remaining items were re-analyzed with pros/cons and tiered; the maintainer a
|
|
|
66
66
|
|---|---|
|
|
67
67
|
| Windows web-artifacts | The artifact build pipeline is bash-script-backed (POSIX-only); `tests/unit/web-artifacts.test.ts` self-skips on win32 and docs now state the boundary. Decide: port the build scripts to a cross-platform runner, or keep the documented "build on POSIX, render anywhere" stance. |
|
|
68
68
|
| Composite parity no longer machine-checked | Since v0.3.0 removed the legacy standalones, the byte-identical composite-vs-legacy guarantee has no head-to-head test — regressions in `buildInput`/`formatResult` reuse surface only via output-shape assertions. Accepted at review; revisit if a composite drifts. |
|
|
69
|
-
| Portal-layer ext-app mount (contingency) | If the maintainer sets the WebKit UX bar at "never any visible gap", inline ext-app iframes must render outside the world transform — an architectural change that reopens the parked M3. 0.4.1 shipped the cheaper mimic (post-settle WebKit repaint nudge, `repaint-nudge` trail event) and the 0.4.1 Copilot report confirms the nudge RUNS but residual black tiles still appear (Diamond on first-load settle, Ellipse after restart settle) — the contingency's trigger condition is now met. This portal-layer mount is the designated next Finding N step; scope it as its own project (drag/zoom sync, z-order, clipping, minimap interplay), not a patch-release bolt-on. |
|
|
69
|
+
| Portal-layer ext-app mount (contingency) | If the maintainer sets the WebKit UX bar at "never any visible gap", inline ext-app iframes must render outside the world transform — an architectural change that reopens the parked M3. 0.4.1 shipped the cheaper mimic (post-settle WebKit repaint nudge, `repaint-nudge` trail event) and the 0.4.1 Copilot report confirms the nudge RUNS but residual black tiles still appear (Diamond on first-load settle, Ellipse after restart settle) — the contingency's trigger condition is now met. 0.4.3 Codex hardens the evidence: three ready tiles blank in WebKit with **byte-identical** early/late screenshots while the recovery trail reported initialized/settled — DOM-level recovery completion does not prove compositor paint, so no DOM-side nudge can close this. This portal-layer mount is the designated next Finding N step; scope it as its own project (drag/zoom sync, z-order, clipping, minimap interplay), not a patch-release bolt-on. |
|
|
70
70
|
|
|
71
71
|
## Added post-0.4.0 (2026-08-04 report cycle)
|
|
72
72
|
|
|
@@ -75,6 +75,7 @@ The remaining items were re-analyzed with pros/cons and tiered; the maintainer a
|
|
|
75
75
|
| Finding S — frame documents vs server restart | **Done + field-confirmed (0.4.1 reports, both hosts):** the browser revalidates minted frame-document URLs on every reconnect epoch (`workbenchConnectionEpoch`, HEAD probe) and re-mints against the new process when the store is gone; `/api/canvas/frame-documents/:id` answers HEAD. Copilot: IDs fully rotated, all post-restart 200, no frame-not-found tiles. Codex: re-mint without workbench reload. Server-side persistence deliberately NOT done — frames are client-derived and re-mintable. |
|
|
76
76
|
| Amp portal nested-iframe embedding | **Done (2026-08-04):** boot-time embed probe (`/api/canvas/iframe-probe`) + fetch()+srcdoc fallback for all same-origin surfaces (`useSurfaceFrame`, `useIframeDocument` srcdoc short-circuit), `?iframe-mode` override, e2e-forced-mode coverage. External (cross-origin) app URLs cannot be inlined — documented limitation. |
|
|
77
77
|
| Copilot adapter `canvas_snapshot list` descriptor lag | Host-side cached tool schema still requires `name`; fresh `--mcp` is correct. Adapter/packaging issue, not a daemon defect — nothing to change in core; 0.4.2 cycle saw the legacy tools gone from the host list after refresh. |
|
|
78
|
-
| Finding T — theme/More menu mis-anchored | **Done (
|
|
79
|
-
| Finding U — sticky `-32001` session-error banner over rendering content | **
|
|
78
|
+
| Finding T — theme/More menu mis-anchored | **Done + field-confirmed (0.4.3 reports, both hosts):** menus moved inside `.toolbar-menu-anchor` wrappers around their triggers. Copilot measured button/menu right edges equal; Codex measured delta 0. Anchor-geometry e2e regression tests in place. |
|
|
79
|
+
| Finding U — sticky `-32001` session-error banner over rendering content | **Presentation fix field-confirmed (0.4.3 reports, both hosts); root cause open:** forced error state renders the soft saved-content notice. The underlying stressed-reconnect MCP timeout is still not clean-reproducible — investigate session re-establishment retry/backoff if a future cycle reproduces it. |
|
|
80
|
+
| Skill-mirror drift automation | **Done (2026-08-05):** `pmx-canvas skills sync` (+ `--check`, exit 1 on drift) discovers the skill copies already installed in the workspace — no assumed host layout, per maintainer direction — and whole-tree-replaces them from the package. Closes the recurring every-cycle hand-sync complaint in the audit addenda. Remaining audit ask: a true model-vs-baseline benchmark of the 17 eval prompts (a test-harness task, not a repo artifact). |
|
|
80
81
|
| Registry-migration dead-code sweep | **Done (2026-08-03):** removed the orphaned `describeOp`/`describeGateOp` (composites.ts, dead since the v0.3.0 registration filter) and the `compactNodePayload`/`compactLayoutPayload`/`wantsFullPayload` duplicates in `mcp/server.ts` (live copies are in `ops/nodes.ts`). |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents \u2014 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",
|
|
@@ -18,6 +18,14 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
18
18
|
|
|
19
19
|
## Required Operating Sequence
|
|
20
20
|
|
|
21
|
+
0. **After a `pmx-canvas` upgrade, refresh this skill first.** Run
|
|
22
|
+
`pmx-canvas skills sync --yes` (`--check` to detect drift; exit 1 when stale) — it finds the skill
|
|
23
|
+
copies already installed in the workspace (whatever layout your agent uses) and replaces the
|
|
24
|
+
complete trees from the package, references/evals/fixtures included. Sync ONLY from the
|
|
25
|
+
installed package, never from a source checkout: this skill documents the runtime it ships
|
|
26
|
+
with, and a newer repo skill paired with an older installed runtime advertises commands and
|
|
27
|
+
themes the binary does not have (0.4.3 report skew finding). Also restart the HTTP daemon and
|
|
28
|
+
any `--mcp` processes; Bun does not hot-reload a running process.
|
|
21
29
|
1. **Open or focus the workbench before mutating.** Reuse one visible canvas surface for the
|
|
22
30
|
session.
|
|
23
31
|
2. **Verify workspace identity.** Read `GET /health` or `pmx-canvas serve status`; the returned
|
|
@@ -36,6 +44,11 @@ Humans curate agent context by pinning nodes; agents read that curation through
|
|
|
36
44
|
human watches intent form and can veto mid-thought. Skip it only for trivial in-place tweaks or
|
|
37
45
|
high-frequency batch churn. The default TTL (~8s) expires between agent turns: signal with
|
|
38
46
|
`ttlMs: 30000` and settle by passing `intentId` on the mutation in the same or next call.
|
|
47
|
+
Since 0.4.5, an agent mutation WITHOUT a signal still shows a server-synthesized **auto-ghost**
|
|
48
|
+
(rendered lighter — dimmer, dotted, no veto — and settled instantly). That is a visibility
|
|
49
|
+
floor, not a replacement: only an explicit signal gives the human a real pre-mutation veto
|
|
50
|
+
window, your reasoning (`reason`), and staged multi-step previews. Batch and browser-human
|
|
51
|
+
actions never auto-ghost.
|
|
39
52
|
6. **Mutate through current composites.** Prefer the 16 composite MCP tools below.
|
|
40
53
|
7. **Arrange and validate.** After batch changes, use `canvas_view { action: "arrange" }` when
|
|
41
54
|
appropriate and always finish with `canvas_query { action: "validate" }`.
|
|
@@ -175,8 +188,8 @@ Use the visible workbench when the human is actively curating layout:
|
|
|
175
188
|
- Drop files or URLs to create matching nodes.
|
|
176
189
|
- Double-click markdown to edit inline.
|
|
177
190
|
- Use toolbar snapshots before experiments and restore only after confirmation.
|
|
178
|
-
- The toolbar carries view controls (fit, reset, zoom), arrange, minimap, a theme picker (
|
|
179
|
-
themes: dark, light, high-contrast, midnight, sepia, arctic, ember, forest), snapshots, trace,
|
|
191
|
+
- The toolbar carries view controls (fit, reset, zoom), arrange, minimap, a theme picker (nine
|
|
192
|
+
themes: dark, light, high-contrast, midnight, sepia, arctic, ember, forest, volt), snapshots, trace,
|
|
180
193
|
the annotation tools, search (Cmd+K), and shortcuts. On narrow viewports (≤900px — most
|
|
181
194
|
embedded host panes and mobile) the secondary controls collapse into a single "⋯" More menu at
|
|
182
195
|
the end of the bar; every action is still there as a labeled row, including the theme list. If
|
|
@@ -228,6 +241,10 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
228
241
|
- Hosted MCP-app/ext-app nodes such as Excalidraw require the in-canvas host bridge and are not
|
|
229
242
|
standalone **Open as site** targets. URL-backed viewers and bundled web artifacts remain
|
|
230
243
|
openable.
|
|
244
|
+
- A standalone html surface (`/api/canvas/surface/:id` opened as a site) is a VISUAL view: it
|
|
245
|
+
renders the same content and theme, but `window.PMX_AX` is not injected without the canvas
|
|
246
|
+
iframe's per-mount nonce, so AX buttons only work inside the in-canvas node (0.4.4 Codex note).
|
|
247
|
+
Do not tell a user a standalone tab's controls will steer the agent.
|
|
231
248
|
- A hosted ext-app (Excalidraw) node that is already on the board when a **WebKit** host panel
|
|
232
249
|
loads (e.g. the GitHub Copilot app's embedded WKWebView) can render as a black tile — a host
|
|
233
250
|
compositor paint race on the nested iframe, **not** a broken node (the session is healthy and
|
|
@@ -264,9 +281,12 @@ Prefer `canvas_query { action: "search" }` over parsing the full layout.
|
|
|
264
281
|
iframe) can block child iframes from loading ANY `src` URL, breaking every iframe-backed node
|
|
265
282
|
with a gray placeholder. The canvas probes this at boot and auto-falls back to fetching
|
|
266
283
|
same-origin surfaces and rendering them inline via `srcdoc` (HTML, graph, json-render, frame
|
|
267
|
-
documents). In Amp orbs specifically, the server sees `AMP_ORB` in its environment and
|
|
268
|
-
|
|
269
|
-
|
|
284
|
+
documents). In Amp orbs specifically, the server sees `AMP_ORB` in its environment and adapts
|
|
285
|
+
end to end: it binds the portal-assigned `$PORT` automatically (no port flag in the service
|
|
286
|
+
command), stamps the page so the embedded client skips the (there-unreliable) probe, goes
|
|
287
|
+
straight to `srcdoc`, AND defaults to the polling transport (the portal proxy buffers SSE —
|
|
288
|
+
waiting out the watchdog could trip the boot modal); HTML surfaces also inline their theme
|
|
289
|
+
stylesheet so they render styled. External app
|
|
270
290
|
URLs cannot be inlined (cross-origin) and may stay blocked in such hosts. Force a mode with
|
|
271
291
|
`/workbench?iframe-mode=srcdoc` (or `iframe-mode=src`) when diagnosing.
|
|
272
292
|
- Graph and json-render standalone surfaces use `display=site` and fill the browser viewport, and
|
|
@@ -132,7 +132,7 @@ pmx-canvas --theme=light # Light theme
|
|
|
132
132
|
pmx-canvas --version # Print installed version and exit
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
`--theme` accepts `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, or `
|
|
135
|
+
`--theme` accepts `dark` (default), `light`, `high-contrast`, `midnight`, `sepia`, `arctic`, `ember`, `forest`, or `volt` (0.4.x; the browser toolbar has a matching theme picker). Same value can be set via
|
|
136
136
|
the `PMX_CANVAS_THEME` environment variable, or toggled live in the browser toolbar.
|
|
137
137
|
|
|
138
138
|
Start the canvas once per session, then reuse it. Use `--no-open` when running as an agent — the
|