pmx-canvas 0.4.8 → 0.5.0
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/.github/extensions/pmx-canvas/extension.mjs +154 -5
- package/.github/extensions/pmx-canvas/steering-delivery.mjs +79 -0
- package/CHANGELOG.md +139 -0
- package/Readme.md +72 -23
- package/dist/canvas/global.css +3069 -796
- package/dist/canvas/index.js +104 -104
- package/dist/json-render/index.css +1 -1
- package/dist/types/cli/agent.d.ts +1 -0
- package/dist/types/cli/commands/pump.d.ts +9 -0
- package/dist/types/client/App.d.ts +1 -1
- package/dist/types/client/canvas/AgentPresenceLayer.d.ts +11 -0
- package/dist/types/client/canvas/BarHint.d.ts +22 -0
- package/dist/types/client/canvas/CanvasNode.d.ts +5 -0
- package/dist/types/client/canvas/CanvasViewport.d.ts +4 -4
- package/dist/types/client/canvas/CommandBar.d.ts +10 -0
- package/dist/types/client/canvas/ConnectionBanner.d.ts +13 -0
- package/dist/types/client/canvas/ContextMenu.d.ts +6 -0
- package/dist/types/client/canvas/EdgeLayer.d.ts +2 -1
- package/dist/types/client/canvas/EmptyState.d.ts +11 -0
- package/dist/types/client/canvas/ExternalWriters.d.ts +5 -0
- package/dist/types/client/canvas/HumanPresenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/Minimap.d.ts +12 -1
- package/dist/types/client/canvas/ScopeFenceLayer.d.ts +8 -0
- package/dist/types/client/canvas/SelectionBar.d.ts +6 -0
- package/dist/types/client/canvas/SessionPanel.d.ts +1 -0
- package/dist/types/client/canvas/SessionReceipt.d.ts +17 -0
- package/dist/types/client/canvas/TextPrompt.d.ts +25 -0
- package/dist/types/client/canvas/ToolRail.d.ts +21 -0
- package/dist/types/client/canvas/TopBar.d.ts +7 -0
- package/dist/types/client/canvas/canvas-area.d.ts +25 -0
- package/dist/types/client/canvas/create-in-view.d.ts +18 -0
- package/dist/types/client/canvas/import-files.d.ts +8 -0
- package/dist/types/client/canvas/kind-colors.d.ts +3 -0
- package/dist/types/client/canvas/use-focus-trap.d.ts +14 -0
- package/dist/types/client/canvas/use-now.d.ts +6 -0
- package/dist/types/client/icons.d.ts +11 -4
- package/dist/types/client/nodes/GroupNode.d.ts +6 -1
- package/dist/types/client/nodes/MarkdownNode.d.ts +2 -0
- package/dist/types/client/nodes/inline-editor-commands.d.ts +3 -4
- package/dist/types/client/state/canvas-store.d.ts +49 -5
- package/dist/types/client/state/human-store.d.ts +26 -0
- package/dist/types/client/state/iframe-mode.d.ts +1 -0
- package/dist/types/client/state/intent-bridge.d.ts +37 -5
- package/dist/types/client/state/node-factory.d.ts +0 -1
- package/dist/types/client/state/presence-store.d.ts +78 -0
- package/dist/types/client/state/session-store.d.ts +176 -0
- package/dist/types/client/types.d.ts +2 -1
- package/dist/types/client/utils/platform.d.ts +2 -0
- package/dist/types/server/agent-presence.d.ts +192 -0
- package/dist/types/server/ax-gate-ttl.d.ts +6 -0
- package/dist/types/server/ax-interaction.d.ts +4 -10
- package/dist/types/server/ax-state-manager.d.ts +21 -2
- package/dist/types/server/ax-state.d.ts +49 -3
- package/dist/types/server/ax-wait.d.ts +13 -0
- package/dist/types/server/canvas-db.d.ts +9 -1
- package/dist/types/server/canvas-operations.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +27 -13
- package/dist/types/server/canvas-validation.d.ts +0 -14
- package/dist/types/server/html-surface.d.ts +1 -1
- package/dist/types/server/human-presence.d.ts +40 -0
- package/dist/types/server/index.d.ts +29 -3
- package/dist/types/server/intent-registry.d.ts +7 -0
- package/dist/types/server/mutation-history.d.ts +15 -2
- package/dist/types/server/operations/invoker.d.ts +13 -2
- package/dist/types/server/operations/ops/ax-presence.d.ts +2 -0
- package/dist/types/server/operations/ops/human-presence.d.ts +3 -0
- package/dist/types/server/operations/registry.d.ts +11 -0
- package/dist/types/server/operations/types.d.ts +1 -1
- package/dist/types/server/scope-fence.d.ts +62 -0
- package/dist/types/server/server.d.ts +0 -12
- package/dist/types/shared/agent-presence.d.ts +130 -0
- package/dist/types/shared/approval-gates.d.ts +19 -0
- package/dist/types/shared/auto-arrange.d.ts +0 -1
- package/dist/types/shared/ax-kinds.d.ts +11 -0
- package/dist/types/shared/human-presence.d.ts +31 -0
- package/dist/types/shared/iframe-probe.d.ts +8 -0
- package/dist/types/shared/scope-fence.d.ts +21 -0
- package/docs/RELEASE.md +6 -0
- package/docs/ax-host-adapter-contract.md +30 -14
- package/docs/cli.md +37 -1
- package/docs/environment.md +3 -0
- package/docs/http-api.md +222 -5
- package/docs/mcp.md +6 -5
- package/docs/node-types.md +3 -1
- package/docs/plans/plan-009-tech-debt-backlog.md +13 -0
- package/docs/screenshot.png +0 -0
- package/docs/screenshots/multi-agent-live.png +0 -0
- package/docs/screenshots/workbench-live-dark.png +0 -0
- package/docs/screenshots/workbench-live-light.png +0 -0
- package/docs/sdk.md +1 -1
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +103 -16
- package/skills/pmx-canvas/references/codex-app-adapter.md +28 -0
- package/skills/pmx-canvas/references/full-reference.md +55 -16
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +41 -24
- package/skills/pmx-canvas-orchestration/SKILL.md +56 -2
- package/skills/pmx-canvas-testing/SKILL.md +17 -0
- package/src/cli/agent.ts +1 -0
- package/src/cli/commands/copilot.ts +15 -8
- package/src/cli/commands/nodes.ts +1 -19
- package/src/cli/commands/pump.ts +252 -0
- package/src/cli/index.ts +1 -5
- package/src/cli/shared.ts +1 -1
- package/src/client/App.tsx +209 -585
- package/src/client/canvas/AgentPresenceLayer.tsx +74 -0
- package/src/client/canvas/AttentionHistory.tsx +0 -10
- package/src/client/canvas/BarHint.tsx +49 -0
- package/src/client/canvas/CanvasNode.tsx +488 -136
- package/src/client/canvas/CanvasViewport.tsx +190 -94
- package/src/client/canvas/CommandBar.tsx +140 -0
- package/src/client/canvas/CommandPalette.tsx +133 -58
- package/src/client/canvas/ConnectionBanner.tsx +65 -0
- package/src/client/canvas/ContextMenu.tsx +88 -27
- package/src/client/canvas/ContextPinBar.tsx +2 -2
- package/src/client/canvas/EdgeLayer.tsx +121 -38
- package/src/client/canvas/EmptyState.tsx +93 -0
- package/src/client/canvas/ExpandedNodeOverlay.tsx +64 -118
- package/src/client/canvas/ExternalWriters.tsx +308 -0
- package/src/client/canvas/FocusFieldLayer.tsx +12 -1
- package/src/client/canvas/HumanPresenceLayer.tsx +45 -0
- package/src/client/canvas/IntentLayer.tsx +1 -1
- package/src/client/canvas/Minimap.tsx +117 -190
- package/src/client/canvas/ScopeFenceLayer.tsx +38 -0
- package/src/client/canvas/SelectionBar.tsx +175 -23
- package/src/client/canvas/SessionPanel.tsx +481 -0
- package/src/client/canvas/SessionReceipt.tsx +141 -0
- package/src/client/canvas/ShortcutOverlay.tsx +21 -6
- package/src/client/canvas/SnapshotPanel.tsx +166 -80
- package/src/client/canvas/TextPrompt.tsx +128 -0
- package/src/client/canvas/ToolRail.tsx +447 -0
- package/src/client/canvas/TopBar.tsx +308 -0
- package/src/client/canvas/auto-fit.ts +1 -5
- package/src/client/canvas/canvas-area.ts +41 -0
- package/src/client/canvas/create-in-view.ts +48 -0
- package/src/client/canvas/import-files.ts +64 -0
- package/src/client/canvas/kind-colors.ts +22 -0
- package/src/client/canvas/snap-guides.ts +1 -1
- package/src/client/canvas/use-focus-trap.ts +54 -0
- package/src/client/canvas/use-now.ts +17 -0
- package/src/client/canvas/use-pan-zoom.ts +13 -2
- package/src/client/icons.tsx +57 -18
- package/src/client/nodes/GroupNode.tsx +9 -28
- package/src/client/nodes/InlineFormatBar.tsx +4 -3
- package/src/client/nodes/InlineMarkdownEditor.tsx +27 -0
- package/src/client/nodes/MarkdownNode.tsx +59 -24
- package/src/client/nodes/MdFormatBar.tsx +2 -4
- package/src/client/nodes/inline-editor-commands.ts +17 -6
- package/src/client/nodes/use-surface-frame.ts +3 -1
- package/src/client/state/attention-bridge.ts +3 -0
- package/src/client/state/canvas-store.ts +273 -84
- package/src/client/state/human-store.ts +175 -0
- package/src/client/state/iframe-mode.ts +33 -9
- package/src/client/state/intent-bridge.ts +75 -13
- package/src/client/state/node-factory.ts +2 -8
- package/src/client/state/presence-store.ts +168 -0
- package/src/client/state/session-store.ts +419 -0
- package/src/client/state/sse-bridge.ts +93 -30
- package/src/client/theme/global.css +3069 -796
- package/src/client/types.ts +3 -1
- package/src/client/utils/platform.ts +6 -1
- package/src/mcp/canvas-access.ts +2 -2
- package/src/server/agent-presence.ts +755 -0
- package/src/server/ax-context.ts +45 -0
- package/src/server/ax-gate-ttl.ts +61 -0
- package/src/server/ax-interaction.ts +29 -1
- package/src/server/ax-state-manager.ts +78 -6
- package/src/server/ax-state.ts +82 -15
- package/src/server/ax-wait.ts +33 -0
- package/src/server/canvas-db.ts +103 -33
- package/src/server/canvas-operations.ts +17 -8
- package/src/server/canvas-state.ts +131 -58
- package/src/server/canvas-validation.ts +3 -53
- package/src/server/demo-state.json +2 -70
- package/src/server/html-surface.ts +11 -2
- package/src/server/human-presence.ts +160 -0
- package/src/server/index.ts +107 -4
- package/src/server/intent-registry.ts +13 -0
- package/src/server/mutation-history.ts +47 -2
- package/src/server/operations/composites.ts +6 -3
- package/src/server/operations/http.ts +10 -1
- package/src/server/operations/index.ts +4 -0
- package/src/server/operations/invoker.ts +33 -6
- package/src/server/operations/ops/ax-presence.ts +62 -0
- package/src/server/operations/ops/ax-read.ts +8 -0
- package/src/server/operations/ops/ax-state.ts +106 -1
- package/src/server/operations/ops/ax-timeline.ts +54 -5
- package/src/server/operations/ops/ax-work.ts +46 -0
- package/src/server/operations/ops/batch.ts +3 -1
- package/src/server/operations/ops/edges.ts +88 -2
- package/src/server/operations/ops/groups.ts +4 -3
- package/src/server/operations/ops/human-presence.ts +35 -0
- package/src/server/operations/ops/json-render.ts +11 -1
- package/src/server/operations/ops/nodes.ts +65 -16
- package/src/server/operations/ops/query.ts +1 -0
- package/src/server/operations/ops/snapshots.ts +16 -0
- package/src/server/operations/registry.ts +196 -1
- package/src/server/operations/types.ts +1 -1
- package/src/server/scope-fence.ts +207 -0
- package/src/server/server.ts +177 -79
- package/src/server/trace-manager.ts +0 -2
- package/src/server/web-artifacts.ts +0 -1
- package/src/shared/agent-presence.ts +162 -0
- package/src/shared/approval-gates.ts +35 -0
- package/src/shared/auto-arrange.ts +121 -13
- package/src/shared/ax-kinds.ts +23 -0
- package/src/shared/human-presence.ts +30 -0
- package/src/shared/iframe-probe.ts +8 -0
- package/src/shared/scope-fence.ts +31 -0
- package/dist/types/client/canvas/DockedNode.d.ts +0 -4
- package/src/client/canvas/DockedNode.tsx +0 -165
package/src/server/canvas-db.ts
CHANGED
|
@@ -65,7 +65,6 @@ const SCHEMA_SQL = `
|
|
|
65
65
|
z_index INTEGER NOT NULL DEFAULT 0,
|
|
66
66
|
collapsed INTEGER NOT NULL DEFAULT 0,
|
|
67
67
|
pinned INTEGER NOT NULL DEFAULT 0,
|
|
68
|
-
dock_position TEXT,
|
|
69
68
|
data TEXT NOT NULL
|
|
70
69
|
);
|
|
71
70
|
|
|
@@ -119,7 +118,6 @@ const SCHEMA_SQL = `
|
|
|
119
118
|
z_index INTEGER NOT NULL DEFAULT 0,
|
|
120
119
|
collapsed INTEGER NOT NULL DEFAULT 0,
|
|
121
120
|
pinned INTEGER NOT NULL DEFAULT 0,
|
|
122
|
-
dock_position TEXT,
|
|
123
121
|
data TEXT NOT NULL,
|
|
124
122
|
PRIMARY KEY (snapshot_id, id)
|
|
125
123
|
);
|
|
@@ -209,6 +207,13 @@ const SCHEMA_SQL = `
|
|
|
209
207
|
);
|
|
210
208
|
CREATE INDEX IF NOT EXISTS idx_ax_steering_seq ON ax_steering (seq);
|
|
211
209
|
|
|
210
|
+
CREATE TABLE IF NOT EXISTS ax_steering_deliveries (
|
|
211
|
+
steering_id TEXT NOT NULL,
|
|
212
|
+
consumer TEXT NOT NULL,
|
|
213
|
+
at TEXT NOT NULL,
|
|
214
|
+
PRIMARY KEY (steering_id, consumer)
|
|
215
|
+
);
|
|
216
|
+
|
|
212
217
|
CREATE TABLE IF NOT EXISTS ax_host_capabilities (
|
|
213
218
|
host TEXT PRIMARY KEY,
|
|
214
219
|
reported_at TEXT NOT NULL,
|
|
@@ -397,18 +402,17 @@ export function saveStateToDB(db: Database, state: PersistedCanvasState): void {
|
|
|
397
402
|
|
|
398
403
|
// Save nodes
|
|
399
404
|
const upsertNode = db.prepare(
|
|
400
|
-
`INSERT INTO nodes (id, type, pos_x, pos_y, width, height, z_index, collapsed, pinned,
|
|
401
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
405
|
+
`INSERT INTO nodes (id, type, pos_x, pos_y, width, height, z_index, collapsed, pinned, data)
|
|
406
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
402
407
|
ON CONFLICT(id) DO UPDATE SET
|
|
403
408
|
type = excluded.type, pos_x = excluded.pos_x, pos_y = excluded.pos_y,
|
|
404
409
|
width = excluded.width, height = excluded.height, z_index = excluded.z_index,
|
|
405
|
-
collapsed = excluded.collapsed, pinned = excluded.pinned,
|
|
406
|
-
dock_position = excluded.dock_position, data = excluded.data
|
|
410
|
+
collapsed = excluded.collapsed, pinned = excluded.pinned, data = excluded.data
|
|
407
411
|
WHERE type IS NOT excluded.type OR pos_x IS NOT excluded.pos_x
|
|
408
412
|
OR pos_y IS NOT excluded.pos_y OR width IS NOT excluded.width
|
|
409
413
|
OR height IS NOT excluded.height OR z_index IS NOT excluded.z_index
|
|
410
414
|
OR collapsed IS NOT excluded.collapsed OR pinned IS NOT excluded.pinned
|
|
411
|
-
OR
|
|
415
|
+
OR data IS NOT excluded.data`,
|
|
412
416
|
);
|
|
413
417
|
for (const node of state.nodes) {
|
|
414
418
|
upsertNode.run(
|
|
@@ -421,7 +425,6 @@ export function saveStateToDB(db: Database, state: PersistedCanvasState): void {
|
|
|
421
425
|
node.zIndex,
|
|
422
426
|
node.collapsed ? 1 : 0,
|
|
423
427
|
node.pinned ? 1 : 0,
|
|
424
|
-
node.dockPosition,
|
|
425
428
|
JSON.stringify(node.data),
|
|
426
429
|
);
|
|
427
430
|
}
|
|
@@ -535,7 +538,6 @@ export function loadStateFromDB(db: Database): PersistedCanvasState | null {
|
|
|
535
538
|
z_index: number;
|
|
536
539
|
collapsed: number;
|
|
537
540
|
pinned: number;
|
|
538
|
-
dock_position: string | null;
|
|
539
541
|
data: string;
|
|
540
542
|
}
|
|
541
543
|
const nodeRows = db.query<NodeRow, []>('SELECT * FROM nodes').all();
|
|
@@ -547,7 +549,6 @@ export function loadStateFromDB(db: Database): PersistedCanvasState | null {
|
|
|
547
549
|
zIndex: row.z_index,
|
|
548
550
|
collapsed: row.collapsed === 1,
|
|
549
551
|
pinned: row.pinned === 1,
|
|
550
|
-
dockPosition: row.dock_position as CanvasNodeState['dockPosition'],
|
|
551
552
|
data: JSON.parse(row.data) as Record<string, unknown>,
|
|
552
553
|
}));
|
|
553
554
|
|
|
@@ -655,8 +656,8 @@ export function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state:
|
|
|
655
656
|
|
|
656
657
|
// Insert snapshot nodes
|
|
657
658
|
const insertNode = db.prepare(
|
|
658
|
-
`INSERT INTO snapshot_nodes (snapshot_id, id, type, pos_x, pos_y, width, height, z_index, collapsed, pinned,
|
|
659
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
|
659
|
+
`INSERT INTO snapshot_nodes (snapshot_id, id, type, pos_x, pos_y, width, height, z_index, collapsed, pinned, data)
|
|
660
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
660
661
|
);
|
|
661
662
|
for (const node of state.nodes) {
|
|
662
663
|
insertNode.run(
|
|
@@ -670,7 +671,6 @@ export function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state:
|
|
|
670
671
|
node.zIndex,
|
|
671
672
|
node.collapsed ? 1 : 0,
|
|
672
673
|
node.pinned ? 1 : 0,
|
|
673
|
-
node.dockPosition,
|
|
674
674
|
JSON.stringify(node.data),
|
|
675
675
|
);
|
|
676
676
|
}
|
|
@@ -780,7 +780,6 @@ export function loadSnapshotFromDB(
|
|
|
780
780
|
z_index: number;
|
|
781
781
|
collapsed: number;
|
|
782
782
|
pinned: number;
|
|
783
|
-
dock_position: string | null;
|
|
784
783
|
data: string;
|
|
785
784
|
}
|
|
786
785
|
const nodeRows = db
|
|
@@ -794,7 +793,6 @@ export function loadSnapshotFromDB(
|
|
|
794
793
|
zIndex: row.z_index,
|
|
795
794
|
collapsed: row.collapsed === 1,
|
|
796
795
|
pinned: row.pinned === 1,
|
|
797
|
-
dockPosition: row.dock_position as CanvasNodeState['dockPosition'],
|
|
798
796
|
data: JSON.parse(row.data) as Record<string, unknown>,
|
|
799
797
|
}));
|
|
800
798
|
|
|
@@ -918,6 +916,10 @@ export function listSnapshotsFromDB(db: Database, options: CanvasSnapshotListOpt
|
|
|
918
916
|
}));
|
|
919
917
|
}
|
|
920
918
|
|
|
919
|
+
export function renameSnapshotInDB(db: Database, id: string, name: string): boolean {
|
|
920
|
+
return db.run('UPDATE snapshots SET name = ? WHERE id = ?', [name, id]).changes > 0;
|
|
921
|
+
}
|
|
922
|
+
|
|
921
923
|
export function deleteSnapshotFromDB(db: Database, id: string): boolean {
|
|
922
924
|
const transaction = db.transaction(() => {
|
|
923
925
|
db.run('DELETE FROM snapshot_nodes WHERE snapshot_id = ?', [id]);
|
|
@@ -979,6 +981,9 @@ function trimAxTable(db: Database, table: 'ax_events' | 'ax_evidence' | 'ax_stee
|
|
|
979
981
|
db.run(`DELETE FROM ${table} WHERE seq <= (SELECT seq FROM ${table} ORDER BY seq DESC LIMIT 1 OFFSET ?)`, [
|
|
980
982
|
AX_TIMELINE_RETENTION,
|
|
981
983
|
]);
|
|
984
|
+
if (table === 'ax_steering') {
|
|
985
|
+
db.run('DELETE FROM ax_steering_deliveries WHERE steering_id NOT IN (SELECT id FROM ax_steering)');
|
|
986
|
+
}
|
|
982
987
|
}
|
|
983
988
|
|
|
984
989
|
function readLastSeq(db: Database, table: 'ax_events' | 'ax_evidence' | 'ax_steering'): number {
|
|
@@ -1036,10 +1041,28 @@ export function appendAxSteeringToDB(db: Database, s: Omit<PmxAxSteeringMessage,
|
|
|
1036
1041
|
return { ...s, seq };
|
|
1037
1042
|
}
|
|
1038
1043
|
|
|
1039
|
-
export function markAxSteeringDeliveredInDB(db: Database, id: string): boolean {
|
|
1040
|
-
//
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1044
|
+
export function markAxSteeringDeliveredInDB(db: Database, id: string, consumer?: string | null): boolean {
|
|
1045
|
+
// ADDRESSED steer (target set): global compare-and-set as before — one
|
|
1046
|
+
// legitimate recipient, true only on the undelivered→delivered transition.
|
|
1047
|
+
// BROADCAST + consumer: per-consumer delivery ("all workers: stop" must
|
|
1048
|
+
// reach every worker) — the mark records THIS consumer's pickup and the
|
|
1049
|
+
// message stays pending for everyone else; true iff this consumer had not
|
|
1050
|
+
// marked it before. BROADCAST without a consumer: the legacy anonymous ack
|
|
1051
|
+
// (global CAS) that hides it from all consumers.
|
|
1052
|
+
const row = db.query<{ target: string | null }, [string]>('SELECT target FROM ax_steering WHERE id = ?').get(id);
|
|
1053
|
+
if (!row) return false;
|
|
1054
|
+
// An addressed steer belongs to its target. A different named consumer marking
|
|
1055
|
+
// it delivered would silently swallow the human's instruction (the target then
|
|
1056
|
+
// never sees it). A consumer-less mark is still the single legitimate host ack.
|
|
1057
|
+
if (row.target !== null && consumer && consumer !== row.target) return false;
|
|
1058
|
+
if (row.target === null && consumer) {
|
|
1059
|
+
const r = db.run('INSERT OR IGNORE INTO ax_steering_deliveries (steering_id, consumer, at) VALUES (?, ?, ?)', [
|
|
1060
|
+
id,
|
|
1061
|
+
consumer,
|
|
1062
|
+
new Date().toISOString(),
|
|
1063
|
+
]);
|
|
1064
|
+
return r.changes > 0;
|
|
1065
|
+
}
|
|
1043
1066
|
const r = db.run('UPDATE ax_steering SET delivered = 1 WHERE id = ? AND delivered = 0', [id]);
|
|
1044
1067
|
return r.changes > 0;
|
|
1045
1068
|
}
|
|
@@ -1112,6 +1135,18 @@ interface AxSteeringRow {
|
|
|
1112
1135
|
target: string | null;
|
|
1113
1136
|
}
|
|
1114
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Broadcasts age out of PENDING (they stay on the timeline): per-consumer
|
|
1140
|
+
* delivery otherwise means every broadcast greets every FUTURE consumer key
|
|
1141
|
+
* forever — a brand-new pump inherited two-day-old "all workers" calls.
|
|
1142
|
+
* Addressed steers never expire here; they wait for their named consumer.
|
|
1143
|
+
*/
|
|
1144
|
+
export const BROADCAST_PENDING_TTL_MS = 15 * 60_000;
|
|
1145
|
+
|
|
1146
|
+
function broadcastCutoffIso(): string {
|
|
1147
|
+
return new Date(Date.now() - BROADCAST_PENDING_TTL_MS).toISOString();
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1115
1150
|
function mapAxSteeringRow(r: AxSteeringRow): PmxAxSteeringMessage | null {
|
|
1116
1151
|
return normalizeAxSteeringMessage({
|
|
1117
1152
|
...r,
|
|
@@ -1129,7 +1164,27 @@ export function loadAxSteeringFromDB(
|
|
|
1129
1164
|
? 'SELECT * FROM ax_steering WHERE delivered = 0 ORDER BY seq DESC LIMIT ?'
|
|
1130
1165
|
: 'SELECT * FROM ax_steering ORDER BY seq DESC LIMIT ?';
|
|
1131
1166
|
const rows = db.query<AxSteeringRow, [number]>(sql).all(clampTimelineLimit(q.limit));
|
|
1132
|
-
|
|
1167
|
+
const deliveredTo = new Map<string, string[]>();
|
|
1168
|
+
for (const row of db
|
|
1169
|
+
.query<{ steering_id: string; consumer: string }, []>('SELECT steering_id, consumer FROM ax_steering_deliveries')
|
|
1170
|
+
.all()) {
|
|
1171
|
+
if (!deliveredTo.has(row.steering_id)) deliveredTo.set(row.steering_id, []);
|
|
1172
|
+
deliveredTo.get(row.steering_id)!.push(row.consumer);
|
|
1173
|
+
}
|
|
1174
|
+
return rows
|
|
1175
|
+
.map((row) => {
|
|
1176
|
+
const mapped = mapAxSteeringRow(row);
|
|
1177
|
+
if (!mapped) return null;
|
|
1178
|
+
const pickups = deliveredTo.get(mapped.id) ?? [];
|
|
1179
|
+
// A broadcast reads as delivered once ANY consumer picked it up; the
|
|
1180
|
+
// per-consumer list is exposed for fleet UIs ("picked up by 3").
|
|
1181
|
+
return {
|
|
1182
|
+
...mapped,
|
|
1183
|
+
delivered: mapped.delivered || pickups.length > 0,
|
|
1184
|
+
...(pickups.length > 0 ? { deliveredTo: pickups } : {}),
|
|
1185
|
+
};
|
|
1186
|
+
})
|
|
1187
|
+
.filter((s): s is PmxAxSteeringMessage => s !== null);
|
|
1133
1188
|
}
|
|
1134
1189
|
|
|
1135
1190
|
export function loadPendingAxSteeringFromDB(
|
|
@@ -1142,13 +1197,20 @@ export function loadPendingAxSteeringFromDB(
|
|
|
1142
1197
|
const limit = clampTimelineLimit(options.limit);
|
|
1143
1198
|
const rows = options.consumer
|
|
1144
1199
|
? db
|
|
1145
|
-
.query<AxSteeringRow, [string, string, string, number]>(
|
|
1146
|
-
|
|
1200
|
+
.query<AxSteeringRow, [string, string, string, string, string, number]>(
|
|
1201
|
+
// A broadcast stays pending PER CONSUMER: it leaves this consumer's
|
|
1202
|
+
// queue only once THIS consumer marked it (or an anonymous global ack
|
|
1203
|
+
// set delivered).
|
|
1204
|
+
'SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq ASC LIMIT ?',
|
|
1147
1205
|
)
|
|
1148
|
-
.all(options.consumer, options.consumer, options.consumer, limit)
|
|
1206
|
+
.all(options.consumer, options.consumer, options.consumer, broadcastCutoffIso(), options.consumer, limit)
|
|
1149
1207
|
: db
|
|
1150
|
-
.query<AxSteeringRow, [number]>(
|
|
1151
|
-
|
|
1208
|
+
.query<AxSteeringRow, [string, number]>(
|
|
1209
|
+
// No consumer given: only broadcasts — addressed steering is claimable
|
|
1210
|
+
// solely by the consumer it names.
|
|
1211
|
+
'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL AND created_at > ? ORDER BY seq ASC LIMIT ?',
|
|
1212
|
+
)
|
|
1213
|
+
.all(broadcastCutoffIso(), limit);
|
|
1152
1214
|
return rows.map(mapAxSteeringRow).filter((s): s is PmxAxSteeringMessage => s !== null);
|
|
1153
1215
|
}
|
|
1154
1216
|
|
|
@@ -1167,25 +1229,33 @@ export function loadNewestPendingAxSteeringFromDB(
|
|
|
1167
1229
|
const limit = clampTimelineLimit(options.limit);
|
|
1168
1230
|
const rows = options.consumer
|
|
1169
1231
|
? db
|
|
1170
|
-
.query<AxSteeringRow, [string, string, string, number]>(
|
|
1171
|
-
'SELECT * FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) AND (agent_id IS NULL OR agent_id != ?) AND (target IS NULL OR target = ?) ORDER BY seq DESC LIMIT ?',
|
|
1232
|
+
.query<AxSteeringRow, [string, string, string, string, string, number]>(
|
|
1233
|
+
'SELECT * FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?) ORDER BY s.seq DESC LIMIT ?',
|
|
1172
1234
|
)
|
|
1173
|
-
.all(options.consumer, options.consumer, options.consumer, limit)
|
|
1235
|
+
.all(options.consumer, options.consumer, options.consumer, broadcastCutoffIso(), options.consumer, limit)
|
|
1174
1236
|
: db
|
|
1175
|
-
.query<AxSteeringRow, [number]>(
|
|
1237
|
+
.query<AxSteeringRow, [number]>(
|
|
1238
|
+
'SELECT * FROM ax_steering WHERE delivered = 0 AND target IS NULL ORDER BY seq DESC LIMIT ?',
|
|
1239
|
+
)
|
|
1176
1240
|
.all(limit);
|
|
1177
1241
|
return rows.map(mapAxSteeringRow).filter((s): s is PmxAxSteeringMessage => s !== null);
|
|
1178
1242
|
}
|
|
1179
1243
|
|
|
1180
1244
|
/** Total undelivered steering for a consumer (loop-safe — excludes the consumer's own; target-scoped). */
|
|
1181
1245
|
export function countPendingAxSteeringFromDB(db: Database, consumer?: string): number {
|
|
1246
|
+
// Must mirror loadPendingAxSteeringFromDB exactly (per-consumer deliveries,
|
|
1247
|
+
// broadcast TTL) — the roster shows this as "what would this consumer claim".
|
|
1182
1248
|
const n = consumer
|
|
1183
1249
|
? db
|
|
1184
|
-
.query<{ n: number }, [string, string, string]>(
|
|
1185
|
-
'SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0 AND (source IS NULL OR source != ?) AND (agent_id IS NULL OR agent_id != ?) AND (target IS NULL OR target = ?)',
|
|
1250
|
+
.query<{ n: number }, [string, string, string, string, string]>(
|
|
1251
|
+
'SELECT COUNT(*) AS n FROM ax_steering s WHERE s.delivered = 0 AND (s.source IS NULL OR s.source != ?) AND (s.agent_id IS NULL OR s.agent_id != ?) AND (s.target IS NULL OR s.target = ?) AND (s.target IS NOT NULL OR s.created_at > ?) AND NOT EXISTS (SELECT 1 FROM ax_steering_deliveries d WHERE d.steering_id = s.id AND d.consumer = ?)',
|
|
1252
|
+
)
|
|
1253
|
+
.get(consumer, consumer, consumer, broadcastCutoffIso(), consumer)?.n
|
|
1254
|
+
: db
|
|
1255
|
+
.query<{ n: number }, [string]>(
|
|
1256
|
+
'SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0 AND target IS NULL AND created_at > ?',
|
|
1186
1257
|
)
|
|
1187
|
-
.get(
|
|
1188
|
-
: db.query<{ n: number }, []>('SELECT COUNT(*) AS n FROM ax_steering WHERE delivered = 0').get()?.n;
|
|
1258
|
+
.get(broadcastCutoffIso())?.n;
|
|
1189
1259
|
return Number(n ?? 0);
|
|
1190
1260
|
}
|
|
1191
1261
|
|
|
@@ -946,7 +946,6 @@ export function addCanvasNode(input: CanvasAddNodeInput): {
|
|
|
946
946
|
zIndex: 1,
|
|
947
947
|
collapsed: false,
|
|
948
948
|
pinned: false,
|
|
949
|
-
dockPosition: null,
|
|
950
949
|
data,
|
|
951
950
|
};
|
|
952
951
|
|
|
@@ -1133,7 +1132,7 @@ export function removeCanvasNode(id: string): {
|
|
|
1133
1132
|
}
|
|
1134
1133
|
|
|
1135
1134
|
function isArrangeLocked(node: CanvasNodeState): boolean {
|
|
1136
|
-
return node.pinned || node.
|
|
1135
|
+
return node.pinned || node.data.arrangeLocked === true;
|
|
1137
1136
|
}
|
|
1138
1137
|
|
|
1139
1138
|
function collectArrangeExcludedNodeIds(nodes: CanvasNodeState[]): Set<string> {
|
|
@@ -1219,7 +1218,7 @@ function rectsOverlap(a: ArrangeObstacleRect, b: ArrangeObstacleRect): boolean {
|
|
|
1219
1218
|
|
|
1220
1219
|
function collectGridArrangeObstacles(nodes: CanvasNodeState[], excludedIds: Set<string>): ArrangeObstacleRect[] {
|
|
1221
1220
|
return nodes
|
|
1222
|
-
.filter((node) => excludedIds.has(node.id)
|
|
1221
|
+
.filter((node) => excludedIds.has(node.id))
|
|
1223
1222
|
.map((node) => ({
|
|
1224
1223
|
id: node.id,
|
|
1225
1224
|
position: { ...node.position },
|
|
@@ -1247,7 +1246,7 @@ function shiftGridUpdatesBelowObstacles(
|
|
|
1247
1246
|
): CanvasNodeUpdate[] {
|
|
1248
1247
|
if (updates.length === 0 || obstacles.length === 0) return updates;
|
|
1249
1248
|
|
|
1250
|
-
// Grid arrange only sees movable nodes, so preserved locked
|
|
1249
|
+
// Grid arrange only sees movable nodes, so preserved locked-group frames
|
|
1251
1250
|
// need a separate obstacle pass before applying the planned positions.
|
|
1252
1251
|
const nodesById = new Map(nodes.map((node) => [node.id, node]));
|
|
1253
1252
|
let shifted = updates;
|
|
@@ -1465,6 +1464,20 @@ export function addCanvasEdge(input: {
|
|
|
1465
1464
|
return edge;
|
|
1466
1465
|
}
|
|
1467
1466
|
|
|
1467
|
+
export function updateCanvasEdge(
|
|
1468
|
+
id: string,
|
|
1469
|
+
patch: Partial<Pick<CanvasEdge, 'type' | 'label' | 'style' | 'animated'>>,
|
|
1470
|
+
): CanvasEdge {
|
|
1471
|
+
if (!canvasState.getEdges().some((edge) => edge.id === id)) {
|
|
1472
|
+
throw new Error(`Edge "${id}" not found.`);
|
|
1473
|
+
}
|
|
1474
|
+
const updated = canvasState.updateEdge(id, patch);
|
|
1475
|
+
if (!updated) {
|
|
1476
|
+
throw new Error('Retyping would duplicate an existing edge.');
|
|
1477
|
+
}
|
|
1478
|
+
return updated;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1468
1481
|
export function removeCanvasEdge(id: string): { removed: boolean } {
|
|
1469
1482
|
return { removed: canvasState.removeEdge(id) };
|
|
1470
1483
|
}
|
|
@@ -1512,7 +1525,6 @@ export function createCanvasGroup(input: CanvasCreateGroupInput): { id: string;
|
|
|
1512
1525
|
zIndex: 0,
|
|
1513
1526
|
collapsed: false,
|
|
1514
1527
|
pinned: false,
|
|
1515
|
-
dockPosition: null,
|
|
1516
1528
|
data,
|
|
1517
1529
|
});
|
|
1518
1530
|
|
|
@@ -1583,7 +1595,6 @@ export function createCanvasJsonRenderNode(input: JsonRenderNodeInput): {
|
|
|
1583
1595
|
zIndex: 1,
|
|
1584
1596
|
collapsed: false,
|
|
1585
1597
|
pinned: false,
|
|
1586
|
-
dockPosition: null,
|
|
1587
1598
|
data: createJsonRenderNodeData(id, input.title?.trim() || inferJsonRenderNodeTitle(spec), spec, {
|
|
1588
1599
|
viewerType: 'json-render',
|
|
1589
1600
|
...(input.strictSize ? { strictSize: true } : {}),
|
|
@@ -1627,7 +1638,6 @@ export function createCanvasStreamingJsonRenderNode(input: {
|
|
|
1627
1638
|
zIndex: 1,
|
|
1628
1639
|
collapsed: false,
|
|
1629
1640
|
pinned: false,
|
|
1630
|
-
dockPosition: null,
|
|
1631
1641
|
data: createJsonRenderNodeData(id, input.title?.trim() || 'Streaming', spec, {
|
|
1632
1642
|
viewerType: 'json-render',
|
|
1633
1643
|
streamStatus: 'open',
|
|
@@ -1703,7 +1713,6 @@ export function createCanvasGraphNode(input: GraphNodeInput): {
|
|
|
1703
1713
|
zIndex: 1,
|
|
1704
1714
|
collapsed: false,
|
|
1705
1715
|
pinned: false,
|
|
1706
|
-
dockPosition: null,
|
|
1707
1716
|
data: createJsonRenderNodeData(id, title, spec, {
|
|
1708
1717
|
viewerType: 'graph',
|
|
1709
1718
|
graphConfig: buildGraphConfig(input),
|