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.
Files changed (210) hide show
  1. package/.github/extensions/pmx-canvas/extension.mjs +154 -5
  2. package/.github/extensions/pmx-canvas/steering-delivery.mjs +79 -0
  3. package/CHANGELOG.md +139 -0
  4. package/Readme.md +72 -23
  5. package/dist/canvas/global.css +3069 -796
  6. package/dist/canvas/index.js +104 -104
  7. package/dist/json-render/index.css +1 -1
  8. package/dist/types/cli/agent.d.ts +1 -0
  9. package/dist/types/cli/commands/pump.d.ts +9 -0
  10. package/dist/types/client/App.d.ts +1 -1
  11. package/dist/types/client/canvas/AgentPresenceLayer.d.ts +11 -0
  12. package/dist/types/client/canvas/BarHint.d.ts +22 -0
  13. package/dist/types/client/canvas/CanvasNode.d.ts +5 -0
  14. package/dist/types/client/canvas/CanvasViewport.d.ts +4 -4
  15. package/dist/types/client/canvas/CommandBar.d.ts +10 -0
  16. package/dist/types/client/canvas/ConnectionBanner.d.ts +13 -0
  17. package/dist/types/client/canvas/ContextMenu.d.ts +6 -0
  18. package/dist/types/client/canvas/EdgeLayer.d.ts +2 -1
  19. package/dist/types/client/canvas/EmptyState.d.ts +11 -0
  20. package/dist/types/client/canvas/ExternalWriters.d.ts +5 -0
  21. package/dist/types/client/canvas/HumanPresenceLayer.d.ts +8 -0
  22. package/dist/types/client/canvas/Minimap.d.ts +12 -1
  23. package/dist/types/client/canvas/ScopeFenceLayer.d.ts +8 -0
  24. package/dist/types/client/canvas/SelectionBar.d.ts +6 -0
  25. package/dist/types/client/canvas/SessionPanel.d.ts +1 -0
  26. package/dist/types/client/canvas/SessionReceipt.d.ts +17 -0
  27. package/dist/types/client/canvas/TextPrompt.d.ts +25 -0
  28. package/dist/types/client/canvas/ToolRail.d.ts +21 -0
  29. package/dist/types/client/canvas/TopBar.d.ts +7 -0
  30. package/dist/types/client/canvas/canvas-area.d.ts +25 -0
  31. package/dist/types/client/canvas/create-in-view.d.ts +18 -0
  32. package/dist/types/client/canvas/import-files.d.ts +8 -0
  33. package/dist/types/client/canvas/kind-colors.d.ts +3 -0
  34. package/dist/types/client/canvas/use-focus-trap.d.ts +14 -0
  35. package/dist/types/client/canvas/use-now.d.ts +6 -0
  36. package/dist/types/client/icons.d.ts +11 -4
  37. package/dist/types/client/nodes/GroupNode.d.ts +6 -1
  38. package/dist/types/client/nodes/MarkdownNode.d.ts +2 -0
  39. package/dist/types/client/nodes/inline-editor-commands.d.ts +3 -4
  40. package/dist/types/client/state/canvas-store.d.ts +49 -5
  41. package/dist/types/client/state/human-store.d.ts +26 -0
  42. package/dist/types/client/state/iframe-mode.d.ts +1 -0
  43. package/dist/types/client/state/intent-bridge.d.ts +37 -5
  44. package/dist/types/client/state/node-factory.d.ts +0 -1
  45. package/dist/types/client/state/presence-store.d.ts +78 -0
  46. package/dist/types/client/state/session-store.d.ts +176 -0
  47. package/dist/types/client/types.d.ts +2 -1
  48. package/dist/types/client/utils/platform.d.ts +2 -0
  49. package/dist/types/server/agent-presence.d.ts +192 -0
  50. package/dist/types/server/ax-gate-ttl.d.ts +6 -0
  51. package/dist/types/server/ax-interaction.d.ts +4 -10
  52. package/dist/types/server/ax-state-manager.d.ts +21 -2
  53. package/dist/types/server/ax-state.d.ts +49 -3
  54. package/dist/types/server/ax-wait.d.ts +13 -0
  55. package/dist/types/server/canvas-db.d.ts +9 -1
  56. package/dist/types/server/canvas-operations.d.ts +1 -0
  57. package/dist/types/server/canvas-state.d.ts +27 -13
  58. package/dist/types/server/canvas-validation.d.ts +0 -14
  59. package/dist/types/server/html-surface.d.ts +1 -1
  60. package/dist/types/server/human-presence.d.ts +40 -0
  61. package/dist/types/server/index.d.ts +29 -3
  62. package/dist/types/server/intent-registry.d.ts +7 -0
  63. package/dist/types/server/mutation-history.d.ts +15 -2
  64. package/dist/types/server/operations/invoker.d.ts +13 -2
  65. package/dist/types/server/operations/ops/ax-presence.d.ts +2 -0
  66. package/dist/types/server/operations/ops/human-presence.d.ts +3 -0
  67. package/dist/types/server/operations/registry.d.ts +11 -0
  68. package/dist/types/server/operations/types.d.ts +1 -1
  69. package/dist/types/server/scope-fence.d.ts +62 -0
  70. package/dist/types/server/server.d.ts +0 -12
  71. package/dist/types/shared/agent-presence.d.ts +130 -0
  72. package/dist/types/shared/approval-gates.d.ts +19 -0
  73. package/dist/types/shared/auto-arrange.d.ts +0 -1
  74. package/dist/types/shared/ax-kinds.d.ts +11 -0
  75. package/dist/types/shared/human-presence.d.ts +31 -0
  76. package/dist/types/shared/iframe-probe.d.ts +8 -0
  77. package/dist/types/shared/scope-fence.d.ts +21 -0
  78. package/docs/RELEASE.md +6 -0
  79. package/docs/ax-host-adapter-contract.md +30 -14
  80. package/docs/cli.md +37 -1
  81. package/docs/environment.md +3 -0
  82. package/docs/http-api.md +222 -5
  83. package/docs/mcp.md +6 -5
  84. package/docs/node-types.md +3 -1
  85. package/docs/plans/plan-009-tech-debt-backlog.md +13 -0
  86. package/docs/screenshot.png +0 -0
  87. package/docs/screenshots/multi-agent-live.png +0 -0
  88. package/docs/screenshots/workbench-live-dark.png +0 -0
  89. package/docs/screenshots/workbench-live-light.png +0 -0
  90. package/docs/sdk.md +1 -1
  91. package/package.json +1 -1
  92. package/skills/pmx-canvas/SKILL.md +103 -16
  93. package/skills/pmx-canvas/references/codex-app-adapter.md +28 -0
  94. package/skills/pmx-canvas/references/full-reference.md +55 -16
  95. package/skills/pmx-canvas/references/github-copilot-app-adapter.md +41 -24
  96. package/skills/pmx-canvas-orchestration/SKILL.md +56 -2
  97. package/skills/pmx-canvas-testing/SKILL.md +17 -0
  98. package/src/cli/agent.ts +1 -0
  99. package/src/cli/commands/copilot.ts +15 -8
  100. package/src/cli/commands/nodes.ts +1 -19
  101. package/src/cli/commands/pump.ts +252 -0
  102. package/src/cli/index.ts +1 -5
  103. package/src/cli/shared.ts +1 -1
  104. package/src/client/App.tsx +209 -585
  105. package/src/client/canvas/AgentPresenceLayer.tsx +74 -0
  106. package/src/client/canvas/AttentionHistory.tsx +0 -10
  107. package/src/client/canvas/BarHint.tsx +49 -0
  108. package/src/client/canvas/CanvasNode.tsx +488 -136
  109. package/src/client/canvas/CanvasViewport.tsx +190 -94
  110. package/src/client/canvas/CommandBar.tsx +140 -0
  111. package/src/client/canvas/CommandPalette.tsx +133 -58
  112. package/src/client/canvas/ConnectionBanner.tsx +65 -0
  113. package/src/client/canvas/ContextMenu.tsx +88 -27
  114. package/src/client/canvas/ContextPinBar.tsx +2 -2
  115. package/src/client/canvas/EdgeLayer.tsx +121 -38
  116. package/src/client/canvas/EmptyState.tsx +93 -0
  117. package/src/client/canvas/ExpandedNodeOverlay.tsx +64 -118
  118. package/src/client/canvas/ExternalWriters.tsx +308 -0
  119. package/src/client/canvas/FocusFieldLayer.tsx +12 -1
  120. package/src/client/canvas/HumanPresenceLayer.tsx +45 -0
  121. package/src/client/canvas/IntentLayer.tsx +1 -1
  122. package/src/client/canvas/Minimap.tsx +117 -190
  123. package/src/client/canvas/ScopeFenceLayer.tsx +38 -0
  124. package/src/client/canvas/SelectionBar.tsx +175 -23
  125. package/src/client/canvas/SessionPanel.tsx +481 -0
  126. package/src/client/canvas/SessionReceipt.tsx +141 -0
  127. package/src/client/canvas/ShortcutOverlay.tsx +21 -6
  128. package/src/client/canvas/SnapshotPanel.tsx +166 -80
  129. package/src/client/canvas/TextPrompt.tsx +128 -0
  130. package/src/client/canvas/ToolRail.tsx +447 -0
  131. package/src/client/canvas/TopBar.tsx +308 -0
  132. package/src/client/canvas/auto-fit.ts +1 -5
  133. package/src/client/canvas/canvas-area.ts +41 -0
  134. package/src/client/canvas/create-in-view.ts +48 -0
  135. package/src/client/canvas/import-files.ts +64 -0
  136. package/src/client/canvas/kind-colors.ts +22 -0
  137. package/src/client/canvas/snap-guides.ts +1 -1
  138. package/src/client/canvas/use-focus-trap.ts +54 -0
  139. package/src/client/canvas/use-now.ts +17 -0
  140. package/src/client/canvas/use-pan-zoom.ts +13 -2
  141. package/src/client/icons.tsx +57 -18
  142. package/src/client/nodes/GroupNode.tsx +9 -28
  143. package/src/client/nodes/InlineFormatBar.tsx +4 -3
  144. package/src/client/nodes/InlineMarkdownEditor.tsx +27 -0
  145. package/src/client/nodes/MarkdownNode.tsx +59 -24
  146. package/src/client/nodes/MdFormatBar.tsx +2 -4
  147. package/src/client/nodes/inline-editor-commands.ts +17 -6
  148. package/src/client/nodes/use-surface-frame.ts +3 -1
  149. package/src/client/state/attention-bridge.ts +3 -0
  150. package/src/client/state/canvas-store.ts +273 -84
  151. package/src/client/state/human-store.ts +175 -0
  152. package/src/client/state/iframe-mode.ts +33 -9
  153. package/src/client/state/intent-bridge.ts +75 -13
  154. package/src/client/state/node-factory.ts +2 -8
  155. package/src/client/state/presence-store.ts +168 -0
  156. package/src/client/state/session-store.ts +419 -0
  157. package/src/client/state/sse-bridge.ts +93 -30
  158. package/src/client/theme/global.css +3069 -796
  159. package/src/client/types.ts +3 -1
  160. package/src/client/utils/platform.ts +6 -1
  161. package/src/mcp/canvas-access.ts +2 -2
  162. package/src/server/agent-presence.ts +755 -0
  163. package/src/server/ax-context.ts +45 -0
  164. package/src/server/ax-gate-ttl.ts +61 -0
  165. package/src/server/ax-interaction.ts +29 -1
  166. package/src/server/ax-state-manager.ts +78 -6
  167. package/src/server/ax-state.ts +82 -15
  168. package/src/server/ax-wait.ts +33 -0
  169. package/src/server/canvas-db.ts +103 -33
  170. package/src/server/canvas-operations.ts +17 -8
  171. package/src/server/canvas-state.ts +131 -58
  172. package/src/server/canvas-validation.ts +3 -53
  173. package/src/server/demo-state.json +2 -70
  174. package/src/server/html-surface.ts +11 -2
  175. package/src/server/human-presence.ts +160 -0
  176. package/src/server/index.ts +107 -4
  177. package/src/server/intent-registry.ts +13 -0
  178. package/src/server/mutation-history.ts +47 -2
  179. package/src/server/operations/composites.ts +6 -3
  180. package/src/server/operations/http.ts +10 -1
  181. package/src/server/operations/index.ts +4 -0
  182. package/src/server/operations/invoker.ts +33 -6
  183. package/src/server/operations/ops/ax-presence.ts +62 -0
  184. package/src/server/operations/ops/ax-read.ts +8 -0
  185. package/src/server/operations/ops/ax-state.ts +106 -1
  186. package/src/server/operations/ops/ax-timeline.ts +54 -5
  187. package/src/server/operations/ops/ax-work.ts +46 -0
  188. package/src/server/operations/ops/batch.ts +3 -1
  189. package/src/server/operations/ops/edges.ts +88 -2
  190. package/src/server/operations/ops/groups.ts +4 -3
  191. package/src/server/operations/ops/human-presence.ts +35 -0
  192. package/src/server/operations/ops/json-render.ts +11 -1
  193. package/src/server/operations/ops/nodes.ts +65 -16
  194. package/src/server/operations/ops/query.ts +1 -0
  195. package/src/server/operations/ops/snapshots.ts +16 -0
  196. package/src/server/operations/registry.ts +196 -1
  197. package/src/server/operations/types.ts +1 -1
  198. package/src/server/scope-fence.ts +207 -0
  199. package/src/server/server.ts +177 -79
  200. package/src/server/trace-manager.ts +0 -2
  201. package/src/server/web-artifacts.ts +0 -1
  202. package/src/shared/agent-presence.ts +162 -0
  203. package/src/shared/approval-gates.ts +35 -0
  204. package/src/shared/auto-arrange.ts +121 -13
  205. package/src/shared/ax-kinds.ts +23 -0
  206. package/src/shared/human-presence.ts +30 -0
  207. package/src/shared/iframe-probe.ts +8 -0
  208. package/src/shared/scope-fence.ts +31 -0
  209. package/dist/types/client/canvas/DockedNode.d.ts +0 -4
  210. package/src/client/canvas/DockedNode.tsx +0 -165
