pmx-canvas 0.1.35 → 0.1.36
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 +52 -0
- package/Readme.md +14 -2
- package/dist/canvas/index.js +82 -41
- package/dist/types/client/nodes/ExtAppFrame.d.ts +2 -0
- package/dist/types/mcp/canvas-access.d.ts +20 -1
- package/dist/types/server/ax-context.d.ts +1 -1
- package/dist/types/server/ax-state.d.ts +28 -0
- package/dist/types/server/ax-wait.d.ts +23 -0
- package/dist/types/server/canvas-state.d.ts +55 -3
- package/dist/types/server/html-surface.d.ts +7 -0
- package/dist/types/server/index.d.ts +60 -2
- package/docs/ax-host-adapter-contract.md +65 -0
- package/docs/http-api.md +34 -2
- package/docs/mcp.md +5 -1
- package/docs/screenshot.png +0 -0
- package/package.json +1 -1
- package/skills/pmx-canvas/SKILL.md +50 -8
- package/skills/pmx-canvas/references/codex-app-adapter.md +15 -1
- package/skills/pmx-canvas/references/github-copilot-app-adapter.md +31 -0
- package/src/client/nodes/ExtAppFrame.tsx +73 -5
- package/src/client/nodes/HtmlNode.tsx +12 -3
- package/src/client/nodes/McpAppNode.tsx +12 -3
- package/src/json-render/renderer/index.tsx +3 -0
- package/src/mcp/canvas-access.ts +74 -5
- package/src/mcp/server.ts +94 -53
- package/src/server/ax-context.ts +7 -1
- package/src/server/ax-state.ts +87 -0
- package/src/server/ax-wait.ts +56 -0
- package/src/server/canvas-state.ts +131 -3
- package/src/server/html-surface.ts +49 -11
- package/src/server/index.ts +82 -2
- package/src/server/server.ts +189 -9
|
@@ -16,6 +16,8 @@ export declare function resolveExtAppDisplayModeRequest(requestedMode: DisplayMo
|
|
|
16
16
|
};
|
|
17
17
|
export declare function sendExtAppBootstrapState(bridge: ExtAppBridgeNotifications, toolInput: Record<string, unknown>, toolResult: CallToolResult | undefined): Promise<void>;
|
|
18
18
|
export declare function resolveExtAppSandbox(value: unknown): string;
|
|
19
|
+
export declare function buildExtAppAxBridgeScript(axToken: string, nodeId: string): string;
|
|
20
|
+
export declare function injectExtAppAxBridgeScript(html: string, axBridgeScript: string): string;
|
|
19
21
|
export declare function resolveExtAppContainerDimensions(target: ExtAppHostDimensionsTarget | null | undefined, fallback: {
|
|
20
22
|
width: number;
|
|
21
23
|
height: number;
|
|
@@ -40,6 +40,11 @@ type ListModeRequestsResult = ReturnType<PmxCanvas['listModeRequests']>;
|
|
|
40
40
|
type RequestModeInput = Parameters<PmxCanvas['requestMode']>[0];
|
|
41
41
|
type RequestModeResult = ReturnType<PmxCanvas['requestMode']>;
|
|
42
42
|
type ResolveModeRequestResult = ReturnType<PmxCanvas['resolveModeRequest']>;
|
|
43
|
+
type IngestActivityInput = Parameters<PmxCanvas['ingestActivity']>[0];
|
|
44
|
+
type IngestActivityResult = ReturnType<PmxCanvas['ingestActivity']>;
|
|
45
|
+
type AwaitApprovalResult = Awaited<ReturnType<PmxCanvas['awaitApproval']>>;
|
|
46
|
+
type AwaitElicitationResult = Awaited<ReturnType<PmxCanvas['awaitElicitation']>>;
|
|
47
|
+
type AwaitModeResult = Awaited<ReturnType<PmxCanvas['awaitMode']>>;
|
|
43
48
|
type GetCommandRegistryResult = ReturnType<PmxCanvas['getCommandRegistry']>;
|
|
44
49
|
type InvokeCommandResult = ReturnType<PmxCanvas['invokeCommand']>;
|
|
45
50
|
type GetPolicyResult = ReturnType<PmxCanvas['getPolicy']>;
|
|
@@ -115,7 +120,9 @@ export interface CanvasAccess {
|
|
|
115
120
|
}): Promise<FocusNodeResult>;
|
|
116
121
|
fitView(options?: FitViewOptions): Promise<FitViewResult>;
|
|
117
122
|
getAxState(): Promise<AxStateResult>;
|
|
118
|
-
getAxContext(
|
|
123
|
+
getAxContext(options?: {
|
|
124
|
+
consumer?: string;
|
|
125
|
+
}): Promise<AxContextResult>;
|
|
119
126
|
setAxFocus(nodeIds: string[], options?: {
|
|
120
127
|
source?: PmxAxSource;
|
|
121
128
|
}): Promise<SetAxFocusResult>;
|
|
@@ -178,6 +185,18 @@ export interface CanvasAccess {
|
|
|
178
185
|
resolution?: string;
|
|
179
186
|
source?: PmxAxSource;
|
|
180
187
|
}): Promise<ResolveModeRequestResult>;
|
|
188
|
+
ingestActivity(input: IngestActivityInput, options?: {
|
|
189
|
+
source?: PmxAxSource;
|
|
190
|
+
}): Promise<IngestActivityResult>;
|
|
191
|
+
awaitApproval(id: string, options?: {
|
|
192
|
+
timeoutMs?: number;
|
|
193
|
+
}): Promise<AwaitApprovalResult>;
|
|
194
|
+
awaitElicitation(id: string, options?: {
|
|
195
|
+
timeoutMs?: number;
|
|
196
|
+
}): Promise<AwaitElicitationResult>;
|
|
197
|
+
awaitMode(id: string, options?: {
|
|
198
|
+
timeoutMs?: number;
|
|
199
|
+
}): Promise<AwaitModeResult>;
|
|
181
200
|
getCommandRegistry(): Promise<GetCommandRegistryResult>;
|
|
182
201
|
invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
|
|
183
202
|
source?: PmxAxSource;
|
|
@@ -23,4 +23,4 @@ export interface PmxAxSurfaceSnapshot {
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function buildCanvasAxSurfaceSnapshot(): PmxAxSurfaceSnapshot;
|
|
25
25
|
export declare function buildCanvasAxPinnedContext(): PmxAxPinnedContext;
|
|
26
|
-
export declare function buildCanvasAxContext(): PmxAxContext;
|
|
26
|
+
export declare function buildCanvasAxContext(consumer?: string): PmxAxContext;
|
|
@@ -135,6 +135,18 @@ export interface PmxAxPinnedContext {
|
|
|
135
135
|
export interface PmxAxFocusContext extends PmxAxFocusState {
|
|
136
136
|
nodes: AgentContextNode[];
|
|
137
137
|
}
|
|
138
|
+
export interface PendingAxActivityItem {
|
|
139
|
+
kind: 'work-item' | 'approval-gate' | 'elicitation' | 'mode-request';
|
|
140
|
+
id: string;
|
|
141
|
+
title: string;
|
|
142
|
+
status: string;
|
|
143
|
+
nodeIds: string[];
|
|
144
|
+
source: PmxAxSource | null;
|
|
145
|
+
}
|
|
146
|
+
export interface PmxAxDeliveryContext {
|
|
147
|
+
pendingSteering: PmxAxSteeringMessage[];
|
|
148
|
+
pendingActivity: PendingAxActivityItem[];
|
|
149
|
+
}
|
|
138
150
|
export interface PmxAxContext {
|
|
139
151
|
version: 1;
|
|
140
152
|
generatedAt: string;
|
|
@@ -142,6 +154,7 @@ export interface PmxAxContext {
|
|
|
142
154
|
nodeCount: number;
|
|
143
155
|
edgeCount: number;
|
|
144
156
|
};
|
|
157
|
+
delivery: PmxAxDeliveryContext;
|
|
145
158
|
pinned: PmxAxPinnedContext;
|
|
146
159
|
focus: PmxAxFocusContext;
|
|
147
160
|
workItems: PmxAxWorkItem[];
|
|
@@ -153,6 +166,20 @@ export interface PmxAxContext {
|
|
|
153
166
|
timeline: PmxAxTimelineSummary;
|
|
154
167
|
host: PmxAxHostCapability | null;
|
|
155
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Open, agent-actionable canvas-bound AX items (open work items + pending approval
|
|
171
|
+
* gates / elicitations / mode requests). Unlike steering (a directive routed through
|
|
172
|
+
* the claim/ack delivery queue), these are STATE the human curates in the browser —
|
|
173
|
+
* they fire `ax-state-changed` (so resource-subscribers are pushed canvas://ax-work),
|
|
174
|
+
* but an adapterless client that only POLLS the delivery surface never saw them.
|
|
175
|
+
* Optionally excludes items the consumer itself originated (loop prevention),
|
|
176
|
+
* mirroring getPendingSteering. Shared by the MCP delivery surface and the HTTP
|
|
177
|
+
* context lead block so the digest never drifts between the two.
|
|
178
|
+
*/
|
|
179
|
+
export declare function buildPendingAxActivity(state: PmxAxState, consumer?: string): PendingAxActivityItem[];
|
|
180
|
+
export type PmxAxActivityKind = 'tool-start' | 'tool-result' | 'failure' | 'error' | 'session-start' | 'session-end' | 'command' | 'note';
|
|
181
|
+
export declare function isAxActivityKind(value: unknown): value is PmxAxActivityKind;
|
|
182
|
+
export declare function mapAxActivityKindToEventKind(kind: PmxAxActivityKind): PmxAxEventKind;
|
|
156
183
|
export interface PmxAxCommandDescriptor {
|
|
157
184
|
name: string;
|
|
158
185
|
description: string;
|
|
@@ -264,6 +291,7 @@ export declare function createAxSteeringMessage(message: string, source: PmxAxSo
|
|
|
264
291
|
export declare function normalizeAxState(input: unknown, validNodeIds?: Set<string>): PmxAxState;
|
|
265
292
|
export declare function buildAxContext(input: {
|
|
266
293
|
layout: CanvasLayout;
|
|
294
|
+
delivery: PmxAxDeliveryContext;
|
|
267
295
|
pinned: PmxAxPinnedContext;
|
|
268
296
|
focus: PmxAxFocusState;
|
|
269
297
|
focusNodes: AgentContextNode[];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Hard ceiling on a single blocking wait, regardless of the requested timeout. */
|
|
2
|
+
export declare const AX_WAIT_MAX_MS = 120000;
|
|
3
|
+
export interface AxWaitResult<T> {
|
|
4
|
+
/** Latest value, or null if the item does not exist / vanished mid-wait. */
|
|
5
|
+
value: T | null;
|
|
6
|
+
/** True only when the item still exists and is still pending after the wait. */
|
|
7
|
+
pending: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Block until a canvas-bound AX item resolves (its status leaves `pending`), the
|
|
11
|
+
* timeout elapses, or the request aborts — the server side of report primitive D
|
|
12
|
+
* ("gates that actually gate"). Resolves immediately when the item is already
|
|
13
|
+
* resolved, missing, or `timeoutMs <= 0` (a plain single read). Subscribes to the
|
|
14
|
+
* `ax` change channel and always disposes the listener + timer.
|
|
15
|
+
*/
|
|
16
|
+
export declare function waitForAxResolution<T extends {
|
|
17
|
+
status: string;
|
|
18
|
+
}>(opts: {
|
|
19
|
+
read: () => T | null;
|
|
20
|
+
isResolved: (value: T) => boolean;
|
|
21
|
+
timeoutMs: number;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
}): Promise<AxWaitResult<T>>;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
|
|
11
11
|
*/
|
|
12
12
|
import { type PersistedCanvasState, type CanvasTheme, type AxTimelineQuery } from './canvas-db.js';
|
|
13
|
-
import { type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
|
|
13
|
+
import { type PmxAxActivityKind, type PmxAxElicitation, type PmxAxModeRequest, type PmxAxMode, type PmxAxCommandDescriptor, type PmxAxPolicy, type PmxAxFocusState, type PmxAxSource, type PmxAxState, type PmxAxWorkItem, type PmxAxWorkItemStatus, type PmxAxApprovalGate, type PmxAxReviewAnnotation, type PmxAxReviewKind, type PmxAxReviewSeverity, type PmxAxReviewStatus, type PmxAxReviewAnchorType, type PmxAxReviewRegion, type PmxAxEvent, type PmxAxEventKind, type PmxAxEvidence, type PmxAxEvidenceKind, type PmxAxSteeringMessage, type PmxAxHostCapability, type PmxAxTimelineSummary } from './ax-state.js';
|
|
14
14
|
export declare const PMX_CANVAS_DIR = ".pmx-canvas";
|
|
15
15
|
export interface PersistedBlobRef {
|
|
16
16
|
__pmxCanvasBlob: 'v1';
|
|
@@ -146,8 +146,13 @@ declare class CanvasStateManager {
|
|
|
146
146
|
private _axHostCapability;
|
|
147
147
|
private _workspaceRoot;
|
|
148
148
|
private _changeListeners;
|
|
149
|
-
/**
|
|
150
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Register a listener for state changes. Used by MCP server to emit resource
|
|
151
|
+
* notifications and by the blocking-wait endpoints to await an AX transition.
|
|
152
|
+
* Returns a disposer that unregisters the listener (callers that don't need it
|
|
153
|
+
* — e.g. the long-lived MCP subscription — may ignore the return value).
|
|
154
|
+
*/
|
|
155
|
+
onChange(cb: (type: CanvasChangeType) => void): () => void;
|
|
151
156
|
private notifyChange;
|
|
152
157
|
private _mutationRecorder;
|
|
153
158
|
private _suppressRecordingDepth;
|
|
@@ -346,6 +351,9 @@ declare class CanvasStateManager {
|
|
|
346
351
|
resolution?: string;
|
|
347
352
|
source?: PmxAxSource;
|
|
348
353
|
}): PmxAxModeRequest | null;
|
|
354
|
+
getApproval(id: string): PmxAxApprovalGate | null;
|
|
355
|
+
getElicitation(id: string): PmxAxElicitation | null;
|
|
356
|
+
getModeRequest(id: string): PmxAxModeRequest | null;
|
|
349
357
|
getCommandRegistry(): PmxAxCommandDescriptor[];
|
|
350
358
|
/** Invoke a registry-gated PMX command intent — records a timeline event (no execution). */
|
|
351
359
|
invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
|
|
@@ -385,6 +393,50 @@ declare class CanvasStateManager {
|
|
|
385
393
|
source?: PmxAxSource;
|
|
386
394
|
}): PmxAxSteeringMessage;
|
|
387
395
|
markSteeringDelivered(id: string): boolean;
|
|
396
|
+
/**
|
|
397
|
+
* Ingest a normalized agent activity (a tool/session event a harness forwards)
|
|
398
|
+
* and apply kind-driven board reactions, so the agent's real work flows back into
|
|
399
|
+
* the board without it remembering to push each item (report primitive A — makes
|
|
400
|
+
* AX bidirectional). Always records a timeline event; then, unless the caller
|
|
401
|
+
* overrides/suppresses via `reactions`, applies defaults by kind/outcome:
|
|
402
|
+
* • failure | error | outcome==='failure' → work item (blocked) + review
|
|
403
|
+
* (finding/error, anchored to a valid nodeId else the `ref` file) + evidence (logs)
|
|
404
|
+
* • tool-result + outcome==='success' → evidence (tool-result)
|
|
405
|
+
* • everything else (tool-start, session-*, command, note) → event only
|
|
406
|
+
* A reaction value of `false` suppresses it; an object overrides its fields/forces it on.
|
|
407
|
+
*/
|
|
408
|
+
ingestActivity(input: {
|
|
409
|
+
kind: PmxAxActivityKind;
|
|
410
|
+
title: string;
|
|
411
|
+
summary?: string | null;
|
|
412
|
+
outcome?: 'success' | 'failure';
|
|
413
|
+
ref?: string | null;
|
|
414
|
+
nodeIds?: string[];
|
|
415
|
+
data?: Record<string, unknown> | null;
|
|
416
|
+
reactions?: {
|
|
417
|
+
workItem?: false | {
|
|
418
|
+
status?: PmxAxWorkItemStatus;
|
|
419
|
+
detail?: string | null;
|
|
420
|
+
};
|
|
421
|
+
evidence?: false | {
|
|
422
|
+
kind?: PmxAxEvidenceKind;
|
|
423
|
+
body?: string | null;
|
|
424
|
+
};
|
|
425
|
+
review?: false | {
|
|
426
|
+
severity?: PmxAxReviewSeverity;
|
|
427
|
+
kind?: PmxAxReviewKind;
|
|
428
|
+
anchorType?: PmxAxReviewAnchorType;
|
|
429
|
+
nodeId?: string | null;
|
|
430
|
+
};
|
|
431
|
+
};
|
|
432
|
+
}, options?: {
|
|
433
|
+
source?: PmxAxSource;
|
|
434
|
+
}): {
|
|
435
|
+
event: PmxAxEvent;
|
|
436
|
+
workItem: PmxAxWorkItem | null;
|
|
437
|
+
evidence: PmxAxEvidence | null;
|
|
438
|
+
review: PmxAxReviewAnnotation | null;
|
|
439
|
+
};
|
|
388
440
|
getAxEvents(q?: AxTimelineQuery): PmxAxEvent[];
|
|
389
441
|
getAxEvidence(q?: AxTimelineQuery): PmxAxEvidence[];
|
|
390
442
|
getAxSteering(q?: AxTimelineQuery & {
|
|
@@ -27,6 +27,13 @@ export declare function normalizeSurfaceTheme(value: string | null | undefined):
|
|
|
27
27
|
* injected when the node's AX capabilities are enabled (opt-in for `html`), and
|
|
28
28
|
* the server re-validates every interaction — so this is a convenience surface,
|
|
29
29
|
* not a trust boundary.
|
|
30
|
+
*
|
|
31
|
+
* `emit` returns a Promise that resolves with the interaction result once the
|
|
32
|
+
* parent acks it (report #55 — built-in confirmation so a click no longer looks
|
|
33
|
+
* like "nothing happened"). Authors can also `window.PMX_AX.on('ack', cb)` or
|
|
34
|
+
* listen for the `pmx-ax-ack` CustomEvent. Resolves with an `ax-ack-timeout`
|
|
35
|
+
* result after 10s if no ack arrives (e.g. an older parent), so `await emit()`
|
|
36
|
+
* never hangs.
|
|
30
37
|
*/
|
|
31
38
|
export declare function buildAxBridge(axToken: string, nodeId: string): string;
|
|
32
39
|
/**
|
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { canvasState } from './canvas-state.js';
|
|
3
3
|
import type { CanvasAnnotation, CanvasNodeState, CanvasEdge, CanvasLayout } from './canvas-state.js';
|
|
4
4
|
import { type AxInteractionInput, type AxInteractionPublicResult } from './ax-interaction.js';
|
|
5
|
-
import type { PmxAxApprovalGate, PmxAxCommandDescriptor, PmxAxContext, PmxAxElicitation, PmxAxEvent, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
|
|
5
|
+
import type { PmxAxActivityKind, PmxAxApprovalGate, PmxAxCommandDescriptor, PmxAxContext, PmxAxElicitation, PmxAxEvent, PmxAxEvidence, PmxAxEvidenceKind, PmxAxFocusState, PmxAxHostCapability, PmxAxMode, PmxAxModeRequest, PmxAxPolicy, PmxAxReviewAnchorType, PmxAxReviewAnnotation, PmxAxReviewKind, PmxAxReviewRegion, PmxAxReviewSeverity, PmxAxReviewStatus, PmxAxSource, PmxAxState, PmxAxSteeringMessage, PmxAxWorkItem, PmxAxWorkItemStatus } from './ax-state.js';
|
|
6
6
|
import type { AxTimelineQuery } from './canvas-db.js';
|
|
7
7
|
import { searchNodes } from './spatial-analysis.js';
|
|
8
8
|
import { diffLayouts } from './mutation-history.js';
|
|
@@ -134,7 +134,9 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
134
134
|
panned: boolean;
|
|
135
135
|
} | null;
|
|
136
136
|
getAxState(): PmxAxState;
|
|
137
|
-
getAxContext(
|
|
137
|
+
getAxContext(options?: {
|
|
138
|
+
consumer?: string;
|
|
139
|
+
}): PmxAxContext;
|
|
138
140
|
setAxFocus(nodeIds: string[], options?: {
|
|
139
141
|
source?: PmxAxSource;
|
|
140
142
|
}): PmxAxFocusState;
|
|
@@ -253,6 +255,62 @@ export declare class PmxCanvas extends EventEmitter {
|
|
|
253
255
|
resolution?: string;
|
|
254
256
|
source?: PmxAxSource;
|
|
255
257
|
}): PmxAxModeRequest | null;
|
|
258
|
+
ingestActivity(input: {
|
|
259
|
+
kind: PmxAxActivityKind;
|
|
260
|
+
title: string;
|
|
261
|
+
summary?: string | null;
|
|
262
|
+
outcome?: 'success' | 'failure';
|
|
263
|
+
ref?: string | null;
|
|
264
|
+
nodeIds?: string[];
|
|
265
|
+
data?: Record<string, unknown> | null;
|
|
266
|
+
reactions?: {
|
|
267
|
+
workItem?: false | {
|
|
268
|
+
status?: PmxAxWorkItemStatus;
|
|
269
|
+
detail?: string | null;
|
|
270
|
+
};
|
|
271
|
+
evidence?: false | {
|
|
272
|
+
kind?: PmxAxEvidenceKind;
|
|
273
|
+
body?: string | null;
|
|
274
|
+
};
|
|
275
|
+
review?: false | {
|
|
276
|
+
severity?: PmxAxReviewSeverity;
|
|
277
|
+
kind?: PmxAxReviewKind;
|
|
278
|
+
anchorType?: PmxAxReviewAnchorType;
|
|
279
|
+
nodeId?: string | null;
|
|
280
|
+
};
|
|
281
|
+
};
|
|
282
|
+
}, options?: {
|
|
283
|
+
source?: PmxAxSource;
|
|
284
|
+
}): {
|
|
285
|
+
event: PmxAxEvent;
|
|
286
|
+
workItem: PmxAxWorkItem | null;
|
|
287
|
+
evidence: PmxAxEvidence | null;
|
|
288
|
+
review: PmxAxReviewAnnotation | null;
|
|
289
|
+
};
|
|
290
|
+
getApproval(id: string): PmxAxApprovalGate | null;
|
|
291
|
+
getElicitation(id: string): PmxAxElicitation | null;
|
|
292
|
+
getModeRequest(id: string): PmxAxModeRequest | null;
|
|
293
|
+
awaitApproval(id: string, options?: {
|
|
294
|
+
timeoutMs?: number;
|
|
295
|
+
signal?: AbortSignal;
|
|
296
|
+
}): Promise<{
|
|
297
|
+
approvalGate: PmxAxApprovalGate | null;
|
|
298
|
+
pending: boolean;
|
|
299
|
+
}>;
|
|
300
|
+
awaitElicitation(id: string, options?: {
|
|
301
|
+
timeoutMs?: number;
|
|
302
|
+
signal?: AbortSignal;
|
|
303
|
+
}): Promise<{
|
|
304
|
+
elicitation: PmxAxElicitation | null;
|
|
305
|
+
pending: boolean;
|
|
306
|
+
}>;
|
|
307
|
+
awaitMode(id: string, options?: {
|
|
308
|
+
timeoutMs?: number;
|
|
309
|
+
signal?: AbortSignal;
|
|
310
|
+
}): Promise<{
|
|
311
|
+
modeRequest: PmxAxModeRequest | null;
|
|
312
|
+
pending: boolean;
|
|
313
|
+
}>;
|
|
256
314
|
getCommandRegistry(): PmxAxCommandDescriptor[];
|
|
257
315
|
invokeCommand(name: string, args?: Record<string, unknown> | null, options?: {
|
|
258
316
|
source?: PmxAxSource;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# AX host-adapter contract
|
|
2
|
+
|
|
3
|
+
PMX Canvas owns the **AX data layer** — work items, approval gates, steering,
|
|
4
|
+
evidence, review annotations, elicitations, mode requests, the timeline, host
|
|
5
|
+
capabilities, and the tool/prompt policy — over HTTP and MCP. What makes AX
|
|
6
|
+
*interactive* on a given coding harness (GitHub Copilot, Codex, Claude Code, …) is
|
|
7
|
+
a thin **adapter** that wires PMX's neutral surfaces to that harness's lifecycle.
|
|
8
|
+
|
|
9
|
+
"Agnostic" means a documented interface plus PMX-side behavior plus one small
|
|
10
|
+
reference adapter per harness — not zero-adapter magic. The genuinely harness-owned
|
|
11
|
+
acts (waking a turn, per-turn context injection, forwarding native tool hooks,
|
|
12
|
+
native modals) still need a per-harness adapter; PMX owns everything on its side of
|
|
13
|
+
the line (queues, endpoints, schemas, the canvas-surface fallback).
|
|
14
|
+
|
|
15
|
+
## The interface
|
|
16
|
+
|
|
17
|
+
Every adapter implements as much of this as its host allows; PMX provides the
|
|
18
|
+
surface each one binds to.
|
|
19
|
+
|
|
20
|
+
| Adapter method | PMX surface (owned) | Harness-owned part |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `pullContext()` | `GET /api/canvas/ax/context?consumer=<id>` · `canvas://ax-context` — full board **plus** a compact `delivery` lead block | When/where to inject it into the model's turn |
|
|
23
|
+
| `deliverSteer()` | `GET /api/canvas/ax/delivery/pending?consumer=<id>` · `canvas_claim_ax_delivery` → act → `POST …/delivery/<id>/mark` · `canvas_mark_ax_delivery` | Calling the host's native send/wake |
|
|
24
|
+
| `ingestActivity(event)` | `POST /api/canvas/ax/activity` · `canvas_ingest_activity` — board auto-reacts | Forwarding the host's tool/session hooks |
|
|
25
|
+
| `awaitGate(id)` | `GET /api/canvas/ax/{approval\|elicitation\|mode}/<id>?waitMs=` · `canvas_await_*` | Optionally surfacing a native modal; the agent must await PMX |
|
|
26
|
+
| `mirrorLog(event)` *(optional)* | `GET /api/canvas/ax/timeline` · `canvas://ax-timeline` | Writing AX events into the host's own chat/session log |
|
|
27
|
+
|
|
28
|
+
## Steering is gated, not pushed (#54)
|
|
29
|
+
|
|
30
|
+
A board action (e.g. an `ax.steer` emit from a surface button) enqueues a steering
|
|
31
|
+
message; it does **not** wake the agent. It reaches the next turn only when:
|
|
32
|
+
|
|
33
|
+
1. **The pin/focus gate is open.** A typical adapter injects `/api/canvas/ax/context`
|
|
34
|
+
only when something is pinned or focused (`pinned.count > 0 || focus.nodeIds.length > 0`).
|
|
35
|
+
A steering board must therefore stay pinned, or its button should also emit
|
|
36
|
+
`ax.focus.set` on the board node, to hold the gate open.
|
|
37
|
+
2. **A human message fires the turn.** A sandbox button click cannot itself create a
|
|
38
|
+
new agent turn (an app-platform constraint). Any human prompt triggers the injection.
|
|
39
|
+
3. **The agent acts, then acks.** Injected `pendingSteering` / `pendingActivity` is
|
|
40
|
+
*to-do*, not narration: act on it, then `canvas_mark_ax_delivery` the steering
|
|
41
|
+
(or resolve the work item / gate). Until acked, steering re-injects every gated turn.
|
|
42
|
+
|
|
43
|
+
The `delivery` lead block (`GET /api/canvas/ax/context?consumer=<id>`) is the
|
|
44
|
+
robustness hedge: it's compact and sits above the full dump, so an adapter can inject
|
|
45
|
+
it un-truncated even on a busy board where the full context is clipped.
|
|
46
|
+
|
|
47
|
+
## The two primitives that close the loop
|
|
48
|
+
|
|
49
|
+
- **Activity ingestion (bidirectional board).** Before, AX was one-directional
|
|
50
|
+
(agent → board). With `ingestActivity`, the agent's *real work* flows back: a failed
|
|
51
|
+
tool becomes a blocked work item + a review finding + evidence without the agent
|
|
52
|
+
remembering to push it. Reactions are kind-driven and overridable per call.
|
|
53
|
+
- **Blocking gates (gates that actually gate).** Before, an approval gate was inert
|
|
54
|
+
data the agent had to poll. With `canvas_await_approval` (and the `?waitMs` HTTP
|
|
55
|
+
long-poll), the agent requests a gate then *blocks* until the human resolves it in
|
|
56
|
+
the browser — real human-in-the-loop control on any harness.
|
|
57
|
+
|
|
58
|
+
## What stays harness-owned
|
|
59
|
+
|
|
60
|
+
Waking a turn, the exact per-turn injection timing, forwarding native tool hooks, and
|
|
61
|
+
native blocking modals are the host's job — PMX defines the neutral interface and owns
|
|
62
|
+
its side. Model/abort control (`setModel`, `abort`) is intentionally out of scope.
|
|
63
|
+
|
|
64
|
+
See [`docs/http-api.md`](http-api.md) and [`docs/mcp.md`](mcp.md) for the full surface,
|
|
65
|
+
and the per-harness notes under `skills/pmx-canvas/references/`.
|
package/docs/http-api.md
CHANGED
|
@@ -46,8 +46,18 @@ curl -X POST http://localhost:4313/api/canvas/node \
|
|
|
46
46
|
curl -X POST http://localhost:4313/api/canvas/node \
|
|
47
47
|
-H "Content-Type: application/json" \
|
|
48
48
|
-d '{"type":"html-primitive","kind":"choice-grid","title":"Options","data":{"items":[{"title":"Small patch","summary":"Least disruption."}]}}'
|
|
49
|
+
|
|
50
|
+
# Opt an html node into AX. Top-level `html` AND `axCapabilities` are accepted on
|
|
51
|
+
# POST add and PATCH update (and may also be nested under `data`).
|
|
52
|
+
curl -X POST http://localhost:4313/api/canvas/node \
|
|
53
|
+
-H "Content-Type: application/json" \
|
|
54
|
+
-d '{"type":"html","title":"AX board","html":"<p>steering board</p>","axCapabilities":{"enabled":true,"allowed":["ax.steer"]}}'
|
|
49
55
|
```
|
|
50
56
|
|
|
57
|
+
A node creation request must resolve a `type` — pass it in the body (`{ "type":
|
|
58
|
+
... }`) or as a `?type=` query param. An empty / type-less body returns `400`
|
|
59
|
+
rather than silently creating a markdown node.
|
|
60
|
+
|
|
51
61
|
## Edges
|
|
52
62
|
|
|
53
63
|
```bash
|
|
@@ -177,7 +187,9 @@ curl http://localhost:4313/api/canvas/ax/host-capability
|
|
|
177
187
|
|
|
178
188
|
Validation: `/ax/event` requires a valid `kind` + `summary` (400 otherwise);
|
|
179
189
|
`/ax/evidence` requires `kind` + `title`; `/ax/steer`, `/ax/work`,
|
|
180
|
-
`/ax/approval`, `/ax/review` require their primary field; `PATCH /ax/work
|
|
190
|
+
`/ax/approval`, `/ax/review` require their primary field; `POST`/`PATCH /ax/work`
|
|
191
|
+
reject an unknown `status` with 400 (the tokens are `todo`, `in-progress`,
|
|
192
|
+
`blocked`, `done`, `cancelled` — hyphens, not underscores); `PATCH /ax/work/:id`
|
|
181
193
|
and `PATCH /ax/review/:id` return 404 for unknown IDs; approval resolve returns
|
|
182
194
|
404 if the gate is missing or already resolved.
|
|
183
195
|
|
|
@@ -218,6 +230,24 @@ curl -X POST http://localhost:4313/api/canvas/ax/mode/<id>/resolve \
|
|
|
218
230
|
-d '{"decision":"approved"}'
|
|
219
231
|
curl http://localhost:4313/api/canvas/ax/mode
|
|
220
232
|
|
|
233
|
+
# Activity ingestion — forward an agent tool/session event; the board auto-reacts
|
|
234
|
+
# (kind-driven, overridable: failure → work item + review + evidence; tool-result
|
|
235
|
+
# + outcome:"success" → evidence). Set a reaction to false to suppress it.
|
|
236
|
+
curl -X POST http://localhost:4313/api/canvas/ax/activity \
|
|
237
|
+
-H "Content-Type: application/json" \
|
|
238
|
+
-d '{"kind":"failure","title":"tsc failed","summary":"type error in x.ts","nodeIds":["node-1"],"source":"api"}'
|
|
239
|
+
|
|
240
|
+
# Blocking gate read — read one gate, or long-poll with ?waitMs until the human
|
|
241
|
+
# resolves it in the browser (gates that actually gate). Returns { <primitive>, pending }.
|
|
242
|
+
curl "http://localhost:4313/api/canvas/ax/approval/<id>" # immediate read
|
|
243
|
+
curl "http://localhost:4313/api/canvas/ax/approval/<id>?waitMs=30000" # blocks ≤30s / until resolved
|
|
244
|
+
curl "http://localhost:4313/api/canvas/ax/elicitation/<id>?waitMs=30000"
|
|
245
|
+
curl "http://localhost:4313/api/canvas/ax/mode/<id>?waitMs=30000"
|
|
246
|
+
|
|
247
|
+
# Context — optional ?consumer= filters the compact, loop-safe `delivery` lead block
|
|
248
|
+
# (undelivered steering + open work/approvals it can act on) for per-turn injection.
|
|
249
|
+
curl "http://localhost:4313/api/canvas/ax/context?consumer=copilot"
|
|
250
|
+
|
|
221
251
|
# Commands — list the registry, invoke a command (records a `command` agent-event)
|
|
222
252
|
curl http://localhost:4313/api/canvas/ax/command
|
|
223
253
|
curl -X POST http://localhost:4313/api/canvas/ax/command \
|
|
@@ -234,7 +264,9 @@ curl -X POST http://localhost:4313/api/canvas/ax/policy \
|
|
|
234
264
|
Validation: `/ax/interaction` returns `{ ok: false, code }` (403 `ax-disabled` /
|
|
235
265
|
`not-allowed`, 400 `invalid-payload` / `unknown-command`, 404 `unknown-node`);
|
|
236
266
|
`/ax/command` rejects an unknown command name with 400; `/ax/elicitation/:id/respond`
|
|
237
|
-
and `/ax/mode/:id/resolve` return 404 for unknown IDs
|
|
267
|
+
and `/ax/mode/:id/resolve` return 404 for unknown IDs; `/ax/activity` requires a
|
|
268
|
+
valid `kind` + `title` (400 otherwise); the single-item gate GETs return 404 for
|
|
269
|
+
unknown IDs and clamp `?waitMs` to ≤120000.
|
|
238
270
|
|
|
239
271
|
## Diagrams (Excalidraw preset)
|
|
240
272
|
|
package/docs/mcp.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MCP reference
|
|
2
2
|
|
|
3
|
-
PMX Canvas ships an MCP stdio server with **
|
|
3
|
+
PMX Canvas ships an MCP stdio server with **69 tools** + **14 core resources**,
|
|
4
4
|
plus per-skill resources at `canvas://skills/<name>`. The server emits
|
|
5
5
|
`notifications/resources/updated` when canvas state changes — humans pin
|
|
6
6
|
nodes in the browser, agents are notified immediately.
|
|
@@ -72,6 +72,10 @@ searchable and readable in pinned/spatial context.
|
|
|
72
72
|
| `canvas_respond_elicitation` | Respond to / resolve a pending elicitation |
|
|
73
73
|
| `canvas_request_mode` | Request a workflow `mode-request` transition (plan/execute/autonomous) |
|
|
74
74
|
| `canvas_resolve_mode` | Resolve a pending mode request |
|
|
75
|
+
| `canvas_ingest_activity` | Ingest a harness-forwarded agent activity (tool/session event); the board auto-reacts with kind-driven, overridable defaults (failure → work item + review + evidence; `tool-result`+success → evidence). Makes AX bidirectional |
|
|
76
|
+
| `canvas_await_approval` | Block until an approval gate resolves (human approves/rejects in the browser) or the timeout elapses (`timeoutMs` 0 = immediate read). Gates that actually gate |
|
|
77
|
+
| `canvas_await_elicitation` | Block until an elicitation is answered or the timeout elapses |
|
|
78
|
+
| `canvas_await_mode` | Block until a mode request resolves or the timeout elapses |
|
|
75
79
|
| `canvas_invoke_command` | Invoke a registry command (`pmx.plan`, `pmx.execute`, `pmx.promote-context`, `pmx.summarize`, `pmx.review`); records a `command` agent-event, unknown names rejected |
|
|
76
80
|
| `canvas_set_ax_policy` | Patch the canvas-bound tool/prompt policy (`tools.allowed\|excluded\|approvalRequired`, `prompt.systemAppend\|mode`); patches merge and are normalized |
|
|
77
81
|
| `canvas_pin_nodes` | Pin nodes to include in agent context |
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.36",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -843,6 +843,12 @@ Eligible nodes can emit one normalized, validated AX interaction that maps onto
|
|
|
843
843
|
AX operation — work item, evidence, approval, review, focus, steering, event,
|
|
844
844
|
elicitation, or mode request. One envelope, many transports:
|
|
845
845
|
|
|
846
|
+
This is the **agent-native nodes** model: existing canvas node types become
|
|
847
|
+
interactive agent controls when their AX capabilities allow it. Do not describe
|
|
848
|
+
this as a separate node type; it is a capability layer on top of markdown,
|
|
849
|
+
status, HTML, json-render, graph, web-artifact, MCP app, and other supported
|
|
850
|
+
nodes.
|
|
851
|
+
|
|
846
852
|
- **Endpoint:** `POST /api/canvas/ax/interaction` with
|
|
847
853
|
`{ type, sourceNodeId, payload }` (MCP: `canvas_ax_interaction`; CLI:
|
|
848
854
|
`pmx-canvas ax interaction`). Returns `{ ok, primitive }` or
|
|
@@ -857,10 +863,12 @@ elicitation, or mode request. One envelope, many transports:
|
|
|
857
863
|
before submitting: `html` / `html-primitive` nodes (when opted in) call
|
|
858
864
|
`window.PMX_AX.emit(type, payload)`; **json-render / graph** viewers forward a
|
|
859
865
|
spec action named after an AX type (e.g. `on.press → { action:
|
|
860
|
-
"ax.work.create", params }`, `sourceSurface: 'json-render'`);
|
|
861
|
-
**`mcp-app`** nodes
|
|
862
|
-
(`
|
|
863
|
-
|
|
866
|
+
"ax.work.create", params }`, `sourceSurface: 'json-render'`); web-artifact
|
|
867
|
+
**`mcp-app`** nodes use the same parent bridge; external MCP app frames
|
|
868
|
+
(`mode: "ext-app"`) can emit through an injected `window.PMX_AX.emit` with
|
|
869
|
+
Promise acknowledgements, but do not get the read-state bridge. The server
|
|
870
|
+
re-validates capabilities regardless of transport — bridges are convenience,
|
|
871
|
+
not a trust boundary.
|
|
864
872
|
- **Delivery (adapterless):** `canvas://ax-pending-steering` /
|
|
865
873
|
`canvas_claim_ax_delivery` return two things, both loop-safe (a consumer never
|
|
866
874
|
receives items it originated):
|
|
@@ -877,6 +885,26 @@ elicitation, or mode request. One envelope, many transports:
|
|
|
877
885
|
live. Clients that **poll** instead should poll `canvas_claim_ax_delivery` —
|
|
878
886
|
`pendingActivity` is how non-steering browser changes reach them. Only steering
|
|
879
887
|
flows through the claim/ack queue.
|
|
888
|
+
- **Steering is gated, not pushed.** A surface button that emits `ax.steer`
|
|
889
|
+
enqueues a steer — it does NOT wake the agent. With a prompt-injecting host
|
|
890
|
+
adapter (e.g. Copilot), it reaches the next turn only when (1) the **pin/focus
|
|
891
|
+
gate is open** (something pinned or focused — so keep a steering board pinned, or
|
|
892
|
+
have its button also emit `ax.focus.set` on itself), (2) a **human message** fires
|
|
893
|
+
the turn, and (3) the agent **acts then acks** (`canvas_mark_ax_delivery`), or the
|
|
894
|
+
steer re-injects every gated turn. `GET /api/canvas/ax/context?consumer=<id>` adds
|
|
895
|
+
a compact, loop-safe `delivery: { pendingSteering, pendingActivity }` lead block an
|
|
896
|
+
adapter can inject un-truncated, so steering survives the full-context char clip.
|
|
897
|
+
- **Activity ingestion (bidirectional board):** a host adapter forwards the agent's
|
|
898
|
+
tool/session events with `canvas_ingest_activity` (HTTP `POST /api/canvas/ax/activity`)
|
|
899
|
+
and the board auto-reacts — `failure`/`error` (or `outcome:"failure"`) → a blocked
|
|
900
|
+
work item + a review finding + `logs` evidence; `tool-result` + `outcome:"success"` →
|
|
901
|
+
`tool-result` evidence; everything else records a timeline event only. Override or
|
|
902
|
+
suppress per call via `reactions` (`{ workItem: false }`, `{ review: { severity } }`, …).
|
|
903
|
+
- **Blocking gates (gates that actually gate):** after requesting an approval /
|
|
904
|
+
elicitation / mode, `canvas_await_approval` / `canvas_await_elicitation` /
|
|
905
|
+
`canvas_await_mode` (HTTP `GET /api/canvas/ax/<kind>/<id>?waitMs=`) BLOCK until the
|
|
906
|
+
human resolves it in the browser or the timeout elapses (`timeoutMs` 0 = immediate
|
|
907
|
+
read; ≤120000). Use this to pause real work on a human decision instead of polling.
|
|
880
908
|
- **Elicitation / mode:** request structured human input
|
|
881
909
|
(`canvas_request_elicitation` → `canvas_respond_elicitation`) or a workflow
|
|
882
910
|
mode transition (`canvas_request_mode` → `canvas_resolve_mode`); both are
|
|
@@ -916,8 +944,10 @@ AX interactions are gated per node type. The lists below are each type's **ceili
|
|
|
916
944
|
| `webpage` | `ax.evidence.add`, `ax.review.add`, `ax.focus.set`, `ax.event.record` |
|
|
917
945
|
| `group` | `ax.focus.set`, `ax.work.create`, `ax.command.invoke`, `ax.event.record` |
|
|
918
946
|
|
|
919
|
-
**Opt-in** — set `
|
|
920
|
-
`canvas_add_html_node`
|
|
947
|
+
**Opt-in** — set `axCapabilities.enabled = true` (MCP: pass `axCapabilities` to
|
|
948
|
+
`canvas_add_html_node` / `canvas_update_node`. HTTP: `axCapabilities` **and** the
|
|
949
|
+
`html` body are accepted **top-level on both `POST /api/canvas/node` and
|
|
950
|
+
`PATCH /api/canvas/node/<id>`**, or nested under `data` — both work, top-level wins):
|
|
921
951
|
|
|
922
952
|
| Node type | Allowed AX interaction types |
|
|
923
953
|
|-----------|------------------------------|
|
|
@@ -928,7 +958,9 @@ AX interactions are gated per node type. The lists below are each type's **ceili
|
|
|
928
958
|
only, no human-facing emit.
|
|
929
959
|
|
|
930
960
|
The 11 interaction types and what they create: `ax.work.create` / `ax.work.update`
|
|
931
|
-
(work-queue items
|
|
961
|
+
(work-queue items; status is exactly one of `todo`, `in-progress`, `blocked`, `done`,
|
|
962
|
+
`cancelled` — **hyphens, not underscores**; `POST`/`PATCH /api/canvas/ax/work` reject an
|
|
963
|
+
unknown token like `in_progress` with `400`), `ax.evidence.add`
|
|
932
964
|
(timeline evidence), `ax.review.add` (review annotation), `ax.focus.set` (agent focus
|
|
933
965
|
pointer), `ax.steer` (a steering message delivered to the agent), `ax.approval.request`
|
|
934
966
|
(approval gate), `ax.elicitation.request` (structured human input), `ax.mode.request`
|
|
@@ -949,6 +981,12 @@ state. The read side mirrors the write side:
|
|
|
949
981
|
- **Emit (write):** in `html`, call `window.PMX_AX.emit("ax.work.create", { title })`;
|
|
950
982
|
in `json-render`, bind a control action named after the AX type
|
|
951
983
|
(`on: { press: { action: "ax.work.create", params: { title } } }`).
|
|
984
|
+
- **Confirm (#55):** for `html` / `html-primitive` and PMX_AX-enabled `mcp-app`
|
|
985
|
+
surfaces, `emit` returns a Promise that resolves with the result once the
|
|
986
|
+
canvas acks it, so a button can self-confirm: `const r = await
|
|
987
|
+
window.PMX_AX.emit(...); if (r.ok) showQueued();`. You can also
|
|
988
|
+
`window.PMX_AX.on('ack', cb)` or listen for the `pmx-ax-ack` event. (Falls back
|
|
989
|
+
to an `ax-ack-timeout` result after 10s, so `await` never hangs.)
|
|
952
990
|
- **Reflect (read):** the canvas seeds the surface with a compact AX snapshot at
|
|
953
991
|
load (the same shape as `GET /api/canvas/ax/surface-snapshot`) and live-updates it
|
|
954
992
|
as AX state changes. Works on all three authored surface types:
|
|
@@ -966,11 +1004,15 @@ state. The read side mirrors the write side:
|
|
|
966
1004
|
Minimal html work board (drop-in via `canvas_add_html_node`, `axCapabilities.enabled: true`):
|
|
967
1005
|
|
|
968
1006
|
```html
|
|
969
|
-
<button
|
|
1007
|
+
<button id="add">+ Task</button> <span id="ok"></span>
|
|
970
1008
|
<ul id="q"></ul>
|
|
971
1009
|
<script>
|
|
972
1010
|
function render(s){ document.getElementById('q').innerHTML =
|
|
973
1011
|
((s&&s.workItems)||[]).map(w => '<li>['+w.status+'] '+w.title+'</li>').join(''); }
|
|
1012
|
+
document.getElementById('add').onclick = async () => {
|
|
1013
|
+
const r = await window.PMX_AX.emit('ax.work.create',{title:'New task'});
|
|
1014
|
+
document.getElementById('ok').textContent = r && r.ok ? 'queued ✓' : 'failed'; // #55 self-confirm
|
|
1015
|
+
};
|
|
974
1016
|
render(window.PMX_AX && window.PMX_AX.state);
|
|
975
1017
|
window.addEventListener('pmx-ax-update', e => render(e.detail));
|
|
976
1018
|
</script>
|
|
@@ -86,10 +86,24 @@ against `canvas_set_ax_focus`.
|
|
|
86
86
|
Codex agents should treat PMX AX context as host-native working context:
|
|
87
87
|
|
|
88
88
|
- `canvas://pinned-context` is the explicit human-curated node set.
|
|
89
|
-
- `canvas://ax-context` combines pins, focus, and surface metadata
|
|
89
|
+
- `canvas://ax-context` combines pins, focus, and surface metadata, plus a compact
|
|
90
|
+
loop-safe `delivery: { pendingSteering, pendingActivity }` lead block
|
|
91
|
+
(`GET /api/canvas/ax/context?consumer=codex` filters out Codex-originated items).
|
|
90
92
|
- `canvas_get_ax` returns both persisted AX state and agent-ready context.
|
|
91
93
|
- Focus is a current attention target, not a command to ignore the rest of the repository.
|
|
92
94
|
|
|
95
|
+
The adapterless MCP+Browser path is poll-based: there is no automatic prompt injection,
|
|
96
|
+
so a board click does not wake the current turn. Codex agents poll
|
|
97
|
+
`canvas_claim_ax_delivery` (steering + `pendingActivity`) and act/ack explicitly. The
|
|
98
|
+
loop-closing surfaces work over MCP today even without a dedicated extension:
|
|
99
|
+
|
|
100
|
+
- **Self-report work** with `canvas_ingest_activity` (the board auto-reacts: a failed
|
|
101
|
+
tool → a blocked work item + review + evidence). Automatic forwarding of Codex's own
|
|
102
|
+
tool hooks would need a Codex adapter; manual ingestion works now.
|
|
103
|
+
- **Block on a decision** with `canvas_await_approval` / `canvas_await_elicitation` /
|
|
104
|
+
`canvas_await_mode` (they long-poll PMX until the human resolves the gate in the
|
|
105
|
+
Browser or the timeout elapses) instead of looping on `canvas_get_ax`.
|
|
106
|
+
|
|
93
107
|
## Live-Test Checklist
|
|
94
108
|
|
|
95
109
|
1. Open `http://127.0.0.1:4313/workbench` in the Codex in-app Browser first so the user can see
|