pilotswarm-sdk 0.5.39 → 0.5.40
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/index.js +1 -0
- package/api/src/api-client.js +46 -0
- package/api/src/canvas-live-mirror.js +134 -0
- package/api/src/http-api-transport.js +92 -1
- package/api/src/protocol.js +4 -0
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +102 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +110 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +157 -0
- package/dist/cms.js.map +1 -1
- package/dist/facts-store.d.ts.map +1 -1
- package/dist/facts-store.js +7 -2
- package/dist/facts-store.js.map +1 -1
- package/dist/facts-tools.d.ts +7 -0
- package/dist/facts-tools.d.ts.map +1 -1
- package/dist/facts-tools.js +338 -3
- package/dist/facts-tools.js.map +1 -1
- package/dist/managed-session.d.ts.map +1 -1
- package/dist/managed-session.js +72 -20
- package/dist/managed-session.js.map +1 -1
- package/dist/management-client.d.ts +42 -0
- package/dist/management-client.d.ts.map +1 -1
- package/dist/management-client.js +66 -0
- package/dist/management-client.js.map +1 -1
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +4 -0
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +186 -27
- package/dist/session-proxy.js.map +1 -1
- package/dist/web/generated-op-methods.d.ts +31 -0
- package/dist/web/generated-op-methods.d.ts.map +1 -1
- package/dist/web/generated-op-methods.js +8 -0
- package/dist/web/generated-op-methods.js.map +1 -1
- package/dist/web/web-management-client.d.ts +22 -0
- package/dist/web/web-management-client.d.ts.map +1 -1
- package/dist/web/web-management-client.js +18 -0
- package/dist/web/web-management-client.js.map +1 -1
- package/package.json +3 -3
- package/plugins/default-agents/agents/generic-crawler.agent.md +10 -2
- package/plugins/mgmt/agents/facts-manager.agent.md +11 -1
- package/plugins/mgmt/agents/pilotswarm.agent.md +2 -2
- package/plugins/system/agents/default.agent.md +2 -2
- package/plugins/system/skills/html-visuals/SKILL.md +36 -1
package/dist/session-proxy.js
CHANGED
|
@@ -1298,6 +1298,42 @@ artifactStore) {
|
|
|
1298
1298
|
return latest;
|
|
1299
1299
|
}
|
|
1300
1300
|
let canvasDrawChain = Promise.resolve();
|
|
1301
|
+
// Per-slot tick throttle (the plane makes ticks cheap; a runaway
|
|
1302
|
+
// loop must not flood viewers). Execution-scoped, like the chain.
|
|
1303
|
+
// Keys are `${targetSessionId}:${slot}` — a child ticking its own
|
|
1304
|
+
// canvas and its parent's dashboard are separate budgets.
|
|
1305
|
+
const canvasTickClock = new Map();
|
|
1306
|
+
const CANVAS_TICK_MIN_INTERVAL_MS = 100;
|
|
1307
|
+
/**
|
|
1308
|
+
* Cross-session canvas targeting: ANCESTORS ONLY. A sub-agent may
|
|
1309
|
+
* draw on its parent's (or grandparent's, or the root's) surface —
|
|
1310
|
+
* never a sibling's, a child's, or a stranger's. Worker-trusted,
|
|
1311
|
+
* the same stance as fromArtifact; the walk is the catalog's parent
|
|
1312
|
+
* chain, capped at the spawn-nesting depth.
|
|
1313
|
+
*/
|
|
1314
|
+
const resolveCanvasTarget = async (requested) => {
|
|
1315
|
+
const requestedId = String(requested ?? "").trim();
|
|
1316
|
+
if (!requestedId || requestedId === input.sessionId) {
|
|
1317
|
+
return { target: input.sessionId, crossSession: false };
|
|
1318
|
+
}
|
|
1319
|
+
let cursor = input.sessionId;
|
|
1320
|
+
for (let depth = 0; depth < 8 && cursor; depth++) {
|
|
1321
|
+
let row;
|
|
1322
|
+
try {
|
|
1323
|
+
row = await catalog?.getSession?.(cursor);
|
|
1324
|
+
}
|
|
1325
|
+
catch (err) {
|
|
1326
|
+
return { error: `could not verify the session lineage: ${err?.message || String(err)}` };
|
|
1327
|
+
}
|
|
1328
|
+
const parent = row?.parentSessionId ?? null;
|
|
1329
|
+
if (!parent)
|
|
1330
|
+
break;
|
|
1331
|
+
if (parent === requestedId)
|
|
1332
|
+
return { target: requestedId, crossSession: true };
|
|
1333
|
+
cursor = parent;
|
|
1334
|
+
}
|
|
1335
|
+
return { error: `session_id ${requestedId} is not an ancestor of this session — canvas tools may target only your parent chain (parent, grandparent, root)` };
|
|
1336
|
+
};
|
|
1301
1337
|
const controlToolBridge = {
|
|
1302
1338
|
/**
|
|
1303
1339
|
* Send a message to a session AS ITS USER.
|
|
@@ -2078,8 +2114,11 @@ artifactStore) {
|
|
|
2078
2114
|
if (rawName !== undefined && rawName.length > 60) {
|
|
2079
2115
|
return { error: "name must be 60 characters or fewer" };
|
|
2080
2116
|
}
|
|
2117
|
+
const resolved = await resolveCanvasTarget(args.session_id);
|
|
2118
|
+
if ("error" in resolved)
|
|
2119
|
+
return { error: resolved.error };
|
|
2120
|
+
const { target, crossSession } = resolved;
|
|
2081
2121
|
const run = canvasDrawChain.then(async () => {
|
|
2082
|
-
const rev = (await latestCanvasRev(catalog, input.sessionId, slot)) + 1;
|
|
2083
2122
|
// Source resolution. fromArtifact pulls bytes store-side —
|
|
2084
2123
|
// the same trust stance as read_artifact/write_artifact's
|
|
2085
2124
|
// cross-session paths (the artifact layer is worker-trusted);
|
|
@@ -2143,7 +2182,22 @@ artifactStore) {
|
|
|
2143
2182
|
}
|
|
2144
2183
|
const app = canvasAppCard(extraction.manifest);
|
|
2145
2184
|
const note = args.note ? String(args.note) : undefined;
|
|
2146
|
-
|
|
2185
|
+
// Atomic rev mint (multi-writer safe), AFTER every
|
|
2186
|
+
// validation refusal above: the mint writes latest_rev
|
|
2187
|
+
// into the 0045 cache, and a rev minted for a draw
|
|
2188
|
+
// that then failed validation would unlock ticks on an
|
|
2189
|
+
// empty slot (latestCanvasRev is table-first). The
|
|
2190
|
+
// seed from the table/event read floors legacy
|
|
2191
|
+
// sessions whose cache row never landed. Residual
|
|
2192
|
+
// window: an upload/record failure AFTER the mint
|
|
2193
|
+
// burns a phantom rev — infra-failure only, and the
|
|
2194
|
+
// next successful draw overwrites it; read_canvas
|
|
2195
|
+
// stays honest because it is log-first.
|
|
2196
|
+
const seedRev = await latestCanvasRev(catalog, target, slot);
|
|
2197
|
+
const rev = typeof catalog.mintCanvasRev === "function"
|
|
2198
|
+
? await catalog.mintCanvasRev(target, slot, seedRev)
|
|
2199
|
+
: seedRev + 1;
|
|
2200
|
+
await artifactStore.uploadArtifact(target, canvasArtifactFilename(slot), html, "text/html",
|
|
2147
2201
|
// Pinned on EVERY draw, not just the first: uploads
|
|
2148
2202
|
// replace artifact metadata wholesale, so a pin set
|
|
2149
2203
|
// once at rev 1 was silently erased by rev 2.
|
|
@@ -2154,7 +2208,7 @@ artifactStore) {
|
|
|
2154
2208
|
// The response contract rides the event so every
|
|
2155
2209
|
// client learns it exactly where it learns the rev —
|
|
2156
2210
|
// live push and cold snapshot alike, no extra fetch.
|
|
2157
|
-
await catalog.recordEvents(
|
|
2211
|
+
await catalog.recordEvents(target, [{
|
|
2158
2212
|
eventType: "session.canvas_updated",
|
|
2159
2213
|
data: {
|
|
2160
2214
|
rev,
|
|
@@ -2164,6 +2218,10 @@ artifactStore) {
|
|
|
2164
2218
|
...(note ? { note } : {}),
|
|
2165
2219
|
...(effectiveContract ? { responseContract: effectiveContract } : {}),
|
|
2166
2220
|
...(source ? { source } : {}),
|
|
2221
|
+
// Attribution: who actually drew this revision.
|
|
2222
|
+
// Only present on cross-session draws, so the
|
|
2223
|
+
// portal can badge "drawn by sub-agent X".
|
|
2224
|
+
...(crossSession ? { by: input.sessionId } : {}),
|
|
2167
2225
|
},
|
|
2168
2226
|
}], workerNodeId);
|
|
2169
2227
|
// The per-slot cache (migration 0045). Non-fatal on
|
|
@@ -2172,9 +2230,21 @@ artifactStore) {
|
|
|
2172
2230
|
// missing — failing the draw over a cache write would
|
|
2173
2231
|
// invert the dependency.
|
|
2174
2232
|
try {
|
|
2175
|
-
await catalog.upsertSessionCanvas?.(
|
|
2233
|
+
await catalog.upsertSessionCanvas?.(target, slot, rawName ?? null, rev, sizeBytes);
|
|
2176
2234
|
}
|
|
2177
2235
|
catch { /* self-heals on the next draw */ }
|
|
2236
|
+
// The data plane (migration 0047): doc pointer for
|
|
2237
|
+
// live viewers + RESET of the data mirror — the new
|
|
2238
|
+
// page starts from its own initial state, never the
|
|
2239
|
+
// old page's stale ticks. Non-fatal: the plane is the
|
|
2240
|
+
// acceleration path, the event above is the truth.
|
|
2241
|
+
try {
|
|
2242
|
+
if (await catalog.canvasLiveAvailable?.()) {
|
|
2243
|
+
const sha = createHash("sha256").update(html, "utf8").digest("hex");
|
|
2244
|
+
await catalog.upsertCanvasLiveDoc?.(target, slot, { rev, sha }, input.sessionId);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
catch { /* live viewers resync from events */ }
|
|
2178
2248
|
return {
|
|
2179
2249
|
rev,
|
|
2180
2250
|
slot,
|
|
@@ -2195,25 +2265,87 @@ artifactStore) {
|
|
|
2195
2265
|
const slot = normalizeCanvasSlot(args.slot);
|
|
2196
2266
|
if (slot === null)
|
|
2197
2267
|
return { error: "slot must be an integer 1-5" };
|
|
2268
|
+
const resolved = await resolveCanvasTarget(args.session_id);
|
|
2269
|
+
if ("error" in resolved)
|
|
2270
|
+
return { error: resolved.error };
|
|
2271
|
+
const { target, crossSession } = resolved;
|
|
2272
|
+
const isPatch = args.patch !== undefined;
|
|
2273
|
+
const body = isPatch ? args.patch : args.data;
|
|
2274
|
+
// Rate limit at the source: the plane makes ticks cheap
|
|
2275
|
+
// enough that a runaway loop could flood viewers. Applies
|
|
2276
|
+
// per (target, slot) within this execution.
|
|
2277
|
+
const now = Date.now();
|
|
2278
|
+
const tickKey = `${target}:${slot}`;
|
|
2279
|
+
const lastTick = canvasTickClock.get(tickKey) || 0;
|
|
2280
|
+
if (now - lastTick < CANVAS_TICK_MIN_INTERVAL_MS) {
|
|
2281
|
+
return { error: `ticking slot ${slot} faster than ${CANVAS_TICK_MIN_INTERVAL_MS} ms apart — aggregate your changes into fewer, larger ticks` };
|
|
2282
|
+
}
|
|
2283
|
+
canvasTickClock.set(tickKey, now);
|
|
2198
2284
|
const run = canvasDrawChain.then(async () => {
|
|
2199
|
-
if ((await latestCanvasRev(catalog,
|
|
2285
|
+
if ((await latestCanvasRev(catalog, target, slot)) === 0) {
|
|
2200
2286
|
return { error: `no canvas has been drawn in slot ${slot} — draw_canvas first; ticks patch an existing page` };
|
|
2201
2287
|
}
|
|
2202
|
-
const
|
|
2203
|
-
|
|
2204
|
-
//
|
|
2205
|
-
//
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2288
|
+
const planeAvailable = await catalog.canvasLiveAvailable?.().catch(() => false);
|
|
2289
|
+
// The plane is where merges live. Without it (older
|
|
2290
|
+
// deployment), a patch has no current state to merge
|
|
2291
|
+
// against — refuse with the actionable alternative
|
|
2292
|
+
// instead of guessing.
|
|
2293
|
+
if (isPatch && !planeAvailable) {
|
|
2294
|
+
return { error: "this deployment predates the canvas data plane — send data (the whole state) instead of patch" };
|
|
2295
|
+
}
|
|
2296
|
+
// Whole state after this write, whoever computes it:
|
|
2297
|
+
// the plane merges patches server-side; a plain PUT is
|
|
2298
|
+
// its own whole state.
|
|
2299
|
+
let mergedPayload = (body ?? {});
|
|
2300
|
+
let planeSeq;
|
|
2301
|
+
if (planeAvailable) {
|
|
2302
|
+
const live = await catalog.upsertCanvasLiveTick(target, slot, isPatch ? { patch: body } : { data: body }, input.sessionId, 32_768);
|
|
2303
|
+
if (live?.refused) {
|
|
2304
|
+
return {
|
|
2305
|
+
error: `the merged canvas state would exceed 32768 bytes`
|
|
2306
|
+
+ (live.currentSizeBytes != null ? ` (currently ${live.currentSizeBytes} bytes)` : "")
|
|
2307
|
+
+ `. Aggregate the data, remove stale keys (patch with null deletes), or redraw if the shape truly grew.`,
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
mergedPayload = live.payload ?? mergedPayload;
|
|
2311
|
+
planeSeq = live.seq;
|
|
2312
|
+
}
|
|
2313
|
+
const sizeBytes = Buffer.byteLength(JSON.stringify(mergedPayload), "utf8");
|
|
2314
|
+
// Dual-write phase: the durable event carries the
|
|
2315
|
+
// MERGED whole state so pre-plane readers (older
|
|
2316
|
+
// portals, cold loads) stay complete. Flipping
|
|
2317
|
+
// PILOTSWARM_CANVAS_DURABLE_TICKS=0 ends this — only
|
|
2318
|
+
// after every reader in the environment prefers the
|
|
2319
|
+
// plane, and the completion checkpoint ships with
|
|
2320
|
+
// that flip.
|
|
2321
|
+
let dataRev;
|
|
2322
|
+
// The flag only silences durable ticks where the
|
|
2323
|
+
// plane actually took the write. Plane absent =
|
|
2324
|
+
// today's path stays, whatever the flag says —
|
|
2325
|
+
// otherwise a PUT would be written NOWHERE and still
|
|
2326
|
+
// report success.
|
|
2327
|
+
if (!planeAvailable || process.env.PILOTSWARM_CANVAS_DURABLE_TICKS !== "0") {
|
|
2328
|
+
dataRev = (await latestCanvasDataRev(catalog, target, slot)) + 1;
|
|
2329
|
+
await catalog.recordEvents(target, [{
|
|
2330
|
+
eventType: "session.canvas_data",
|
|
2331
|
+
data: {
|
|
2332
|
+
dataRev,
|
|
2333
|
+
slot,
|
|
2334
|
+
sizeBytes,
|
|
2335
|
+
payload: mergedPayload,
|
|
2336
|
+
...(isPatch ? { patched: true } : {}),
|
|
2337
|
+
...(crossSession ? { by: input.sessionId } : {}),
|
|
2338
|
+
...(args.note ? { note: String(args.note) } : {}),
|
|
2339
|
+
},
|
|
2340
|
+
}], workerNodeId);
|
|
2341
|
+
}
|
|
2342
|
+
return {
|
|
2343
|
+
...(dataRev !== undefined ? { dataRev } : {}),
|
|
2344
|
+
...(planeSeq !== undefined ? { seq: planeSeq } : {}),
|
|
2345
|
+
slot,
|
|
2346
|
+
sizeBytes,
|
|
2347
|
+
mode: isPatch ? "patch" : "data",
|
|
2348
|
+
};
|
|
2217
2349
|
}).catch((err) => ({ error: err?.message || String(err) }));
|
|
2218
2350
|
canvasDrawChain = run.then(() => undefined, () => undefined);
|
|
2219
2351
|
return run;
|
|
@@ -2228,13 +2360,17 @@ artifactStore) {
|
|
|
2228
2360
|
const slot = normalizeCanvasSlot(args.slot);
|
|
2229
2361
|
if (slot === null)
|
|
2230
2362
|
return { error: "slot must be an integer 1-5" };
|
|
2231
|
-
const
|
|
2363
|
+
const resolved = await resolveCanvasTarget(args.session_id);
|
|
2364
|
+
if ("error" in resolved)
|
|
2365
|
+
return { error: resolved.error };
|
|
2366
|
+
const { target, crossSession } = resolved;
|
|
2367
|
+
const rev = await latestCanvasRev(catalog, target, slot);
|
|
2232
2368
|
if (rev === 0) {
|
|
2233
2369
|
return { error: `nothing has been drawn in slot ${slot} — draw_canvas first; show_canvas only presents an existing canvas` };
|
|
2234
2370
|
}
|
|
2235
|
-
await catalog.recordEvents(
|
|
2371
|
+
await catalog.recordEvents(target, [{
|
|
2236
2372
|
eventType: "session.canvas_presented",
|
|
2237
|
-
data: { slot, rev },
|
|
2373
|
+
data: { slot, rev, ...(crossSession ? { by: input.sessionId } : {}) },
|
|
2238
2374
|
}], workerNodeId);
|
|
2239
2375
|
return { presented: true, slot, rev };
|
|
2240
2376
|
},
|
|
@@ -2246,6 +2382,27 @@ artifactStore) {
|
|
|
2246
2382
|
const slot = normalizeCanvasSlot(args.slot);
|
|
2247
2383
|
if (slot === null)
|
|
2248
2384
|
return { error: "slot must be an integer 1-5" };
|
|
2385
|
+
const resolved = await resolveCanvasTarget(args.session_id);
|
|
2386
|
+
if ("error" in resolved)
|
|
2387
|
+
return { error: resolved.error };
|
|
2388
|
+
const { target } = resolved;
|
|
2389
|
+
// "What is the page showing right now?" — the plane's
|
|
2390
|
+
// last-value row, one PK read. Opt-in (the payload can be
|
|
2391
|
+
// 32 KB of model context) and independent of paging.
|
|
2392
|
+
const liveState = async () => {
|
|
2393
|
+
if (!args.includeData)
|
|
2394
|
+
return {};
|
|
2395
|
+
try {
|
|
2396
|
+
if (!(await catalog.canvasLiveAvailable?.()))
|
|
2397
|
+
return {};
|
|
2398
|
+
const rows = await catalog.getCanvasLive?.(target);
|
|
2399
|
+
const hit = (rows || []).find((r) => Number(r.slot) === slot);
|
|
2400
|
+
return hit ? { live: { seq: hit.seq, data: hit.payload, updatedBy: hit.updatedBy, updatedAt: hit.updatedAt } } : {};
|
|
2401
|
+
}
|
|
2402
|
+
catch {
|
|
2403
|
+
return {};
|
|
2404
|
+
}
|
|
2405
|
+
};
|
|
2249
2406
|
try {
|
|
2250
2407
|
// Log FIRST: the event log is the single authority on
|
|
2251
2408
|
// whether a canvas exists. Orphan bytes from a crashed
|
|
@@ -2261,10 +2418,10 @@ artifactStore) {
|
|
|
2261
2418
|
// ARMED contract from the latest draw event — the
|
|
2262
2419
|
// pair an inheriting agent needs to interpret
|
|
2263
2420
|
// canvas-action messages and author ticks.
|
|
2264
|
-
const latest = await latestCanvasEventData(catalog,
|
|
2421
|
+
const latest = await latestCanvasEventData(catalog, target, slot);
|
|
2265
2422
|
if (latest.rev === 0)
|
|
2266
2423
|
return { exists: false };
|
|
2267
|
-
const docText = await artifactStore.downloadArtifactText(
|
|
2424
|
+
const docText = await artifactStore.downloadArtifactText(target, canvasArtifactFilename(slot));
|
|
2268
2425
|
const extraction = extractCanvasAppManifest(docText);
|
|
2269
2426
|
const card = canvasAppCard(extraction.manifest);
|
|
2270
2427
|
// Events are writable via send_session_event with no
|
|
@@ -2279,12 +2436,13 @@ artifactStore) {
|
|
|
2279
2436
|
...(card ? { app: card } : {}),
|
|
2280
2437
|
...(armed.contract ? { responseContract: armed.contract } : {}),
|
|
2281
2438
|
...(extraction.error ? { manifestError: extraction.error } : {}),
|
|
2439
|
+
...(await liveState()),
|
|
2282
2440
|
};
|
|
2283
2441
|
}
|
|
2284
|
-
const rev = await latestCanvasRev(catalog,
|
|
2442
|
+
const rev = await latestCanvasRev(catalog, target, slot);
|
|
2285
2443
|
if (rev === 0)
|
|
2286
2444
|
return { exists: false };
|
|
2287
|
-
const text = await artifactStore.downloadArtifactText(
|
|
2445
|
+
const text = await artifactStore.downloadArtifactText(target, canvasArtifactFilename(slot));
|
|
2288
2446
|
const offset = Math.max(0, Math.floor(Number(args.offset) || 0));
|
|
2289
2447
|
const maxChars = Math.min(262_144, Math.max(1, Math.floor(Number(args.maxBytes) || 65_536)));
|
|
2290
2448
|
const content = text.slice(offset, offset + maxChars);
|
|
@@ -2298,6 +2456,7 @@ artifactStore) {
|
|
|
2298
2456
|
offset,
|
|
2299
2457
|
content,
|
|
2300
2458
|
truncated: offset + content.length < text.length,
|
|
2459
|
+
...(await liveState()),
|
|
2301
2460
|
};
|
|
2302
2461
|
}
|
|
2303
2462
|
catch (err) {
|