pmx-canvas 0.1.36 → 0.2.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/CHANGELOG.md +409 -0
- package/Readme.md +2 -2
- package/dist/json-render/index.js +89 -334
- package/dist/types/mcp/canvas-access.d.ts +5 -171
- package/dist/types/server/ax-state-manager.d.ts +256 -0
- package/dist/types/server/ax-state.d.ts +1 -1
- package/dist/types/server/canvas-operations.d.ts +1 -12
- package/dist/types/server/canvas-state.d.ts +3 -23
- package/dist/types/server/index.d.ts +6 -24
- package/dist/types/server/operations/composites.d.ts +121 -0
- package/dist/types/server/operations/http.d.ts +7 -0
- package/dist/types/server/operations/index.d.ts +8 -0
- package/dist/types/server/operations/invoker.d.ts +13 -0
- package/dist/types/server/operations/mcp.d.ts +15 -0
- package/dist/types/server/operations/ops/annotation.d.ts +2 -0
- package/dist/types/server/operations/ops/app.d.ts +33 -0
- package/dist/types/server/operations/ops/ax-await.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-shared.d.ts +31 -0
- package/dist/types/server/operations/ops/ax-state.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-timeline.d.ts +2 -0
- package/dist/types/server/operations/ops/ax-work.d.ts +2 -0
- package/dist/types/server/operations/ops/batch.d.ts +19 -0
- package/dist/types/server/operations/ops/edges.d.ts +2 -0
- package/dist/types/server/operations/ops/groups.d.ts +2 -0
- package/dist/types/server/operations/ops/json-render.d.ts +31 -0
- package/dist/types/server/operations/ops/nodes.d.ts +62 -0
- package/dist/types/server/operations/ops/query.d.ts +2 -0
- package/dist/types/server/operations/ops/snapshots.d.ts +2 -0
- package/dist/types/server/operations/ops/validate.d.ts +2 -0
- package/dist/types/server/operations/ops/viewport.d.ts +2 -0
- package/dist/types/server/operations/ops/webview.d.ts +2 -0
- package/dist/types/server/operations/registry.d.ts +15 -0
- package/dist/types/server/operations/types.d.ts +116 -0
- package/dist/types/server/operations/webview-runner.d.ts +69 -0
- package/docs/RELEASE.md +5 -0
- package/docs/adr-001-bun-only-runtime.md +46 -0
- package/docs/api-stability.md +57 -0
- package/docs/ax-state-contract.md +72 -0
- package/docs/mcp.md +60 -11
- package/docs/plans/plan-005-operation-registry.md +84 -0
- package/docs/plans/plan-006-mcp-tool-consolidation.md +109 -0
- package/docs/plans/plan-007-ax-domain.md +99 -0
- package/docs/plans/plan-008-registry-finish.md +91 -0
- package/docs/tech-debt-assessment-2026-06.md +90 -0
- package/package.json +3 -3
- package/skills/pmx-canvas/SKILL.md +192 -186
- package/skills/pmx-canvas/evals/evals.json +3 -3
- package/skills/pmx-canvas/references/codex-app-adapter.md +13 -14
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +4 -5
- package/src/cli/agent.ts +52 -31
- package/src/mcp/canvas-access.ts +30 -830
- package/src/mcp/server.ts +162 -2014
- package/src/server/ax-state-manager.ts +808 -0
- package/src/server/ax-state.ts +2 -2
- package/src/server/canvas-operations.ts +2 -328
- package/src/server/canvas-schema.ts +2 -2
- package/src/server/canvas-state.ts +95 -465
- package/src/server/index.ts +54 -190
- package/src/server/operations/composites.ts +355 -0
- package/src/server/operations/http.ts +103 -0
- package/src/server/operations/index.ts +65 -0
- package/src/server/operations/invoker.ts +87 -0
- package/src/server/operations/mcp.ts +221 -0
- package/src/server/operations/ops/annotation.ts +60 -0
- package/src/server/operations/ops/app.ts +447 -0
- package/src/server/operations/ops/ax-await.ts +216 -0
- package/src/server/operations/ops/ax-shared.ts +38 -0
- package/src/server/operations/ops/ax-state.ts +249 -0
- package/src/server/operations/ops/ax-timeline.ts +381 -0
- package/src/server/operations/ops/ax-work.ts +635 -0
- package/src/server/operations/ops/batch.ts +365 -0
- package/src/server/operations/ops/edges.ts +166 -0
- package/src/server/operations/ops/groups.ts +176 -0
- package/src/server/operations/ops/json-render.ts +691 -0
- package/src/server/operations/ops/nodes.ts +1047 -0
- package/src/server/operations/ops/query.ts +281 -0
- package/src/server/operations/ops/snapshots.ts +366 -0
- package/src/server/operations/ops/validate.ts +37 -0
- package/src/server/operations/ops/viewport.ts +219 -0
- package/src/server/operations/ops/webview.ts +339 -0
- package/src/server/operations/registry.ts +79 -0
- package/src/server/operations/types.ts +150 -0
- package/src/server/operations/webview-runner.ts +77 -0
- package/src/server/server.ts +158 -2255
- package/src/server/web-artifacts.ts +6 -2
|
@@ -29,17 +29,6 @@ import {
|
|
|
29
29
|
isDbPopulated,
|
|
30
30
|
checkpointCanvasDb,
|
|
31
31
|
finalizeCanvasDbForClose,
|
|
32
|
-
appendAxEventToDB,
|
|
33
|
-
appendAxEvidenceToDB,
|
|
34
|
-
appendAxSteeringToDB,
|
|
35
|
-
markAxSteeringDeliveredInDB,
|
|
36
|
-
loadAxEventsFromDB,
|
|
37
|
-
loadAxEvidenceFromDB,
|
|
38
|
-
loadAxSteeringFromDB,
|
|
39
|
-
loadPendingAxSteeringFromDB,
|
|
40
|
-
loadAxTimelineSummaryFromDB,
|
|
41
|
-
upsertAxHostCapabilityToDB,
|
|
42
|
-
loadAxHostCapabilityFromDB,
|
|
43
32
|
type PersistedCanvasState,
|
|
44
33
|
type CanvasTheme,
|
|
45
34
|
type AxTimelineQuery,
|
|
@@ -55,22 +44,6 @@ import {
|
|
|
55
44
|
} from './placement.js';
|
|
56
45
|
import {
|
|
57
46
|
createEmptyAxState,
|
|
58
|
-
createEmptyAxHostCapability,
|
|
59
|
-
normalizeAxState,
|
|
60
|
-
normalizeAxHostCapability,
|
|
61
|
-
createAxWorkItem,
|
|
62
|
-
createAxApprovalGate,
|
|
63
|
-
createAxReviewAnnotation,
|
|
64
|
-
createAxEvent,
|
|
65
|
-
createAxEvidence,
|
|
66
|
-
createAxSteeringMessage,
|
|
67
|
-
createAxElicitation,
|
|
68
|
-
createAxModeRequest,
|
|
69
|
-
isAxCommand,
|
|
70
|
-
listAxCommands,
|
|
71
|
-
AX_COMMAND_REGISTRY,
|
|
72
|
-
normalizeAxPolicy,
|
|
73
|
-
mapAxActivityKindToEventKind,
|
|
74
47
|
type PmxAxActivityKind,
|
|
75
48
|
type PmxAxElicitation,
|
|
76
49
|
type PmxAxModeRequest,
|
|
@@ -97,6 +70,7 @@ import {
|
|
|
97
70
|
type PmxAxHostCapability,
|
|
98
71
|
type PmxAxTimelineSummary,
|
|
99
72
|
} from './ax-state.js';
|
|
73
|
+
import { AxStateManager } from './ax-state-manager.js';
|
|
100
74
|
|
|
101
75
|
function logCanvasStateWarning(action: string, error: unknown, details?: Record<string, unknown>): void {
|
|
102
76
|
console.warn(`[canvas-state] ${action}`, { error, ...(details ?? {}) });
|
|
@@ -329,10 +303,21 @@ class CanvasStateManager {
|
|
|
329
303
|
private _viewport: ViewportState = { x: 0, y: 0, scale: 1 };
|
|
330
304
|
private _theme: CanvasTheme = 'dark';
|
|
331
305
|
private _contextPinnedNodeIds = new Set<string>();
|
|
332
|
-
private _axState: PmxAxState = createEmptyAxState();
|
|
333
|
-
private _axHostCapability: PmxAxHostCapability | null = null;
|
|
334
306
|
private _workspaceRoot = process.cwd();
|
|
335
307
|
|
|
308
|
+
// ── AX state (canvas-bound + timeline + host partitions) ──────────
|
|
309
|
+
// Extracted into a dedicated manager (plan-007 Slice A). CanvasStateManager
|
|
310
|
+
// holds it and delegates its public AX methods so the SDK/HTTP/MCP surface is
|
|
311
|
+
// byte-stable; the manager receives the host hooks it needs as injected deps.
|
|
312
|
+
private readonly ax = new AxStateManager({
|
|
313
|
+
getNodeIds: () => this.currentNodeIdSet(),
|
|
314
|
+
getDb: () => this._db,
|
|
315
|
+
scheduleSave: () => this.scheduleSave(),
|
|
316
|
+
notifyChange: (type) => this.notifyChange(type),
|
|
317
|
+
recordMutation: (info) => this.recordMutation(info),
|
|
318
|
+
suppressed: (fn) => this.suppressed(fn),
|
|
319
|
+
});
|
|
320
|
+
|
|
336
321
|
// ── Change listeners (for MCP resource notifications) ──────
|
|
337
322
|
private _changeListeners: ((type: CanvasChangeType) => void)[] = [];
|
|
338
323
|
|
|
@@ -396,14 +381,6 @@ class CanvasStateManager {
|
|
|
396
381
|
return new Set(this.nodes.keys());
|
|
397
382
|
}
|
|
398
383
|
|
|
399
|
-
private normalizeAxForCurrentNodes(state: unknown): PmxAxState {
|
|
400
|
-
return normalizeAxState(state, this.currentNodeIdSet());
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
private applyAxState(state: PmxAxState): void {
|
|
404
|
-
this._axState = this.normalizeAxForCurrentNodes(state);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
384
|
private applyResolvedGroupBounds(
|
|
408
385
|
group: CanvasNodeState,
|
|
409
386
|
groupId: string,
|
|
@@ -899,13 +876,7 @@ class CanvasStateManager {
|
|
|
899
876
|
/** Load canvas state from SQLite (or legacy JSON fallback). Call once on server startup. */
|
|
900
877
|
loadFromDisk(options: LoadFromDiskOptions = {}): boolean {
|
|
901
878
|
// Host capability lives in its own table (not snapshotted / not in PmxAxState).
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
this._axHostCapability = loadAxHostCapabilityFromDB(this._db);
|
|
905
|
-
} catch (error) {
|
|
906
|
-
logCanvasStateWarning('load host capability failed', error, {});
|
|
907
|
-
}
|
|
908
|
-
}
|
|
879
|
+
this.ax.loadHostCapabilityFromDb();
|
|
909
880
|
// Try SQLite first (only if DB has been populated)
|
|
910
881
|
if (this._db && isDbPopulated(this._db)) {
|
|
911
882
|
try {
|
|
@@ -1029,7 +1000,7 @@ class CanvasStateManager {
|
|
|
1029
1000
|
this.edges.clear();
|
|
1030
1001
|
this.annotations.clear();
|
|
1031
1002
|
this._contextPinnedNodeIds.clear();
|
|
1032
|
-
this.
|
|
1003
|
+
this.ax.resetCanvasBound();
|
|
1033
1004
|
|
|
1034
1005
|
this._viewport = {
|
|
1035
1006
|
x: state.viewport?.x ?? 0,
|
|
@@ -1060,7 +1031,7 @@ class CanvasStateManager {
|
|
|
1060
1031
|
if (this.nodes.has(pinId)) this._contextPinnedNodeIds.add(pinId);
|
|
1061
1032
|
}
|
|
1062
1033
|
}
|
|
1063
|
-
this.
|
|
1034
|
+
this.ax.applyPersistedAx(state.ax);
|
|
1064
1035
|
}
|
|
1065
1036
|
|
|
1066
1037
|
private readResolvedSnapshot(idOrName: string): {
|
|
@@ -1456,11 +1427,41 @@ class CanvasStateManager {
|
|
|
1456
1427
|
this.nodes.delete(id);
|
|
1457
1428
|
this.removeEdgesForNode(id);
|
|
1458
1429
|
this._contextPinnedNodeIds.delete(id);
|
|
1459
|
-
|
|
1430
|
+
// Re-normalize canvas-bound AX against the surviving node set. This strips the
|
|
1431
|
+
// dangling node ref from work items / approval gates / elicitations / mode
|
|
1432
|
+
// requests (re-anchored) and drops node-anchored review annotations (removed).
|
|
1433
|
+
// Previously SILENT — now audited (plan-007 Slice A): if the deleted node
|
|
1434
|
+
// orphaned anything, record one `note` timeline event so the human and a
|
|
1435
|
+
// resuming agent can see the work that changed instead of it changing quietly.
|
|
1436
|
+
const orphaned = this.ax.revalidateAfterNodeRemoval(id);
|
|
1460
1437
|
this.scheduleSave();
|
|
1461
1438
|
this.notifyChange('nodes');
|
|
1462
1439
|
this.notifyChange('pins');
|
|
1463
1440
|
this.notifyChange('ax');
|
|
1441
|
+
// Only record the audit note on a real (user-initiated) deletion. Undo/redo
|
|
1442
|
+
// replay removeNode inside `suppressed()` (_suppressRecordingDepth > 0); the
|
|
1443
|
+
// original deletion already recorded the note, so replaying must NOT append a
|
|
1444
|
+
// duplicate (the timeline is append-only). `revalidateAfterNodeRemoval` above
|
|
1445
|
+
// still runs unconditionally — only the timeline note is gated.
|
|
1446
|
+
const affected = orphaned.reanchoredIds.length > 0 || orphaned.removedReviewIds.length > 0 || orphaned.reanchoredFocus;
|
|
1447
|
+
if (existing && this._suppressRecordingDepth === 0 && affected) {
|
|
1448
|
+
const title = (existing.data.title as string) ?? id;
|
|
1449
|
+
const focusNote = orphaned.reanchoredFocus ? ' (focus anchor cleared)' : '';
|
|
1450
|
+
this.recordAxEvent(
|
|
1451
|
+
{
|
|
1452
|
+
kind: 'note',
|
|
1453
|
+
summary: `Node "${title}" deleted — re-anchored ${orphaned.reanchoredIds.length} AX item(s), removed ${orphaned.removedReviewIds.length} node-anchored review annotation(s).${focusNote}`,
|
|
1454
|
+
data: {
|
|
1455
|
+
systemEvent: 'ax-node-orphan',
|
|
1456
|
+
removedNodeId: id,
|
|
1457
|
+
reanchoredIds: orphaned.reanchoredIds,
|
|
1458
|
+
removedReviewIds: orphaned.removedReviewIds,
|
|
1459
|
+
reanchoredFocus: orphaned.reanchoredFocus,
|
|
1460
|
+
},
|
|
1461
|
+
},
|
|
1462
|
+
{ source: 'system' },
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1464
1465
|
if (cloned) {
|
|
1465
1466
|
this.recordMutation({
|
|
1466
1467
|
operationType: 'removeNode',
|
|
@@ -1469,7 +1470,7 @@ class CanvasStateManager {
|
|
|
1469
1470
|
inverse: this.suppressed(() => {
|
|
1470
1471
|
this.addNode(structuredClone(cloned));
|
|
1471
1472
|
for (const edge of connectedEdges) this.addEdge(structuredClone(edge));
|
|
1472
|
-
this.
|
|
1473
|
+
this.ax.applyPersistedAx(oldAxState);
|
|
1473
1474
|
this.scheduleSave();
|
|
1474
1475
|
this.notifyChange('ax');
|
|
1475
1476
|
}),
|
|
@@ -1728,73 +1729,35 @@ class CanvasStateManager {
|
|
|
1728
1729
|
return new Set(this._contextPinnedNodeIds);
|
|
1729
1730
|
}
|
|
1730
1731
|
|
|
1732
|
+
// ── AX state delegation (canvas-bound + timeline + host) ──────────
|
|
1733
|
+
// All AX state lives in `this.ax` (AxStateManager); these are byte-stable
|
|
1734
|
+
// delegations so SDK/HTTP/MCP keep calling canvasState.<method>(...) unchanged.
|
|
1731
1735
|
getAxState(): PmxAxState {
|
|
1732
|
-
return
|
|
1736
|
+
return this.ax.getAxState();
|
|
1733
1737
|
}
|
|
1734
1738
|
|
|
1735
1739
|
getAxFocus(): PmxAxFocusState {
|
|
1736
|
-
return this.
|
|
1740
|
+
return this.ax.getAxFocus();
|
|
1737
1741
|
}
|
|
1738
1742
|
|
|
1739
1743
|
setAxFocus(nodeIds: string[], options: { source?: PmxAxSource; recordHistory?: boolean } = {}): PmxAxFocusState {
|
|
1740
|
-
|
|
1741
|
-
const nextAxState: PmxAxState = {
|
|
1742
|
-
...oldAxState,
|
|
1743
|
-
focus: {
|
|
1744
|
-
nodeIds,
|
|
1745
|
-
primaryNodeId: nodeIds[0] ?? null,
|
|
1746
|
-
updatedAt: new Date().toISOString(),
|
|
1747
|
-
source: options.source ?? 'api',
|
|
1748
|
-
},
|
|
1749
|
-
};
|
|
1750
|
-
this.applyAxState(nextAxState);
|
|
1751
|
-
const appliedAxState = this.getAxState();
|
|
1752
|
-
this.scheduleSave();
|
|
1753
|
-
this.notifyChange('ax');
|
|
1754
|
-
if (options.recordHistory === false) return appliedAxState.focus;
|
|
1755
|
-
this.recordMutation({
|
|
1756
|
-
operationType: 'setAxFocus',
|
|
1757
|
-
description: `Set AX focus (${appliedAxState.focus.nodeIds.length} nodes)`,
|
|
1758
|
-
forward: this.suppressed(() => {
|
|
1759
|
-
this.applyAxState(appliedAxState);
|
|
1760
|
-
this.scheduleSave();
|
|
1761
|
-
this.notifyChange('ax');
|
|
1762
|
-
}),
|
|
1763
|
-
inverse: this.suppressed(() => {
|
|
1764
|
-
this.applyAxState(oldAxState);
|
|
1765
|
-
this.scheduleSave();
|
|
1766
|
-
this.notifyChange('ax');
|
|
1767
|
-
}),
|
|
1768
|
-
});
|
|
1769
|
-
return appliedAxState.focus;
|
|
1744
|
+
return this.ax.setAxFocus(nodeIds, options);
|
|
1770
1745
|
}
|
|
1771
1746
|
|
|
1772
1747
|
clearAxFocus(): PmxAxFocusState {
|
|
1773
|
-
return this.
|
|
1748
|
+
return this.ax.clearAxFocus();
|
|
1774
1749
|
}
|
|
1775
1750
|
|
|
1776
1751
|
// ── Work items (canvas-bound; snapshotted via getAxState blob) ────
|
|
1777
1752
|
getWorkItems(): PmxAxWorkItem[] {
|
|
1778
|
-
return this.
|
|
1753
|
+
return this.ax.getWorkItems();
|
|
1779
1754
|
}
|
|
1780
1755
|
|
|
1781
1756
|
addWorkItem(
|
|
1782
1757
|
input: { title: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
|
|
1783
1758
|
options: { source?: PmxAxSource } = {},
|
|
1784
1759
|
): PmxAxWorkItem {
|
|
1785
|
-
|
|
1786
|
-
const item = createAxWorkItem(input, options.source ?? 'api', this.currentNodeIdSet());
|
|
1787
|
-
this.applyAxState({ ...oldAxState, workItems: [...oldAxState.workItems, item] });
|
|
1788
|
-
const applied = this.getAxState();
|
|
1789
|
-
this.scheduleSave();
|
|
1790
|
-
this.notifyChange('ax');
|
|
1791
|
-
this.recordMutation({
|
|
1792
|
-
operationType: 'addWorkItem',
|
|
1793
|
-
description: `Added work item "${item.title}"`,
|
|
1794
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1795
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1796
|
-
});
|
|
1797
|
-
return applied.workItems.find((w) => w.id === item.id) ?? item;
|
|
1760
|
+
return this.ax.addWorkItem(input, options);
|
|
1798
1761
|
}
|
|
1799
1762
|
|
|
1800
1763
|
updateWorkItem(
|
|
@@ -1802,53 +1765,19 @@ class CanvasStateManager {
|
|
|
1802
1765
|
patch: { title?: string; status?: PmxAxWorkItemStatus; detail?: string | null; nodeIds?: string[] },
|
|
1803
1766
|
options: { source?: PmxAxSource } = {},
|
|
1804
1767
|
): PmxAxWorkItem | null {
|
|
1805
|
-
|
|
1806
|
-
const existing = oldAxState.workItems.find((w) => w.id === id);
|
|
1807
|
-
if (!existing) return null;
|
|
1808
|
-
const merged: PmxAxWorkItem = {
|
|
1809
|
-
...existing,
|
|
1810
|
-
...(patch.title !== undefined ? { title: patch.title } : {}),
|
|
1811
|
-
...(patch.status !== undefined ? { status: patch.status } : {}),
|
|
1812
|
-
...(patch.detail !== undefined ? { detail: patch.detail } : {}),
|
|
1813
|
-
...(patch.nodeIds !== undefined ? { nodeIds: patch.nodeIds.filter((n) => this.nodes.has(n)) } : {}),
|
|
1814
|
-
updatedAt: new Date().toISOString(),
|
|
1815
|
-
source: options.source ?? existing.source,
|
|
1816
|
-
};
|
|
1817
|
-
this.applyAxState({ ...oldAxState, workItems: replaceById(oldAxState.workItems, merged) });
|
|
1818
|
-
const applied = this.getAxState();
|
|
1819
|
-
this.scheduleSave();
|
|
1820
|
-
this.notifyChange('ax');
|
|
1821
|
-
this.recordMutation({
|
|
1822
|
-
operationType: 'updateWorkItem',
|
|
1823
|
-
description: `Updated work item ${id}`,
|
|
1824
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1825
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1826
|
-
});
|
|
1827
|
-
return applied.workItems.find((w) => w.id === id) ?? null;
|
|
1768
|
+
return this.ax.updateWorkItem(id, patch, options);
|
|
1828
1769
|
}
|
|
1829
1770
|
|
|
1830
1771
|
// ── Approval gates (canvas-bound) ─────────────────────────────────
|
|
1831
1772
|
getApprovalGates(): PmxAxApprovalGate[] {
|
|
1832
|
-
return this.
|
|
1773
|
+
return this.ax.getApprovalGates();
|
|
1833
1774
|
}
|
|
1834
1775
|
|
|
1835
1776
|
requestApproval(
|
|
1836
1777
|
input: { title: string; detail?: string | null; action?: string | null; nodeIds?: string[] },
|
|
1837
1778
|
options: { source?: PmxAxSource } = {},
|
|
1838
1779
|
): PmxAxApprovalGate {
|
|
1839
|
-
|
|
1840
|
-
const gate = createAxApprovalGate(input, options.source ?? 'api', this.currentNodeIdSet());
|
|
1841
|
-
this.applyAxState({ ...oldAxState, approvalGates: [...oldAxState.approvalGates, gate] });
|
|
1842
|
-
const applied = this.getAxState();
|
|
1843
|
-
this.scheduleSave();
|
|
1844
|
-
this.notifyChange('ax');
|
|
1845
|
-
this.recordMutation({
|
|
1846
|
-
operationType: 'requestApproval',
|
|
1847
|
-
description: `Requested approval "${gate.title}"`,
|
|
1848
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1849
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1850
|
-
});
|
|
1851
|
-
return applied.approvalGates.find((g) => g.id === gate.id) ?? gate;
|
|
1780
|
+
return this.ax.requestApproval(input, options);
|
|
1852
1781
|
}
|
|
1853
1782
|
|
|
1854
1783
|
resolveApproval(
|
|
@@ -1856,32 +1785,12 @@ class CanvasStateManager {
|
|
|
1856
1785
|
decision: 'approved' | 'rejected',
|
|
1857
1786
|
options: { resolution?: string; source?: PmxAxSource } = {},
|
|
1858
1787
|
): PmxAxApprovalGate | null {
|
|
1859
|
-
|
|
1860
|
-
const gate = oldAxState.approvalGates.find((g) => g.id === id);
|
|
1861
|
-
if (!gate || gate.status !== 'pending') return null;
|
|
1862
|
-
const resolved: PmxAxApprovalGate = {
|
|
1863
|
-
...gate,
|
|
1864
|
-
status: decision,
|
|
1865
|
-
resolvedAt: new Date().toISOString(),
|
|
1866
|
-
resolution: options.resolution ?? null,
|
|
1867
|
-
source: options.source ?? gate.source,
|
|
1868
|
-
};
|
|
1869
|
-
this.applyAxState({ ...oldAxState, approvalGates: replaceById(oldAxState.approvalGates, resolved) });
|
|
1870
|
-
const applied = this.getAxState();
|
|
1871
|
-
this.scheduleSave();
|
|
1872
|
-
this.notifyChange('ax');
|
|
1873
|
-
this.recordMutation({
|
|
1874
|
-
operationType: 'resolveApproval',
|
|
1875
|
-
description: `Resolved approval ${id} -> ${decision}`,
|
|
1876
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1877
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1878
|
-
});
|
|
1879
|
-
return applied.approvalGates.find((g) => g.id === id) ?? null;
|
|
1788
|
+
return this.ax.resolveApproval(id, decision, options);
|
|
1880
1789
|
}
|
|
1881
1790
|
|
|
1882
1791
|
// ── Review annotations (canvas-bound) ─────────────────────────────
|
|
1883
1792
|
getReviewAnnotations(): PmxAxReviewAnnotation[] {
|
|
1884
|
-
return this.
|
|
1793
|
+
return this.ax.getReviewAnnotations();
|
|
1885
1794
|
}
|
|
1886
1795
|
|
|
1887
1796
|
addReviewAnnotation(
|
|
@@ -1897,33 +1806,7 @@ class CanvasStateManager {
|
|
|
1897
1806
|
},
|
|
1898
1807
|
options: { source?: PmxAxSource } = {},
|
|
1899
1808
|
): PmxAxReviewAnnotation | null {
|
|
1900
|
-
|
|
1901
|
-
// missing or unknown would otherwise be silently dropped by
|
|
1902
|
-
// normalizeAxForCurrentNodes after apply, yet still returned as a phantom
|
|
1903
|
-
// success object — false success / silent data loss. Reject instead so the
|
|
1904
|
-
// HTTP/MCP layers surface ok:false / 4xx.
|
|
1905
|
-
// Context-aware default: only fall back to a node anchor when a usable nodeId
|
|
1906
|
-
// is present; otherwise treat it as an unanchored (body-only) note so a
|
|
1907
|
-
// `{ body }`-only annotation succeeds (anchorType is documented optional).
|
|
1908
|
-
const anchorType = input.anchorType ?? (typeof input.nodeId === 'string' && input.nodeId ? 'node' : 'file');
|
|
1909
|
-
// An EXPLICIT node anchor still requires a real nodeId — reject a phantom
|
|
1910
|
-
// node-anchored review rather than silently dropping it post-apply.
|
|
1911
|
-
if (anchorType === 'node' && (typeof input.nodeId !== 'string' || !this.currentNodeIdSet().has(input.nodeId))) {
|
|
1912
|
-
return null;
|
|
1913
|
-
}
|
|
1914
|
-
const oldAxState = this.getAxState();
|
|
1915
|
-
const annotation = createAxReviewAnnotation(input, options.source ?? 'api');
|
|
1916
|
-
this.applyAxState({ ...oldAxState, reviewAnnotations: [...oldAxState.reviewAnnotations, annotation] });
|
|
1917
|
-
const applied = this.getAxState();
|
|
1918
|
-
this.scheduleSave();
|
|
1919
|
-
this.notifyChange('ax');
|
|
1920
|
-
this.recordMutation({
|
|
1921
|
-
operationType: 'addReviewAnnotation',
|
|
1922
|
-
description: `Added review ${annotation.kind} (${annotation.severity})`,
|
|
1923
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1924
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1925
|
-
});
|
|
1926
|
-
return applied.reviewAnnotations.find((r) => r.id === annotation.id) ?? annotation;
|
|
1809
|
+
return this.ax.addReviewAnnotation(input, options);
|
|
1927
1810
|
}
|
|
1928
1811
|
|
|
1929
1812
|
updateReviewAnnotation(
|
|
@@ -1931,57 +1814,23 @@ class CanvasStateManager {
|
|
|
1931
1814
|
patch: { body?: string; status?: PmxAxReviewStatus; severity?: PmxAxReviewSeverity; kind?: PmxAxReviewKind },
|
|
1932
1815
|
options: { source?: PmxAxSource } = {},
|
|
1933
1816
|
): PmxAxReviewAnnotation | null {
|
|
1934
|
-
|
|
1935
|
-
const existing = oldAxState.reviewAnnotations.find((r) => r.id === id);
|
|
1936
|
-
if (!existing) return null;
|
|
1937
|
-
const merged: PmxAxReviewAnnotation = {
|
|
1938
|
-
...existing,
|
|
1939
|
-
...(patch.body !== undefined ? { body: patch.body } : {}),
|
|
1940
|
-
...(patch.status !== undefined ? { status: patch.status } : {}),
|
|
1941
|
-
...(patch.severity !== undefined ? { severity: patch.severity } : {}),
|
|
1942
|
-
...(patch.kind !== undefined ? { kind: patch.kind } : {}),
|
|
1943
|
-
updatedAt: new Date().toISOString(),
|
|
1944
|
-
source: options.source ?? existing.source,
|
|
1945
|
-
};
|
|
1946
|
-
this.applyAxState({ ...oldAxState, reviewAnnotations: replaceById(oldAxState.reviewAnnotations, merged) });
|
|
1947
|
-
const applied = this.getAxState();
|
|
1948
|
-
this.scheduleSave();
|
|
1949
|
-
this.notifyChange('ax');
|
|
1950
|
-
this.recordMutation({
|
|
1951
|
-
operationType: 'updateReviewAnnotation',
|
|
1952
|
-
description: `Updated review ${id}`,
|
|
1953
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1954
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1955
|
-
});
|
|
1956
|
-
return applied.reviewAnnotations.find((r) => r.id === id) ?? null;
|
|
1817
|
+
return this.ax.updateReviewAnnotation(id, patch, options);
|
|
1957
1818
|
}
|
|
1958
1819
|
|
|
1959
1820
|
// ── Host capability (own table; reported by adapters) ─────────────
|
|
1960
1821
|
getHostCapability(): PmxAxHostCapability | null {
|
|
1961
|
-
return this.
|
|
1822
|
+
return this.ax.getHostCapability();
|
|
1962
1823
|
}
|
|
1963
1824
|
|
|
1964
1825
|
getElicitations(): PmxAxElicitation[] {
|
|
1965
|
-
return this.
|
|
1826
|
+
return this.ax.getElicitations();
|
|
1966
1827
|
}
|
|
1967
1828
|
|
|
1968
1829
|
requestElicitation(
|
|
1969
1830
|
input: { prompt: string; fields?: string[]; nodeIds?: string[] },
|
|
1970
1831
|
options: { source?: PmxAxSource } = {},
|
|
1971
1832
|
): PmxAxElicitation {
|
|
1972
|
-
|
|
1973
|
-
const elicitation = createAxElicitation(input, options.source ?? 'api', this.currentNodeIdSet());
|
|
1974
|
-
this.applyAxState({ ...oldAxState, elicitations: [...oldAxState.elicitations, elicitation] });
|
|
1975
|
-
const applied = this.getAxState();
|
|
1976
|
-
this.scheduleSave();
|
|
1977
|
-
this.notifyChange('ax');
|
|
1978
|
-
this.recordMutation({
|
|
1979
|
-
operationType: 'requestElicitation',
|
|
1980
|
-
description: `Requested elicitation "${elicitation.prompt}"`,
|
|
1981
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1982
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
1983
|
-
});
|
|
1984
|
-
return applied.elicitations.find((e) => e.id === elicitation.id) ?? elicitation;
|
|
1833
|
+
return this.ax.requestElicitation(input, options);
|
|
1985
1834
|
}
|
|
1986
1835
|
|
|
1987
1836
|
respondElicitation(
|
|
@@ -1989,50 +1838,18 @@ class CanvasStateManager {
|
|
|
1989
1838
|
response: Record<string, unknown>,
|
|
1990
1839
|
options: { source?: PmxAxSource } = {},
|
|
1991
1840
|
): PmxAxElicitation | null {
|
|
1992
|
-
|
|
1993
|
-
const existing = oldAxState.elicitations.find((e) => e.id === id);
|
|
1994
|
-
if (!existing || existing.status !== 'pending') return null;
|
|
1995
|
-
const merged: PmxAxElicitation = {
|
|
1996
|
-
...existing,
|
|
1997
|
-
status: 'answered',
|
|
1998
|
-
response,
|
|
1999
|
-
resolvedAt: new Date().toISOString(),
|
|
2000
|
-
source: options.source ?? existing.source,
|
|
2001
|
-
};
|
|
2002
|
-
this.applyAxState({ ...oldAxState, elicitations: replaceById(oldAxState.elicitations, merged) });
|
|
2003
|
-
const applied = this.getAxState();
|
|
2004
|
-
this.scheduleSave();
|
|
2005
|
-
this.notifyChange('ax');
|
|
2006
|
-
this.recordMutation({
|
|
2007
|
-
operationType: 'respondElicitation',
|
|
2008
|
-
description: `Answered elicitation ${id}`,
|
|
2009
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2010
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2011
|
-
});
|
|
2012
|
-
return applied.elicitations.find((e) => e.id === id) ?? null;
|
|
1841
|
+
return this.ax.respondElicitation(id, response, options);
|
|
2013
1842
|
}
|
|
2014
1843
|
|
|
2015
1844
|
getModeRequests(): PmxAxModeRequest[] {
|
|
2016
|
-
return this.
|
|
1845
|
+
return this.ax.getModeRequests();
|
|
2017
1846
|
}
|
|
2018
1847
|
|
|
2019
1848
|
requestMode(
|
|
2020
1849
|
input: { mode: PmxAxMode; reason?: string | null; nodeIds?: string[] },
|
|
2021
1850
|
options: { source?: PmxAxSource } = {},
|
|
2022
1851
|
): PmxAxModeRequest {
|
|
2023
|
-
|
|
2024
|
-
const request = createAxModeRequest(input, options.source ?? 'api', this.currentNodeIdSet());
|
|
2025
|
-
this.applyAxState({ ...oldAxState, modeRequests: [...oldAxState.modeRequests, request] });
|
|
2026
|
-
const applied = this.getAxState();
|
|
2027
|
-
this.scheduleSave();
|
|
2028
|
-
this.notifyChange('ax');
|
|
2029
|
-
this.recordMutation({
|
|
2030
|
-
operationType: 'requestMode',
|
|
2031
|
-
description: `Requested mode "${request.mode}"`,
|
|
2032
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2033
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2034
|
-
});
|
|
2035
|
-
return applied.modeRequests.find((m) => m.id === request.id) ?? request;
|
|
1852
|
+
return this.ax.requestMode(input, options);
|
|
2036
1853
|
}
|
|
2037
1854
|
|
|
2038
1855
|
resolveModeRequest(
|
|
@@ -2040,98 +1857,45 @@ class CanvasStateManager {
|
|
|
2040
1857
|
decision: 'approved' | 'rejected',
|
|
2041
1858
|
options: { resolution?: string; source?: PmxAxSource } = {},
|
|
2042
1859
|
): PmxAxModeRequest | null {
|
|
2043
|
-
|
|
2044
|
-
const existing = oldAxState.modeRequests.find((m) => m.id === id);
|
|
2045
|
-
if (!existing || existing.status !== 'pending') return null;
|
|
2046
|
-
const merged: PmxAxModeRequest = {
|
|
2047
|
-
...existing,
|
|
2048
|
-
status: decision,
|
|
2049
|
-
resolvedAt: new Date().toISOString(),
|
|
2050
|
-
resolution: options.resolution ?? null,
|
|
2051
|
-
source: options.source ?? existing.source,
|
|
2052
|
-
};
|
|
2053
|
-
this.applyAxState({ ...oldAxState, modeRequests: replaceById(oldAxState.modeRequests, merged) });
|
|
2054
|
-
const applied = this.getAxState();
|
|
2055
|
-
this.scheduleSave();
|
|
2056
|
-
this.notifyChange('ax');
|
|
2057
|
-
this.recordMutation({
|
|
2058
|
-
operationType: 'resolveModeRequest',
|
|
2059
|
-
description: `Resolved mode request ${id} -> ${decision}`,
|
|
2060
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2061
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2062
|
-
});
|
|
2063
|
-
return applied.modeRequests.find((m) => m.id === id) ?? null;
|
|
1860
|
+
return this.ax.resolveModeRequest(id, decision, options);
|
|
2064
1861
|
}
|
|
2065
1862
|
|
|
2066
1863
|
// ── Single-item AX readers (canvas-bound; for the blocking-wait endpoints) ──
|
|
2067
1864
|
getApproval(id: string): PmxAxApprovalGate | null {
|
|
2068
|
-
return this.
|
|
1865
|
+
return this.ax.getApproval(id);
|
|
2069
1866
|
}
|
|
2070
1867
|
|
|
2071
1868
|
getElicitation(id: string): PmxAxElicitation | null {
|
|
2072
|
-
return this.
|
|
1869
|
+
return this.ax.getElicitation(id);
|
|
2073
1870
|
}
|
|
2074
1871
|
|
|
2075
1872
|
getModeRequest(id: string): PmxAxModeRequest | null {
|
|
2076
|
-
return this.
|
|
1873
|
+
return this.ax.getModeRequest(id);
|
|
2077
1874
|
}
|
|
2078
1875
|
|
|
2079
1876
|
getCommandRegistry(): PmxAxCommandDescriptor[] {
|
|
2080
|
-
return
|
|
1877
|
+
return this.ax.getCommandRegistry();
|
|
2081
1878
|
}
|
|
2082
1879
|
|
|
2083
1880
|
/** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
|
|
2084
1881
|
invokeCommand(name: string, args: Record<string, unknown> | null = null, options: { source?: PmxAxSource } = {}): PmxAxEvent | null {
|
|
2085
|
-
|
|
2086
|
-
return this.recordAxEvent(
|
|
2087
|
-
{ kind: 'command', summary: name, detail: AX_COMMAND_REGISTRY[name].description, data: { command: name, ...(args ? { args } : {}) } },
|
|
2088
|
-
options,
|
|
2089
|
-
);
|
|
1882
|
+
return this.ax.invokeCommand(name, args, options);
|
|
2090
1883
|
}
|
|
2091
1884
|
|
|
2092
1885
|
getPolicy(): PmxAxPolicy {
|
|
2093
|
-
return this.
|
|
1886
|
+
return this.ax.getPolicy();
|
|
2094
1887
|
}
|
|
2095
1888
|
|
|
2096
1889
|
/** Merge a declarative tool/prompt policy patch (canvas-bound, snapshotted). */
|
|
2097
1890
|
setPolicy(
|
|
2098
1891
|
patch: { tools?: Partial<PmxAxPolicy['tools']>; prompt?: Partial<PmxAxPolicy['prompt']> },
|
|
2099
|
-
|
|
1892
|
+
options: { source?: PmxAxSource } = {},
|
|
2100
1893
|
): PmxAxPolicy {
|
|
2101
|
-
|
|
2102
|
-
const merged = normalizeAxPolicy({
|
|
2103
|
-
tools: { ...oldAxState.policy.tools, ...(patch.tools ?? {}) },
|
|
2104
|
-
prompt: { ...oldAxState.policy.prompt, ...(patch.prompt ?? {}) },
|
|
2105
|
-
});
|
|
2106
|
-
this.applyAxState({ ...oldAxState, policy: merged });
|
|
2107
|
-
const applied = this.getAxState();
|
|
2108
|
-
this.scheduleSave();
|
|
2109
|
-
this.notifyChange('ax');
|
|
2110
|
-
this.recordMutation({
|
|
2111
|
-
operationType: 'setPolicy',
|
|
2112
|
-
description: 'Updated AX policy',
|
|
2113
|
-
forward: this.suppressed(() => { this.applyAxState(applied); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2114
|
-
inverse: this.suppressed(() => { this.applyAxState(oldAxState); this.scheduleSave(); this.notifyChange('ax'); }),
|
|
2115
|
-
});
|
|
2116
|
-
return applied.policy;
|
|
1894
|
+
return this.ax.setPolicy(patch, options);
|
|
2117
1895
|
}
|
|
2118
1896
|
|
|
2119
|
-
setHostCapability(input: unknown,
|
|
2120
|
-
|
|
2121
|
-
isRecord(input)
|
|
2122
|
-
? { ...input, reportedAt: new Date().toISOString() }
|
|
2123
|
-
: { reportedAt: new Date().toISOString() },
|
|
2124
|
-
) ?? createEmptyAxHostCapability();
|
|
2125
|
-
this._axHostCapability = cap;
|
|
2126
|
-
if (this._db) {
|
|
2127
|
-
try {
|
|
2128
|
-
upsertAxHostCapabilityToDB(this._db, cap);
|
|
2129
|
-
} catch (error) {
|
|
2130
|
-
logCanvasStateWarning('save host capability failed', error, {});
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
this.notifyChange('ax');
|
|
2134
|
-
return cap;
|
|
1897
|
+
setHostCapability(input: unknown, options: { source?: PmxAxSource } = {}): PmxAxHostCapability {
|
|
1898
|
+
return this.ax.setHostCapability(input, options);
|
|
2135
1899
|
}
|
|
2136
1900
|
|
|
2137
1901
|
// ── Timeline (DB-direct; NOT in _axState; NOT history-recorded) ───
|
|
@@ -2139,77 +1903,24 @@ class CanvasStateManager {
|
|
|
2139
1903
|
input: { kind: PmxAxEventKind; summary: string; detail?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
|
|
2140
1904
|
options: { source?: PmxAxSource } = {},
|
|
2141
1905
|
): PmxAxEvent {
|
|
2142
|
-
|
|
2143
|
-
if (this._db) {
|
|
2144
|
-
try {
|
|
2145
|
-
const ev = appendAxEventToDB(this._db, draft);
|
|
2146
|
-
this.notifyChange('ax-timeline');
|
|
2147
|
-
return ev;
|
|
2148
|
-
} catch (error) {
|
|
2149
|
-
logCanvasStateWarning('record ax event failed', error, { id: draft.id });
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
this.notifyChange('ax-timeline');
|
|
2153
|
-
return { ...draft, seq: 0 };
|
|
1906
|
+
return this.ax.recordAxEvent(input, options);
|
|
2154
1907
|
}
|
|
2155
1908
|
|
|
2156
1909
|
addEvidence(
|
|
2157
1910
|
input: { kind: PmxAxEvidenceKind; title: string; body?: string | null; ref?: string | null; nodeIds?: string[]; data?: Record<string, unknown> | null },
|
|
2158
1911
|
options: { source?: PmxAxSource } = {},
|
|
2159
1912
|
): PmxAxEvidence {
|
|
2160
|
-
|
|
2161
|
-
if (this._db) {
|
|
2162
|
-
try {
|
|
2163
|
-
const ev = appendAxEvidenceToDB(this._db, draft);
|
|
2164
|
-
this.notifyChange('ax-timeline');
|
|
2165
|
-
return ev;
|
|
2166
|
-
} catch (error) {
|
|
2167
|
-
logCanvasStateWarning('add evidence failed', error, { id: draft.id });
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
this.notifyChange('ax-timeline');
|
|
2171
|
-
return { ...draft, seq: 0 };
|
|
1913
|
+
return this.ax.addEvidence(input, options);
|
|
2172
1914
|
}
|
|
2173
1915
|
|
|
2174
1916
|
recordSteeringMessage(message: string, options: { source?: PmxAxSource } = {}): PmxAxSteeringMessage {
|
|
2175
|
-
|
|
2176
|
-
if (this._db) {
|
|
2177
|
-
try {
|
|
2178
|
-
const s = appendAxSteeringToDB(this._db, draft);
|
|
2179
|
-
this.notifyChange('ax-timeline');
|
|
2180
|
-
return s;
|
|
2181
|
-
} catch (error) {
|
|
2182
|
-
logCanvasStateWarning('record steering failed', error, { id: draft.id });
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
this.notifyChange('ax-timeline');
|
|
2186
|
-
return { ...draft, seq: 0 };
|
|
1917
|
+
return this.ax.recordSteeringMessage(message, options);
|
|
2187
1918
|
}
|
|
2188
1919
|
|
|
2189
1920
|
markSteeringDelivered(id: string): boolean {
|
|
2190
|
-
|
|
2191
|
-
try {
|
|
2192
|
-
const ok = markAxSteeringDeliveredInDB(this._db, id);
|
|
2193
|
-
if (ok) this.notifyChange('ax-timeline');
|
|
2194
|
-
return ok;
|
|
2195
|
-
} catch (error) {
|
|
2196
|
-
logCanvasStateWarning('mark steering delivered failed', error, { id });
|
|
2197
|
-
return false;
|
|
2198
|
-
}
|
|
1921
|
+
return this.ax.markSteeringDelivered(id);
|
|
2199
1922
|
}
|
|
2200
1923
|
|
|
2201
|
-
/**
|
|
2202
|
-
* Ingest a normalized agent activity (a tool/session event a harness forwards)
|
|
2203
|
-
* and apply kind-driven board reactions, so the agent's real work flows back into
|
|
2204
|
-
* the board without it remembering to push each item (report primitive A — makes
|
|
2205
|
-
* AX bidirectional). Always records a timeline event; then, unless the caller
|
|
2206
|
-
* overrides/suppresses via `reactions`, applies defaults by kind/outcome:
|
|
2207
|
-
* • failure | error | outcome==='failure' → work item (blocked) + review
|
|
2208
|
-
* (finding/error, anchored to a valid nodeId else the `ref` file) + evidence (logs)
|
|
2209
|
-
* • tool-result + outcome==='success' → evidence (tool-result)
|
|
2210
|
-
* • everything else (tool-start, session-*, command, note) → event only
|
|
2211
|
-
* A reaction value of `false` suppresses it; an object overrides its fields/forces it on.
|
|
2212
|
-
*/
|
|
2213
1924
|
ingestActivity(
|
|
2214
1925
|
input: {
|
|
2215
1926
|
kind: PmxAxActivityKind;
|
|
@@ -2227,112 +1938,31 @@ class CanvasStateManager {
|
|
|
2227
1938
|
},
|
|
2228
1939
|
options: { source?: PmxAxSource } = {},
|
|
2229
1940
|
): { event: PmxAxEvent; workItem: PmxAxWorkItem | null; evidence: PmxAxEvidence | null; review: PmxAxReviewAnnotation | null } {
|
|
2230
|
-
|
|
2231
|
-
const summary = input.summary ?? input.title;
|
|
2232
|
-
const isFailure = input.kind === 'failure' || input.kind === 'error' || input.outcome === 'failure';
|
|
2233
|
-
const isToolSuccess = input.kind === 'tool-result' && input.outcome === 'success';
|
|
2234
|
-
const nodeIds = input.nodeIds ?? [];
|
|
2235
|
-
const anchorNodeId = nodeIds.find((n) => this.nodes.has(n)) ?? null;
|
|
2236
|
-
|
|
2237
|
-
// (1) Always record the activity on the timeline (precise kind on data.activityKind).
|
|
2238
|
-
const event = this.recordAxEvent(
|
|
2239
|
-
{
|
|
2240
|
-
kind: mapAxActivityKindToEventKind(input.kind),
|
|
2241
|
-
summary: input.title,
|
|
2242
|
-
detail: input.summary ?? null,
|
|
2243
|
-
nodeIds,
|
|
2244
|
-
// Caller data first so the canonical fields always win — a malformed/hostile
|
|
2245
|
-
// payload can't overwrite activityKind/outcome/ref (which the docstring +
|
|
2246
|
-
// reaction logic treat as authoritative).
|
|
2247
|
-
data: {
|
|
2248
|
-
...(input.data ?? {}),
|
|
2249
|
-
activityKind: input.kind,
|
|
2250
|
-
...(input.outcome ? { outcome: input.outcome } : {}),
|
|
2251
|
-
...(input.ref ? { ref: input.ref } : {}),
|
|
2252
|
-
},
|
|
2253
|
-
},
|
|
2254
|
-
{ source },
|
|
2255
|
-
);
|
|
2256
|
-
|
|
2257
|
-
// (2) Resolve reactions: kind-driven defaults, overridable per call.
|
|
2258
|
-
const r = input.reactions ?? {};
|
|
2259
|
-
const wantWorkItem = r.workItem === false ? null : (r.workItem ?? (isFailure ? {} : null));
|
|
2260
|
-
const wantEvidence = r.evidence === false
|
|
2261
|
-
? null
|
|
2262
|
-
: (r.evidence ?? (isFailure ? { kind: 'logs' as PmxAxEvidenceKind } : isToolSuccess ? { kind: 'tool-result' as PmxAxEvidenceKind } : null));
|
|
2263
|
-
const wantReview = r.review === false ? null : (r.review ?? (isFailure ? {} : null));
|
|
2264
|
-
|
|
2265
|
-
let workItem: PmxAxWorkItem | null = null;
|
|
2266
|
-
if (wantWorkItem) {
|
|
2267
|
-
workItem = this.addWorkItem(
|
|
2268
|
-
{ title: input.title, status: wantWorkItem.status ?? 'blocked', detail: wantWorkItem.detail ?? summary, nodeIds },
|
|
2269
|
-
{ source },
|
|
2270
|
-
);
|
|
2271
|
-
}
|
|
2272
|
-
|
|
2273
|
-
let evidence: PmxAxEvidence | null = null;
|
|
2274
|
-
if (wantEvidence) {
|
|
2275
|
-
evidence = this.addEvidence(
|
|
2276
|
-
{ kind: wantEvidence.kind ?? 'logs', title: input.title, body: wantEvidence.body ?? input.summary ?? null, ref: input.ref ?? null, nodeIds },
|
|
2277
|
-
{ source },
|
|
2278
|
-
);
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
let review: PmxAxReviewAnnotation | null = null;
|
|
2282
|
-
if (wantReview) {
|
|
2283
|
-
const reviewNodeId = wantReview.nodeId ?? anchorNodeId;
|
|
2284
|
-
// addReviewAnnotation returns null on a bad node anchor — that just skips the
|
|
2285
|
-
// review; it never fails the whole ingest (the event + other reactions stand).
|
|
2286
|
-
review = this.addReviewAnnotation(
|
|
2287
|
-
{
|
|
2288
|
-
body: summary,
|
|
2289
|
-
kind: wantReview.kind ?? 'finding',
|
|
2290
|
-
severity: wantReview.severity ?? 'error',
|
|
2291
|
-
...(wantReview.anchorType ? { anchorType: wantReview.anchorType } : {}),
|
|
2292
|
-
...(reviewNodeId ? { nodeId: reviewNodeId } : {}),
|
|
2293
|
-
...(input.ref ? { file: input.ref } : {}),
|
|
2294
|
-
},
|
|
2295
|
-
{ source },
|
|
2296
|
-
);
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
return { event, workItem, evidence, review };
|
|
1941
|
+
return this.ax.ingestActivity(input, options);
|
|
2300
1942
|
}
|
|
2301
1943
|
|
|
2302
1944
|
getAxEvents(q: AxTimelineQuery = {}): PmxAxEvent[] {
|
|
2303
|
-
return this.
|
|
1945
|
+
return this.ax.getAxEvents(q);
|
|
2304
1946
|
}
|
|
2305
1947
|
|
|
2306
1948
|
getAxEvidence(q: AxTimelineQuery = {}): PmxAxEvidence[] {
|
|
2307
|
-
return this.
|
|
1949
|
+
return this.ax.getAxEvidence(q);
|
|
2308
1950
|
}
|
|
2309
1951
|
|
|
2310
1952
|
getAxSteering(q: AxTimelineQuery & { onlyPending?: boolean } = {}): PmxAxSteeringMessage[] {
|
|
2311
|
-
return this.
|
|
1953
|
+
return this.ax.getAxSteering(q);
|
|
2312
1954
|
}
|
|
2313
1955
|
|
|
2314
|
-
/**
|
|
2315
|
-
* Undelivered steering for a consumer (Phase 4 delivery). Excludes messages
|
|
2316
|
-
* whose source equals the consumer to prevent delivery loops (e.g. Copilot
|
|
2317
|
-
* should not be handed back steering it originated).
|
|
2318
|
-
*/
|
|
2319
1956
|
getPendingSteering(options: { consumer?: string; limit?: number } = {}): PmxAxSteeringMessage[] {
|
|
2320
|
-
return this.
|
|
1957
|
+
return this.ax.getPendingSteering(options);
|
|
2321
1958
|
}
|
|
2322
1959
|
|
|
2323
1960
|
getAxTimelineSummary(): PmxAxTimelineSummary {
|
|
2324
|
-
return this.
|
|
2325
|
-
? loadAxTimelineSummaryFromDB(this._db)
|
|
2326
|
-
: { recentEvents: [], recentEvidence: [], pendingSteering: [], counts: { events: 0, evidence: 0, steering: 0 } };
|
|
1961
|
+
return this.ax.getAxTimelineSummary();
|
|
2327
1962
|
}
|
|
2328
1963
|
|
|
2329
1964
|
getAxTimeline(q: AxTimelineQuery = {}): { events: PmxAxEvent[]; evidence: PmxAxEvidence[]; steering: PmxAxSteeringMessage[]; summary: PmxAxTimelineSummary } {
|
|
2330
|
-
return
|
|
2331
|
-
events: this.getAxEvents(q),
|
|
2332
|
-
evidence: this.getAxEvidence(q),
|
|
2333
|
-
steering: this.getAxSteering(q),
|
|
2334
|
-
summary: this.getAxTimelineSummary(),
|
|
2335
|
-
};
|
|
1965
|
+
return this.ax.getAxTimeline(q);
|
|
2336
1966
|
}
|
|
2337
1967
|
|
|
2338
1968
|
setContextPins(nodeIds: string[]): void {
|
|
@@ -2469,7 +2099,7 @@ class CanvasStateManager {
|
|
|
2469
2099
|
// Clears canvas-bound AX state (focus, work items, approvals, review annotations).
|
|
2470
2100
|
// Timeline tables (ax_events/ax_evidence/ax_steering) and host capability are
|
|
2471
2101
|
// deliberately retained per the AX state-partition policy.
|
|
2472
|
-
this.
|
|
2102
|
+
this.ax.resetCanvasBound();
|
|
2473
2103
|
this._viewport = { x: 0, y: 0, scale: 1 };
|
|
2474
2104
|
this.scheduleSave();
|
|
2475
2105
|
this.notifyChange('nodes');
|
|
@@ -2484,7 +2114,7 @@ class CanvasStateManager {
|
|
|
2484
2114
|
for (const e of oldEdges) this.addEdge(structuredClone(e));
|
|
2485
2115
|
for (const annotation of oldAnnotations) this.addAnnotation(structuredClone(annotation));
|
|
2486
2116
|
this.setContextPins(oldPins);
|
|
2487
|
-
this.
|
|
2117
|
+
this.ax.applyPersistedAx(oldAxState);
|
|
2488
2118
|
this.setViewport(oldViewport);
|
|
2489
2119
|
this.notifyChange('ax');
|
|
2490
2120
|
}),
|