@@ -65,7 +65,6 @@ export interface CanvasNodeState {
65
65
  zIndex: number;
66
66
  collapsed: boolean;
67
67
  pinned: boolean;
68
- dockPosition: 'left' | 'right' | null;
69
68
  data: Record<string, unknown>;
70
69
  }
71
70
  export interface ViewportState {
@@ -120,11 +119,10 @@ export interface CanvasNodeUpdate {
120
119
  height: number;
121
120
  };
122
121
  collapsed?: boolean;
123
- dockPosition?: 'left' | 'right' | null;
124
122
  }
125
123
  export type CanvasChangeType = 'pins' | 'nodes' | 'ax' | 'ax-timeline';
126
124
  export interface MutationRecordInfo {
127
- operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'arrange' | 'batch' | 'groupNodes' | 'ungroupNodes' | 'viewport';
125
+ operationType: 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'updateEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'arrange' | 'batch' | 'groupNodes' | 'releaseGroupChildren' | 'viewport';
128
126
  description: string;
129
127
  forward: () => void;
130
128
  inverse: () => void;
@@ -202,13 +200,6 @@ declare class CanvasStateManager {
202
200
  private emptyPersistedState;
203
201
  /** Load canvas state from SQLite. Call once on server startup. */
204
202
  loadFromDisk(options?: LoadFromDiskOptions): boolean;
205
- /**
206
- * Whether this workspace's canvas DB already holds saved state. Used to gate
207
- * brand-new-workspace seeding (e.g. the default docked status/context widgets)
208
- * so we never add nodes to a canvas that already has content. Reflects the
209
- * pre-run persisted flag until the next save.
210
- */
211
- hasPersistedState(): boolean;
212
203
  /** Debounced save — coalesces rapid mutations into a single write. */
213
204
  private scheduleSave;
214
205
  flushToDisk(): void;
@@ -247,6 +238,7 @@ declare class CanvasStateManager {
247
238
  annotations: CanvasAnnotation[];
248
239
  } | null;
249
240
  /** Delete a snapshot. */
241
+ renameSnapshot(id: string, name: string): boolean;
250
242
  deleteSnapshot(id: string): boolean;
251
243
  /** Remove all snapshots from the DB. Used by test teardown. */
252
244
  clearAllSnapshots(): void;
@@ -259,6 +251,7 @@ declare class CanvasStateManager {
259
251
  getNode(id: string): CanvasNodeState | undefined;
260
252
  getNodeForPersistence(id: string): CanvasNodeState | undefined;
261
253
  addEdge(edge: CanvasEdge): boolean;
254
+ updateEdge(id: string, patch: Partial<Pick<CanvasEdge, 'type' | 'label' | 'style' | 'animated'>>): CanvasEdge | null;
262
255
  removeEdge(id: string): boolean;
263
256
  getEdges(): CanvasEdge[];
264
257
  getEdgesForNode(nodeId: string): CanvasEdge[];
@@ -323,13 +316,19 @@ declare class CanvasStateManager {
323
316
  detail?: string | null;
324
317
  action?: string | null;
325
318
  nodeIds?: string[];
319
+ ttlMs?: number;
326
320
  }, options?: {
327
321
  source?: PmxAxSource;
328
322
  }): PmxAxApprovalGate;
329
- resolveApproval(id: string, decision: 'approved' | 'rejected', options?: {
323
+ resolveApproval(id: string, decision: 'approved' | 'rejected' | 'held', options?: {
330
324
  resolution?: string;
331
325
  source?: PmxAxSource;
332
326
  }): PmxAxApprovalGate | null;
327
+ /** Reopen a resolved (typically auto-held) gate with a fresh TTL. */
328
+ reopenApproval(id: string, options?: {
329
+ ttlMs?: number;
330
+ source?: PmxAxSource;
331
+ }): PmxAxApprovalGate | null;
333
332
  getReviewAnnotations(): PmxAxReviewAnnotation[];
334
333
  addReviewAnnotation(input: {
335
334
  body: string;
@@ -388,6 +387,10 @@ declare class CanvasStateManager {
388
387
  setPolicy(patch: {
389
388
  tools?: Partial<PmxAxPolicy['tools']>;
390
389
  prompt?: Partial<PmxAxPolicy['prompt']>;
390
+ scope?: {
391
+ nodeIds: string[];
392
+ padding?: number;
393
+ } | null;
391
394
  }, options?: {
392
395
  source?: PmxAxSource;
393
396
  }): PmxAxPolicy;
@@ -419,7 +422,7 @@ declare class CanvasStateManager {
419
422
  agentId?: string | null;
420
423
  target?: string | null;
421
424
  }): PmxAxSteeringMessage;
422
- markSteeringDelivered(id: string): boolean;
425
+ markSteeringDelivered(id: string, consumer?: string | null): boolean;
423
426
  ingestActivity(input: {
424
427
  kind: PmxAxActivityKind;
425
428
  title: string;
@@ -477,8 +480,19 @@ declare class CanvasStateManager {
477
480
  clearContextPins(): void;
478
481
  /** Move child nodes into a group. Sets data.parentGroup on children and data.children on the group. */
479
482
  groupNodes(groupId: string, childIds: string[], options?: GroupNodesOptions): boolean;
480
- /** Remove all children from a group, clearing their parentGroup. */
483
+ /**
484
+ * Ungroup = dissolve: the children become independent nodes (members of the
485
+ * enclosing group when nested) and the frame is removed, in one undo step.
486
+ * Delegates to `removeNode` so the human's Ungroup and the agent's
487
+ * `group.remove` are the same operation. False when `groupId` is not a group.
488
+ */
481
489
  ungroupNodes(groupId: string): boolean;
490
+ /**
491
+ * Internal membership step: release every child but keep the frame (the
492
+ * first half of "set this group's children to …"). Not an ungroup — a
493
+ * human or agent ungroup dissolves the frame (`ungroupNodes`).
494
+ */
495
+ releaseGroupChildren(groupId: string): boolean;
482
496
  clear(): void;
483
497
  }
484
498
  export declare const canvasState: CanvasStateManager;
@@ -21,12 +21,6 @@ export interface CanvasSizeWarning {
21
21
  minWidth: number;
22
22
  minHeight: number;
23
23
  }
24
- export interface CanvasHiddenEdgeEndpoint {
25
- edgeId: string;
26
- nodeId: string;
27
- nodeTitle: string | null;
28
- dockPosition: 'left' | 'right';
29
- }
30
24
  export interface CanvasValidationResult {
31
25
  ok: boolean;
32
26
  collisions: CanvasValidationPair[];
@@ -37,13 +31,6 @@ export interface CanvasValidationResult {
37
31
  from: string;
38
32
  to: string;
39
33
  }>;
40
- /**
41
- * Edges whose endpoint node is DOCKED — it renders in the HUD column, not on
42
- * the canvas, so the edge visually terminates in empty space even though both
43
- * endpoint IDs resolve (0.4.6 orb feedback #1). Advisory: reported for
44
- * diagnosis, but does NOT fail `ok` (see the note at the return site).
45
- */
46
- hiddenEdgeEndpoints: CanvasHiddenEdgeEndpoint[];
47
34
  /** Nodes below their type's readable minimum (advisory — does not fail `ok`). */
48
35
  sizeWarnings: CanvasSizeWarning[];
49
36
  summary: {
@@ -53,7 +40,6 @@ export interface CanvasValidationResult {
53
40
  containments: number;
54
41
  containmentViolations: number;
55
42
  missingEdgeEndpoints: number;
56
- hiddenEdgeEndpoints: number;
57
43
  sizeWarnings: number;
58
44
  };
59
45
  }
@@ -60,7 +60,7 @@ export declare function buildContentHeightReporter(frameToken: string): string;
60
60
  * of the main SPA bundle). Escaping is mandatory: the source is arbitrary node
61
61
  * data and must not be parsed as markup.
62
62
  */
63
- export declare function buildMermaidSurfaceHtml(source: string): string;
63
+ export declare function buildMermaidSurfaceHtml(source: string, inlineEntry?: string | null): string;
64
64
  export interface HtmlSurfaceOptions {
65
65
  theme: SurfaceTheme;
66
66
  /**
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import { type HumanPresence, type HumanPresenceSnapshot } from '../shared/human-presence.js';
3
+ /**
4
+ * Registry of open workbench tabs (rail-chrome-v2 phase 8). Fed by the
5
+ * browser's own `human.presence.set` heartbeats; broadcast as one coalesced
6
+ * `human-presence` SSE frame on every change including expiry, so clients
7
+ * never run their own expiry timers. `lockedNodes()` is the edit lock the
8
+ * operation registry consults for agent writes (user wins).
9
+ */
10
+ export declare const HUMAN_PRESENCE_SHAPE: {
11
+ clientId: z.ZodString;
12
+ name: z.ZodOptional<z.ZodString>;
13
+ cursor: z.ZodOptional<z.ZodNullable<z.ZodObject<{
14
+ x: z.ZodNumber;
15
+ y: z.ZodNumber;
16
+ }, z.core.$strip>>>;
17
+ grabbingNodeId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
+ /** Explicit leave (tab closing): drop the presence now. */
19
+ left: z.ZodOptional<z.ZodBoolean>;
20
+ };
21
+ type Emitter = (event: string, payload: Record<string, unknown>) => void;
22
+ export declare class HumanPresenceRegistry {
23
+ private readonly humans;
24
+ private emit;
25
+ private sweepTimer;
26
+ private emitTimer;
27
+ setEmitter(emitter: Emitter | null): void;
28
+ set(raw: unknown, now?: number): HumanPresence | null;
29
+ snapshot(now?: number): HumanPresenceSnapshot;
30
+ /** Nodes a human is holding right now → that human's name. The edit lock. */
31
+ lockedNodes(now?: number): Map<string, string>;
32
+ reset(): void;
33
+ private publicView;
34
+ private sweep;
35
+ private scheduleEmit;
36
+ private ensureSweeper;
37
+ private maybeStopSweeper;
38
+ }
39
+ export declare const humanPresence: HumanPresenceRegistry;
40
+ export {};
@@ -1,4 +1,5 @@
1
1
  import { EventEmitter } from 'node:events';
2
+ import type { AgentPhase, AgentPresence, AgentPresenceSnapshot } from '../shared/agent-presence.js';
2
3
  import { canvasState } from './canvas-state.js';
3
4
  import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
4
5
  import { type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
@@ -32,6 +33,7 @@ export declare class PmxCanvas extends EventEmitter {
32
33
  port?: number;
33
34
  });
34
35
  private runIntentCommit;
36
+ private runIntentCommitInner;
35
37
  start(options?: {
36
38
  open?: boolean;
37
39
  automationWebView?: boolean | CanvasAutomationWebViewOptions;
@@ -134,7 +136,7 @@ export declare class PmxCanvas extends EventEmitter {
134
136
  childLayout?: 'grid' | 'column' | 'flow';
135
137
  intentId?: string;
136
138
  }): boolean;
137
- /** Remove all children from a group (the group node remains). */
139
+ /** Dissolve a group: children released (into the enclosing group when nested), frame removed — one undo step. */
138
140
  ungroupNodes(groupId: string, options?: {
139
141
  intentId?: string;
140
142
  }): boolean;
@@ -168,7 +170,7 @@ export declare class PmxCanvas extends EventEmitter {
168
170
  agentId?: string | null;
169
171
  target?: string | null;
170
172
  }): PmxAxSteeringMessage;
171
- markSteeringDelivered(id: string): boolean;
173
+ markSteeringDelivered(id: string, consumer?: string | null): boolean;
172
174
  /**
173
175
  * Ghost Cursor of Intent — announce a spatial move before making it. The ghost
174
176
  * is ephemeral presence (auto-expiring, never snapshotted); the registry emits
@@ -227,6 +229,11 @@ export declare class PmxCanvas extends EventEmitter {
227
229
  resolution?: string;
228
230
  source?: PmxAxSource;
229
231
  }): PmxAxApprovalGate | null;
232
+ /** Reopen a resolved (typically auto-held) approval gate with a fresh TTL. */
233
+ reopenApproval(id: string, options?: {
234
+ ttlMs?: number;
235
+ source?: PmxAxSource;
236
+ }): PmxAxApprovalGate | null;
230
237
  addEvidence(input: {
231
238
  kind: PmxAxEvidenceKind;
232
239
  title: string;
@@ -262,6 +269,26 @@ export declare class PmxCanvas extends EventEmitter {
262
269
  reportHostCapability(input: unknown, options?: {
263
270
  source?: PmxAxSource;
264
271
  }): PmxAxHostCapability;
272
+ /** Who is writing to this board right now, their phase, and the context budget. */
273
+ getAgentPresence(): AgentPresenceSnapshot;
274
+ /**
275
+ * Update this SDK caller's presence (rail-chrome-v2 phase 2): phase, detail,
276
+ * focus node, cursor, and `attached` (true starts a session, false ends it).
277
+ */
278
+ setAgentPresence(input: {
279
+ agentId?: string | null;
280
+ label?: string;
281
+ phase?: AgentPhase;
282
+ detail?: string | null;
283
+ focusNodeId?: string | null;
284
+ cursor?: {
285
+ x: number;
286
+ y: number;
287
+ } | null;
288
+ attached?: boolean;
289
+ }, options?: {
290
+ source?: PmxAxSource;
291
+ }): AgentPresence;
265
292
  listElicitations(): PmxAxElicitation[];
266
293
  requestElicitation(input: {
267
294
  prompt: string;
@@ -388,7 +415,6 @@ export declare class PmxCanvas extends EventEmitter {
388
415
  height: number;
389
416
  };
390
417
  collapsed?: boolean;
391
- dockPosition?: 'left' | 'right' | null;
392
418
  }>): {
393
419
  applied: number;
394
420
  skipped: number;
@@ -27,6 +27,13 @@ export declare class IntentRegistry {
27
27
  * synchronous: once this method has accepted the intent, a later veto cannot
28
28
  * race in between the check and the mutation.
29
29
  */
30
+ /**
31
+ * How a linked commit would fare right now. 'absent' = the ghost expired
32
+ * quietly (TTL sweep) with no veto — the mutation should proceed UNLINKED
33
+ * instead of failing: a slow spawn is not a human "no", and inside a batch
34
+ * the 409 poisoned every sibling op (orchestration review #5).
35
+ */
36
+ commitDisposition(id: string): 'live' | 'vetoed' | 'committing' | 'absent';
30
37
  beginCommit(id: string, allowedKinds: readonly PmxAxIntentKind[]): PmxAxIntent;
31
38
  completeCommit(id: string, settledNodeId?: string): void;
32
39
  abortCommit(id: string): void;
@@ -12,12 +12,18 @@
12
12
  * - _replaying flag prevents undo/redo from recording new entries
13
13
  */
14
14
  import type { CanvasNodeState, CanvasEdge } from './canvas-state.js';
15
- export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'batch' | 'viewport' | 'groupNodes' | 'ungroupNodes';
15
+ export type MutationOp = 'addNode' | 'updateNode' | 'removeNode' | 'addEdge' | 'updateEdge' | 'removeEdge' | 'addAnnotation' | 'removeAnnotation' | 'clear' | 'arrange' | 'restoreSnapshot' | 'setPins' | 'setAxFocus' | 'addWorkItem' | 'updateWorkItem' | 'requestApproval' | 'resolveApproval' | 'addReviewAnnotation' | 'updateReviewAnnotation' | 'requestElicitation' | 'respondElicitation' | 'requestMode' | 'resolveModeRequest' | 'setPolicy' | 'batch' | 'viewport' | 'groupNodes' | 'releaseGroupChildren';
16
+ /** Who made a mutation: the human in the workbench, or an agent through any transport. */
17
+ export type MutationActor = 'human' | 'agent';
16
18
  export interface MutationEntry {
17
19
  id: string;
18
20
  timestamp: string;
19
21
  description: string;
20
22
  operationType: MutationOp;
23
+ actor: MutationActor;
24
+ /** Writer key (agentId, else transport source) for agent entries — lets a
25
+ * client prove "the top edit is MINE" before undoing on a multi-agent board. */
26
+ writer?: string;
21
27
  forward: () => void;
22
28
  inverse: () => void;
23
29
  }
@@ -25,6 +31,8 @@ export interface MutationSummary {
25
31
  id: string;
26
32
  timestamp: string;
27
33
  description: string;
34
+ actor: MutationActor;
35
+ writer?: string;
28
36
  operationType: MutationOp;
29
37
  isCurrent: boolean;
30
38
  isUndone: boolean;
@@ -71,7 +79,9 @@ declare class MutationHistory {
71
79
  * Record a new mutation. Truncates any redo-able future, then appends.
72
80
  * If called while replaying (undo/redo), the call is silently ignored.
73
81
  */
74
- record(entry: Omit<MutationEntry, 'id' | 'timestamp'>): void;
82
+ record(entry: Omit<MutationEntry, 'id' | 'timestamp' | 'actor'> & {
83
+ actor?: MutationActor;
84
+ }): void;
75
85
  /** Undo the last applied mutation. Returns the entry that was undone, or null. */
76
86
  undo(): MutationEntry | null;
77
87
  /** Redo the next undone mutation. Returns the entry that was redone, or null. */
@@ -80,6 +90,8 @@ declare class MutationHistory {
80
90
  canRedo(): boolean;
81
91
  /** Get all entries with current/undone status for display. */
82
92
  getSummaries(): MutationSummary[];
93
+ /** The entry Ctrl+Z would undo next (top of the shared stack), or null. */
94
+ top(): MutationSummary | null;
83
95
  /** Human-readable timeline for the canvas://history resource. */
84
96
  toHumanReadable(): string;
85
97
  /** Number of recorded entries. */
@@ -102,4 +114,5 @@ export declare function diffLayouts(snapshotName: string, snapshotLayout: {
102
114
  */
103
115
  export declare function formatDiff(diff: SnapshotDiffResult): string;
104
116
  export declare const mutationHistory: MutationHistory;
117
+ export declare function setMutationActor(actor: MutationActor | null, writer?: string | null): void;
105
118
  export {};
@@ -1,13 +1,24 @@
1
1
  export interface OperationInvoker {
2
2
  invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
3
3
  }
4
+ /**
5
+ * The agent-presence label for a transport: `PMX_CANVAS_AGENT_SOURCE` lets a
6
+ * host name its agent ('codex', 'claude-code', …) so writes, attach and cursor
7
+ * all key on one identity; otherwise the transport's own label.
8
+ */
9
+ export declare function agentSourceLabel(fallback: string): string;
4
10
  /** Runs operations in-process against the shared canvasState singleton. */
5
11
  export declare class LocalOperationInvoker implements OperationInvoker {
12
+ private readonly source;
13
+ /** `source` labels this caller's agent presence ('mcp', 'sdk', …). */
14
+ constructor(source?: string);
6
15
  invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
7
16
  }
8
17
  /** Builds the HTTP request from the op's route template (`:id` from input, GET flags to query). */
9
18
  export declare class HttpOperationInvoker implements OperationInvoker {
10
19
  private readonly baseUrl;
11
- constructor(baseUrl: string);
12
- invoke(name: string, input: Record<string, unknown>): Promise<unknown>;
20
+ private readonly source;
21
+ /** `source` labels this caller's agent presence on the server ('cli', 'mcp', …). */
22
+ constructor(baseUrl: string, source?: string);
23
+ invoke(name: string, rawInput: Record<string, unknown>): Promise<unknown>;
13
24
  }
@@ -0,0 +1,2 @@
1
+ import { type Operation } from '../types.js';
2
+ export declare const axPresenceOperations: Operation[];
@@ -0,0 +1,3 @@
1
+ export declare const humanPresenceSetOperation: import("../types.js").Operation;
2
+ export declare const humanPresenceGetOperation: import("../types.js").Operation;
3
+ export declare const humanPresenceOperations: import("../types.js").Operation[];
@@ -18,6 +18,17 @@ export interface ExecuteOperationMeta {
18
18
  * its inner dispatches (batch churn is exempt, matching the skill contract).
19
19
  */
20
20
  suppressAutoGhost?: boolean;
21
+ /**
22
+ * Who is calling — the presence writer label ('mcp', 'sdk', 'api', …).
23
+ * Defaults to 'api'. Workbench calls (suppressAutoGhost) never touch presence.
24
+ */
25
+ source?: string;
26
+ /**
27
+ * The human's own browser issued this call. Distinct from suppressAutoGhost
28
+ * (which batch also sets for its inner dispatches): the scope fence applies
29
+ * to agents only, and batch inner writes are always agent-originated.
30
+ */
31
+ fromWorkbench?: boolean;
21
32
  }
22
33
  export declare function executeOperation(name: string, rawInput: unknown, meta?: ExecuteOperationMeta): Promise<unknown>;
23
34
  export {};
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { ZodRawShape } from 'zod';
13
13
  import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
14
- export type OperationErrorStatus = 400 | 404 | 409;
14
+ export type OperationErrorStatus = 400 | 403 | 404 | 409;
15
15
  /** Operation failure that maps to an HTTP status + `{ ok:false, error }` body and MCP `isError`. */
16
16
  export declare class OperationError extends Error {
17
17
  readonly status: OperationErrorStatus;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Scope fence enforcement (rail-chrome-v2 phase 4, design item 4).
3
+ *
4
+ * When the AX policy carries a scope fence, an attached agent may only WRITE
5
+ * inside it: existing-node writes must target fenced nodes, new nodes must
6
+ * land inside the fence's bounding box (fenced nodes + padding), and
7
+ * board-wide writes (arrange, clear, restore) are refused. Reads are never
8
+ * fenced, and neither are the human's own workbench writes — the fence is the
9
+ * human's tool, not a cage for the human.
10
+ *
11
+ * One core check (`checkFenceTarget`) serves two describers: the operation
12
+ * registry (`checkScopeFence`, every HTTP/MCP/CLI write) and the sync SDK,
13
+ * whose methods bypass the registry and describe their own targets.
14
+ *
15
+ * Safe default: a mutating op this module does not know how to scope is
16
+ * refused while a fence is set, rather than silently allowed. Likewise a
17
+ * write this module cannot resolve (an edge given by search, a missing id)
18
+ * is refused — fail closed, never a bypass.
19
+ *
20
+ * Trust note: the workbench marker is a self-reported header. Under the
21
+ * local single-workspace model that is by design (any local process may
22
+ * write; the safety model is human veto plus this fence), so the fence holds
23
+ * cooperating agents to the region the human granted — it is not an
24
+ * authentication boundary.
25
+ */
26
+ import { type FenceRect } from '../shared/scope-fence.js';
27
+ import type { Operation } from './operations/types.js';
28
+ /** Bounding box of the fenced nodes plus padding; null when none of them exist. */
29
+ export declare function scopeFenceRect(fence: {
30
+ nodeIds: string[];
31
+ padding: number;
32
+ }): FenceRect | null;
33
+ /** What a write touches, in fence terms. */
34
+ export interface FenceTarget {
35
+ /** Existing nodes the write targets — every one must be fenced. */
36
+ nodeIds?: string[];
37
+ /** World points a write introduces (a new node, annotation points) — all must be inside the box. */
38
+ points?: Array<{
39
+ x: number;
40
+ y: number;
41
+ }>;
42
+ /** A new node with no position: refused (it cannot be placed inside the fence). */
43
+ unplacedCreate?: boolean;
44
+ /** Rewrites the whole board — never allowed under a fence. */
45
+ boardWide?: boolean;
46
+ /** The op is not fence-aware — refused while a fence is set. */
47
+ unknown?: string;
48
+ }
49
+ /** Returns a human-readable refusal, or null when the target is inside the fence. */
50
+ export declare function checkFenceTarget(target: FenceTarget, opName: string, writerKey?: string): string | null;
51
+ /** Describe what a registry op would write, in fence terms. */
52
+ export declare function describeOpTarget(op: Operation, rawInput: unknown): FenceTarget;
53
+ /** Registry entry point: refusal text or null. Only call for mutating, agent-originated ops. */
54
+ export declare function checkScopeFence(op: Operation, rawInput: unknown, writerKey?: string): string | null;
55
+ /**
56
+ * The GLOBAL fence belongs to the human: an agent must not clear, widen, or
57
+ * replace it through `ax.policy.set`. Per-agent territories (`agentScopes`)
58
+ * are the orchestration surface — an agent MAY set territories for OTHER
59
+ * writers (fencing its workers), but never its own: granting yourself a wider
60
+ * territory (or deleting it) is escalation.
61
+ */
62
+ export declare function checkScopeOwnership(rawInput: unknown, writerKey?: string): string | null;
@@ -93,18 +93,6 @@ export declare function setPrimaryWorkbenchCanvasPromptHandler(handler: PrimaryW
93
93
  export declare function isCanvasBundlePath(distPath: string, bundleDir: string): boolean;
94
94
  export declare function buildMacBrowserOpenScript(appName: string, url: string): string;
95
95
  export declare function openUrlInExternalBrowser(url: string): boolean;
96
- /**
97
- * Seed the docked status (left) + context (right) widgets so a freshly opened
98
- * canvas shows them by default — the same nodes the agent-event path creates on
99
- * demand (`status-main`, `context-main`), just present from the start.
100
- *
101
- * First-run only: we bail if the workspace canvas already has persisted state,
102
- * so we never add them to a board with content, and — because first-run state is
103
- * persisted on save — deleting or undocking them later is respected (they are
104
- * not re-seeded). Create-if-missing keeps it idempotent if the agent path
105
- * already made one. Returns true if anything was seeded.
106
- */
107
- export declare function ensureDefaultDockedNodes(): boolean;
108
96
  export declare function emitPrimaryWorkbenchEvent(event: string, payload?: PrimaryWorkbenchEventPayload): void;
109
97
  export declare function consumePrimaryWorkbenchIntents(limit?: number): PrimaryWorkbenchIntent[];
110
98
  export declare function getPrimaryWorkbenchUrl(workspaceRoot?: string): string | null;
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Agent presence — the contract behind every agent surface in the rail chrome
3
+ * (rail-chrome-v2 phase 2). Shared by server and client so the two cannot
4
+ * drift. See design/rail-chrome-v2/PLAN.md, "Phase 2 contract".
5
+ *
6
+ * Presence is DERIVED from feeds that already exist (AX activity ingest,
7
+ * agent-originated mutations) plus an explicit set for adapters with richer
8
+ * hooks. Nothing here is a second source of truth for work items, gates, or
9
+ * steering — those stay in the AX state.
10
+ */
11
+ export type AgentPhase = 'idle' | 'thinking' | 'tooling' | 'waiting-approval';
12
+ export declare const AGENT_PHASES: readonly AgentPhase[];
13
+ export interface AgentPresence {
14
+ /** Writer key: `agentId` when the host supplies one, else the source label. */
15
+ sessionId: string;
16
+ /** Host label (copilot, codex, mcp, api, cli, …). */
17
+ source: string;
18
+ agentId: string | null;
19
+ /** Display name. */
20
+ label: string;
21
+ phase: AgentPhase;
22
+ /** Tool or operation name while `tooling`. */
23
+ detail: string | null;
24
+ focusNodeId: string | null;
25
+ /** World coordinates. */
26
+ cursor: {
27
+ x: number;
28
+ y: number;
29
+ } | null;
30
+ /** A session is attached: `session-start` (or explicit attach) seen and no `session-end`. */
31
+ attached: boolean;
32
+ /** Fleet membership: the orchestrating writer's key this worker belongs to (chrome rolls workers up under it). */
33
+ parentAgentId?: string | null;
34
+ /** True when steering addressed to this writer can actually reach it: it is
35
+ * an attached session, or its consumer key has claimed deliveries before. */
36
+ steerable?: boolean;
37
+ /** Steers sitting unclaimed in this writer's delivery queue (pump-health:
38
+ * a growing count means the writer is not polling). Only set for steerable rows. */
39
+ pendingSteers?: number;
40
+ /** When this writer's consumer key last claimed its delivery queue — the
41
+ * proof-of-polling behind `steerable`. Absent for writers that never claimed
42
+ * (steerable only via an attached adapter loop). */
43
+ lastClaimAt?: string;
44
+ /** Agent writes observed for this writer — feeds the external-steering indicator. */
45
+ opCount: number;
46
+ /**
47
+ * The agent's REAL context window, when its host reports it (tokens used /
48
+ * window size). Null when no adapter reports — the top-bar meter then shows
49
+ * the pinned-context estimate against the configured budget instead, and
50
+ * says so.
51
+ */
52
+ contextUsage: {
53
+ used: number;
54
+ total: number;
55
+ } | null;
56
+ lastSeenAt: string;
57
+ }
58
+ export interface ContextBudget {
59
+ /** Estimated tokens of the pinned-context payload. */
60
+ used: number;
61
+ total: number;
62
+ }
63
+ /**
64
+ * One agent write, as the External Steering activity feed lists it. Derived
65
+ * from the same presence touch every agent-originated operation already
66
+ * makes — not a second log.
67
+ */
68
+ export interface AgentActivityEntry {
69
+ id: string;
70
+ at: string;
71
+ /** Writer key after attribution (the session's when the write was folded into it). */
72
+ sessionId: string;
73
+ label: string;
74
+ /** Registry operation name (node.add, edge.remove, ax.work.create, …). */
75
+ op: string;
76
+ /** Human sentence: "Created markdown “Release plan”". */
77
+ summary: string;
78
+ nodeId: string | null;
79
+ }
80
+ export interface AgentPresenceSnapshot {
81
+ presences: AgentPresence[];
82
+ budget: ContextBudget;
83
+ /** True when any presence is attached — the master gate for agent chrome. */
84
+ sessionActive: boolean;
85
+ /** Most recent agent writes, newest first (bounded by MAX_ACTIVITY_ENTRIES). */
86
+ activity: AgentActivityEntry[];
87
+ }
88
+ /** Activity entries kept for the feed — oldest fall off. */
89
+ export declare const MAX_ACTIVITY_ENTRIES = 50;
90
+ /** Unattached writers fade this long after their last write. */
91
+ export declare const PRESENCE_ACTIVITY_TTL_MS: number;
92
+ /** Attached sessions expire after this much quiet without a `session-end`. */
93
+ export declare const PRESENCE_ATTACHED_IDLE_TTL_MS: number;
94
+ /** `tooling` decays to `idle` after this much quiet. */
95
+ export declare const PRESENCE_TOOLING_SETTLE_MS = 4000;
96
+ /** Explicit `thinking` without any fresh touch settles to idle — hosts set it
97
+ * at turn start and often never send the turn-end idle, leaving three
98
+ * "Thinking" chips on a silent board. */
99
+ export declare const PRESENCE_THINKING_SETTLE_MS: number;
100
+ export declare const MAX_PRESENCES = 16;
101
+ /** Default `budget.total` when `PMX_CANVAS_CONTEXT_BUDGET_TOKENS` is unset. */
102
+ export declare const CONTEXT_BUDGET_DEFAULT_TOKENS = 32000;
103
+ /**
104
+ * Source labels that name a TRANSPORT, not an agent. A write arriving under
105
+ * one of these while exactly one session is attached is the attached agent's
106
+ * own work reaching the board through that transport (Copilot/Codex/Claude
107
+ * Code all write via MCP or HTTP while their host holds the session).
108
+ */
109
+ export declare const TRANSPORT_SOURCES: readonly string[];
110
+ /**
111
+ * The label a human-started session (*Start agent session* in the browser)
112
+ * carries until an agent writes into it — the session is a placeholder for
113
+ * whichever agent comes next, so the first identified writer names it.
114
+ */
115
+ export declare const HUMAN_STARTED_SESSION_LABEL = "Agent session";
116
+ /** The one selector every agent surface reads. */
117
+ export declare function isSessionActive(presences: readonly AgentPresence[]): boolean;
118
+ /** Writers that are live but not attached — the External Steering mode. */
119
+ export declare function externalWriters(presences: readonly AgentPresence[]): AgentPresence[];
120
+ /** Rough token estimate for a JSON payload (chars / 4) — good enough for a meter. */
121
+ export declare function estimateTokens(text: string): number;
122
+ /** Chip label per phase — shared by the top-bar chip and the on-canvas cursor chip. */
123
+ export declare function agentPhaseLabel(presence: Pick<AgentPresence, 'phase' | 'detail'>): string;
124
+ /**
125
+ * Stable per-agent identity hue (0-359) from the writer key. Phase colors
126
+ * kept telling the human WHAT an agent is doing while erasing WHO — two
127
+ * thinking agents rendered identically. Identity rides the glyph/border;
128
+ * phase stays on dots and labels.
129
+ */
130
+ export declare function agentIdentityHue(key: string): number;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Unattended approval policy (rail-chrome-v2 phase 4, design item 3).
3
+ *
4
+ * A pending approval gate carries a TTL. If the human does not answer in time
5
+ * the gate resolves to `held` — the safe default: the action does NOT proceed,
6
+ * a `policy` entry lands in the timeline, and the gate can be reopened from the
7
+ * session panel. Shared so the server's sweeper and the client's countdown read
8
+ * one clock.
9
+ */
10
+ export declare const DEFAULT_GATE_TTL_MS: number;
11
+ export declare const MIN_GATE_TTL_MS = 1000;
12
+ export declare const MAX_GATE_TTL_MS: number;
13
+ export declare function clampGateTtlMs(value: unknown, fallback?: number): number;
14
+ /** Milliseconds until a gate auto-holds (0 when expired, null when it has no TTL). */
15
+ export declare function gateRemainingMs(gate: {
16
+ expiresAt: string | null;
17
+ }, now?: number): number | null;
18
+ /** `M:SS` for the gate countdown ("auto-holds in 4:31"). */
19
+ export declare function formatCountdown(ms: number): string;
@@ -12,7 +12,6 @@ export interface ArrangeNode {
12
12
  position: ArrangePosition;
13
13
  size: ArrangeSize;
14
14
  pinned: boolean;
15
- dockPosition: 'left' | 'right' | null;
16
15
  data: Record<string, unknown>;
17
16
  }
18
17
  export interface ArrangeEdge {