pmx-canvas 0.7.0 → 0.7.2
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 +149 -70
- package/CHANGELOG.md +32 -1
- package/Readme.md +40 -13
- package/dist/canvas/global.css +94 -15
- package/dist/canvas/index.js +76 -76
- package/dist/types/cli/daemon.d.ts +5 -5
- package/dist/types/cli/shared.d.ts +3 -1
- package/dist/types/client/canvas/ExternalWriters.d.ts +3 -3
- package/dist/types/client/icons.d.ts +15 -0
- package/dist/types/client/nodes/ExtAppFrame.d.ts +1 -1
- package/dist/types/client/nodes/FileNode.d.ts +0 -12
- package/dist/types/shared/delimited-text.d.ts +14 -0
- package/docs/cli.md +14 -2
- package/docs/http-api.md +12 -8
- package/docs/mcp.md +8 -1
- package/docs/sdk.md +2 -2
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +35 -14
- package/skills/pmx-canvas/evals/evals.json +171 -0
- package/skills/pmx-canvas/references/full-reference.md +6 -1
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +17 -3
- package/skills/pmx-canvas/references/installing-pmx-canvas.md +4 -4
- package/skills/pmx-canvas/references/tours-and-recording.md +69 -0
- package/skills/pmx-canvas-testing/SKILL.md +7 -2
- package/src/cli/commands/apps.ts +1 -1
- package/src/cli/commands/backup.ts +15 -4
- package/src/cli/commands/boards.ts +16 -3
- package/src/cli/commands/export.ts +9 -2
- package/src/cli/daemon.ts +37 -7
- package/src/cli/shared.ts +11 -2
- package/src/client/App.tsx +0 -1
- package/src/client/canvas/AttentionHistory.tsx +2 -1
- package/src/client/canvas/AttentionToast.tsx +5 -3
- package/src/client/canvas/BoardSwitcher.tsx +3 -3
- package/src/client/canvas/CanvasNode.tsx +17 -9
- package/src/client/canvas/CommandBar.tsx +2 -2
- package/src/client/canvas/ContextMenu.tsx +0 -1
- package/src/client/canvas/ContextPinBar.tsx +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +3 -3
- package/src/client/canvas/ExportDialog.tsx +3 -1
- package/src/client/canvas/ExternalWriters.tsx +4 -3
- package/src/client/canvas/FeedbackDialog.tsx +2 -1
- package/src/client/canvas/IntentLayer.tsx +2 -2
- package/src/client/canvas/Presentation.tsx +39 -9
- package/src/client/canvas/SelectionBar.tsx +2 -4
- package/src/client/canvas/SessionPanel.tsx +6 -4
- package/src/client/canvas/SessionReceipt.tsx +2 -1
- package/src/client/canvas/SnapshotPanel.tsx +3 -2
- package/src/client/canvas/ToolRail.tsx +2 -1
- package/src/client/canvas/TopBar.tsx +2 -2
- package/src/client/icons.tsx +118 -1
- package/src/client/nodes/AttachmentNode.tsx +27 -30
- package/src/client/nodes/BoardNode.tsx +2 -1
- package/src/client/nodes/ExtAppFrame.tsx +3 -1
- package/src/client/nodes/FileNode.tsx +4 -78
- package/src/client/nodes/ImageNode.tsx +2 -1
- package/src/client/nodes/MarkdownNode.tsx +2 -1
- package/src/client/nodes/PromptNode.tsx +3 -1
- package/src/client/state/attention-bridge.ts +3 -1
- package/src/client/state/sse-bridge.ts +3 -1
- package/src/client/theme/global.css +94 -15
- package/src/mcp/server.ts +2 -2
- package/src/server/agent-presence.ts +17 -11
- package/src/server/board-export.ts +97 -32
- package/src/server/index.ts +9 -7
- package/src/server/operations/composites.ts +3 -2
- package/src/server/operations/ops/ax-presence.ts +7 -3
- package/src/server/operations/ops/boards.ts +12 -5
- package/src/server/operations/ops/imports.ts +19 -3
- package/src/server/operations/registry.ts +4 -13
- package/src/server/server.ts +10 -3
- package/src/shared/delimited-text.ts +75 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
|
|
2
|
+
import { IconClose } from '../icons';
|
|
2
3
|
import { updateNodeData } from '../state/canvas-store';
|
|
3
4
|
import { fetchSlashCommands, renderMarkdown, submitCanvasPrompt, submitThreadReply } from '../state/intent-bridge';
|
|
4
5
|
import type { CanvasNodeState } from '../types';
|
|
@@ -98,6 +99,7 @@ function ErrorBanner({ message, onDismiss }: { message: string | null; onDismiss
|
|
|
98
99
|
<button
|
|
99
100
|
type="button"
|
|
100
101
|
onClick={onDismiss}
|
|
102
|
+
aria-label="Dismiss error"
|
|
101
103
|
style={{
|
|
102
104
|
background: 'none',
|
|
103
105
|
border: 'none',
|
|
@@ -107,7 +109,7 @@ function ErrorBanner({ message, onDismiss }: { message: string | null; onDismiss
|
|
|
107
109
|
padding: '0 2px',
|
|
108
110
|
}}
|
|
109
111
|
>
|
|
110
|
-
{
|
|
112
|
+
<IconClose size={14} />
|
|
111
113
|
</button>
|
|
112
114
|
</div>
|
|
113
115
|
);
|
|
@@ -274,7 +274,9 @@ function enqueueToast(entry: AttentionEntry): void {
|
|
|
274
274
|
|
|
275
275
|
/** Show a transient toast from arbitrary client code (e.g. AX action feedback). */
|
|
276
276
|
export function showToast(tone: AttentionTone, title: string, detail = '', nodeIds: string[] = []): void {
|
|
277
|
-
|
|
277
|
+
const entry = makeEntry(tone, title, detail, nodeIds);
|
|
278
|
+
pushAttentionHistory(entry);
|
|
279
|
+
enqueueToast(entry);
|
|
278
280
|
}
|
|
279
281
|
|
|
280
282
|
function pulseNodes(nodeIds: string[]): void {
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
import { agentActivity, applyPresenceSnapshot, sessionActive } from './presence-store';
|
|
42
42
|
import { applyHumanSnapshot, startHumanPresence } from './human-store';
|
|
43
43
|
import type { HumanPresenceSnapshot } from '../../shared/human-presence.js';
|
|
44
|
-
import { applySessionReceipt, refreshTimeline } from './session-store';
|
|
44
|
+
import { applySessionReceipt, dismissSessionReceipt, refreshTimeline } from './session-store';
|
|
45
45
|
import { initSessionThemeOverride, themeOverrideActive } from './theme-override';
|
|
46
46
|
import { DEFAULT_POSITIONS, makeNodeState } from './node-factory';
|
|
47
47
|
import { invalidateTokenCache } from '../theme/tokens';
|
|
@@ -479,6 +479,8 @@ function resyncForBoardSwitch(): void {
|
|
|
479
479
|
hasInitialServerLayout.value = false;
|
|
480
480
|
resetIntents();
|
|
481
481
|
resetCanvasInteractionState();
|
|
482
|
+
resetAttentionBridge();
|
|
483
|
+
dismissSessionReceipt();
|
|
482
484
|
axSurfaceState.value = null;
|
|
483
485
|
void refreshAxSurface();
|
|
484
486
|
const layoutAtRequest = layoutRevision;
|
|
@@ -760,6 +760,11 @@ body,
|
|
|
760
760
|
border-radius: calc(3px * var(--node-chrome-scale, 1));
|
|
761
761
|
}
|
|
762
762
|
|
|
763
|
+
.canvas-node .node-controls button svg {
|
|
764
|
+
width: 1em;
|
|
765
|
+
height: 1em;
|
|
766
|
+
}
|
|
767
|
+
|
|
763
768
|
.canvas-node .node-controls button:hover {
|
|
764
769
|
background: var(--c-surface-hover);
|
|
765
770
|
color: var(--c-text);
|
|
@@ -1466,6 +1471,10 @@ body,
|
|
|
1466
1471
|
animation: presence-pulse 1.4s ease-in-out infinite;
|
|
1467
1472
|
}
|
|
1468
1473
|
.session-collapse {
|
|
1474
|
+
display: inline-flex;
|
|
1475
|
+
align-items: center;
|
|
1476
|
+
justify-content: center;
|
|
1477
|
+
padding: 0;
|
|
1469
1478
|
width: 26px;
|
|
1470
1479
|
height: 26px;
|
|
1471
1480
|
border: 1px solid transparent;
|
|
@@ -1682,8 +1691,9 @@ body,
|
|
|
1682
1691
|
}
|
|
1683
1692
|
}
|
|
1684
1693
|
.session-section-chevron {
|
|
1694
|
+
display: inline-flex;
|
|
1695
|
+
flex-shrink: 0;
|
|
1685
1696
|
color: var(--c-muted);
|
|
1686
|
-
font-size: 13px;
|
|
1687
1697
|
transition: transform 0.15s ease;
|
|
1688
1698
|
}
|
|
1689
1699
|
.session-section-chevron.is-open {
|
|
@@ -1966,8 +1976,13 @@ body,
|
|
|
1966
1976
|
white-space: nowrap;
|
|
1967
1977
|
}
|
|
1968
1978
|
.command-bar-chip-unpin {
|
|
1969
|
-
|
|
1970
|
-
|
|
1979
|
+
display: inline-flex;
|
|
1980
|
+
align-items: center;
|
|
1981
|
+
justify-content: center;
|
|
1982
|
+
flex-shrink: 0;
|
|
1983
|
+
padding: 0;
|
|
1984
|
+
width: 20px;
|
|
1985
|
+
height: 20px;
|
|
1971
1986
|
border: none;
|
|
1972
1987
|
border-radius: 50%;
|
|
1973
1988
|
background: none;
|
|
@@ -4784,7 +4799,7 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4784
4799
|
position: absolute;
|
|
4785
4800
|
top: 18px;
|
|
4786
4801
|
right: 18px;
|
|
4787
|
-
max-width: min(
|
|
4802
|
+
max-width: min(360px, calc(100% - 36px));
|
|
4788
4803
|
display: inline-flex;
|
|
4789
4804
|
align-items: center;
|
|
4790
4805
|
gap: 8px;
|
|
@@ -4792,7 +4807,7 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4792
4807
|
background: color-mix(in srgb, var(--c-panel-glass) 96%, transparent);
|
|
4793
4808
|
backdrop-filter: blur(14px);
|
|
4794
4809
|
border: 1px solid color-mix(in srgb, var(--c-line) 76%, var(--c-warn) 24%);
|
|
4795
|
-
border-radius:
|
|
4810
|
+
border-radius: 12px;
|
|
4796
4811
|
box-shadow: 0 8px 22px var(--c-shadow);
|
|
4797
4812
|
color: var(--c-text);
|
|
4798
4813
|
font: inherit;
|
|
@@ -4831,15 +4846,27 @@ html.is-node-resizing .ext-app-preview-catcher {
|
|
|
4831
4846
|
box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 28%, transparent);
|
|
4832
4847
|
}
|
|
4833
4848
|
|
|
4849
|
+
.attention-toast-copy {
|
|
4850
|
+
display: grid;
|
|
4851
|
+
gap: 4px;
|
|
4852
|
+
min-width: 0;
|
|
4853
|
+
text-align: left;
|
|
4854
|
+
overflow-wrap: anywhere;
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
.attention-toast-detail {
|
|
4858
|
+
color: var(--c-text-soft);
|
|
4859
|
+
font-size: 12px;
|
|
4860
|
+
line-height: 1.45;
|
|
4861
|
+
max-height: 9em;
|
|
4862
|
+
overflow: auto;
|
|
4863
|
+
}
|
|
4864
|
+
|
|
4834
4865
|
.attention-toast-title {
|
|
4835
4866
|
font-size: 12px;
|
|
4836
4867
|
font-weight: 600;
|
|
4837
4868
|
line-height: 1.2;
|
|
4838
4869
|
letter-spacing: -0.005em;
|
|
4839
|
-
white-space: nowrap;
|
|
4840
|
-
overflow: hidden;
|
|
4841
|
-
text-overflow: ellipsis;
|
|
4842
|
-
max-width: 200px;
|
|
4843
4870
|
}
|
|
4844
4871
|
|
|
4845
4872
|
.attention-history {
|
|
@@ -6841,12 +6868,23 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6841
6868
|
padding: 4px 12px;
|
|
6842
6869
|
cursor: pointer;
|
|
6843
6870
|
}
|
|
6844
|
-
.is-presenting > :not(.app-main):not(.presentation-error),
|
|
6871
|
+
.is-presenting > :not(.app-main):not(.presentation-error):not(.presentation-navigation),
|
|
6845
6872
|
.is-presenting .app-main > :not(.canvas-region),
|
|
6846
6873
|
.is-presenting .canvas-region > :not(.canvas-viewport) {
|
|
6847
6874
|
display: none !important;
|
|
6848
6875
|
}
|
|
6849
6876
|
.is-presenting .canvas-viewport { pointer-events: none; }
|
|
6877
|
+
.presentation-navigation {
|
|
6878
|
+
position: fixed;
|
|
6879
|
+
inset: 0;
|
|
6880
|
+
z-index: 99;
|
|
6881
|
+
}
|
|
6882
|
+
.presentation-exit {
|
|
6883
|
+
position: absolute;
|
|
6884
|
+
top: 16px;
|
|
6885
|
+
right: 16px;
|
|
6886
|
+
box-shadow: 0 2px 12px var(--c-shadow);
|
|
6887
|
+
}
|
|
6850
6888
|
.presentation-error {
|
|
6851
6889
|
position: fixed;
|
|
6852
6890
|
bottom: 16px;
|
|
@@ -6873,7 +6911,7 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6873
6911
|
align-items: center;
|
|
6874
6912
|
gap: 6px;
|
|
6875
6913
|
max-width: 280px;
|
|
6876
|
-
padding:
|
|
6914
|
+
padding: 6px 8px;
|
|
6877
6915
|
margin-left: -8px;
|
|
6878
6916
|
background: none;
|
|
6879
6917
|
border: 1px solid transparent;
|
|
@@ -6891,7 +6929,8 @@ html.is-node-dragging .intent-line-layer {
|
|
|
6891
6929
|
white-space: nowrap;
|
|
6892
6930
|
}
|
|
6893
6931
|
.board-switcher-caret {
|
|
6894
|
-
|
|
6932
|
+
display: inline-flex;
|
|
6933
|
+
flex-shrink: 0;
|
|
6895
6934
|
color: var(--c-text-soft);
|
|
6896
6935
|
}
|
|
6897
6936
|
.board-switcher-menu {
|
|
@@ -7104,10 +7143,17 @@ html.is-node-dragging .intent-line-layer {
|
|
|
7104
7143
|
}
|
|
7105
7144
|
.export-dialog-links {
|
|
7106
7145
|
display: flex;
|
|
7146
|
+
flex-wrap: wrap;
|
|
7107
7147
|
gap: 12px;
|
|
7148
|
+
margin-top: 12px;
|
|
7108
7149
|
}
|
|
7109
7150
|
.export-dialog-links a {
|
|
7110
|
-
|
|
7151
|
+
display: inline-flex;
|
|
7152
|
+
align-items: center;
|
|
7153
|
+
justify-content: center;
|
|
7154
|
+
box-sizing: border-box;
|
|
7155
|
+
text-decoration: none;
|
|
7156
|
+
word-break: normal;
|
|
7111
7157
|
}
|
|
7112
7158
|
|
|
7113
7159
|
/* ── Home categories ── */
|
|
@@ -7267,9 +7313,42 @@ html.is-node-dragging .intent-line-layer {
|
|
|
7267
7313
|
.attachment-node p { margin: 0; color: var(--c-text-soft); line-height: 1.5; }
|
|
7268
7314
|
.attachment-node a, .md-import-source a { color: var(--c-accent); }
|
|
7269
7315
|
.attachment-status { font-weight: 600; }
|
|
7270
|
-
.attachment-
|
|
7271
|
-
.
|
|
7316
|
+
.attachment-button,
|
|
7317
|
+
.presentation-exit,
|
|
7318
|
+
.export-dialog-links a {
|
|
7319
|
+
min-height: 38px;
|
|
7320
|
+
padding: 8px 14px;
|
|
7321
|
+
border: 1px solid var(--c-text-soft);
|
|
7322
|
+
border-radius: 8px;
|
|
7323
|
+
background: var(--c-panel);
|
|
7324
|
+
color: var(--c-text);
|
|
7325
|
+
font: inherit;
|
|
7326
|
+
font-weight: 600;
|
|
7327
|
+
cursor: pointer;
|
|
7328
|
+
}
|
|
7329
|
+
.attachment-button.is-primary,
|
|
7330
|
+
.export-dialog-links a.is-primary { background: var(--c-accent); border-color: var(--c-accent); color: var(--c-bg); }
|
|
7331
|
+
.attachment-button:hover:not(:disabled),
|
|
7332
|
+
.presentation-exit:hover,
|
|
7333
|
+
.export-dialog-links a:hover { filter: brightness(1.12); }
|
|
7334
|
+
.attachment-button:focus-visible,
|
|
7335
|
+
.presentation-exit:focus-visible,
|
|
7336
|
+
.export-dialog-links a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }
|
|
7337
|
+
.attachment-button:disabled { opacity: 0.5; cursor: default; }
|
|
7272
7338
|
.attachment-review { width: 100%; border-top: 1px solid var(--c-line); padding-top: 12px; }
|
|
7339
|
+
.attachment-review-actions {
|
|
7340
|
+
position: sticky;
|
|
7341
|
+
bottom: -16px;
|
|
7342
|
+
display: flex;
|
|
7343
|
+
flex-wrap: wrap;
|
|
7344
|
+
justify-content: flex-end;
|
|
7345
|
+
gap: 12px;
|
|
7346
|
+
margin-top: 24px;
|
|
7347
|
+
padding: 16px 0;
|
|
7348
|
+
border-top: 1px solid var(--c-line);
|
|
7349
|
+
background: var(--c-panel);
|
|
7350
|
+
z-index: 1;
|
|
7351
|
+
}
|
|
7273
7352
|
.attachment-review pre { white-space: pre-wrap; overflow-wrap: anywhere; font: 12px/1.6 var(--mono); padding: 12px; background: var(--c-bg); border-radius: 8px; }
|
|
7274
7353
|
.attachment-review h4 { margin-bottom: 6px; }
|
|
7275
7354
|
.md-import-source { padding: 6px 12px; font-size: 11px; overflow-wrap: anywhere; color: var(--c-text-soft); border-top: 1px solid var(--c-line); }
|
package/src/mcp/server.ts
CHANGED
|
@@ -797,8 +797,8 @@ export async function startMcpServer(): Promise<void> {
|
|
|
797
797
|
'canvas://boards',
|
|
798
798
|
{
|
|
799
799
|
description:
|
|
800
|
-
'The boards in this workspace with their categories, most recently opened first, and which one
|
|
801
|
-
'
|
|
800
|
+
'The boards in this workspace with their categories, most recently opened first, and which one is ' +
|
|
801
|
+
'open (activeBoardId; null = Home). Writes go to the open board; use canvas_board action "open" to switch the shared workbench.',
|
|
802
802
|
mimeType: 'application/json',
|
|
803
803
|
},
|
|
804
804
|
async () => {
|
|
@@ -278,10 +278,10 @@ export class AgentPresenceRegistry {
|
|
|
278
278
|
private attributedKey(input: PresenceTouch): string {
|
|
279
279
|
const own = presenceKey(input.source, input.agentId);
|
|
280
280
|
// Identified writers keep their key; attaches run the twin-merge in touch().
|
|
281
|
-
if (input.agentId?.trim() ||
|
|
281
|
+
if (input.agentId?.trim() || this.presences.get(own)?.attached) return own;
|
|
282
282
|
// A channel that attach-merged into a session stays that session's — its
|
|
283
|
-
// writes AND its detach belong to the merged session,
|
|
284
|
-
// sessions are attached.
|
|
283
|
+
// writes, repeat attaches AND its detach belong to the merged session,
|
|
284
|
+
// however many other sessions are attached.
|
|
285
285
|
const alias = this.aliases.get(own);
|
|
286
286
|
if (alias && this.presences.get(alias)?.attached) return alias;
|
|
287
287
|
if (input.attached !== undefined) return own;
|
|
@@ -338,15 +338,21 @@ export class AgentPresenceRegistry {
|
|
|
338
338
|
touch(input: PresenceTouch, now = Date.now()): AgentPresence {
|
|
339
339
|
let key = this.attributedKey(input);
|
|
340
340
|
const own = presenceKey(input.source, input.agentId);
|
|
341
|
-
if (input.attached === true && !input.agentId?.trim()) {
|
|
342
|
-
// One agent, two channels:
|
|
343
|
-
// attached
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
341
|
+
if (input.attached === true && !input.agentId?.trim() && key === own) {
|
|
342
|
+
// One agent, two channels: a transport attach belongs to the sole
|
|
343
|
+
// attached host even when the agent gives its task a different label.
|
|
344
|
+
// With several hosts, only the existing matching-label rule applies.
|
|
345
|
+
// Keep explicit agentIds separate, and preserve the host's consumer key
|
|
346
|
+
// so steering still reaches its poller. Browser placeholders are handled
|
|
347
|
+
// below, where their baseline and adopted-work rules are preserved.
|
|
347
348
|
const label = input.label ?? input.source;
|
|
348
|
-
const
|
|
349
|
-
|
|
349
|
+
const attached = [...this.presences.values()].filter((presence) => presence.attached);
|
|
350
|
+
const twin = attached.find(
|
|
351
|
+
(presence) =>
|
|
352
|
+
presence.sessionId !== own &&
|
|
353
|
+
!presence.agentId &&
|
|
354
|
+
(presence.label === label ||
|
|
355
|
+
(TRANSPORT_SOURCES.includes(input.source) && attached.length === 1 && presence.source !== 'browser')),
|
|
350
356
|
);
|
|
351
357
|
if (twin) {
|
|
352
358
|
this.aliases.set(own, twin.sessionId);
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
|
|
13
13
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
14
|
-
import { Marked, type RendererThis, type Tokens } from 'marked';
|
|
14
|
+
import { Marked, Renderer, type RendererThis, type Tokens } from 'marked';
|
|
15
15
|
import { canvasThemeScheme } from '../shared/themes.js';
|
|
16
|
+
import { delimiterForPath, parseDelimitedText } from '../shared/delimited-text.js';
|
|
16
17
|
import { buildJsonRenderViewerHtml, escapeInlineScriptSource, readJsonRenderBundle } from '../json-render/server.js';
|
|
17
18
|
import type { JsonRenderSpec } from '../json-render/server.js';
|
|
18
19
|
import { getCanvasNodeTitle } from './canvas-serialization.js';
|
|
@@ -89,6 +90,9 @@ function createMarkdown(remoteImages: Set<string>, links: Set<string>): Marked {
|
|
|
89
90
|
html({ text: raw }: Tokens.HTML | Tokens.Tag) {
|
|
90
91
|
return escapeHtml(raw);
|
|
91
92
|
},
|
|
93
|
+
table(this: RendererThis, token: Tokens.Table) {
|
|
94
|
+
return tableScroll(Renderer.prototype.table.call(this, token));
|
|
95
|
+
},
|
|
92
96
|
link(this: RendererThis, { href, tokens }: Tokens.Link) {
|
|
93
97
|
const inner = this.parser.parseInline(tokens);
|
|
94
98
|
if (/^https?:/i.test(href)) links.add(href);
|
|
@@ -136,6 +140,24 @@ function pre(value: string, className = ''): string {
|
|
|
136
140
|
return `<pre class="${className}">${escapeHtml(value)}</pre>`;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
function tableScroll(html: string): string {
|
|
144
|
+
return `<div class="table-scroll" role="region" aria-label="Table" tabindex="0">${html}</div>`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function fileBody(value: string, name: string): string {
|
|
148
|
+
const delimiter = delimiterForPath(name);
|
|
149
|
+
if (delimiter) {
|
|
150
|
+
const table = parseDelimitedText(value, delimiter);
|
|
151
|
+
if (table.header.length > 1) {
|
|
152
|
+
// Match the workbench's bounded table preview without losing the remainder.
|
|
153
|
+
const preview = table.rows.slice(0, 500);
|
|
154
|
+
const html = `<table><thead><tr>${table.header.map((cell) => `<th scope="col">${escapeHtml(cell)}</th>`).join('')}</tr></thead><tbody>${preview.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join('')}</tr>`).join('')}</tbody></table>`;
|
|
155
|
+
return `${tableScroll(html)}${table.rows.length > preview.length ? `<details><summary>Showing 500 of ${table.rows.length} rows — full text</summary>${pre(value, 'code')}</details>` : ''}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return pre(value, 'code');
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
function rows(entries: Array<[string, unknown]>): string {
|
|
140
162
|
const cells = entries
|
|
141
163
|
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
@@ -258,20 +280,27 @@ async function collect(
|
|
|
258
280
|
break;
|
|
259
281
|
case 'status':
|
|
260
282
|
card.html = rows([
|
|
261
|
-
['Phase', data.phase],
|
|
283
|
+
['Phase', text(data.phase).trim() || text(data.content).trim() || text(data.status).trim() || 'idle'],
|
|
262
284
|
['Message', data.message],
|
|
263
285
|
['Detail', data.detail],
|
|
286
|
+
['Active tool', data.activeTool],
|
|
264
287
|
]);
|
|
265
288
|
break;
|
|
266
289
|
case 'context': {
|
|
267
290
|
const items = Array.isArray(data.cards) ? (data.cards as Array<Record<string, unknown>>) : [];
|
|
268
|
-
card.html =
|
|
269
|
-
|
|
270
|
-
|
|
291
|
+
card.html = `${rows([
|
|
292
|
+
['Tokens', data.currentTokens],
|
|
293
|
+
['Token limit', data.tokenLimit],
|
|
294
|
+
['Messages', data.messagesLength],
|
|
295
|
+
])}${
|
|
296
|
+
items.length
|
|
297
|
+
? `<ul class="list">${items.map((item) => `<li><strong>${escapeHtml(text(item.title) || text(item.label))}</strong><p>${escapeHtml(text(item.summary))}</p></li>`).join('')}</ul>`
|
|
298
|
+
: `<p class="prose">${escapeHtml(text(data.content) || text(data.rendered).replace(/<[^>]+>/g, ' '))}</p>`
|
|
299
|
+
}`;
|
|
271
300
|
break;
|
|
272
301
|
}
|
|
273
302
|
case 'ledger':
|
|
274
|
-
card.html = `${text(data.content) ? pre(text(data.content)) : ''}${rows(
|
|
303
|
+
card.html = `${text(data.content) ? pre(text(data.content).replaceAll('\\n', '\n')) : ''}${rows(
|
|
275
304
|
Object.entries(data).filter(
|
|
276
305
|
([key]) => !['title', 'content', '__type', 'strictSize', 'arrangeLocked'].includes(key),
|
|
277
306
|
),
|
|
@@ -279,10 +308,11 @@ async function collect(
|
|
|
279
308
|
break;
|
|
280
309
|
case 'trace':
|
|
281
310
|
card.html = rows([
|
|
282
|
-
['Tool', data.toolName],
|
|
283
|
-
['Status', data.status],
|
|
311
|
+
['Tool', text(data.toolName) || title],
|
|
312
|
+
['Status', text(data.status) || 'running'],
|
|
284
313
|
['Duration', data.duration],
|
|
285
|
-
['Result', data.resultSummary],
|
|
314
|
+
['Result', text(data.resultSummary) || text(data.content)],
|
|
315
|
+
['Error', data.error],
|
|
286
316
|
]);
|
|
287
317
|
break;
|
|
288
318
|
case 'diff':
|
|
@@ -301,7 +331,7 @@ async function collect(
|
|
|
301
331
|
.join('\n')}</pre>`;
|
|
302
332
|
break;
|
|
303
333
|
case 'prompt':
|
|
304
|
-
card.html =
|
|
334
|
+
card.html = `<div class="prose">${escapeHtml(text(data.text))}</div>`;
|
|
305
335
|
break;
|
|
306
336
|
case 'response':
|
|
307
337
|
card.html = `<div class="md">${await markdown.parse(text(data.content))}</div>`;
|
|
@@ -327,9 +357,9 @@ async function collect(
|
|
|
327
357
|
if (img) manifest.embeddedImages += 1;
|
|
328
358
|
}
|
|
329
359
|
card.html = img
|
|
330
|
-
? `<img class="image" src="${escapeHtml(img)}" alt="${escapeHtml(alt)}" referrerpolicy="no-referrer">${
|
|
331
|
-
text(data.caption) ? `<
|
|
332
|
-
}
|
|
360
|
+
? `<figure class="image-figure"><img class="image" src="${escapeHtml(img)}" alt="${escapeHtml(alt)}" referrerpolicy="no-referrer">${
|
|
361
|
+
text(data.caption) ? `<figcaption class="dim">${escapeHtml(text(data.caption))}</figcaption>` : ''
|
|
362
|
+
}</figure>`
|
|
333
363
|
: `<div class="placeholder">Image not available: ${escapeHtml(src)}</div>`;
|
|
334
364
|
break;
|
|
335
365
|
}
|
|
@@ -337,14 +367,14 @@ async function collect(
|
|
|
337
367
|
const path = text(data.path);
|
|
338
368
|
if (!path) {
|
|
339
369
|
// Inline content the board itself holds, not a file on disk.
|
|
340
|
-
card.html =
|
|
370
|
+
card.html = fileBody(text(data.fileContent) || text(data.content), title);
|
|
341
371
|
break;
|
|
342
372
|
}
|
|
343
373
|
const body = includeFiles ? fileText(node, path) : null;
|
|
344
374
|
manifest.files.push({ nodeId: node.id, path, included: body !== null });
|
|
345
375
|
card.html = `<p class="dim">${escapeHtml(path)}</p>${
|
|
346
376
|
body !== null
|
|
347
|
-
?
|
|
377
|
+
? fileBody(body, path)
|
|
348
378
|
: '<div class="placeholder">File contents not included in this export.</div>'
|
|
349
379
|
}`;
|
|
350
380
|
break;
|
|
@@ -527,7 +557,7 @@ function exportPage(input: {
|
|
|
527
557
|
</div>
|
|
528
558
|
</header>
|
|
529
559
|
<main id="stage" aria-label="Board"><div id="world"><svg id="edges" aria-hidden="true"></svg></div></main>
|
|
530
|
-
<div id="overlay" hidden><div class="sheet" role="dialog" aria-modal="true"><header><strong id="overlay-title"></strong><button type="button" id="overlay-close" aria-label="Close">×</button></header><div id="overlay-body"></div></div></div>
|
|
560
|
+
<div id="overlay" hidden><div class="sheet" role="dialog" aria-modal="true" aria-labelledby="overlay-title"><header><strong id="overlay-title"></strong><button type="button" id="overlay-close" aria-label="Close">×</button></header><div id="overlay-body"></div></div></div>
|
|
531
561
|
<script type="application/json" id="pmx-board">${scriptJson(input.board)}</script>
|
|
532
562
|
<script type="application/json" id="pmx-assets">${scriptJson(input.assets)}</script>
|
|
533
563
|
<script>${EXPORT_VIEWER_JS}</script>
|
|
@@ -540,7 +570,7 @@ const EXPORT_CSS = `
|
|
|
540
570
|
:root{--bg:#0b1016;--panel:#121a23;--line:#263241;--text:#e8edf2;--soft:#9aa7b4;--accent:#4bbcff;--add:#2ea66f;--del:#e25d6e}
|
|
541
571
|
[data-scheme=light]{--bg:#f4f6f8;--panel:#ffffff;--line:#d5dbe1;--text:#17202a;--soft:#5b6875;--accent:#0a6fc2;--add:#1c7a4f;--del:#b3263a}
|
|
542
572
|
*{box-sizing:border-box}
|
|
543
|
-
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:
|
|
573
|
+
html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:15px/1.65 system-ui,-apple-system,Segoe UI,sans-serif}
|
|
544
574
|
.bar{position:fixed;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:0 16px;background:var(--panel);border-bottom:1px solid var(--line);z-index:5}
|
|
545
575
|
.bar .name{display:flex;flex-direction:column;min-width:0}
|
|
546
576
|
.bar .name strong,.bar .name span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
@@ -548,6 +578,7 @@ html,body{margin:0;height:100%;background:var(--bg);color:var(--text);font:14px/
|
|
|
548
578
|
.tools{display:flex;gap:6px;flex-shrink:0}
|
|
549
579
|
button{font:inherit;color:var(--text);background:none;border:1px solid var(--line);border-radius:7px;padding:4px 10px;cursor:pointer}
|
|
550
580
|
button:hover{border-color:var(--accent)}
|
|
581
|
+
button:focus-visible,a:focus-visible,[tabindex]:focus-visible{outline:2px solid var(--accent);outline-offset:-3px}
|
|
551
582
|
#stage{position:fixed;top:48px;left:0;right:0;bottom:0;overflow:hidden;cursor:grab;touch-action:none}
|
|
552
583
|
#stage.panning{cursor:grabbing}
|
|
553
584
|
#world{position:absolute;left:0;top:0;transform-origin:0 0}
|
|
@@ -556,25 +587,43 @@ button:hover{border-color:var(--accent)}
|
|
|
556
587
|
#edges text{fill:var(--soft);font-size:12px;text-anchor:middle}
|
|
557
588
|
.card{position:absolute;display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;box-shadow:0 6px 24px rgba(0,0,0,.18)}
|
|
558
589
|
.card.group{background:color-mix(in srgb,var(--accent) 5%,transparent);border-style:dashed;box-shadow:none}
|
|
559
|
-
.card-title{flex-shrink:0;padding:
|
|
590
|
+
.card-title{position:relative;flex-shrink:0;width:100%;text-align:left;padding:12px 42px 12px 18px;font-weight:600;font-size:13px;border:0;border-bottom:1px solid var(--line);border-radius:0;cursor:zoom-in;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;background:color-mix(in srgb,var(--soft) 5%,var(--panel))}
|
|
591
|
+
button.card-title:after{content:'↗';position:absolute;right:16px;color:var(--soft);font-size:16px}
|
|
560
592
|
.card.group .card-title{border-bottom:none;cursor:default;color:var(--soft)}
|
|
561
|
-
.card-body{flex:1;min-height:0;overflow:auto;padding:
|
|
562
|
-
.card
|
|
563
|
-
.
|
|
564
|
-
.md
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
593
|
+
.card-body{flex:1;min-height:0;min-width:0;overflow:auto;padding:20px;overflow-wrap:anywhere;overscroll-behavior:contain;cursor:auto}
|
|
594
|
+
.card-body>:first-child,.md>:first-child{margin-top:0}.card-body>:last-child,.md>:last-child{margin-bottom:0}
|
|
595
|
+
.card iframe,#overlay-body iframe{flex:1;min-height:0;width:100%;height:100%;border:0;background:var(--bg)}
|
|
596
|
+
.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:1.4em 0 .6em;line-height:1.3;font-weight:650;letter-spacing:-.02em}
|
|
597
|
+
.md h1{font-size:26px}.md h2{font-size:21px}.md h3{font-size:18px}.md h4,.md h5,.md h6{font-size:15px}
|
|
598
|
+
.md p{margin:0 0 1em}.md img{max-width:100%;height:auto;border-radius:6px}
|
|
599
|
+
.md ul,.md ol{padding-left:1.6em;margin:0 0 1em}.md li+li{margin-top:.35em}.md li>ul,.md li>ol{margin-top:.4em;margin-bottom:.4em}
|
|
600
|
+
.md input[type=checkbox]{accent-color:var(--accent);margin-right:.5em}
|
|
601
|
+
.md blockquote{margin:1em 0;padding:10px 16px;border-left:3px solid var(--accent);background:color-mix(in srgb,var(--accent) 7%,var(--panel));color:var(--soft)}
|
|
602
|
+
.md blockquote>:last-child{margin-bottom:0}.md hr{border:0;border-top:1px solid var(--line);margin:24px 0}
|
|
603
|
+
.md code,pre{font:13px/1.65 ui-monospace,SFMono-Regular,Menlo,monospace;tab-size:2}
|
|
604
|
+
.md :not(pre)>code{padding:2px 5px;border:1px solid var(--line);border-radius:4px;background:color-mix(in srgb,var(--soft) 8%,var(--panel))}
|
|
605
|
+
pre{margin:0 0 16px;padding:14px 16px;border:1px solid var(--line);border-radius:8px;background:color-mix(in srgb,var(--soft) 5%,var(--panel));white-space:pre;overflow:auto;overflow-wrap:normal;overscroll-behavior:contain}
|
|
606
|
+
.prose{white-space:pre-wrap}.table-scroll{width:100%;max-width:100%;overflow:auto;margin:16px 0;border:1px solid var(--line);border-radius:8px;overscroll-behavior:contain}
|
|
607
|
+
.table-scroll table{border-collapse:separate;border-spacing:0;width:100%;font-size:13px;line-height:1.55;font-variant-numeric:tabular-nums}
|
|
608
|
+
.table-scroll th,.table-scroll td{padding:10px 14px;min-width:90px;max-width:36ch;border-bottom:1px solid var(--line);border-right:1px solid var(--line);vertical-align:top;overflow-wrap:normal;white-space:pre-wrap}
|
|
609
|
+
.table-scroll th{font-weight:650;background:color-mix(in srgb,var(--soft) 10%,var(--panel))}.table-scroll th:not([align]){text-align:left}
|
|
610
|
+
.table-scroll tr:nth-child(even) td{background:color-mix(in srgb,var(--soft) 4%,var(--panel))}
|
|
611
|
+
.table-scroll tr:last-child td{border-bottom:0}.table-scroll tr>:last-child{border-right:0}
|
|
612
|
+
pre.diff .add{color:var(--add);background:color-mix(in srgb,var(--add) 9%,transparent)}pre.diff .del{color:var(--del);background:color-mix(in srgb,var(--del) 9%,transparent)}pre.diff .hunk{color:var(--accent)}
|
|
613
|
+
a{color:var(--accent);text-underline-offset:3px}
|
|
568
614
|
.dim{color:var(--soft)}
|
|
569
|
-
.kv{border-collapse:collapse;font-size:
|
|
570
|
-
.list{margin:0;padding-
|
|
571
|
-
.image{max-width:100%;
|
|
572
|
-
.placeholder{padding:
|
|
615
|
+
.kv{border-collapse:collapse;width:100%;font-size:14px;table-layout:fixed}.kv th{text-align:left;color:var(--soft);width:30%;font-weight:500}.kv th,.kv td{padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top;white-space:pre-wrap}.kv tr:last-child>*{border-bottom:0}
|
|
616
|
+
.list{list-style:none;margin:0;padding:0}.list li{padding:12px 0;border-bottom:1px solid var(--line)}.list li:last-child{border-bottom:0}.list p{margin:6px 0 0;color:var(--soft)}
|
|
617
|
+
.image-figure{margin:0;height:100%;display:flex;flex-direction:column;gap:12px}.image{flex:1;min-height:0;max-width:100%;width:100%;object-fit:contain;display:block}.image-figure figcaption{flex-shrink:0;font-size:13px;text-align:center}
|
|
618
|
+
.placeholder{padding:16px;border:1px dashed var(--line);border-radius:8px;color:var(--soft);font-size:14px;line-height:1.6}
|
|
619
|
+
details{margin-top:16px}summary{cursor:pointer;color:var(--soft);margin-bottom:12px}
|
|
573
620
|
#overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;padding:24px;z-index:10}
|
|
574
621
|
#overlay[hidden]{display:none}
|
|
575
622
|
#overlay .sheet{width:min(1100px,100%);height:min(90vh,100%);display:flex;flex-direction:column;background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden}
|
|
576
623
|
#overlay .sheet>header{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line)}
|
|
577
|
-
#overlay-body{flex:1;min-height:0;overflow:
|
|
624
|
+
#overlay-body{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column}
|
|
625
|
+
#overlay-body .card-body{padding:28px 32px}#overlay-body .md{max-width:960px;margin:0 auto}
|
|
626
|
+
@media(max-width:600px){#overlay{padding:8px}#overlay .sheet{height:100%}#overlay-body .card-body{padding:18px}.bar{padding:0 10px}.bar .name span{display:none}}
|
|
578
627
|
`;
|
|
579
628
|
|
|
580
629
|
const EXPORT_VIEWER_JS = `(() => {
|
|
@@ -606,12 +655,15 @@ for (const card of ordered) {
|
|
|
606
655
|
const el = document.createElement('section');
|
|
607
656
|
el.className = 'card' + (card.group ? ' group' : '');
|
|
608
657
|
el.dataset.nodeId = card.id;
|
|
658
|
+
el.dataset.nodeType = card.kind;
|
|
609
659
|
Object.assign(el.style, { left: card.x + 'px', top: card.y + 'px', width: card.w + 'px', height: card.h + 'px' });
|
|
610
|
-
const title = document.createElement('div');
|
|
660
|
+
const title = document.createElement(card.group ? 'div' : 'button');
|
|
611
661
|
title.className = 'card-title';
|
|
612
662
|
title.textContent = card.title;
|
|
613
663
|
el.appendChild(title);
|
|
614
664
|
if (!card.group) {
|
|
665
|
+
title.type = 'button';
|
|
666
|
+
title.setAttribute('aria-label', 'Expand ' + card.title);
|
|
615
667
|
el.appendChild(body(card));
|
|
616
668
|
title.addEventListener('click', () => expand(card));
|
|
617
669
|
}
|
|
@@ -653,6 +705,7 @@ const zoomAt = (factor, cx, cy) => {
|
|
|
653
705
|
apply();
|
|
654
706
|
};
|
|
655
707
|
stage.addEventListener('wheel', (e) => {
|
|
708
|
+
if (e.target.closest('.card-body') && !e.ctrlKey && !e.metaKey) return;
|
|
656
709
|
e.preventDefault();
|
|
657
710
|
const r = stage.getBoundingClientRect();
|
|
658
711
|
zoomAt(Math.exp(-e.deltaY * 0.0015), e.clientX - r.left, e.clientY - r.top);
|
|
@@ -673,11 +726,23 @@ stage.addEventListener('pointerup', () => { drag = null; stage.classList.remove(
|
|
|
673
726
|
document.querySelector('[data-zoom=in]').addEventListener('click', () => zoomAt(1.25, stage.clientWidth / 2, stage.clientHeight / 2));
|
|
674
727
|
document.querySelector('[data-zoom=out]').addEventListener('click', () => zoomAt(0.8, stage.clientWidth / 2, stage.clientHeight / 2));
|
|
675
728
|
document.querySelector('[data-zoom=fit]').addEventListener('click', fit);
|
|
676
|
-
|
|
729
|
+
let invoker = null;
|
|
730
|
+
const close = () => {
|
|
731
|
+
if (overlay.hidden) return;
|
|
732
|
+
overlay.hidden = true;
|
|
733
|
+
document.getElementById('overlay-body').replaceChildren();
|
|
734
|
+
stage.inert = false;
|
|
735
|
+
document.querySelector('.bar').inert = false;
|
|
736
|
+
invoker?.focus({ preventScroll: true });
|
|
737
|
+
};
|
|
677
738
|
function expand(card) {
|
|
739
|
+
invoker = document.activeElement;
|
|
678
740
|
document.getElementById('overlay-title').textContent = card.title;
|
|
679
741
|
document.getElementById('overlay-body').replaceChildren(body(card));
|
|
680
742
|
overlay.hidden = false;
|
|
743
|
+
stage.inert = true;
|
|
744
|
+
document.querySelector('.bar').inert = true;
|
|
745
|
+
document.getElementById('overlay-close').focus();
|
|
681
746
|
}
|
|
682
747
|
document.getElementById('overlay-close').addEventListener('click', close);
|
|
683
748
|
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
|
package/src/server/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type { BackupFile, BackupStatus } from './backup.js';
|
|
|
44
44
|
import type { ExportManifest } from './board-export.js';
|
|
45
45
|
import { onFileNodeChanged } from './file-watcher.js';
|
|
46
46
|
import { findOpenCanvasPosition, computeGroupBounds } from './placement.js';
|
|
47
|
+
import { overlapsAny } from '../shared/placement.js';
|
|
47
48
|
import { searchNodes, buildSpatialContext } from './spatial-analysis.js';
|
|
48
49
|
import { mutationHistory, diffLayouts, formatDiff } from './mutation-history.js';
|
|
49
50
|
import { recomputeCodeGraph, buildCodeGraphSummary, formatCodeGraph } from './code-graph.js';
|
|
@@ -741,14 +742,19 @@ export class PmxCanvas extends EventEmitter {
|
|
|
741
742
|
} {
|
|
742
743
|
if (canvasState.activeBoardId !== input.boardId) throw new Error('The captured target board is not active.');
|
|
743
744
|
assertInsideFence('node.add', createTarget(input));
|
|
745
|
+
const obstacles = canvasState.getLayout().nodes;
|
|
746
|
+
const requested = { x: input.x ?? 40, y: input.y ?? 80 };
|
|
747
|
+
const position = overlapsAny(requested, 440, 360, obstacles, 48)
|
|
748
|
+
? findOpenCanvasPosition(obstacles, 440, 360, 48)
|
|
749
|
+
: requested;
|
|
750
|
+
assertInsideFence('node.add', createTarget(position));
|
|
744
751
|
const attachment = canvasState.storeAttachment({ ...input, mime: input.mime ?? 'application/octet-stream' });
|
|
745
752
|
if (!attachment) throw new Error('Board not found.');
|
|
746
753
|
const { id: nodeId, node } = addCanvasNode({
|
|
747
754
|
type: 'file',
|
|
748
755
|
title: input.name,
|
|
749
756
|
data: { attachmentId: attachment.id, mime: attachment.mime, size: attachment.size },
|
|
750
|
-
|
|
751
|
-
y: input.y,
|
|
757
|
+
...position,
|
|
752
758
|
defaultWidth: 440,
|
|
753
759
|
defaultHeight: 360,
|
|
754
760
|
fileMode: 'auto',
|
|
@@ -840,11 +846,7 @@ export class PmxCanvas extends EventEmitter {
|
|
|
840
846
|
|
|
841
847
|
/** Open a board, or Home with null. */
|
|
842
848
|
async openBoard(id: string | null): Promise<void> {
|
|
843
|
-
await executeOperation(
|
|
844
|
-
'board.open',
|
|
845
|
-
{ id },
|
|
846
|
-
{ source: 'sdk', fromWorkbench: true, humanAuthor: true, suppressAutoGhost: true },
|
|
847
|
-
);
|
|
849
|
+
await executeOperation('board.open', { id }, { source: 'sdk', suppressAutoGhost: true });
|
|
848
850
|
}
|
|
849
851
|
|
|
850
852
|
async deleteBoard(id: string): Promise<void> {
|
|
@@ -151,13 +151,14 @@ export const compositeToolDefinitions: CompositeToolDefinition[] = [
|
|
|
151
151
|
{
|
|
152
152
|
toolName: 'canvas_board',
|
|
153
153
|
description:
|
|
154
|
-
'Boards in this workspace — the library of the human\'s and your working memory, filed in categories on Home. Your writes always go to the board
|
|
155
|
-
actionSummary: 'list | get | create | create-from | update',
|
|
154
|
+
'Boards in this workspace — the library of the human\'s and your working memory, filed in categories on Home. Your writes always go to the open board (activeBoardId); on Home (no board open) your first write opens the most recent board, or a new one when there are none. Action "list" returns every board (with its category), most recently opened first; "get" reads one (default: the open board); "create" makes a new empty board WITHOUT opening it (optional category); "open" explicitly switches the shared workbench to id (null or omitted opens Home), changing the visible board and subsequent write target. After creating a board, open its returned id and verify activeBoardId before writing; another participant can switch the board later. "update" renames a board, files it under a category (category "" removes it), or designates its README via readmeNodeId. As part of normal board authoring, read the board metadata; if readmeNodeId is null, choose or create a concise markdown introduction explaining its purpose and key context, then designate that node with "update". Preserve an existing README unless the human asks to change it; humans can override the designation manually. Only the human deletes boards.',
|
|
155
|
+
actionSummary: 'list | get | create | create-from | open | update',
|
|
156
156
|
actions: {
|
|
157
157
|
list: 'board.list',
|
|
158
158
|
get: 'board.get',
|
|
159
159
|
create: 'board.create',
|
|
160
160
|
'create-from': 'board.create-from',
|
|
161
|
+
open: 'board.open',
|
|
161
162
|
update: 'board.update',
|
|
162
163
|
},
|
|
163
164
|
},
|