pilotswarm-sdk 0.5.44 → 0.5.47
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/api/src/api-client.js +6 -1
- package/api/src/http-api-transport.js +51 -0
- package/api/src/protocol.js +16 -0
- package/dist/agent-manager-tools.d.ts +2 -0
- package/dist/agent-manager-tools.d.ts.map +1 -1
- package/dist/agent-manager-tools.js +4 -1
- package/dist/agent-manager-tools.js.map +1 -1
- package/dist/agent-package-format.d.ts +6 -0
- package/dist/agent-package-format.d.ts.map +1 -1
- package/dist/agent-package-format.js +33 -0
- package/dist/agent-package-format.js.map +1 -1
- package/dist/agent-package-service.d.ts.map +1 -1
- package/dist/agent-package-service.js +7 -2
- package/dist/agent-package-service.js.map +1 -1
- package/dist/artifact-tools.js +1 -1
- package/dist/artifact-tools.js.map +1 -1
- package/dist/canvas-app-catalog.d.ts +61 -0
- package/dist/canvas-app-catalog.d.ts.map +1 -0
- package/dist/canvas-app-catalog.js +146 -0
- package/dist/canvas-app-catalog.js.map +1 -0
- package/dist/canvas-app-manifest.d.ts +94 -2
- package/dist/canvas-app-manifest.d.ts.map +1 -1
- package/dist/canvas-app-manifest.js +140 -1
- package/dist/canvas-app-manifest.js.map +1 -1
- package/dist/canvas-kv.d.ts +176 -0
- package/dist/canvas-kv.d.ts.map +1 -0
- package/dist/canvas-kv.js +281 -0
- package/dist/canvas-kv.js.map +1 -0
- package/dist/canvas-support.d.ts +40 -0
- package/dist/canvas-support.d.ts.map +1 -0
- package/dist/canvas-support.js +79 -0
- package/dist/canvas-support.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +10 -1
- package/dist/client.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +1021 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +101 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +80 -1
- package/dist/cms.js.map +1 -1
- package/dist/facts-migrations.d.ts.map +1 -1
- package/dist/facts-migrations.js +30 -0
- package/dist/facts-migrations.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect-tools.d.ts +6 -0
- package/dist/inspect-tools.d.ts.map +1 -1
- package/dist/inspect-tools.js +1 -0
- package/dist/inspect-tools.js.map +1 -1
- package/dist/managed-session.d.ts +7 -0
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +193 -64
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +60 -1
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +107 -3
- package/dist/management-client.js.map +1 -1
- package/dist/mcp-loader.d.ts +68 -0
- package/dist/mcp-loader.d.ts.map +1 -1
- package/dist/mcp-loader.js +79 -0
- package/dist/mcp-loader.js.map +1 -1
- package/dist/orchestration/index.d.ts +2 -2
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/queue.d.ts +13 -0
- package/dist/orchestration/queue.d.ts.map +1 -1
- package/dist/orchestration/queue.js +53 -2
- package/dist/orchestration/queue.js.map +1 -1
- package/dist/orchestration/runtime.d.ts +1 -1
- package/dist/orchestration/turn.d.ts +1 -1
- package/dist/orchestration/turn.d.ts.map +1 -1
- package/dist/orchestration/turn.js +20 -4
- package/dist/orchestration/turn.js.map +1 -1
- package/dist/orchestration-registry.d.ts.map +1 -1
- package/dist/orchestration-registry.js +4 -2
- package/dist/orchestration-registry.js.map +1 -1
- package/dist/orchestration-version.d.ts +1 -1
- package/dist/orchestration-version.js +1 -1
- package/dist/orchestration.d.ts +2 -2
- package/dist/orchestration.js +1 -1
- package/dist/orchestration_1_0_46.d.ts +1 -1
- package/dist/orchestration_1_0_47.d.ts +1 -1
- package/dist/orchestration_1_0_48.d.ts +1 -1
- package/dist/orchestration_1_0_49.d.ts +1 -1
- package/dist/orchestration_1_0_50.d.ts +1 -1
- package/dist/orchestration_1_0_69/agents.d.ts +61 -0
- package/dist/orchestration_1_0_69/agents.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/agents.js +840 -0
- package/dist/orchestration_1_0_69/agents.js.map +1 -0
- package/dist/orchestration_1_0_69/index.d.ts +33 -0
- package/dist/orchestration_1_0_69/index.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/index.js +13 -0
- package/dist/orchestration_1_0_69/index.js.map +1 -0
- package/dist/orchestration_1_0_69/lifecycle.d.ts +49 -0
- package/dist/orchestration_1_0_69/lifecycle.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/lifecycle.js +947 -0
- package/dist/orchestration_1_0_69/lifecycle.js.map +1 -0
- package/dist/orchestration_1_0_69/queue.d.ts +7 -0
- package/dist/orchestration_1_0_69/queue.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/queue.js +805 -0
- package/dist/orchestration_1_0_69/queue.js.map +1 -0
- package/dist/orchestration_1_0_69/runtime.d.ts +29 -0
- package/dist/orchestration_1_0_69/runtime.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/runtime.js +228 -0
- package/dist/orchestration_1_0_69/runtime.js.map +1 -0
- package/dist/orchestration_1_0_69/state.d.ts +196 -0
- package/dist/orchestration_1_0_69/state.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/state.js +159 -0
- package/dist/orchestration_1_0_69/state.js.map +1 -0
- package/dist/orchestration_1_0_69/turn.d.ts +34 -0
- package/dist/orchestration_1_0_69/turn.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/turn.js +1342 -0
- package/dist/orchestration_1_0_69/turn.js.map +1 -0
- package/dist/orchestration_1_0_69/utils.d.ts +39 -0
- package/dist/orchestration_1_0_69/utils.d.ts.map +1 -0
- package/dist/orchestration_1_0_69/utils.js +316 -0
- package/dist/orchestration_1_0_69/utils.js.map +1 -0
- package/dist/provider-store.d.ts +18 -0
- package/dist/provider-store.d.ts.map +1 -1
- package/dist/provider-store.js +18 -0
- package/dist/provider-store.js.map +1 -1
- package/dist/provider-tools.d.ts.map +1 -1
- package/dist/provider-tools.js +24 -2
- package/dist/provider-tools.js.map +1 -1
- package/dist/session-manager.d.ts +20 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +13 -1
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +1 -9
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +134 -82
- package/dist/session-proxy.js.map +1 -1
- package/dist/web/generated-op-methods.d.ts +69 -0
- package/dist/web/generated-op-methods.d.ts.map +1 -1
- package/dist/web/generated-op-methods.js +16 -0
- package/dist/web/generated-op-methods.js.map +1 -1
- package/dist/web/web-management-client.d.ts +26 -0
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +33 -0
- package/dist/web/web-management-client.js.map +1 -1
- package/dist/worker.d.ts +27 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +145 -13
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
- package/plugins/system/agents/default.agent.md +30 -126
- package/plugins/system/skills/canvas-apps/SKILL.md +224 -111
- package/plugins/system/skills/html-visuals/SKILL.md +2 -2
|
@@ -1,158 +1,271 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: canvas-apps
|
|
3
|
-
description:
|
|
3
|
+
description: Interactive and SHARED canvas apps — page actions, the canvas_kv store several people write at once, requests the page queues for you, saving and reusing apps. Load before any canvas that takes input or that more than one person uses.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Canvas Apps
|
|
7
7
|
|
|
8
|
-
A canvas visual is disposable presentation. A canvas app
|
|
9
|
-
|
|
8
|
+
A canvas visual is disposable presentation. A canvas app holds state, lets
|
|
9
|
+
several people use it at once, and asks the session to do things. For page
|
|
10
|
+
layout, chart form, color and the sandbox rules, load `html-visuals` first.
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
## The three lanes
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
- a
|
|
14
|
+
| Lane | Who writes | Reaches | Wakes you | Durable | Costs you |
|
|
15
|
+
|---|---|---|---|---|---|
|
|
16
|
+
| **KV** (`canvas_kv`) | any permitted viewer, and you | every viewer, ~50 ms | no | yes | tens of tokens per key |
|
|
17
|
+
| **Action** (`canvas-action`) | a viewer with session write | you | yes — one turn | as a message | a whole turn |
|
|
18
|
+
| **Tick** (`update_canvas`) | you only | every viewer, ~50 ms | no | no | patch: tens · data: thousands |
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
**The KV is for state, the action is for attention.** Never put a payload
|
|
21
|
+
in an action when you can put an id there and the payload in the KV.
|
|
22
|
+
Outbound costs output tokens, inbound costs a turn — the turn is the unit.
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
Cheapest first: `canvas_kv(put)` one key · `update_canvas(patch)` a subtree ·
|
|
25
|
+
`draw_canvas(fromArtifact)` a filename · `update_canvas(data)` whole state ·
|
|
26
|
+
`draw_canvas(html)` the whole document.
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
## The KV store
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
| `update_canvas` | no | no | transient whole-state or merge-patch updates |
|
|
29
|
-
| canvas action | user message | yes | a creator asking the session to act |
|
|
30
|
+
One row per key, per session, per slot. The page supplies only the key; the
|
|
31
|
+
server stamps the writer (`by`) and the time (`at`). Keys: letters, digits,
|
|
32
|
+
`. _ / -`, ≤200 chars. Values ≤16 KB; 1000 keys and 2 MB per canvas.
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
invent `kv.ready`, `kv.put`, `canvas_kv`, `find_canvas_app`, or
|
|
33
|
-
`publish_canvas_app`. Multi-viewer editable state and catalog search/publish are
|
|
34
|
-
future proposal work.
|
|
34
|
+
Reserved prefixes:
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
```
|
|
37
|
+
app/… the app's shared state — notes, votes, the board
|
|
38
|
+
req/<reqId> a request from the page to you (below)
|
|
39
|
+
evt/<n> a note from you to the page (you write, viewers read)
|
|
40
|
+
ui/<writerId> one viewer's ephemeral state (presence, drafts) — only theirs
|
|
41
|
+
cfg/… you and the owner write, everyone reads
|
|
42
|
+
```
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
**Never keep a list in one key.** One key per item (`app/note/<uuid>`), one
|
|
45
|
+
key per person (`app/vote/<me.id>`), a claim is `put` with `ifMatch: 0`,
|
|
46
|
+
ordering is a field inside the item. Counters: per-person keys, count them.
|
|
47
|
+
Free text several people edit: split into per-section keys and show `by`.
|
|
48
|
+
Frame-rate state (a game board, live spectating) does not belong here.
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
Your tool: `canvas_kv({op: get|put|list|delete, key, value, prefix, limit,
|
|
51
|
+
after, ifMatch, slot, session_id})`. `ifMatch: 0` = create only; `ifMatch: N`
|
|
52
|
+
= the current rev must be N. `list` pages by `after` (the last key of the
|
|
53
|
+
previous page). `slot` picks the canvas (1-5, default 1); `session_id`
|
|
54
|
+
targets an ANCESTOR session's canvas, as the other canvas tools do.
|
|
55
|
+
|
|
56
|
+
### Who may write
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
Two switches, both needed. The **owner's policy** on the canvas
|
|
59
|
+
(`owner` default | `readers` = anyone the session is read-shared with |
|
|
60
|
+
`link`), set in the share dialog. And **your app's declaration** in the
|
|
61
|
+
manifest: `"kv": { "write": "viewers" }`. Without both, only the owner, the
|
|
62
|
+
session's writers, and you write. Rows are author-bound: a collaborator
|
|
63
|
+
edits only rows they wrote unless you share the prefix
|
|
64
|
+
(`"kv": { "write": "viewers", "shared": ["app/task/*"] }`).
|
|
50
65
|
|
|
51
|
-
|
|
66
|
+
Everyone reads. Every write is attributed: `by.kind` is `user` (signed in),
|
|
67
|
+
`link` (bearer — label unverified) or `agent`.
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
`responseContract`:
|
|
69
|
+
## The page side — paste this helper
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
Nothing is injected into the document. Paste `CanvasKV()` into your page:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
function CanvasKV() {
|
|
75
|
+
const waiting = new Map(); let n = 0; const listeners = [];
|
|
76
|
+
const ask = (msg, retryMs) => new Promise((res, rej) => {
|
|
77
|
+
const id = ++n; waiting.set(id, { res, rej });
|
|
78
|
+
const send = () => parent.postMessage({ type: "canvas-kv", id, ...msg }, "*");
|
|
79
|
+
send();
|
|
80
|
+
// The host may not be listening yet on a cold open: re-post until answered.
|
|
81
|
+
if (retryMs) { const t = setInterval(() => waiting.has(id) ? send() : clearInterval(t), retryMs); }
|
|
82
|
+
});
|
|
83
|
+
window.addEventListener("message", (e) => {
|
|
84
|
+
const m = e.data || {};
|
|
85
|
+
if (m.type === "canvas-kv-change") { listeners.forEach((f) => f(m)); return; }
|
|
86
|
+
if (m.type !== "canvas-kv-result" && m.type !== "canvas-kv-ready") return;
|
|
87
|
+
const w = waiting.get(m.id); if (!w) return; waiting.delete(m.id);
|
|
88
|
+
m.ok ? w.res(m) : w.rej(Object.assign(new Error(m.error || "failed"), { code: m.code, rev: m.rev }));
|
|
89
|
+
});
|
|
90
|
+
const listAll = async (prefix) => { // every key under a prefix, across pages
|
|
91
|
+
let after = null, out = [];
|
|
92
|
+
do { const r = await ask({ op: "list", prefix, after }); out = out.concat(r.entries); after = r.nextAfter; } while (after);
|
|
93
|
+
return out;
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
ready: ask({ op: "ready" }, 500), // → { me, canWrite, policy, entries (first 200) }
|
|
97
|
+
listAll, // → [{ key, v, by, at, rev }]
|
|
98
|
+
get: (key) => ask({ op: "get", key }).then((r) => r.entry),
|
|
99
|
+
put: (key, value, ifMatch) => ask({ op: "put", key, value, ifMatch }), // → { rev } | rejects { code, rev }
|
|
100
|
+
del: (key, ifMatch) => ask({ op: "delete", key, ifMatch }),
|
|
101
|
+
onChange: (f) => listeners.push(f), // { key, rev, op, v?, by, at }
|
|
102
|
+
};
|
|
63
103
|
}
|
|
64
104
|
```
|
|
65
105
|
|
|
66
|
-
Supported field types are `string`, `number`, `boolean`, and `json`; append `?`
|
|
67
|
-
to an optional field name. Keep actions small and intention-oriented. The page
|
|
68
|
-
posts:
|
|
69
|
-
|
|
70
106
|
```js
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
const kv = CanvasKV();
|
|
108
|
+
const { canWrite, me } = await kv.ready;
|
|
109
|
+
renderAll(await kv.listAll("app/")); // list the app prefix — ready carries only the first 200 keys
|
|
110
|
+
kv.onChange(async (c) => applyOne(c.v === undefined && c.op !== "delete" ? { ...c, v: (await kv.get(c.key))?.v } : c));
|
|
111
|
+
if (!canWrite) lockTheUI("You can view this board but not edit it.");
|
|
112
|
+
setInterval(() => document.visibilityState === "visible" && kv.listAll("app/").then(renderAll), 15000); // resync if the live feed drops
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`me` is `{ id, kind, label, relation, canWrite }`; `relation` is `owner |
|
|
116
|
+
admin | collaborator | viewer | link`. **`me.id` is `provider/subject`** —
|
|
117
|
+
it contains a slash, so a per-person key `app/vote/<me.id>` spans two
|
|
118
|
+
segments (fine; list by prefix `app/vote/`). `label` is the person's
|
|
119
|
+
display name (their email or subject when no name is known).
|
|
120
|
+
|
|
121
|
+
Entries are `{ key, v, by, at, rev }` — `v` is what a page stored; `by` and
|
|
122
|
+
`at` let you attribute and order. A `canvas-kv-change` may arrive **without
|
|
123
|
+
`v`** (the value was too big to ride the ping): fetch the key, as above.
|
|
124
|
+
`op: "delete"` drops the key. Rejections carry `code` — `FORBIDDEN`,
|
|
125
|
+
`CONFLICT` (with the current `rev`, so a claim can show who won),
|
|
126
|
+
`INVALID_KEY`, `TOO_LARGE`, `QUOTA`, `NOT_READY` (retry). Render from the KV,
|
|
127
|
+
not from state baked into the document; preserve in-flight drafts across
|
|
128
|
+
re-renders.
|
|
129
|
+
|
|
130
|
+
New keys are open to every admitted writer; only overwrites are
|
|
131
|
+
author-bound. "Only the owner may add" is a UI rule, not a server rule.
|
|
132
|
+
|
|
133
|
+
## Requests: the KV carries the payload, the action rings the doorbell
|
|
134
|
+
|
|
76
135
|
```
|
|
136
|
+
page put req/<id> { op, args, status: "queued" } every viewer sees it
|
|
137
|
+
page canvas-action { action: "request", data: { id } } turn starts (if the viewer may ring)
|
|
138
|
+
you canvas_kv get req/<id> → put { status: "working" } → put { status: "done", result }
|
|
139
|
+
page renders the answer. No redraw.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Contract: `"responseContract": { "actions": { "request": { "id": "string" } } }`.
|
|
143
|
+
Rows carry `{ op, args, status, result?, error? }` with `status` in
|
|
144
|
+
`suggested | queued | working | done | failed`.
|
|
77
145
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
146
|
+
**Only the owner and you decide what you act on. Everyone else suggests.**
|
|
147
|
+
A collaborator's `req/*` row is capped to `status: "suggested"` by the
|
|
148
|
+
server; the owner promotes it by writing `status: "queued"`. Your drain, on
|
|
149
|
+
EVERY wake (a cron tick, a child completion, any turn):
|
|
82
150
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
151
|
+
```
|
|
152
|
+
canvas_kv list req/ → take every row with status "queued" (never "suggested")
|
|
153
|
+
do the work, land each row in "done" or "failed" — never leave one "working"
|
|
154
|
+
```
|
|
86
155
|
|
|
87
|
-
|
|
156
|
+
The doorbell only shortens the wait; the status is the boundary. An app
|
|
157
|
+
whose requests progress only while the owner has a tab open is broken for
|
|
158
|
+
autonomous use: render `queued` honestly ("picked up on the next run"), and
|
|
159
|
+
`suggested` as "waiting for the owner", never a spinner.
|
|
88
160
|
|
|
89
|
-
|
|
161
|
+
Recovery after a memory gap: `canvas_kv list req/` (unfinished work),
|
|
162
|
+
`canvas_kv list app/` (current state), `read_canvas(manifestOnly)` (the
|
|
163
|
+
armed contract). This is why app state lives in the KV, not in chat.
|
|
90
164
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
165
|
+
**Promotion is a decision, never a reflex.** Only the owner's ask or your own
|
|
166
|
+
task moves KV content into an artifact, a shared fact, or an external write
|
|
167
|
+
(ADO, a PR comment, email). Carry `by` across. Never promote link-written
|
|
168
|
+
content into a shared fact without the owner saying so.
|
|
95
169
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
170
|
+
## Actions (the doorbell)
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
parent.postMessage({ type: "canvas-action", action: "request", data: { id } }, "*");
|
|
174
|
+
```
|
|
99
175
|
|
|
100
|
-
|
|
176
|
+
The browser validates against your contract (no contract → nothing is
|
|
177
|
+
accepted) and delivers conforming posts as `[canvas-action] {...}` user
|
|
178
|
+
messages hidden from the chat pane. Answer on the canvas (a KV put or a
|
|
179
|
+
patch), not with a redraw that wipes drafts. Actions are accepted only from
|
|
180
|
+
a viewer with session write; everyone else's request lands `suggested`.
|
|
181
|
+
Never BLOCK on a canvas control — the chat box is the fallback, and real
|
|
182
|
+
approval gates stay on `ask_user`. Batch a whole form into ONE action with a
|
|
183
|
+
`json` field.
|
|
101
184
|
|
|
102
|
-
|
|
185
|
+
## The manifest: the app's interface, for the agent who did not write it
|
|
186
|
+
|
|
187
|
+
A canvas worth drawing twice is an app, and an app is driven by agents who
|
|
188
|
+
never read its HTML — you after a context regeneration, and every other
|
|
189
|
+
session that finds it in the catalog. The manifest comment right after
|
|
190
|
+
`<!doctype html>` is the whole interface. Write it as if for a stranger:
|
|
103
191
|
|
|
104
192
|
```html
|
|
105
193
|
<!-- CANVAS-APP-MANIFEST
|
|
106
|
-
{
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
194
|
+
{ "manifestVersion": 1, "name": "release-signoff", "version": "1.2.0",
|
|
195
|
+
"description": "Use when several approvers sign off a release train together.",
|
|
196
|
+
"tags": ["release", "approval", "collaborative"],
|
|
197
|
+
"kv": { "write": "viewers", "shared": ["app/item/*"] },
|
|
198
|
+
"responseContract": { "actions": { "request": { "id": "string" } } },
|
|
199
|
+
"interface": {
|
|
200
|
+
"keys": [
|
|
201
|
+
{ "key": "app/item/<id>", "writer": "both",
|
|
202
|
+
"shape": { "title": "Fix login redirect", "owner": "sam", "decision": "approve|reject|null", "note": "" },
|
|
203
|
+
"note": "One row per checklist item. The AGENT seeds them; approvers set decision and note." },
|
|
204
|
+
{ "key": "cfg/deadline", "writer": "agent", "shape": "2026-09-01T17:00:00Z" },
|
|
205
|
+
{ "key": "ui/<writerId>", "writer": "page", "shape": { "at": "iso" }, "note": "Presence heartbeat." }
|
|
206
|
+
],
|
|
207
|
+
"requests": [
|
|
208
|
+
{ "op": "seed", "args": { "items": [{ "id": "5502432", "title": "…", "owner": "…" }] },
|
|
209
|
+
"result": { "seeded": 12 }, "note": "Owner asks the agent to (re)load the checklist from the train." },
|
|
210
|
+
{ "op": "store", "args": {}, "result": { "artifact": "signoff-<train>.md" },
|
|
211
|
+
"note": "Owner presses Store: the agent writes the decisions as a report artifact." }
|
|
212
|
+
],
|
|
213
|
+
"events": [ { "key": "evt/banner", "shape": { "text": "Freeze at 17:00 UTC" }, "note": "Rendered at the top." } ],
|
|
214
|
+
"notes": "Render everything from app/item/*. A row with decision null is pending. Count approvals by listing." } }
|
|
119
215
|
-->
|
|
120
216
|
```
|
|
121
217
|
|
|
122
|
-
|
|
123
|
-
|
|
218
|
+
`interface.keys` says which KV keys exist, who writes them and what a value
|
|
219
|
+
looks like. `interface.requests` says which `req/*` ops the page queues, with
|
|
220
|
+
their args and what `result` the page renders when you land them.
|
|
221
|
+
`interface.events` says which `evt/*` notes the page shows. Caps: 32 entries
|
|
222
|
+
per list, 512 bytes per shape, 6 KB for the block. `description` is the text
|
|
223
|
+
the catalog ranks on: say WHEN to use the app, not how it works.
|
|
224
|
+
|
|
225
|
+
Re-learn any app with `read_canvas({manifestOnly: true})` — never by
|
|
226
|
+
re-reading the bytes. `draw_canvas({fromArtifact})` hands you the same card.
|
|
124
227
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
228
|
+
## Publish and find
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
publish_canvas_app({ name: "release-signoff", description: "Use when …", tags: [...] })
|
|
232
|
+
→ the canvas bytes → pinned artifact app-<name>.html on this session
|
|
233
|
+
→ the card (manifest + armed contract + source) → SHARED fact apps/<name>
|
|
234
|
+
→ every session can find it
|
|
235
|
+
|
|
236
|
+
find_canvas_app({ query: "sign off a release" })
|
|
237
|
+
→ ranked cards { key, name, description, tags, kv, source }
|
|
238
|
+
read_facts({ key_pattern: "apps/<name>", scope: "shared" }) → the full card, incl. interface
|
|
239
|
+
draw_canvas({ fromArtifact: card.source }) → on screen, card returned again
|
|
132
240
|
```
|
|
133
241
|
|
|
134
|
-
|
|
135
|
-
|
|
242
|
+
**Look before building.** When the user asks for an app — a board, a poll, a
|
|
243
|
+
sign-off sheet, a review workbench — search first and offer what exists.
|
|
244
|
+
When the user is doing something an app fits (reading a diff, running a
|
|
245
|
+
meeting, watching a deploy), search and suggest rather than waiting to be
|
|
246
|
+
asked. Publish when the user says to share it.
|
|
247
|
+
|
|
248
|
+
**Driving an app you found:** draw it, `canvas_kv(list, "app/")` to see its
|
|
249
|
+
state, seed the keys its `interface.keys` name (the shapes are examples to
|
|
250
|
+
copy), drain `req/*` on every wake and land each with the `result` shape the
|
|
251
|
+
card gives. You never need the HTML.
|
|
136
252
|
|
|
137
|
-
|
|
253
|
+
**Publish the shell, never the data.** Publishing makes the document readable
|
|
254
|
+
by every session. A reusable app reads its rows from the KV; a page with a
|
|
255
|
+
customer list baked into a JS literal is not reusable and must not be
|
|
256
|
+
published. Republishing the same name replaces the card and the artifact.
|
|
138
257
|
|
|
139
|
-
|
|
140
|
-
regeneration:
|
|
258
|
+
## Where html-visuals differs
|
|
141
259
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
260
|
+
`html-visuals` was written for single-writer canvases. For a KV app, two of
|
|
261
|
+
its rules invert: state lives in the KV, not batched into one submit action
|
|
262
|
+
(the "Forms: batch the input" pattern is for canvases WITHOUT a store), and
|
|
263
|
+
any viewer with session write may post actions, not only the creator.
|
|
146
264
|
|
|
147
|
-
##
|
|
265
|
+
## Fit the pane
|
|
148
266
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- Save reusable documents as artifacts before a redraw or regeneration can make
|
|
155
|
-
them hard to reconstruct.
|
|
156
|
-
- Do not claim persistence for `update_canvas` data; redraw from durable source
|
|
157
|
-
and repopulate it when necessary.
|
|
158
|
-
- Do not claim multi-user editing or catalog publication until those APIs ship.
|
|
267
|
+
Design the shell to the viewport (`html, body { height: 100% }`, grid rows
|
|
268
|
+
`auto 1fr auto`, only the middle scrolls with `min-height: 0`), never
|
|
269
|
+
`overflow: hidden` on `body`, `100dvh` not `100vh`, inputs at 16 px, sticky
|
|
270
|
+
headers on long lists. A resize reflows and never reloads. Test at ~380,
|
|
271
|
+
~700 and full width.
|
|
@@ -85,7 +85,7 @@ essays through it).
|
|
|
85
85
|
|
|
86
86
|
## Sharing caveat
|
|
87
87
|
|
|
88
|
-
Only the session
|
|
88
|
+
Only a viewer with session WRITE access (the creator, and anyone the session is write-shared with) can post canvas actions — read-only viewers see the
|
|
89
89
|
form, but their clicks are refused by the platform (their view of a mutating
|
|
90
90
|
canvas may be stale). If the session is shared, say in the page footer who
|
|
91
91
|
the form is addressed to, and let others respond in chat.
|
|
@@ -333,7 +333,7 @@ forever, and never courier the bytes through your own context again.
|
|
|
333
333
|
makes later fromArtifact draws FAIL CLOSED, so keep it valid.
|
|
334
334
|
|
|
335
335
|
2. **Save = server-side copy, never a re-emit:**
|
|
336
|
-
`write_artifact({fromArtifact: {filename: "canvas.html"}, filename: "
|
|
336
|
+
`write_artifact({fromArtifact: {filename: "canvas.html"}, filename: "app-<name>.html"})` — the source defaults to this session.
|
|
337
337
|
|
|
338
338
|
3. **Load = draw_canvas({fromArtifact: {sessionId?, filename}}).** The bytes go
|
|
339
339
|
store→canvas; your tool result is the interface card — manifest summary plus
|