@aigne/afs-ui 1.11.0-beta.12
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/LICENSE.md +26 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.cjs +11 -0
- package/dist/_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs +10 -0
- package/dist/aup-protocol.cjs +235 -0
- package/dist/aup-protocol.d.cts +78 -0
- package/dist/aup-protocol.d.cts.map +1 -0
- package/dist/aup-protocol.d.mts +78 -0
- package/dist/aup-protocol.d.mts.map +1 -0
- package/dist/aup-protocol.mjs +235 -0
- package/dist/aup-protocol.mjs.map +1 -0
- package/dist/aup-registry.cjs +2489 -0
- package/dist/aup-registry.mjs +2487 -0
- package/dist/aup-registry.mjs.map +1 -0
- package/dist/aup-spec.cjs +1467 -0
- package/dist/aup-spec.mjs +1466 -0
- package/dist/aup-spec.mjs.map +1 -0
- package/dist/aup-types.cjs +165 -0
- package/dist/aup-types.d.cts +157 -0
- package/dist/aup-types.d.cts.map +1 -0
- package/dist/aup-types.d.mts +157 -0
- package/dist/aup-types.d.mts.map +1 -0
- package/dist/aup-types.mjs +157 -0
- package/dist/aup-types.mjs.map +1 -0
- package/dist/backend.cjs +14 -0
- package/dist/backend.d.cts +104 -0
- package/dist/backend.d.cts.map +1 -0
- package/dist/backend.d.mts +104 -0
- package/dist/backend.d.mts.map +1 -0
- package/dist/backend.mjs +13 -0
- package/dist/backend.mjs.map +1 -0
- package/dist/degradation.cjs +85 -0
- package/dist/degradation.d.cts +17 -0
- package/dist/degradation.d.cts.map +1 -0
- package/dist/degradation.d.mts +17 -0
- package/dist/degradation.d.mts.map +1 -0
- package/dist/degradation.mjs +84 -0
- package/dist/degradation.mjs.map +1 -0
- package/dist/index.cjs +36 -0
- package/dist/index.d.cts +12 -0
- package/dist/index.d.mts +12 -0
- package/dist/index.mjs +13 -0
- package/dist/runtime.cjs +117 -0
- package/dist/runtime.d.cts +59 -0
- package/dist/runtime.d.cts.map +1 -0
- package/dist/runtime.d.mts +59 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +118 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/session.cjs +159 -0
- package/dist/session.d.cts +80 -0
- package/dist/session.d.cts.map +1 -0
- package/dist/session.d.mts +80 -0
- package/dist/session.d.mts.map +1 -0
- package/dist/session.mjs +159 -0
- package/dist/session.mjs.map +1 -0
- package/dist/snapshot.cjs +162 -0
- package/dist/snapshot.mjs +163 -0
- package/dist/snapshot.mjs.map +1 -0
- package/dist/term-page.cjs +264 -0
- package/dist/term-page.mjs +264 -0
- package/dist/term-page.mjs.map +1 -0
- package/dist/term.cjs +295 -0
- package/dist/term.d.cts +84 -0
- package/dist/term.d.cts.map +1 -0
- package/dist/term.d.mts +84 -0
- package/dist/term.d.mts.map +1 -0
- package/dist/term.mjs +296 -0
- package/dist/term.mjs.map +1 -0
- package/dist/tty.cjs +136 -0
- package/dist/tty.d.cts +53 -0
- package/dist/tty.d.cts.map +1 -0
- package/dist/tty.d.mts +53 -0
- package/dist/tty.d.mts.map +1 -0
- package/dist/tty.mjs +135 -0
- package/dist/tty.mjs.map +1 -0
- package/dist/ui-provider.cjs +4615 -0
- package/dist/ui-provider.d.cts +307 -0
- package/dist/ui-provider.d.cts.map +1 -0
- package/dist/ui-provider.d.mts +307 -0
- package/dist/ui-provider.d.mts.map +1 -0
- package/dist/ui-provider.mjs +4616 -0
- package/dist/ui-provider.mjs.map +1 -0
- package/dist/web-page/core.cjs +1388 -0
- package/dist/web-page/core.mjs +1387 -0
- package/dist/web-page/core.mjs.map +1 -0
- package/dist/web-page/css.cjs +1699 -0
- package/dist/web-page/css.mjs +1698 -0
- package/dist/web-page/css.mjs.map +1 -0
- package/dist/web-page/icons.cjs +248 -0
- package/dist/web-page/icons.mjs +248 -0
- package/dist/web-page/icons.mjs.map +1 -0
- package/dist/web-page/overlay-themes.cjs +514 -0
- package/dist/web-page/overlay-themes.mjs +513 -0
- package/dist/web-page/overlay-themes.mjs.map +1 -0
- package/dist/web-page/renderers/action.cjs +72 -0
- package/dist/web-page/renderers/action.mjs +72 -0
- package/dist/web-page/renderers/action.mjs.map +1 -0
- package/dist/web-page/renderers/broadcast.cjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs +160 -0
- package/dist/web-page/renderers/broadcast.mjs.map +1 -0
- package/dist/web-page/renderers/calendar.cjs +137 -0
- package/dist/web-page/renderers/calendar.mjs +137 -0
- package/dist/web-page/renderers/calendar.mjs.map +1 -0
- package/dist/web-page/renderers/canvas.cjs +173 -0
- package/dist/web-page/renderers/canvas.mjs +173 -0
- package/dist/web-page/renderers/canvas.mjs.map +1 -0
- package/dist/web-page/renderers/cdn-loader.cjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs +25 -0
- package/dist/web-page/renderers/cdn-loader.mjs.map +1 -0
- package/dist/web-page/renderers/chart.cjs +101 -0
- package/dist/web-page/renderers/chart.mjs +101 -0
- package/dist/web-page/renderers/chart.mjs.map +1 -0
- package/dist/web-page/renderers/deck.cjs +390 -0
- package/dist/web-page/renderers/deck.mjs +390 -0
- package/dist/web-page/renderers/deck.mjs.map +1 -0
- package/dist/web-page/renderers/device.cjs +1015 -0
- package/dist/web-page/renderers/device.mjs +1015 -0
- package/dist/web-page/renderers/device.mjs.map +1 -0
- package/dist/web-page/renderers/editor.cjs +127 -0
- package/dist/web-page/renderers/editor.mjs +127 -0
- package/dist/web-page/renderers/editor.mjs.map +1 -0
- package/dist/web-page/renderers/finance-chart.cjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs +178 -0
- package/dist/web-page/renderers/finance-chart.mjs.map +1 -0
- package/dist/web-page/renderers/frame.cjs +274 -0
- package/dist/web-page/renderers/frame.mjs +274 -0
- package/dist/web-page/renderers/frame.mjs.map +1 -0
- package/dist/web-page/renderers/globe.cjs +119 -0
- package/dist/web-page/renderers/globe.mjs +119 -0
- package/dist/web-page/renderers/globe.mjs.map +1 -0
- package/dist/web-page/renderers/input.cjs +137 -0
- package/dist/web-page/renderers/input.mjs +137 -0
- package/dist/web-page/renderers/input.mjs.map +1 -0
- package/dist/web-page/renderers/list.cjs +1243 -0
- package/dist/web-page/renderers/list.mjs +1243 -0
- package/dist/web-page/renderers/list.mjs.map +1 -0
- package/dist/web-page/renderers/map.cjs +126 -0
- package/dist/web-page/renderers/map.mjs +126 -0
- package/dist/web-page/renderers/map.mjs.map +1 -0
- package/dist/web-page/renderers/media.cjs +106 -0
- package/dist/web-page/renderers/media.mjs +106 -0
- package/dist/web-page/renderers/media.mjs.map +1 -0
- package/dist/web-page/renderers/moonphase.cjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs +105 -0
- package/dist/web-page/renderers/moonphase.mjs.map +1 -0
- package/dist/web-page/renderers/natal-chart.cjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs +222 -0
- package/dist/web-page/renderers/natal-chart.mjs.map +1 -0
- package/dist/web-page/renderers/overlay.cjs +531 -0
- package/dist/web-page/renderers/overlay.mjs +531 -0
- package/dist/web-page/renderers/overlay.mjs.map +1 -0
- package/dist/web-page/renderers/table.cjs +74 -0
- package/dist/web-page/renderers/table.mjs +74 -0
- package/dist/web-page/renderers/table.mjs.map +1 -0
- package/dist/web-page/renderers/terminal.cjs +30 -0
- package/dist/web-page/renderers/terminal.mjs +30 -0
- package/dist/web-page/renderers/terminal.mjs.map +1 -0
- package/dist/web-page/renderers/text.cjs +109 -0
- package/dist/web-page/renderers/text.mjs +109 -0
- package/dist/web-page/renderers/text.mjs.map +1 -0
- package/dist/web-page/renderers/ticker.cjs +133 -0
- package/dist/web-page/renderers/ticker.mjs +133 -0
- package/dist/web-page/renderers/ticker.mjs.map +1 -0
- package/dist/web-page/renderers/time.cjs +69 -0
- package/dist/web-page/renderers/time.mjs +69 -0
- package/dist/web-page/renderers/time.mjs.map +1 -0
- package/dist/web-page/renderers/unknown.cjs +20 -0
- package/dist/web-page/renderers/unknown.mjs +20 -0
- package/dist/web-page/renderers/unknown.mjs.map +1 -0
- package/dist/web-page/renderers/view.cjs +161 -0
- package/dist/web-page/renderers/view.mjs +161 -0
- package/dist/web-page/renderers/view.mjs.map +1 -0
- package/dist/web-page/renderers/wm.cjs +669 -0
- package/dist/web-page/renderers/wm.mjs +669 -0
- package/dist/web-page/renderers/wm.mjs.map +1 -0
- package/dist/web-page/skeleton.cjs +103 -0
- package/dist/web-page/skeleton.mjs +103 -0
- package/dist/web-page/skeleton.mjs.map +1 -0
- package/dist/web-page.cjs +114 -0
- package/dist/web-page.d.cts +19 -0
- package/dist/web-page.d.cts.map +1 -0
- package/dist/web-page.d.mts +19 -0
- package/dist/web-page.d.mts.map +1 -0
- package/dist/web-page.mjs +115 -0
- package/dist/web-page.mjs.map +1 -0
- package/dist/web.cjs +827 -0
- package/dist/web.d.cts +144 -0
- package/dist/web.d.cts.map +1 -0
- package/dist/web.d.mts +144 -0
- package/dist/web.d.mts.map +1 -0
- package/dist/web.mjs +828 -0
- package/dist/web.mjs.map +1 -0
- package/dist/wm-state.cjs +172 -0
- package/dist/wm-state.mjs +171 -0
- package/dist/wm-state.mjs.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,4616 @@
|
|
|
1
|
+
import { DEVICE_CAPS_TERM, DEVICE_CAPS_TTY, DEVICE_CAPS_WEB_FULL, validateDeviceCaps, validateNode } from "./aup-types.mjs";
|
|
2
|
+
import { AUPNodeStore, AUPSceneManager } from "./aup-protocol.mjs";
|
|
3
|
+
import { isAUPTransport, isSessionAware } from "./backend.mjs";
|
|
4
|
+
import { degradeTree } from "./degradation.mjs";
|
|
5
|
+
import { SessionManager } from "./session.mjs";
|
|
6
|
+
import { TTYBackend } from "./tty.mjs";
|
|
7
|
+
import { TermBackend } from "./term.mjs";
|
|
8
|
+
import { OVERLAY_THEMES, PRIMITIVES, THEMES } from "./aup-registry.mjs";
|
|
9
|
+
import { AUP_EXAMPLES, AUP_SPEC } from "./aup-spec.mjs";
|
|
10
|
+
import { generateSnapshot } from "./snapshot.mjs";
|
|
11
|
+
import { WebBackend } from "./web.mjs";
|
|
12
|
+
import { PANEL_PRESETS, autoArrange } from "./wm-state.mjs";
|
|
13
|
+
import { __decorate } from "./_virtual/_@oxc-project_runtime@0.108.0/helpers/decorate.mjs";
|
|
14
|
+
import { joinURL } from "ufo";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { AFSNotFoundError } from "@aigne/afs";
|
|
18
|
+
import { AFSBaseProvider, Actions, Delete, Explain, List, Meta, Read, Stat, Write } from "@aigne/afs/provider";
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
|
|
21
|
+
//#region src/ui-provider.ts
|
|
22
|
+
const VALID_ACCESS_LEVELS = new Set([
|
|
23
|
+
"guest",
|
|
24
|
+
"link-only",
|
|
25
|
+
"user",
|
|
26
|
+
"admin"
|
|
27
|
+
]);
|
|
28
|
+
const VALID_SHARING_MODES = new Set(["static", "live"]);
|
|
29
|
+
const afsUISchema = z.object({
|
|
30
|
+
backend: z.enum([
|
|
31
|
+
"tty",
|
|
32
|
+
"web",
|
|
33
|
+
"term"
|
|
34
|
+
]).default("tty"),
|
|
35
|
+
port: z.coerce.number().optional()
|
|
36
|
+
});
|
|
37
|
+
var AFSUIProvider = class extends AFSBaseProvider {
|
|
38
|
+
name;
|
|
39
|
+
description;
|
|
40
|
+
accessMode = "readwrite";
|
|
41
|
+
backend;
|
|
42
|
+
inputTimeout;
|
|
43
|
+
pages = /* @__PURE__ */ new Map();
|
|
44
|
+
pagesDir;
|
|
45
|
+
initPromise = null;
|
|
46
|
+
serverUrl = null;
|
|
47
|
+
/** Web sharing entries: slug -> SharingEntry */
|
|
48
|
+
sharingEntries = /* @__PURE__ */ new Map();
|
|
49
|
+
/** Session management */
|
|
50
|
+
sessions = new SessionManager();
|
|
51
|
+
/** Per-session AUP node stores */
|
|
52
|
+
aupStores = /* @__PURE__ */ new Map();
|
|
53
|
+
/** Per-session AUP scene managers (for stage-to-live dual buffer) */
|
|
54
|
+
aupManagers = /* @__PURE__ */ new Map();
|
|
55
|
+
/** AFS root reference for auto-dispatching AUP events to afs.exec() */
|
|
56
|
+
afsRoot = null;
|
|
57
|
+
/** Endpoint type derived from backend (tty, web, term) */
|
|
58
|
+
endpointType;
|
|
59
|
+
/** External callback for AUP events — allows app code to handle navigation etc. */
|
|
60
|
+
onAupEvent;
|
|
61
|
+
constructor(options = {}) {
|
|
62
|
+
super();
|
|
63
|
+
this.name = options.name ?? "ui";
|
|
64
|
+
this.description = options.description ?? "Interactive UI device";
|
|
65
|
+
this.inputTimeout = options.inputTimeout ?? 0;
|
|
66
|
+
this.pagesDir = options.pagesDir ?? "";
|
|
67
|
+
if (typeof options.backend === "object") {
|
|
68
|
+
this.backend = options.backend;
|
|
69
|
+
if ("url" in this.backend && typeof this.backend.url === "string") this.serverUrl = this.backend.url;
|
|
70
|
+
} else if (options.backend === "web") {
|
|
71
|
+
const webOpts = { ...options.webOptions };
|
|
72
|
+
if (options.port != null) webOpts.port = options.port;
|
|
73
|
+
const web = new WebBackend(webOpts);
|
|
74
|
+
this.backend = web;
|
|
75
|
+
this.initPromise = web.listen().then(() => {
|
|
76
|
+
this.serverUrl = web.url;
|
|
77
|
+
});
|
|
78
|
+
} else if (options.backend === "term") {
|
|
79
|
+
const termOpts = { ...options.termOptions };
|
|
80
|
+
if (options.port != null) termOpts.port = options.port;
|
|
81
|
+
const term = new TermBackend(termOpts);
|
|
82
|
+
this.backend = term;
|
|
83
|
+
this.initPromise = term.listen().then(() => {
|
|
84
|
+
this.serverUrl = term.url;
|
|
85
|
+
});
|
|
86
|
+
} else this.backend = new TTYBackend({
|
|
87
|
+
...options.ttyOptions,
|
|
88
|
+
inputTimeout: this.inputTimeout
|
|
89
|
+
});
|
|
90
|
+
this.endpointType = this.backend.type;
|
|
91
|
+
if (isSessionAware(this.backend)) {
|
|
92
|
+
const defaultCaps = this.backend.type === "term" ? DEVICE_CAPS_TERM : DEVICE_CAPS_WEB_FULL;
|
|
93
|
+
this.backend.setSessionFactory((endpoint, requestedSid, requestedSessionToken, rawCaps) => {
|
|
94
|
+
const caps = rawCaps && !validateDeviceCaps(rawCaps) ? rawCaps : defaultCaps;
|
|
95
|
+
if (requestedSid && this.sessions.has(requestedSid)) {
|
|
96
|
+
const existing = this.sessions.get(requestedSid);
|
|
97
|
+
if (requestedSessionToken && requestedSessionToken === existing.resumeToken) {
|
|
98
|
+
existing.touch();
|
|
99
|
+
existing.setDeviceCaps(caps);
|
|
100
|
+
return {
|
|
101
|
+
sessionId: requestedSid,
|
|
102
|
+
sessionToken: existing.resumeToken
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const rotated = this.sessions.create(endpoint, caps);
|
|
106
|
+
return {
|
|
107
|
+
sessionId: rotated.id,
|
|
108
|
+
sessionToken: rotated.resumeToken
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const fresh = this.sessions.create(endpoint, caps);
|
|
112
|
+
return {
|
|
113
|
+
sessionId: fresh.id,
|
|
114
|
+
sessionToken: fresh.resumeToken
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (isAUPTransport(this.backend)) {
|
|
119
|
+
this.backend.setAupEventHandler(async (msg, sessionId, channelId) => {
|
|
120
|
+
const storeId = channelId ? this.channelAupKey(channelId) : sessionId;
|
|
121
|
+
if (!storeId) throw new Error("No session or channel for AUP event");
|
|
122
|
+
return this.handleAupEvent(storeId, msg.nodeId, msg.event, msg.data);
|
|
123
|
+
});
|
|
124
|
+
this.backend.setChannelJoinHandler((channelId, send) => {
|
|
125
|
+
const store = this.aupStores.get(this.channelAupKey(channelId));
|
|
126
|
+
const root = store?.getRoot();
|
|
127
|
+
if (store && root) {
|
|
128
|
+
const opts = store.renderOptions;
|
|
129
|
+
const msg = {
|
|
130
|
+
type: "aup",
|
|
131
|
+
action: "render",
|
|
132
|
+
root,
|
|
133
|
+
treeVersion: store.version
|
|
134
|
+
};
|
|
135
|
+
if (opts.fullPage) msg.fullPage = true;
|
|
136
|
+
if (opts.chrome) msg.chrome = true;
|
|
137
|
+
if (opts.theme) msg.theme = opts.theme;
|
|
138
|
+
if (opts.style) msg.style = opts.style;
|
|
139
|
+
if (opts.locale) msg.locale = opts.locale;
|
|
140
|
+
send(msg);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
this.backend.setSessionJoinHandler((sessionId, clientVersion, send) => {
|
|
144
|
+
const session = this.sessions.has(sessionId) ? this.sessions.get(sessionId) : null;
|
|
145
|
+
const activeScene = this.aupManagers.get(sessionId)?.getActiveScene();
|
|
146
|
+
if (activeScene) {
|
|
147
|
+
const { store: store$1 } = activeScene;
|
|
148
|
+
const root$1 = store$1.getRoot();
|
|
149
|
+
if (root$1) {
|
|
150
|
+
if (clientVersion > 0 && clientVersion >= store$1.version) return;
|
|
151
|
+
const degraded$1 = session ? this.degradeForSession(root$1, session) : root$1;
|
|
152
|
+
const opts$1 = store$1.renderOptions;
|
|
153
|
+
const msg$1 = {
|
|
154
|
+
type: "aup",
|
|
155
|
+
action: "stage",
|
|
156
|
+
sceneId: activeScene.sceneId,
|
|
157
|
+
root: degraded$1,
|
|
158
|
+
treeVersion: store$1.version
|
|
159
|
+
};
|
|
160
|
+
if (opts$1.fullPage) msg$1.fullPage = true;
|
|
161
|
+
if (opts$1.chrome) msg$1.chrome = true;
|
|
162
|
+
if (opts$1.theme) msg$1.theme = opts$1.theme;
|
|
163
|
+
if (opts$1.style) msg$1.style = opts$1.style;
|
|
164
|
+
if (opts$1.locale) msg$1.locale = opts$1.locale;
|
|
165
|
+
send(msg$1);
|
|
166
|
+
send({
|
|
167
|
+
type: "aup",
|
|
168
|
+
action: "take",
|
|
169
|
+
sceneId: activeScene.sceneId
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const store = this.aupStores.get(sessionId);
|
|
175
|
+
if (!store) return;
|
|
176
|
+
const root = store.getRoot();
|
|
177
|
+
if (!root) return;
|
|
178
|
+
if (clientVersion > 0 && clientVersion >= store.version) return;
|
|
179
|
+
const degraded = session ? this.degradeForSession(root, session) : root;
|
|
180
|
+
const opts = store.renderOptions;
|
|
181
|
+
const msg = {
|
|
182
|
+
type: "aup",
|
|
183
|
+
action: "render",
|
|
184
|
+
root: degraded,
|
|
185
|
+
treeVersion: store.version
|
|
186
|
+
};
|
|
187
|
+
if (opts.fullPage) msg.fullPage = true;
|
|
188
|
+
if (opts.chrome) msg.chrome = true;
|
|
189
|
+
if (opts.theme) msg.theme = opts.theme;
|
|
190
|
+
if (opts.style) msg.style = opts.style;
|
|
191
|
+
if (opts.locale) msg.locale = opts.locale;
|
|
192
|
+
send(msg);
|
|
193
|
+
});
|
|
194
|
+
this.backend.setPageResolver(async (pageId, sessionId, sessionToken) => {
|
|
195
|
+
if (sessionId && sessionToken && this.sessions.has(sessionId)) {
|
|
196
|
+
const session = this.sessions.get(sessionId);
|
|
197
|
+
if (session.resumeToken === sessionToken) {
|
|
198
|
+
const page$1 = session.getPage(pageId);
|
|
199
|
+
if (page$1) return {
|
|
200
|
+
content: page$1.content,
|
|
201
|
+
format: page$1.format
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const page = this.pages.get(pageId);
|
|
206
|
+
if (page) return {
|
|
207
|
+
content: page.content,
|
|
208
|
+
format: page.format
|
|
209
|
+
};
|
|
210
|
+
return null;
|
|
211
|
+
});
|
|
212
|
+
if ("setSnapshotResolver" in this.backend) this.backend.setSnapshotResolver((slug) => {
|
|
213
|
+
const entry = this.sharingEntries.get(slug);
|
|
214
|
+
if (!entry?.snapshot) return null;
|
|
215
|
+
return entry.snapshot;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (this.endpointType === "tty") this.sessions.create("tty", DEVICE_CAPS_TTY);
|
|
219
|
+
}
|
|
220
|
+
/** Inject AFS root into AUP transport for browser AFS proxy + discover pages dir. */
|
|
221
|
+
onMount(root, _mountPath) {
|
|
222
|
+
this.afsRoot = root;
|
|
223
|
+
if (isAUPTransport(this.backend)) this.backend.setAFS(root);
|
|
224
|
+
if (this.pagesDir !== false) this.discoverAndLoadPages(root);
|
|
225
|
+
}
|
|
226
|
+
/** Discover workspace path from AFS root meta, then load persisted pages. */
|
|
227
|
+
async discoverAndLoadPages(root) {
|
|
228
|
+
try {
|
|
229
|
+
if (this.pagesDir) {
|
|
230
|
+
this.loadPagesFromDisk(this.pagesDir);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (root.read) try {
|
|
234
|
+
const storagePath = (await root.read("/.meta"))?.data?.content?.storagePath;
|
|
235
|
+
if (storagePath) {
|
|
236
|
+
this.pagesDir = join(storagePath, ".afs-ui", "pages");
|
|
237
|
+
this.loadPagesFromDisk(this.pagesDir);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
} catch {}
|
|
241
|
+
this.pagesDir = join(process.cwd(), ".afs-ui", "pages");
|
|
242
|
+
this.loadPagesFromDisk(this.pagesDir);
|
|
243
|
+
} catch {}
|
|
244
|
+
}
|
|
245
|
+
/** Load persisted page files from disk into the pages Map. */
|
|
246
|
+
loadPagesFromDisk(dir) {
|
|
247
|
+
if (!existsSync(dir)) return;
|
|
248
|
+
for (const file of readdirSync(dir)) {
|
|
249
|
+
if (file.startsWith(".")) continue;
|
|
250
|
+
try {
|
|
251
|
+
const content = readFileSync(join(dir, file), "utf-8");
|
|
252
|
+
const now = Date.now();
|
|
253
|
+
this.pages.set(file, {
|
|
254
|
+
content,
|
|
255
|
+
format: "html",
|
|
256
|
+
createdAt: now,
|
|
257
|
+
updatedAt: now
|
|
258
|
+
});
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Persist a page to disk as a plain file (best-effort). */
|
|
263
|
+
persistPageToDisk(pageId, data) {
|
|
264
|
+
if (!this.pagesDir) return;
|
|
265
|
+
try {
|
|
266
|
+
if (!existsSync(this.pagesDir)) mkdirSync(this.pagesDir, { recursive: true });
|
|
267
|
+
writeFileSync(join(this.pagesDir, pageId), data.content, "utf-8");
|
|
268
|
+
} catch {}
|
|
269
|
+
}
|
|
270
|
+
/** Remove a persisted page from disk (best-effort). */
|
|
271
|
+
removePageFromDisk(pageId) {
|
|
272
|
+
if (!this.pagesDir) return;
|
|
273
|
+
try {
|
|
274
|
+
const filePath = join(this.pagesDir, pageId);
|
|
275
|
+
if (existsSync(filePath)) unlinkSync(filePath);
|
|
276
|
+
} catch {}
|
|
277
|
+
}
|
|
278
|
+
/** Assert the endpoint param matches this provider's type */
|
|
279
|
+
assertEndpoint(ctx) {
|
|
280
|
+
const endpoint = ctx.params.endpoint;
|
|
281
|
+
if (endpoint !== this.endpointType) throw new AFSNotFoundError(`/${endpoint}`);
|
|
282
|
+
return endpoint;
|
|
283
|
+
}
|
|
284
|
+
/** Resolve session from route params, throwing if not found */
|
|
285
|
+
resolveSession(ctx) {
|
|
286
|
+
this.assertEndpoint(ctx);
|
|
287
|
+
const sid = ctx.params.sid;
|
|
288
|
+
return this.sessions.get(sid);
|
|
289
|
+
}
|
|
290
|
+
/** Get or create AUP node store for a session */
|
|
291
|
+
getAupStore(sessionId) {
|
|
292
|
+
let store = this.aupStores.get(sessionId);
|
|
293
|
+
if (!store) {
|
|
294
|
+
store = new AUPNodeStore();
|
|
295
|
+
this.aupStores.set(sessionId, store);
|
|
296
|
+
}
|
|
297
|
+
return store;
|
|
298
|
+
}
|
|
299
|
+
/** Get or create AUP scene manager for a session */
|
|
300
|
+
getAupManager(sessionId) {
|
|
301
|
+
let mgr = this.aupManagers.get(sessionId);
|
|
302
|
+
if (!mgr) {
|
|
303
|
+
mgr = new AUPSceneManager();
|
|
304
|
+
this.aupManagers.set(sessionId, mgr);
|
|
305
|
+
}
|
|
306
|
+
return mgr;
|
|
307
|
+
}
|
|
308
|
+
/** Get the WM node from the AUP tree, or null if it doesn't exist yet. */
|
|
309
|
+
getWmNode(sessionId) {
|
|
310
|
+
const store = this.aupStores.get(sessionId);
|
|
311
|
+
if (!store) return null;
|
|
312
|
+
const wmNode = store.findNode("wm");
|
|
313
|
+
if (!wmNode) return null;
|
|
314
|
+
return {
|
|
315
|
+
store,
|
|
316
|
+
wmNode
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Ensure a WM node exists in the session's AUP tree.
|
|
321
|
+
* Creates a minimal root + wm node if the tree is empty.
|
|
322
|
+
*/
|
|
323
|
+
ensureWmNode(sessionId, strategy = "floating") {
|
|
324
|
+
const store = this.getAupStore(sessionId);
|
|
325
|
+
let wmNode = store.findNode("wm");
|
|
326
|
+
if (wmNode) return {
|
|
327
|
+
store,
|
|
328
|
+
wmNode
|
|
329
|
+
};
|
|
330
|
+
if (!store.getRoot()) {
|
|
331
|
+
store.setRoot({
|
|
332
|
+
id: "root",
|
|
333
|
+
type: "view",
|
|
334
|
+
children: [{
|
|
335
|
+
id: "wm",
|
|
336
|
+
type: "wm",
|
|
337
|
+
props: { strategy }
|
|
338
|
+
}]
|
|
339
|
+
});
|
|
340
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(sessionId, {
|
|
341
|
+
type: "aup",
|
|
342
|
+
action: "render",
|
|
343
|
+
root: store.getRoot(),
|
|
344
|
+
treeVersion: store.version,
|
|
345
|
+
fullPage: true
|
|
346
|
+
});
|
|
347
|
+
} else store.applyPatch([{
|
|
348
|
+
op: "create",
|
|
349
|
+
id: "wm",
|
|
350
|
+
parentId: store.getRoot().id,
|
|
351
|
+
node: {
|
|
352
|
+
id: "wm",
|
|
353
|
+
type: "wm",
|
|
354
|
+
props: { strategy }
|
|
355
|
+
}
|
|
356
|
+
}]);
|
|
357
|
+
wmNode = store.findNode("wm");
|
|
358
|
+
return {
|
|
359
|
+
store,
|
|
360
|
+
wmNode
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Apply WM-related AUP patch ops and notify the client.
|
|
365
|
+
*/
|
|
366
|
+
applyWmPatch(sessionId, ctx, ops) {
|
|
367
|
+
const store = this.getAupStore(sessionId);
|
|
368
|
+
store.applyPatch(ops);
|
|
369
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(sessionId, {
|
|
370
|
+
type: "aup",
|
|
371
|
+
action: "patch",
|
|
372
|
+
ops,
|
|
373
|
+
treeVersion: store.version
|
|
374
|
+
});
|
|
375
|
+
const endpoint = ctx.params.endpoint;
|
|
376
|
+
const treePath = joinURL("/", endpoint, "sessions", sessionId, "tree");
|
|
377
|
+
this.emit({
|
|
378
|
+
type: "afs:write",
|
|
379
|
+
path: treePath,
|
|
380
|
+
data: { root: store.getRoot() }
|
|
381
|
+
});
|
|
382
|
+
return store;
|
|
383
|
+
}
|
|
384
|
+
/** Find a WM surface child node by surface name. */
|
|
385
|
+
findWmSurface(wmNode, name) {
|
|
386
|
+
return (wmNode.children || []).find((c) => c.type === "wm-surface" && (c.props?.surfaceName === name || c.id === `wm-surface-${name}`));
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Handle built-in WM events from client-side panel interactions.
|
|
390
|
+
* Returns undefined if the event is not a WM-specific event.
|
|
391
|
+
*/
|
|
392
|
+
handleWmEvent(sessionId, event, data) {
|
|
393
|
+
const wm = this.getWmNode(sessionId);
|
|
394
|
+
if (!wm) return void 0;
|
|
395
|
+
const panels = (wm.wmNode.props ?? {}).panels ?? [];
|
|
396
|
+
if (event === "panel-collapse") {
|
|
397
|
+
const panelId = data.panelId;
|
|
398
|
+
if (!panelId) return void 0;
|
|
399
|
+
const updated = panels.map((p) => p.id === panelId ? {
|
|
400
|
+
...p,
|
|
401
|
+
collapsed: true
|
|
402
|
+
} : p);
|
|
403
|
+
this.applyWmPatchDirect(sessionId, [{
|
|
404
|
+
op: "update",
|
|
405
|
+
id: "wm",
|
|
406
|
+
props: { panels: updated }
|
|
407
|
+
}]);
|
|
408
|
+
return {
|
|
409
|
+
success: true,
|
|
410
|
+
panelId,
|
|
411
|
+
collapsed: true
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
if (event === "panel-expand") {
|
|
415
|
+
const panelId = data.panelId;
|
|
416
|
+
if (!panelId) return void 0;
|
|
417
|
+
const updated = panels.map((p) => p.id === panelId ? {
|
|
418
|
+
...p,
|
|
419
|
+
collapsed: false
|
|
420
|
+
} : p);
|
|
421
|
+
this.applyWmPatchDirect(sessionId, [{
|
|
422
|
+
op: "update",
|
|
423
|
+
id: "wm",
|
|
424
|
+
props: { panels: updated }
|
|
425
|
+
}]);
|
|
426
|
+
return {
|
|
427
|
+
success: true,
|
|
428
|
+
panelId,
|
|
429
|
+
collapsed: false
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (event === "panel-resize") {
|
|
433
|
+
const panelId = data.panelId;
|
|
434
|
+
const size = data.size;
|
|
435
|
+
if (!panelId || !size) return void 0;
|
|
436
|
+
const updated = panels.map((p) => p.id === panelId ? {
|
|
437
|
+
...p,
|
|
438
|
+
defaultSize: size
|
|
439
|
+
} : p);
|
|
440
|
+
this.applyWmPatchDirect(sessionId, [{
|
|
441
|
+
op: "update",
|
|
442
|
+
id: "wm",
|
|
443
|
+
props: { panels: updated }
|
|
444
|
+
}]);
|
|
445
|
+
return {
|
|
446
|
+
success: true,
|
|
447
|
+
panelId,
|
|
448
|
+
size
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Apply WM patch directly without needing a RouteContext.
|
|
454
|
+
* Used for client-initiated WM events.
|
|
455
|
+
*/
|
|
456
|
+
applyWmPatchDirect(sessionId, ops) {
|
|
457
|
+
const store = this.getAupStore(sessionId);
|
|
458
|
+
store.applyPatch(ops);
|
|
459
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(sessionId, {
|
|
460
|
+
type: "aup",
|
|
461
|
+
action: "patch",
|
|
462
|
+
ops,
|
|
463
|
+
treeVersion: store.version
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/** AUP store key for a live channel */
|
|
467
|
+
channelAupKey(channelId) {
|
|
468
|
+
return `live:${channelId}`;
|
|
469
|
+
}
|
|
470
|
+
/** Get or create AUP node store for a live channel */
|
|
471
|
+
getChannelAupStore(channelId) {
|
|
472
|
+
const key = this.channelAupKey(channelId);
|
|
473
|
+
let store = this.aupStores.get(key);
|
|
474
|
+
if (!store) {
|
|
475
|
+
store = new AUPNodeStore();
|
|
476
|
+
this.aupStores.set(key, store);
|
|
477
|
+
}
|
|
478
|
+
return store;
|
|
479
|
+
}
|
|
480
|
+
/** Degrade an AUP root for a session's device capabilities (D14). */
|
|
481
|
+
degradeForSession(root, session) {
|
|
482
|
+
return degradeTree(root, session.deviceCaps);
|
|
483
|
+
}
|
|
484
|
+
/** Count active live channels (stores with live: prefix that have content) */
|
|
485
|
+
getActiveChannelCount() {
|
|
486
|
+
let count = 0;
|
|
487
|
+
for (const key of this.aupStores.keys()) if (key.startsWith("live:")) count++;
|
|
488
|
+
return count;
|
|
489
|
+
}
|
|
490
|
+
/** List active channel IDs */
|
|
491
|
+
getActiveChannelIds() {
|
|
492
|
+
const ids = [];
|
|
493
|
+
for (const key of this.aupStores.keys()) if (key.startsWith("live:")) ids.push(key.slice(5));
|
|
494
|
+
if (isAUPTransport(this.backend)) {
|
|
495
|
+
for (const id of this.backend.getActiveChannelIds()) if (!ids.includes(id)) ids.push(id);
|
|
496
|
+
}
|
|
497
|
+
return ids;
|
|
498
|
+
}
|
|
499
|
+
/** Handle an AUP event from a client — resolve exec path and call it */
|
|
500
|
+
async handleAupEvent(sessionId, nodeId, event, data) {
|
|
501
|
+
const node = this.getAupStore(sessionId).findNode(nodeId);
|
|
502
|
+
if (!node) throw new Error(`AUP node not found: ${nodeId}`);
|
|
503
|
+
if (node.type === "wm") {
|
|
504
|
+
const wmResult = this.handleWmEvent(sessionId, event, data ?? {});
|
|
505
|
+
if (wmResult !== void 0) return wmResult;
|
|
506
|
+
}
|
|
507
|
+
const evtConfig = node.events?.[event];
|
|
508
|
+
if (!evtConfig) {
|
|
509
|
+
if (this.onAupEvent) {
|
|
510
|
+
const config$1 = {
|
|
511
|
+
exec: event,
|
|
512
|
+
args: data ?? {}
|
|
513
|
+
};
|
|
514
|
+
const result = await this.onAupEvent(sessionId, nodeId, event, config$1);
|
|
515
|
+
if (result !== void 0) return result;
|
|
516
|
+
}
|
|
517
|
+
if (!node.events) throw new Error(`Node '${nodeId}' has no events`);
|
|
518
|
+
throw new Error(`Node '${nodeId}' has no '${event}' event`);
|
|
519
|
+
}
|
|
520
|
+
const execPath = evtConfig.exec;
|
|
521
|
+
if (!execPath || typeof execPath !== "string") throw new Error("Event exec path is required");
|
|
522
|
+
if (execPath.includes("..")) throw new Error("Event exec path cannot contain '..'");
|
|
523
|
+
const config = {
|
|
524
|
+
exec: execPath,
|
|
525
|
+
args: {
|
|
526
|
+
...evtConfig.args ?? {},
|
|
527
|
+
...data ?? {}
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (this.onAupEvent) {
|
|
531
|
+
const result = await this.onAupEvent(sessionId, nodeId, event, config);
|
|
532
|
+
if (result !== void 0) return result;
|
|
533
|
+
}
|
|
534
|
+
if (this.afsRoot?.exec) return (await this.afsRoot.exec(config.exec, config.args, {}))?.data;
|
|
535
|
+
return {
|
|
536
|
+
...config,
|
|
537
|
+
nodeId,
|
|
538
|
+
event
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
static schema() {
|
|
542
|
+
return afsUISchema;
|
|
543
|
+
}
|
|
544
|
+
static manifest() {
|
|
545
|
+
return {
|
|
546
|
+
name: "ui",
|
|
547
|
+
description: "Interactive UI device — terminal (xterm.js), web chat, or messaging",
|
|
548
|
+
uriTemplate: "ui://{backend}",
|
|
549
|
+
category: "device",
|
|
550
|
+
schema: afsUISchema,
|
|
551
|
+
tags: [
|
|
552
|
+
"ui",
|
|
553
|
+
"device",
|
|
554
|
+
"interactive",
|
|
555
|
+
"term"
|
|
556
|
+
],
|
|
557
|
+
capabilityTags: [
|
|
558
|
+
"read-write",
|
|
559
|
+
"auth:none",
|
|
560
|
+
"local"
|
|
561
|
+
],
|
|
562
|
+
security: {
|
|
563
|
+
riskLevel: "local",
|
|
564
|
+
resourceAccess: ["local-network"],
|
|
565
|
+
notes: ["Interacts with local terminal, web, or messaging backend"]
|
|
566
|
+
},
|
|
567
|
+
capabilities: { network: {
|
|
568
|
+
egress: true,
|
|
569
|
+
ingress: true
|
|
570
|
+
} }
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
static treeSchema() {
|
|
574
|
+
return {
|
|
575
|
+
operations: [
|
|
576
|
+
"list",
|
|
577
|
+
"read",
|
|
578
|
+
"write",
|
|
579
|
+
"delete",
|
|
580
|
+
"exec",
|
|
581
|
+
"stat",
|
|
582
|
+
"explain"
|
|
583
|
+
],
|
|
584
|
+
tree: {
|
|
585
|
+
"/": {
|
|
586
|
+
kind: "device",
|
|
587
|
+
operations: [
|
|
588
|
+
"list",
|
|
589
|
+
"read",
|
|
590
|
+
"exec"
|
|
591
|
+
],
|
|
592
|
+
actions: [
|
|
593
|
+
"prompt",
|
|
594
|
+
"clear",
|
|
595
|
+
"notify",
|
|
596
|
+
"navigate",
|
|
597
|
+
"dialog",
|
|
598
|
+
"progress",
|
|
599
|
+
"form",
|
|
600
|
+
"table",
|
|
601
|
+
"toast"
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
"/input": {
|
|
605
|
+
kind: "input-channel",
|
|
606
|
+
operations: ["read"]
|
|
607
|
+
},
|
|
608
|
+
"/output": {
|
|
609
|
+
kind: "output-channel",
|
|
610
|
+
operations: ["read", "write"]
|
|
611
|
+
},
|
|
612
|
+
"/pages": {
|
|
613
|
+
kind: "pages-directory",
|
|
614
|
+
operations: ["list", "read"]
|
|
615
|
+
},
|
|
616
|
+
"/pages/{id}": {
|
|
617
|
+
kind: "page",
|
|
618
|
+
operations: [
|
|
619
|
+
"read",
|
|
620
|
+
"write",
|
|
621
|
+
"delete"
|
|
622
|
+
]
|
|
623
|
+
}
|
|
624
|
+
},
|
|
625
|
+
auth: { type: "none" },
|
|
626
|
+
bestFor: [
|
|
627
|
+
"user interaction",
|
|
628
|
+
"terminal UI",
|
|
629
|
+
"web chat"
|
|
630
|
+
],
|
|
631
|
+
notFor: ["data storage"]
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
async readCapabilities(_ctx) {
|
|
635
|
+
return {
|
|
636
|
+
id: "/.meta/.capabilities",
|
|
637
|
+
path: "/.meta/.capabilities",
|
|
638
|
+
content: {
|
|
639
|
+
schemaVersion: 1,
|
|
640
|
+
provider: this.name,
|
|
641
|
+
description: this.description,
|
|
642
|
+
tools: [],
|
|
643
|
+
actions: [],
|
|
644
|
+
operations: this.getOperationsDeclaration()
|
|
645
|
+
},
|
|
646
|
+
meta: { kind: "afs:capabilities" }
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
async metaRoot(_ctx) {
|
|
650
|
+
if (this.initPromise) await this.initPromise;
|
|
651
|
+
const viewport = this.backend.getViewport();
|
|
652
|
+
const meta = {
|
|
653
|
+
kind: "device",
|
|
654
|
+
backend: this.backend.type,
|
|
655
|
+
supportedFormats: this.backend.supportedFormats,
|
|
656
|
+
capabilities: this.backend.capabilities,
|
|
657
|
+
viewport,
|
|
658
|
+
childrenCount: 10
|
|
659
|
+
};
|
|
660
|
+
if (this.serverUrl) meta.url = this.serverUrl;
|
|
661
|
+
return {
|
|
662
|
+
id: ".meta",
|
|
663
|
+
path: "/.meta",
|
|
664
|
+
meta
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
async metaInput(_ctx) {
|
|
668
|
+
return {
|
|
669
|
+
id: ".meta",
|
|
670
|
+
path: "/input/.meta",
|
|
671
|
+
meta: {
|
|
672
|
+
kind: "input-channel",
|
|
673
|
+
childrenCount: 0,
|
|
674
|
+
description: "Read user input from this device",
|
|
675
|
+
pending: this.backend.hasPendingInput()
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
async metaOutput(_ctx) {
|
|
680
|
+
return {
|
|
681
|
+
id: ".meta",
|
|
682
|
+
path: "/output/.meta",
|
|
683
|
+
meta: {
|
|
684
|
+
kind: "output-channel",
|
|
685
|
+
childrenCount: 0,
|
|
686
|
+
description: "Write content to this device"
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
async metaPages(_ctx) {
|
|
691
|
+
return {
|
|
692
|
+
id: ".meta",
|
|
693
|
+
path: "/pages/.meta",
|
|
694
|
+
meta: {
|
|
695
|
+
kind: "pages-directory",
|
|
696
|
+
childrenCount: this.pages.size,
|
|
697
|
+
description: "Managed pages for full-page rendering"
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
async metaPage(ctx) {
|
|
702
|
+
const pageId = ctx.params.id;
|
|
703
|
+
const page = this.pages.get(pageId);
|
|
704
|
+
if (!page) throw new Error(`Page not found: ${pageId}`);
|
|
705
|
+
return {
|
|
706
|
+
id: ".meta",
|
|
707
|
+
path: joinURL("/pages", pageId, ".meta"),
|
|
708
|
+
meta: {
|
|
709
|
+
kind: "page",
|
|
710
|
+
format: page.format,
|
|
711
|
+
childrenCount: 0,
|
|
712
|
+
createdAt: page.createdAt,
|
|
713
|
+
updatedAt: page.updatedAt
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
async listInput(_ctx) {
|
|
718
|
+
return { data: [] };
|
|
719
|
+
}
|
|
720
|
+
async listOutput(_ctx) {
|
|
721
|
+
return { data: [] };
|
|
722
|
+
}
|
|
723
|
+
async listPages(_ctx) {
|
|
724
|
+
const entries = [];
|
|
725
|
+
for (const [id, page] of this.pages) entries.push({
|
|
726
|
+
id,
|
|
727
|
+
path: joinURL("/pages", id),
|
|
728
|
+
meta: {
|
|
729
|
+
kind: "page",
|
|
730
|
+
format: page.format,
|
|
731
|
+
childrenCount: 0,
|
|
732
|
+
createdAt: page.createdAt,
|
|
733
|
+
updatedAt: page.updatedAt
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
return { data: entries };
|
|
737
|
+
}
|
|
738
|
+
async listRoot(_ctx) {
|
|
739
|
+
return { data: [
|
|
740
|
+
{
|
|
741
|
+
id: "spec",
|
|
742
|
+
path: "/spec",
|
|
743
|
+
meta: {
|
|
744
|
+
kind: "aup:spec",
|
|
745
|
+
description: "AUP document format specification — start here"
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
id: "primitives",
|
|
750
|
+
path: "/primitives",
|
|
751
|
+
meta: {
|
|
752
|
+
kind: "primitives-directory",
|
|
753
|
+
childrenCount: Object.keys(PRIMITIVES).length,
|
|
754
|
+
description: "Available UI components with full props, events, and examples"
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
id: "themes",
|
|
759
|
+
path: "/themes",
|
|
760
|
+
meta: {
|
|
761
|
+
kind: "themes-directory",
|
|
762
|
+
childrenCount: Object.keys(THEMES).length,
|
|
763
|
+
description: "Visual themes with color tokens"
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
id: "overlay-themes",
|
|
768
|
+
path: "/overlay-themes",
|
|
769
|
+
meta: {
|
|
770
|
+
kind: "overlay-themes-directory",
|
|
771
|
+
childrenCount: Object.keys(OVERLAY_THEMES).length,
|
|
772
|
+
description: "Broadcast overlay themes (graphics packages) for overlay-grid"
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
id: "examples",
|
|
777
|
+
path: "/examples",
|
|
778
|
+
meta: {
|
|
779
|
+
kind: "examples-directory",
|
|
780
|
+
childrenCount: Object.keys(AUP_EXAMPLES).length,
|
|
781
|
+
description: "Complete working AUP documents to learn from"
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: this.endpointType,
|
|
786
|
+
path: joinURL("/", this.endpointType),
|
|
787
|
+
meta: {
|
|
788
|
+
kind: "endpoint",
|
|
789
|
+
childrenCount: 1,
|
|
790
|
+
description: `${this.endpointType} endpoint with session management`
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
id: "input",
|
|
795
|
+
path: "/input",
|
|
796
|
+
meta: {
|
|
797
|
+
kind: "input-channel",
|
|
798
|
+
childrenCount: 0,
|
|
799
|
+
description: "Read user input (legacy — use sessions/:id/messages instead)"
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
{
|
|
803
|
+
id: "output",
|
|
804
|
+
path: "/output",
|
|
805
|
+
meta: {
|
|
806
|
+
kind: "output-channel",
|
|
807
|
+
childrenCount: 0,
|
|
808
|
+
description: "Write content (legacy — use sessions/:id/messages instead)"
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
id: "pages",
|
|
813
|
+
path: "/pages",
|
|
814
|
+
meta: {
|
|
815
|
+
kind: "pages-directory",
|
|
816
|
+
childrenCount: this.pages.size,
|
|
817
|
+
description: "Managed pages (legacy — use sessions/:id/pages instead)"
|
|
818
|
+
}
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
id: "sharing",
|
|
822
|
+
path: "/sharing",
|
|
823
|
+
meta: {
|
|
824
|
+
kind: "sharing-directory",
|
|
825
|
+
childrenCount: this.sharingEntries.size,
|
|
826
|
+
description: "Web sharing entries — publish AFS subtrees as public URLs"
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
] };
|
|
830
|
+
}
|
|
831
|
+
async readRoot(_ctx) {
|
|
832
|
+
return {
|
|
833
|
+
id: this.name,
|
|
834
|
+
path: "/",
|
|
835
|
+
content: `UI Device (${this.backend.type})`,
|
|
836
|
+
meta: {
|
|
837
|
+
kind: "device",
|
|
838
|
+
childrenCount: 10,
|
|
839
|
+
backend: this.backend.type
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
async readInput(_ctx) {
|
|
844
|
+
return {
|
|
845
|
+
id: "input",
|
|
846
|
+
path: "/input",
|
|
847
|
+
content: await this.backend.read({ timeout: this.inputTimeout })
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
async readOutput(_ctx) {
|
|
851
|
+
return {
|
|
852
|
+
id: "output",
|
|
853
|
+
path: "/output",
|
|
854
|
+
content: "",
|
|
855
|
+
meta: { kind: "output-channel" }
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
async readPages(_ctx) {
|
|
859
|
+
return {
|
|
860
|
+
id: "pages",
|
|
861
|
+
path: "/pages",
|
|
862
|
+
content: "",
|
|
863
|
+
meta: {
|
|
864
|
+
kind: "pages-directory",
|
|
865
|
+
childrenCount: this.pages.size
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
async readPage(ctx) {
|
|
870
|
+
const pageId = ctx.params.id;
|
|
871
|
+
const page = this.pages.get(pageId);
|
|
872
|
+
if (!page) throw new Error(`Page not found: ${pageId}`);
|
|
873
|
+
return {
|
|
874
|
+
id: pageId,
|
|
875
|
+
path: joinURL("/pages", pageId),
|
|
876
|
+
content: page.content,
|
|
877
|
+
meta: {
|
|
878
|
+
kind: "page",
|
|
879
|
+
format: page.format,
|
|
880
|
+
layout: page.layout
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
async listPrimitives(_ctx) {
|
|
885
|
+
return { data: Object.values(PRIMITIVES).map((p) => ({
|
|
886
|
+
id: p.name,
|
|
887
|
+
name: p.name,
|
|
888
|
+
path: joinURL("/primitives", p.name),
|
|
889
|
+
meta: {
|
|
890
|
+
kind: "aup:primitive",
|
|
891
|
+
category: p.category,
|
|
892
|
+
description: p.description
|
|
893
|
+
}
|
|
894
|
+
})) };
|
|
895
|
+
}
|
|
896
|
+
async readPrimitivesDir(_ctx) {
|
|
897
|
+
return {
|
|
898
|
+
id: "primitives",
|
|
899
|
+
path: "/primitives",
|
|
900
|
+
content: `AUP Primitive Registry (${Object.keys(PRIMITIVES).length} primitives)`,
|
|
901
|
+
meta: {
|
|
902
|
+
kind: "primitives-directory",
|
|
903
|
+
childrenCount: Object.keys(PRIMITIVES).length
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
async statPrimitivesDir(_ctx) {
|
|
908
|
+
return { data: {
|
|
909
|
+
id: "primitives",
|
|
910
|
+
path: "/primitives",
|
|
911
|
+
meta: {
|
|
912
|
+
kind: "primitives-directory",
|
|
913
|
+
childrenCount: Object.keys(PRIMITIVES).length
|
|
914
|
+
}
|
|
915
|
+
} };
|
|
916
|
+
}
|
|
917
|
+
async metaPrimitivesDir(_ctx) {
|
|
918
|
+
return {
|
|
919
|
+
id: "primitives",
|
|
920
|
+
path: "/primitives/.meta",
|
|
921
|
+
meta: {
|
|
922
|
+
kind: "primitives-directory",
|
|
923
|
+
childrenCount: Object.keys(PRIMITIVES).length
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
async readPrimitive(ctx) {
|
|
928
|
+
const name = ctx.params.name;
|
|
929
|
+
const prim = PRIMITIVES[name];
|
|
930
|
+
if (!prim) throw new AFSNotFoundError(joinURL("/primitives", name));
|
|
931
|
+
return {
|
|
932
|
+
id: prim.name,
|
|
933
|
+
path: joinURL("/primitives", prim.name),
|
|
934
|
+
content: prim,
|
|
935
|
+
meta: {
|
|
936
|
+
kind: "aup:primitive",
|
|
937
|
+
category: prim.category,
|
|
938
|
+
description: prim.description
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
async statPrimitive(ctx) {
|
|
943
|
+
const name = ctx.params.name;
|
|
944
|
+
const prim = PRIMITIVES[name];
|
|
945
|
+
if (!prim) throw new AFSNotFoundError(joinURL("/primitives", name));
|
|
946
|
+
return { data: {
|
|
947
|
+
id: prim.name,
|
|
948
|
+
path: joinURL("/primitives", prim.name),
|
|
949
|
+
meta: {
|
|
950
|
+
kind: "aup:primitive",
|
|
951
|
+
category: prim.category,
|
|
952
|
+
description: prim.description
|
|
953
|
+
}
|
|
954
|
+
} };
|
|
955
|
+
}
|
|
956
|
+
async metaPrimitive(ctx) {
|
|
957
|
+
const name = ctx.params.name;
|
|
958
|
+
const prim = PRIMITIVES[name];
|
|
959
|
+
if (!prim) throw new AFSNotFoundError(joinURL("/primitives", name));
|
|
960
|
+
return {
|
|
961
|
+
id: prim.name,
|
|
962
|
+
path: joinURL("/primitives", prim.name, ".meta"),
|
|
963
|
+
meta: {
|
|
964
|
+
kind: "aup:primitive",
|
|
965
|
+
category: prim.category,
|
|
966
|
+
description: prim.description
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
async listThemes(_ctx) {
|
|
971
|
+
return { data: Object.values(THEMES).map((t) => ({
|
|
972
|
+
id: t.name,
|
|
973
|
+
name: t.name,
|
|
974
|
+
path: joinURL("/themes", t.name),
|
|
975
|
+
meta: {
|
|
976
|
+
kind: "aup:theme",
|
|
977
|
+
description: t.description
|
|
978
|
+
}
|
|
979
|
+
})) };
|
|
980
|
+
}
|
|
981
|
+
async readThemesDir(_ctx) {
|
|
982
|
+
return {
|
|
983
|
+
id: "themes",
|
|
984
|
+
path: "/themes",
|
|
985
|
+
content: `AUP Theme Registry (${Object.keys(THEMES).length} themes)`,
|
|
986
|
+
meta: {
|
|
987
|
+
kind: "themes-directory",
|
|
988
|
+
childrenCount: Object.keys(THEMES).length
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
async statThemesDir(_ctx) {
|
|
993
|
+
return { data: {
|
|
994
|
+
id: "themes",
|
|
995
|
+
path: "/themes",
|
|
996
|
+
meta: {
|
|
997
|
+
kind: "themes-directory",
|
|
998
|
+
childrenCount: Object.keys(THEMES).length
|
|
999
|
+
}
|
|
1000
|
+
} };
|
|
1001
|
+
}
|
|
1002
|
+
async metaThemesDir(_ctx) {
|
|
1003
|
+
return {
|
|
1004
|
+
id: "themes",
|
|
1005
|
+
path: "/themes/.meta",
|
|
1006
|
+
meta: {
|
|
1007
|
+
kind: "themes-directory",
|
|
1008
|
+
childrenCount: Object.keys(THEMES).length
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
async readTheme(ctx) {
|
|
1013
|
+
const name = ctx.params.name;
|
|
1014
|
+
const theme = THEMES[name];
|
|
1015
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/themes", name));
|
|
1016
|
+
return {
|
|
1017
|
+
id: theme.name,
|
|
1018
|
+
path: joinURL("/themes", theme.name),
|
|
1019
|
+
content: theme,
|
|
1020
|
+
meta: {
|
|
1021
|
+
kind: "aup:theme",
|
|
1022
|
+
description: theme.description
|
|
1023
|
+
}
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
async statTheme(ctx) {
|
|
1027
|
+
const name = ctx.params.name;
|
|
1028
|
+
const theme = THEMES[name];
|
|
1029
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/themes", name));
|
|
1030
|
+
return { data: {
|
|
1031
|
+
id: theme.name,
|
|
1032
|
+
path: joinURL("/themes", theme.name),
|
|
1033
|
+
meta: {
|
|
1034
|
+
kind: "aup:theme",
|
|
1035
|
+
description: theme.description
|
|
1036
|
+
}
|
|
1037
|
+
} };
|
|
1038
|
+
}
|
|
1039
|
+
async metaTheme(ctx) {
|
|
1040
|
+
const name = ctx.params.name;
|
|
1041
|
+
const theme = THEMES[name];
|
|
1042
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/themes", name));
|
|
1043
|
+
return {
|
|
1044
|
+
id: theme.name,
|
|
1045
|
+
path: joinURL("/themes", theme.name, ".meta"),
|
|
1046
|
+
meta: {
|
|
1047
|
+
kind: "aup:theme",
|
|
1048
|
+
description: theme.description
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
async listOverlayThemes(_ctx) {
|
|
1053
|
+
return { data: Object.values(OVERLAY_THEMES).map((t) => ({
|
|
1054
|
+
id: t.name,
|
|
1055
|
+
name: t.name,
|
|
1056
|
+
path: joinURL("/overlay-themes", t.name),
|
|
1057
|
+
meta: {
|
|
1058
|
+
kind: "aup:overlay-theme",
|
|
1059
|
+
description: t.description
|
|
1060
|
+
}
|
|
1061
|
+
})) };
|
|
1062
|
+
}
|
|
1063
|
+
async readOverlayThemesDir(_ctx) {
|
|
1064
|
+
return {
|
|
1065
|
+
id: "overlay-themes",
|
|
1066
|
+
path: "/overlay-themes",
|
|
1067
|
+
content: `AUP Overlay Theme Registry (${Object.keys(OVERLAY_THEMES).length} themes)\n\nBroadcast "graphics packages" for overlay-grid layouts. Apply via theme prop on view with layout: "overlay-grid".\n\nAvailable: ${Object.keys(OVERLAY_THEMES).join(", ")}`,
|
|
1068
|
+
meta: {
|
|
1069
|
+
kind: "overlay-themes-directory",
|
|
1070
|
+
childrenCount: Object.keys(OVERLAY_THEMES).length
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
async statOverlayThemesDir(_ctx) {
|
|
1075
|
+
return { data: {
|
|
1076
|
+
id: "overlay-themes",
|
|
1077
|
+
path: "/overlay-themes",
|
|
1078
|
+
meta: {
|
|
1079
|
+
kind: "overlay-themes-directory",
|
|
1080
|
+
childrenCount: Object.keys(OVERLAY_THEMES).length
|
|
1081
|
+
}
|
|
1082
|
+
} };
|
|
1083
|
+
}
|
|
1084
|
+
async metaOverlayThemesDir(_ctx) {
|
|
1085
|
+
return {
|
|
1086
|
+
id: "overlay-themes",
|
|
1087
|
+
path: "/overlay-themes/.meta",
|
|
1088
|
+
meta: {
|
|
1089
|
+
kind: "overlay-themes-directory",
|
|
1090
|
+
childrenCount: Object.keys(OVERLAY_THEMES).length
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
async readOverlayTheme(ctx) {
|
|
1095
|
+
const name = ctx.params.name;
|
|
1096
|
+
const theme = OVERLAY_THEMES[name];
|
|
1097
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/overlay-themes", name));
|
|
1098
|
+
return {
|
|
1099
|
+
id: theme.name,
|
|
1100
|
+
path: joinURL("/overlay-themes", theme.name),
|
|
1101
|
+
content: theme,
|
|
1102
|
+
meta: {
|
|
1103
|
+
kind: "aup:overlay-theme",
|
|
1104
|
+
description: theme.description
|
|
1105
|
+
}
|
|
1106
|
+
};
|
|
1107
|
+
}
|
|
1108
|
+
async statOverlayTheme(ctx) {
|
|
1109
|
+
const name = ctx.params.name;
|
|
1110
|
+
const theme = OVERLAY_THEMES[name];
|
|
1111
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/overlay-themes", name));
|
|
1112
|
+
return { data: {
|
|
1113
|
+
id: theme.name,
|
|
1114
|
+
path: joinURL("/overlay-themes", theme.name),
|
|
1115
|
+
meta: {
|
|
1116
|
+
kind: "aup:overlay-theme",
|
|
1117
|
+
description: theme.description
|
|
1118
|
+
}
|
|
1119
|
+
} };
|
|
1120
|
+
}
|
|
1121
|
+
async metaOverlayTheme(ctx) {
|
|
1122
|
+
const name = ctx.params.name;
|
|
1123
|
+
const theme = OVERLAY_THEMES[name];
|
|
1124
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/overlay-themes", name));
|
|
1125
|
+
return {
|
|
1126
|
+
id: theme.name,
|
|
1127
|
+
path: joinURL("/overlay-themes", theme.name, ".meta"),
|
|
1128
|
+
meta: {
|
|
1129
|
+
kind: "aup:overlay-theme",
|
|
1130
|
+
description: theme.description
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
async listOverlayExamples(ctx) {
|
|
1135
|
+
const name = ctx.params.name;
|
|
1136
|
+
const theme = OVERLAY_THEMES[name];
|
|
1137
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/overlay-themes", name));
|
|
1138
|
+
return { data: (theme.examples || []).map((ex) => ({
|
|
1139
|
+
id: ex.name,
|
|
1140
|
+
name: ex.name,
|
|
1141
|
+
path: joinURL("/overlay-themes", name, "examples", ex.name),
|
|
1142
|
+
meta: {
|
|
1143
|
+
kind: "aup:overlay-example",
|
|
1144
|
+
description: ex.description
|
|
1145
|
+
}
|
|
1146
|
+
})) };
|
|
1147
|
+
}
|
|
1148
|
+
async readOverlayExample(ctx) {
|
|
1149
|
+
const name = ctx.params.name;
|
|
1150
|
+
const exName = ctx.params.example;
|
|
1151
|
+
const theme = OVERLAY_THEMES[name];
|
|
1152
|
+
if (!theme) throw new AFSNotFoundError(joinURL("/overlay-themes", name));
|
|
1153
|
+
const ex = (theme.examples || []).find((e) => e.name === exName);
|
|
1154
|
+
if (!ex) throw new AFSNotFoundError(joinURL("/overlay-themes", name, "examples", exName));
|
|
1155
|
+
return {
|
|
1156
|
+
id: ex.name,
|
|
1157
|
+
path: joinURL("/overlay-themes", name, "examples", ex.name),
|
|
1158
|
+
content: ex.tree,
|
|
1159
|
+
meta: {
|
|
1160
|
+
kind: "aup:overlay-example",
|
|
1161
|
+
description: ex.description
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
async readSpec(_ctx) {
|
|
1166
|
+
return {
|
|
1167
|
+
id: "spec",
|
|
1168
|
+
path: "/spec",
|
|
1169
|
+
content: AUP_SPEC,
|
|
1170
|
+
meta: {
|
|
1171
|
+
kind: "aup:spec",
|
|
1172
|
+
version: AUP_SPEC.version
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
async statSpec(_ctx) {
|
|
1177
|
+
return { data: {
|
|
1178
|
+
id: "spec",
|
|
1179
|
+
path: "/spec",
|
|
1180
|
+
meta: {
|
|
1181
|
+
kind: "aup:spec",
|
|
1182
|
+
version: AUP_SPEC.version
|
|
1183
|
+
}
|
|
1184
|
+
} };
|
|
1185
|
+
}
|
|
1186
|
+
async listSpec(_ctx) {
|
|
1187
|
+
return { data: [] };
|
|
1188
|
+
}
|
|
1189
|
+
async metaSpec(_ctx) {
|
|
1190
|
+
return {
|
|
1191
|
+
id: ".meta",
|
|
1192
|
+
path: "/spec/.meta",
|
|
1193
|
+
meta: {
|
|
1194
|
+
kind: "aup:spec",
|
|
1195
|
+
version: AUP_SPEC.version,
|
|
1196
|
+
childrenCount: 0
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
async explainSpec(_ctx) {
|
|
1201
|
+
return {
|
|
1202
|
+
format: "markdown",
|
|
1203
|
+
content: [
|
|
1204
|
+
"# AUP Document Specification",
|
|
1205
|
+
"",
|
|
1206
|
+
"Read `/spec` to get the full AUP wire format definition,",
|
|
1207
|
+
"including node fields, spatial intent system, sizing, event model, and workflow.",
|
|
1208
|
+
"",
|
|
1209
|
+
"This is the starting point for any agent building UI with AUP."
|
|
1210
|
+
].join("\n")
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
async listExamples(_ctx) {
|
|
1214
|
+
return { data: Object.values(AUP_EXAMPLES).map((ex) => ({
|
|
1215
|
+
id: ex.name,
|
|
1216
|
+
name: ex.name,
|
|
1217
|
+
path: joinURL("/examples", ex.name),
|
|
1218
|
+
meta: {
|
|
1219
|
+
kind: "aup:example",
|
|
1220
|
+
title: ex.title,
|
|
1221
|
+
description: ex.description,
|
|
1222
|
+
concepts: ex.concepts
|
|
1223
|
+
}
|
|
1224
|
+
})) };
|
|
1225
|
+
}
|
|
1226
|
+
async readExamplesDir(_ctx) {
|
|
1227
|
+
return {
|
|
1228
|
+
id: "examples",
|
|
1229
|
+
path: "/examples",
|
|
1230
|
+
content: `AUP Examples (${Object.keys(AUP_EXAMPLES).length} examples)`,
|
|
1231
|
+
meta: {
|
|
1232
|
+
kind: "examples-directory",
|
|
1233
|
+
childrenCount: Object.keys(AUP_EXAMPLES).length
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
async statExamplesDir(_ctx) {
|
|
1238
|
+
return { data: {
|
|
1239
|
+
id: "examples",
|
|
1240
|
+
path: "/examples",
|
|
1241
|
+
meta: {
|
|
1242
|
+
kind: "examples-directory",
|
|
1243
|
+
childrenCount: Object.keys(AUP_EXAMPLES).length
|
|
1244
|
+
}
|
|
1245
|
+
} };
|
|
1246
|
+
}
|
|
1247
|
+
async metaExamplesDir(_ctx) {
|
|
1248
|
+
return {
|
|
1249
|
+
id: "examples",
|
|
1250
|
+
path: "/examples/.meta",
|
|
1251
|
+
meta: {
|
|
1252
|
+
kind: "examples-directory",
|
|
1253
|
+
childrenCount: Object.keys(AUP_EXAMPLES).length
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
async readExample(ctx) {
|
|
1258
|
+
const name = ctx.params.name;
|
|
1259
|
+
const ex = AUP_EXAMPLES[name];
|
|
1260
|
+
if (!ex) throw new AFSNotFoundError(joinURL("/examples", name));
|
|
1261
|
+
return {
|
|
1262
|
+
id: ex.name,
|
|
1263
|
+
path: joinURL("/examples", ex.name),
|
|
1264
|
+
content: ex,
|
|
1265
|
+
meta: {
|
|
1266
|
+
kind: "aup:example",
|
|
1267
|
+
title: ex.title,
|
|
1268
|
+
description: ex.description,
|
|
1269
|
+
concepts: ex.concepts
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
async statExample(ctx) {
|
|
1274
|
+
const name = ctx.params.name;
|
|
1275
|
+
const ex = AUP_EXAMPLES[name];
|
|
1276
|
+
if (!ex) throw new AFSNotFoundError(joinURL("/examples", name));
|
|
1277
|
+
return { data: {
|
|
1278
|
+
id: ex.name,
|
|
1279
|
+
path: joinURL("/examples", ex.name),
|
|
1280
|
+
meta: {
|
|
1281
|
+
kind: "aup:example",
|
|
1282
|
+
title: ex.title,
|
|
1283
|
+
description: ex.description
|
|
1284
|
+
}
|
|
1285
|
+
} };
|
|
1286
|
+
}
|
|
1287
|
+
async metaExample(ctx) {
|
|
1288
|
+
const name = ctx.params.name;
|
|
1289
|
+
const ex = AUP_EXAMPLES[name];
|
|
1290
|
+
if (!ex) throw new AFSNotFoundError(joinURL("/examples", name));
|
|
1291
|
+
return {
|
|
1292
|
+
id: ex.name,
|
|
1293
|
+
path: joinURL("/examples", ex.name, ".meta"),
|
|
1294
|
+
meta: {
|
|
1295
|
+
kind: "aup:example",
|
|
1296
|
+
title: ex.title,
|
|
1297
|
+
description: ex.description
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
async writeOutput(_ctx, payload) {
|
|
1302
|
+
const content = typeof payload.content === "string" ? payload.content : String(payload.content ?? "");
|
|
1303
|
+
const format = payload.meta?.format ?? void 0;
|
|
1304
|
+
const component = payload.meta?.component ?? void 0;
|
|
1305
|
+
const componentProps = payload.meta?.componentProps ?? void 0;
|
|
1306
|
+
await this.backend.write(content, {
|
|
1307
|
+
format,
|
|
1308
|
+
component,
|
|
1309
|
+
componentProps
|
|
1310
|
+
});
|
|
1311
|
+
return { data: {
|
|
1312
|
+
id: "output",
|
|
1313
|
+
path: "/output",
|
|
1314
|
+
content,
|
|
1315
|
+
meta: { kind: "output-channel" }
|
|
1316
|
+
} };
|
|
1317
|
+
}
|
|
1318
|
+
async writePage(ctx, payload) {
|
|
1319
|
+
const pageId = ctx.params.id;
|
|
1320
|
+
const content = typeof payload.content === "string" ? payload.content : String(payload.content ?? "");
|
|
1321
|
+
const format = payload.meta?.format ?? "html";
|
|
1322
|
+
const layout = payload.meta?.layout ?? void 0;
|
|
1323
|
+
const now = Date.now();
|
|
1324
|
+
const pageData = {
|
|
1325
|
+
content,
|
|
1326
|
+
format,
|
|
1327
|
+
layout,
|
|
1328
|
+
createdAt: this.pages.get(pageId)?.createdAt ?? now,
|
|
1329
|
+
updatedAt: now
|
|
1330
|
+
};
|
|
1331
|
+
this.pages.set(pageId, pageData);
|
|
1332
|
+
this.persistPageToDisk(pageId, pageData);
|
|
1333
|
+
return { data: {
|
|
1334
|
+
id: pageId,
|
|
1335
|
+
path: joinURL("/pages", pageId),
|
|
1336
|
+
content,
|
|
1337
|
+
meta: {
|
|
1338
|
+
kind: "page",
|
|
1339
|
+
format
|
|
1340
|
+
}
|
|
1341
|
+
} };
|
|
1342
|
+
}
|
|
1343
|
+
async deletePage(ctx) {
|
|
1344
|
+
const pageId = ctx.params.id;
|
|
1345
|
+
if (!this.pages.has(pageId)) throw new Error(`Page not found: ${pageId}`);
|
|
1346
|
+
this.pages.delete(pageId);
|
|
1347
|
+
this.removePageFromDisk(pageId);
|
|
1348
|
+
return { message: `Deleted page: ${pageId}` };
|
|
1349
|
+
}
|
|
1350
|
+
/** Validate and parse a sharing entry payload */
|
|
1351
|
+
parseSharingPayload(payload, existingCreatedAt) {
|
|
1352
|
+
const raw = payload.content;
|
|
1353
|
+
if (!raw || typeof raw !== "object") throw new Error("Sharing entry requires a content object");
|
|
1354
|
+
const target = raw.target;
|
|
1355
|
+
if (!target || typeof target !== "string") throw new Error("Sharing entry requires 'target' field");
|
|
1356
|
+
const access = raw.access ?? "guest";
|
|
1357
|
+
if (!VALID_ACCESS_LEVELS.has(access)) throw new Error(`Invalid access level: '${access}'. Must be one of: ${[...VALID_ACCESS_LEVELS].join(", ")}`);
|
|
1358
|
+
const mode = raw.mode ?? "static";
|
|
1359
|
+
if (!VALID_SHARING_MODES.has(mode)) throw new Error(`Invalid sharing mode: '${mode}'. Must be one of: ${[...VALID_SHARING_MODES].join(", ")}`);
|
|
1360
|
+
const now = Date.now();
|
|
1361
|
+
return {
|
|
1362
|
+
target,
|
|
1363
|
+
access,
|
|
1364
|
+
mode,
|
|
1365
|
+
slug: "",
|
|
1366
|
+
meta: raw.meta,
|
|
1367
|
+
createdAt: existingCreatedAt ?? now,
|
|
1368
|
+
updatedAt: now
|
|
1369
|
+
};
|
|
1370
|
+
}
|
|
1371
|
+
async writeSharingEntry(ctx, payload) {
|
|
1372
|
+
const slug = ctx.params.slug;
|
|
1373
|
+
const existing = this.sharingEntries.get(slug);
|
|
1374
|
+
const entry = this.parseSharingPayload(payload, existing?.createdAt);
|
|
1375
|
+
entry.slug = slug;
|
|
1376
|
+
this.sharingEntries.set(slug, entry);
|
|
1377
|
+
return { data: {
|
|
1378
|
+
id: slug,
|
|
1379
|
+
path: joinURL("/sharing", slug),
|
|
1380
|
+
content: entry,
|
|
1381
|
+
meta: { kind: "sharing-entry" }
|
|
1382
|
+
} };
|
|
1383
|
+
}
|
|
1384
|
+
async readSharingEntry(ctx) {
|
|
1385
|
+
const slug = ctx.params.slug;
|
|
1386
|
+
const entry = this.sharingEntries.get(slug);
|
|
1387
|
+
if (!entry) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1388
|
+
return {
|
|
1389
|
+
id: slug,
|
|
1390
|
+
path: joinURL("/sharing", slug),
|
|
1391
|
+
content: entry,
|
|
1392
|
+
meta: {
|
|
1393
|
+
kind: "sharing-entry",
|
|
1394
|
+
access: entry.access,
|
|
1395
|
+
mode: entry.mode
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
async readSharingDir(_ctx) {
|
|
1400
|
+
return {
|
|
1401
|
+
id: "sharing",
|
|
1402
|
+
path: "/sharing",
|
|
1403
|
+
content: `Web Sharing Entries (${this.sharingEntries.size} entries)`,
|
|
1404
|
+
meta: {
|
|
1405
|
+
kind: "sharing-directory",
|
|
1406
|
+
childrenCount: this.sharingEntries.size
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
async listSharingEntries(_ctx) {
|
|
1411
|
+
const entries = [];
|
|
1412
|
+
for (const [slug, entry] of this.sharingEntries) entries.push({
|
|
1413
|
+
id: slug,
|
|
1414
|
+
path: joinURL("/sharing", slug),
|
|
1415
|
+
meta: {
|
|
1416
|
+
kind: "sharing-entry",
|
|
1417
|
+
target: entry.target,
|
|
1418
|
+
access: entry.access,
|
|
1419
|
+
mode: entry.mode
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
return { data: entries };
|
|
1423
|
+
}
|
|
1424
|
+
async deleteSharingEntry(ctx) {
|
|
1425
|
+
const slug = ctx.params.slug;
|
|
1426
|
+
if (!this.sharingEntries.has(slug)) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1427
|
+
this.sharingEntries.delete(slug);
|
|
1428
|
+
return { message: `Deleted sharing entry: ${slug}` };
|
|
1429
|
+
}
|
|
1430
|
+
async metaSharingDir(_ctx) {
|
|
1431
|
+
return {
|
|
1432
|
+
id: "sharing",
|
|
1433
|
+
path: "/sharing/.meta",
|
|
1434
|
+
meta: {
|
|
1435
|
+
kind: "sharing-directory",
|
|
1436
|
+
childrenCount: this.sharingEntries.size
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
async metaSharingEntry(ctx) {
|
|
1441
|
+
const slug = ctx.params.slug;
|
|
1442
|
+
const entry = this.sharingEntries.get(slug);
|
|
1443
|
+
if (!entry) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1444
|
+
return {
|
|
1445
|
+
id: slug,
|
|
1446
|
+
path: joinURL("/sharing", slug, ".meta"),
|
|
1447
|
+
meta: {
|
|
1448
|
+
kind: "sharing-entry",
|
|
1449
|
+
access: entry.access,
|
|
1450
|
+
mode: entry.mode
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
async statSharingDir(_ctx) {
|
|
1455
|
+
return { data: {
|
|
1456
|
+
id: "sharing",
|
|
1457
|
+
path: "/sharing",
|
|
1458
|
+
meta: {
|
|
1459
|
+
kind: "sharing-directory",
|
|
1460
|
+
childrenCount: this.sharingEntries.size
|
|
1461
|
+
}
|
|
1462
|
+
} };
|
|
1463
|
+
}
|
|
1464
|
+
async statSharingEntry(ctx) {
|
|
1465
|
+
const slug = ctx.params.slug;
|
|
1466
|
+
const entry = this.sharingEntries.get(slug);
|
|
1467
|
+
if (!entry) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1468
|
+
return { data: {
|
|
1469
|
+
id: slug,
|
|
1470
|
+
path: joinURL("/sharing", slug),
|
|
1471
|
+
meta: {
|
|
1472
|
+
kind: "sharing-entry",
|
|
1473
|
+
access: entry.access,
|
|
1474
|
+
mode: entry.mode
|
|
1475
|
+
}
|
|
1476
|
+
} };
|
|
1477
|
+
}
|
|
1478
|
+
async listSharingActions(ctx) {
|
|
1479
|
+
const slug = ctx.params.slug;
|
|
1480
|
+
const entry = this.sharingEntries.get(slug);
|
|
1481
|
+
if (!entry) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1482
|
+
const basePath = joinURL("/sharing", slug, ".actions");
|
|
1483
|
+
const actions = [];
|
|
1484
|
+
if (entry.mode === "static") actions.push({
|
|
1485
|
+
id: "snapshot",
|
|
1486
|
+
path: joinURL(basePath, "snapshot"),
|
|
1487
|
+
meta: {
|
|
1488
|
+
kind: "afs:executable",
|
|
1489
|
+
description: "Freeze the AUP tree from a session into a static HTML snapshot",
|
|
1490
|
+
inputSchema: {
|
|
1491
|
+
type: "object",
|
|
1492
|
+
required: ["sessionId"],
|
|
1493
|
+
properties: { sessionId: {
|
|
1494
|
+
type: "string",
|
|
1495
|
+
description: "Session ID whose AUP tree to snapshot"
|
|
1496
|
+
} }
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
});
|
|
1500
|
+
return { data: actions };
|
|
1501
|
+
}
|
|
1502
|
+
async execSharingSnapshot(ctx, args) {
|
|
1503
|
+
const slug = ctx.params.slug;
|
|
1504
|
+
const entry = this.sharingEntries.get(slug);
|
|
1505
|
+
if (!entry) throw new AFSNotFoundError(joinURL("/sharing", slug));
|
|
1506
|
+
if (entry.mode !== "static") throw new Error("Static snapshot not available for live mode entries");
|
|
1507
|
+
const sessionId = args.sessionId;
|
|
1508
|
+
if (!sessionId) throw new Error("snapshot action requires 'sessionId' argument");
|
|
1509
|
+
const store = this.aupStores.get(sessionId);
|
|
1510
|
+
const tree = store?.getRoot();
|
|
1511
|
+
if (!tree) throw new Error("No AUP tree found for the specified session — render content first");
|
|
1512
|
+
const html = generateSnapshot({
|
|
1513
|
+
tree,
|
|
1514
|
+
slug,
|
|
1515
|
+
meta: entry.meta,
|
|
1516
|
+
style: store.renderOptions.style,
|
|
1517
|
+
locale: store.renderOptions.locale
|
|
1518
|
+
});
|
|
1519
|
+
entry.snapshot = html;
|
|
1520
|
+
entry.updatedAt = Date.now();
|
|
1521
|
+
return {
|
|
1522
|
+
success: true,
|
|
1523
|
+
data: {
|
|
1524
|
+
slug,
|
|
1525
|
+
size: html.length
|
|
1526
|
+
}
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
async statRoot(_ctx) {
|
|
1530
|
+
if (this.initPromise) await this.initPromise;
|
|
1531
|
+
const meta = {
|
|
1532
|
+
kind: "device",
|
|
1533
|
+
childrenCount: 10,
|
|
1534
|
+
backend: this.backend.type
|
|
1535
|
+
};
|
|
1536
|
+
if (this.serverUrl) meta.url = this.serverUrl;
|
|
1537
|
+
return { data: {
|
|
1538
|
+
id: this.name,
|
|
1539
|
+
path: "/",
|
|
1540
|
+
meta
|
|
1541
|
+
} };
|
|
1542
|
+
}
|
|
1543
|
+
async statInput(_ctx) {
|
|
1544
|
+
return { data: {
|
|
1545
|
+
id: "input",
|
|
1546
|
+
path: "/input",
|
|
1547
|
+
meta: {
|
|
1548
|
+
kind: "input-channel",
|
|
1549
|
+
childrenCount: 0,
|
|
1550
|
+
pending: this.backend.hasPendingInput()
|
|
1551
|
+
}
|
|
1552
|
+
} };
|
|
1553
|
+
}
|
|
1554
|
+
async statOutput(_ctx) {
|
|
1555
|
+
return { data: {
|
|
1556
|
+
id: "output",
|
|
1557
|
+
path: "/output",
|
|
1558
|
+
meta: {
|
|
1559
|
+
kind: "output-channel",
|
|
1560
|
+
childrenCount: 0
|
|
1561
|
+
}
|
|
1562
|
+
} };
|
|
1563
|
+
}
|
|
1564
|
+
async statPages(_ctx) {
|
|
1565
|
+
return { data: {
|
|
1566
|
+
id: "pages",
|
|
1567
|
+
path: "/pages",
|
|
1568
|
+
meta: {
|
|
1569
|
+
kind: "pages-directory",
|
|
1570
|
+
childrenCount: this.pages.size
|
|
1571
|
+
}
|
|
1572
|
+
} };
|
|
1573
|
+
}
|
|
1574
|
+
async statPage(ctx) {
|
|
1575
|
+
const pageId = ctx.params.id;
|
|
1576
|
+
const page = this.pages.get(pageId);
|
|
1577
|
+
if (!page) throw new Error(`Page not found: ${pageId}`);
|
|
1578
|
+
return { data: {
|
|
1579
|
+
id: pageId,
|
|
1580
|
+
path: joinURL("/pages", pageId),
|
|
1581
|
+
meta: {
|
|
1582
|
+
kind: "page",
|
|
1583
|
+
format: page.format,
|
|
1584
|
+
childrenCount: 0
|
|
1585
|
+
}
|
|
1586
|
+
} };
|
|
1587
|
+
}
|
|
1588
|
+
async explainRoot(_ctx) {
|
|
1589
|
+
return {
|
|
1590
|
+
format: "markdown",
|
|
1591
|
+
content: [
|
|
1592
|
+
`# UI Device (${this.backend.type})`,
|
|
1593
|
+
"",
|
|
1594
|
+
"You can build **any UI** by composing AUP (Agentic UI Protocol) component trees.",
|
|
1595
|
+
"",
|
|
1596
|
+
"## Quick Start",
|
|
1597
|
+
"",
|
|
1598
|
+
"1. **Read `/spec`** — Full AUP document format, node fields, event model, and workflow",
|
|
1599
|
+
`2. **Read \`/primitives\`** — ${Object.keys(PRIMITIVES).length} available components with complete props, events, and examples`,
|
|
1600
|
+
`3. **Read \`/themes\`** — ${Object.keys(THEMES).length} visual themes with color tokens`,
|
|
1601
|
+
`4. **Read \`/overlay-themes\`** — ${Object.keys(OVERLAY_THEMES).length} broadcast overlay themes (graphics packages) for overlay-grid`,
|
|
1602
|
+
`5. **Read \`/examples\`** — ${Object.keys(AUP_EXAMPLES).length} complete working documents to learn from`,
|
|
1603
|
+
"",
|
|
1604
|
+
"## Creating a UI",
|
|
1605
|
+
"",
|
|
1606
|
+
"```",
|
|
1607
|
+
"// 1. Build a node tree",
|
|
1608
|
+
"{ id: \"root\", type: \"view\", props: { layout: \"column\" }, children: [...] }",
|
|
1609
|
+
"",
|
|
1610
|
+
"// 2. Render it via session action",
|
|
1611
|
+
`exec('/${this.endpointType}/sessions/:sid/.actions/aup_render', {`,
|
|
1612
|
+
" root: nodeTree,",
|
|
1613
|
+
" fullPage: true,",
|
|
1614
|
+
" style: 'midnight'",
|
|
1615
|
+
"})",
|
|
1616
|
+
"",
|
|
1617
|
+
"// 3. Handle events (user clicks, input changes)",
|
|
1618
|
+
"// Events call your AFS exec paths defined in node.events",
|
|
1619
|
+
"",
|
|
1620
|
+
"// 4. Update UI incrementally",
|
|
1621
|
+
`exec('/${this.endpointType}/sessions/:sid/.actions/aup_patch', {`,
|
|
1622
|
+
" ops: [{ op: \"update\", id: \"node-id\", props: { content: \"new text\" } }]",
|
|
1623
|
+
"})",
|
|
1624
|
+
"```",
|
|
1625
|
+
"",
|
|
1626
|
+
"## Key Concepts",
|
|
1627
|
+
"",
|
|
1628
|
+
`- **${Object.keys(PRIMITIVES).length} primitives**: view, text, action, input, media, overlay, table, time, chart, map, calendar, moonphase, natal-chart, chat, terminal, editor, explorer, canvas, globe, finance-chart, rtc`,
|
|
1629
|
+
"- **Events**: click, change, send, sort, select, confirm, cancel — each triggers an AFS exec call",
|
|
1630
|
+
"- **Live data**: Set `node.src` to an AFS path for auto-updating charts, maps, etc.",
|
|
1631
|
+
"- **Themes**: midnight, clean, glass, brutalist, soft, cyber, editorial",
|
|
1632
|
+
"",
|
|
1633
|
+
"## Session Actions",
|
|
1634
|
+
"",
|
|
1635
|
+
"| Action | Description |",
|
|
1636
|
+
"|--------|-------------|",
|
|
1637
|
+
"| `aup_render` | Render a full AUP component tree |",
|
|
1638
|
+
"| `aup_patch` | Incremental updates (create/update/remove/reorder) |",
|
|
1639
|
+
"| `aup_stage` | Stage a scene off-screen (pre-render for instant switching) |",
|
|
1640
|
+
"| `aup_take` | Take a staged scene live (CSS swap, zero DOM teardown) |",
|
|
1641
|
+
"| `aup_release` | Release a staged scene's resources |",
|
|
1642
|
+
"| `aup_save` | Save current graph to a session page |",
|
|
1643
|
+
"| `aup_load` | Load a saved graph and render it |",
|
|
1644
|
+
"| `prompt` | Ask user a question (text/password/confirm/select) |",
|
|
1645
|
+
"| `dialog` | Show dialog with custom buttons |",
|
|
1646
|
+
"| `toast` | Lightweight toast notification |",
|
|
1647
|
+
"| `form` | Collect structured input |",
|
|
1648
|
+
"| `navigate` | Navigate to a page |",
|
|
1649
|
+
"",
|
|
1650
|
+
`## Capabilities: ${this.backend.capabilities.join(", ")}`,
|
|
1651
|
+
`## Formats: ${this.backend.supportedFormats.join(", ")}`
|
|
1652
|
+
].join("\n")
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
async explainInput(_ctx) {
|
|
1656
|
+
return {
|
|
1657
|
+
format: "text",
|
|
1658
|
+
content: "Input channel — use `read('/input')` to wait for user input. Use `stat('/input')` to check if input is pending without blocking."
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
async explainOutput(_ctx) {
|
|
1662
|
+
return {
|
|
1663
|
+
format: "text",
|
|
1664
|
+
content: "Output channel — use `write('/output', { content: '...' })` to display content to the user."
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
async explainPages(_ctx) {
|
|
1668
|
+
return {
|
|
1669
|
+
format: "text",
|
|
1670
|
+
content: "Pages directory — use `write('/pages/:id', { content, meta: { format } })` to create pages. Use `exec('/.actions/navigate', { page: ':id' })` to display a page."
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
async readEndpoint(ctx) {
|
|
1674
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1675
|
+
const sessions = this.sessions.list(endpoint);
|
|
1676
|
+
const isWeb = endpoint === "web";
|
|
1677
|
+
return {
|
|
1678
|
+
id: endpoint,
|
|
1679
|
+
path: joinURL("/", endpoint),
|
|
1680
|
+
content: `${endpoint} endpoint — ${sessions.length} active session(s)`,
|
|
1681
|
+
meta: {
|
|
1682
|
+
kind: "endpoint",
|
|
1683
|
+
childrenCount: isWeb ? 2 : 1
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
async statEndpoint(ctx) {
|
|
1688
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1689
|
+
const isWeb = endpoint === "web";
|
|
1690
|
+
return { data: {
|
|
1691
|
+
id: endpoint,
|
|
1692
|
+
path: joinURL("/", endpoint),
|
|
1693
|
+
meta: {
|
|
1694
|
+
kind: "endpoint",
|
|
1695
|
+
childrenCount: isWeb ? 2 : 1
|
|
1696
|
+
}
|
|
1697
|
+
} };
|
|
1698
|
+
}
|
|
1699
|
+
async listEndpoint(ctx) {
|
|
1700
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1701
|
+
const sessions = this.sessions.list(endpoint);
|
|
1702
|
+
const entries = [{
|
|
1703
|
+
id: "sessions",
|
|
1704
|
+
path: joinURL("/", endpoint, "sessions"),
|
|
1705
|
+
meta: {
|
|
1706
|
+
kind: "sessions-directory",
|
|
1707
|
+
childrenCount: sessions.length
|
|
1708
|
+
}
|
|
1709
|
+
}];
|
|
1710
|
+
if (endpoint === "web") {
|
|
1711
|
+
const channelCount = this.getActiveChannelCount();
|
|
1712
|
+
entries.push({
|
|
1713
|
+
id: "live",
|
|
1714
|
+
path: joinURL("/", endpoint, "live"),
|
|
1715
|
+
meta: {
|
|
1716
|
+
kind: "live-directory",
|
|
1717
|
+
childrenCount: channelCount
|
|
1718
|
+
}
|
|
1719
|
+
});
|
|
1720
|
+
}
|
|
1721
|
+
return { data: entries };
|
|
1722
|
+
}
|
|
1723
|
+
async metaEndpoint(ctx) {
|
|
1724
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1725
|
+
const isWeb = endpoint === "web";
|
|
1726
|
+
return {
|
|
1727
|
+
id: endpoint,
|
|
1728
|
+
path: joinURL("/", endpoint, ".meta"),
|
|
1729
|
+
meta: {
|
|
1730
|
+
kind: "endpoint",
|
|
1731
|
+
childrenCount: isWeb ? 2 : 1
|
|
1732
|
+
}
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
async readSessions(ctx) {
|
|
1736
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1737
|
+
const sessions = this.sessions.list(endpoint);
|
|
1738
|
+
return {
|
|
1739
|
+
id: "sessions",
|
|
1740
|
+
path: joinURL("/", endpoint, "sessions"),
|
|
1741
|
+
content: `${sessions.length} active session(s)`,
|
|
1742
|
+
meta: {
|
|
1743
|
+
kind: "sessions-directory",
|
|
1744
|
+
childrenCount: sessions.length
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
async metaSessions(ctx) {
|
|
1749
|
+
const endpoint = this.assertEndpoint(ctx);
|
|
1750
|
+
const sessions = this.sessions.list(endpoint);
|
|
1751
|
+
return {
|
|
1752
|
+
id: "sessions",
|
|
1753
|
+
path: joinURL("/", endpoint, "sessions", ".meta"),
|
|
1754
|
+
meta: {
|
|
1755
|
+
kind: "sessions-directory",
|
|
1756
|
+
childrenCount: sessions.length
|
|
1757
|
+
}
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
async listSessions(_ctx) {
|
|
1761
|
+
const endpoint = this.assertEndpoint(_ctx);
|
|
1762
|
+
return { data: this.sessions.list(endpoint).map((s) => ({
|
|
1763
|
+
id: s.id,
|
|
1764
|
+
path: joinURL("/", endpoint, "sessions", s.id),
|
|
1765
|
+
meta: {
|
|
1766
|
+
...s.toMeta(),
|
|
1767
|
+
kind: "session",
|
|
1768
|
+
childrenCount: -1
|
|
1769
|
+
}
|
|
1770
|
+
})) };
|
|
1771
|
+
}
|
|
1772
|
+
async listSession(ctx) {
|
|
1773
|
+
const session = this.resolveSession(ctx);
|
|
1774
|
+
const endpoint = ctx.params.endpoint;
|
|
1775
|
+
const basePath = joinURL("/", endpoint, "sessions", session.id);
|
|
1776
|
+
return { data: [
|
|
1777
|
+
{
|
|
1778
|
+
id: "messages",
|
|
1779
|
+
path: joinURL(basePath, "messages"),
|
|
1780
|
+
meta: {
|
|
1781
|
+
kind: "messages-directory",
|
|
1782
|
+
childrenCount: session.listMessages().length
|
|
1783
|
+
}
|
|
1784
|
+
},
|
|
1785
|
+
{
|
|
1786
|
+
id: "pages",
|
|
1787
|
+
path: joinURL(basePath, "pages"),
|
|
1788
|
+
meta: {
|
|
1789
|
+
kind: "pages-directory",
|
|
1790
|
+
childrenCount: session.listPages().length
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
id: "tree",
|
|
1795
|
+
path: joinURL(basePath, "tree"),
|
|
1796
|
+
meta: {
|
|
1797
|
+
kind: "aup:tree",
|
|
1798
|
+
childrenCount: 0
|
|
1799
|
+
}
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
id: "wm",
|
|
1803
|
+
path: joinURL(basePath, "wm"),
|
|
1804
|
+
meta: {
|
|
1805
|
+
kind: "wm",
|
|
1806
|
+
childrenCount: 3
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
] };
|
|
1810
|
+
}
|
|
1811
|
+
async readSession(ctx) {
|
|
1812
|
+
const session = this.resolveSession(ctx);
|
|
1813
|
+
const endpoint = ctx.params.endpoint;
|
|
1814
|
+
return {
|
|
1815
|
+
id: session.id,
|
|
1816
|
+
path: joinURL("/", endpoint, "sessions", session.id),
|
|
1817
|
+
content: `Session ${session.id} (${session.endpoint})`,
|
|
1818
|
+
meta: {
|
|
1819
|
+
kind: "session",
|
|
1820
|
+
...session.toMeta()
|
|
1821
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async metaSession(ctx) {
|
|
1825
|
+
const session = this.resolveSession(ctx);
|
|
1826
|
+
const endpoint = ctx.params.endpoint;
|
|
1827
|
+
return {
|
|
1828
|
+
id: session.id,
|
|
1829
|
+
path: joinURL("/", endpoint, "sessions", session.id, ".meta"),
|
|
1830
|
+
meta: {
|
|
1831
|
+
kind: "session",
|
|
1832
|
+
...session.toMeta(),
|
|
1833
|
+
capabilities: {
|
|
1834
|
+
supportedTypes: [
|
|
1835
|
+
"text",
|
|
1836
|
+
"table",
|
|
1837
|
+
"form",
|
|
1838
|
+
"select",
|
|
1839
|
+
"confirm",
|
|
1840
|
+
"dialog",
|
|
1841
|
+
"progress",
|
|
1842
|
+
"notification",
|
|
1843
|
+
"markdown",
|
|
1844
|
+
"code"
|
|
1845
|
+
],
|
|
1846
|
+
interactive: [
|
|
1847
|
+
"prompt",
|
|
1848
|
+
"select",
|
|
1849
|
+
"confirm",
|
|
1850
|
+
"form",
|
|
1851
|
+
"dialog"
|
|
1852
|
+
],
|
|
1853
|
+
pages: true,
|
|
1854
|
+
fallback: "degrade_to_text"
|
|
1855
|
+
},
|
|
1856
|
+
viewport: this.backend.getViewport()
|
|
1857
|
+
}
|
|
1858
|
+
};
|
|
1859
|
+
}
|
|
1860
|
+
async statSession(ctx) {
|
|
1861
|
+
const session = this.resolveSession(ctx);
|
|
1862
|
+
const endpoint = ctx.params.endpoint;
|
|
1863
|
+
return { data: {
|
|
1864
|
+
id: session.id,
|
|
1865
|
+
path: joinURL("/", endpoint, "sessions", session.id),
|
|
1866
|
+
meta: {
|
|
1867
|
+
kind: "session",
|
|
1868
|
+
...session.toMeta()
|
|
1869
|
+
}
|
|
1870
|
+
} };
|
|
1871
|
+
}
|
|
1872
|
+
async listSessionMessages(ctx) {
|
|
1873
|
+
const session = this.resolveSession(ctx);
|
|
1874
|
+
const endpoint = ctx.params.endpoint;
|
|
1875
|
+
return { data: session.listMessages().map((m) => ({
|
|
1876
|
+
id: m.id,
|
|
1877
|
+
path: joinURL("/", endpoint, "sessions", session.id, "messages", m.id),
|
|
1878
|
+
content: m,
|
|
1879
|
+
meta: {
|
|
1880
|
+
kind: "message",
|
|
1881
|
+
type: m.type,
|
|
1882
|
+
from: m.from
|
|
1883
|
+
}
|
|
1884
|
+
})) };
|
|
1885
|
+
}
|
|
1886
|
+
async readSessionMessage(ctx) {
|
|
1887
|
+
const session = this.resolveSession(ctx);
|
|
1888
|
+
const mid = ctx.params.mid;
|
|
1889
|
+
const endpoint = ctx.params.endpoint;
|
|
1890
|
+
const msg = session.findMessage(mid);
|
|
1891
|
+
if (!msg) throw new Error(`Message not found: ${mid}`);
|
|
1892
|
+
return {
|
|
1893
|
+
id: msg.id,
|
|
1894
|
+
path: joinURL("/", endpoint, "sessions", session.id, "messages", mid),
|
|
1895
|
+
content: msg,
|
|
1896
|
+
meta: {
|
|
1897
|
+
kind: "message",
|
|
1898
|
+
type: msg.type,
|
|
1899
|
+
from: msg.from
|
|
1900
|
+
}
|
|
1901
|
+
};
|
|
1902
|
+
}
|
|
1903
|
+
async writeSessionMessage(ctx, payload) {
|
|
1904
|
+
const session = this.resolveSession(ctx);
|
|
1905
|
+
const endpoint = ctx.params.endpoint;
|
|
1906
|
+
const msgData = typeof payload.content === "object" && payload.content !== null ? payload.content : {
|
|
1907
|
+
type: "text",
|
|
1908
|
+
from: "agent",
|
|
1909
|
+
content: String(payload.content ?? "")
|
|
1910
|
+
};
|
|
1911
|
+
const msg = session.addMessage(msgData);
|
|
1912
|
+
if (msg.type === "text" && msg.from === "agent" && typeof msg.content === "string") await this.backend.write(msg.content);
|
|
1913
|
+
return { data: {
|
|
1914
|
+
id: msg.id,
|
|
1915
|
+
path: joinURL("/", endpoint, "sessions", session.id, "messages", msg.id),
|
|
1916
|
+
content: msg,
|
|
1917
|
+
meta: {
|
|
1918
|
+
kind: "message",
|
|
1919
|
+
type: msg.type
|
|
1920
|
+
}
|
|
1921
|
+
} };
|
|
1922
|
+
}
|
|
1923
|
+
async listSessionPages(ctx) {
|
|
1924
|
+
const session = this.resolveSession(ctx);
|
|
1925
|
+
const endpoint = ctx.params.endpoint;
|
|
1926
|
+
return { data: session.listPages().map(({ id, page }) => ({
|
|
1927
|
+
id,
|
|
1928
|
+
path: joinURL("/", endpoint, "sessions", session.id, "pages", id),
|
|
1929
|
+
meta: {
|
|
1930
|
+
kind: "page",
|
|
1931
|
+
format: page.format,
|
|
1932
|
+
childrenCount: 0
|
|
1933
|
+
}
|
|
1934
|
+
})) };
|
|
1935
|
+
}
|
|
1936
|
+
async readSessionPage(ctx) {
|
|
1937
|
+
const session = this.resolveSession(ctx);
|
|
1938
|
+
const pid = ctx.params.pid;
|
|
1939
|
+
const endpoint = ctx.params.endpoint;
|
|
1940
|
+
const page = session.getPage(pid);
|
|
1941
|
+
if (!page) throw new Error(`Page not found: ${pid}`);
|
|
1942
|
+
return {
|
|
1943
|
+
id: pid,
|
|
1944
|
+
path: joinURL("/", endpoint, "sessions", session.id, "pages", pid),
|
|
1945
|
+
content: page.content,
|
|
1946
|
+
meta: {
|
|
1947
|
+
kind: "page",
|
|
1948
|
+
format: page.format,
|
|
1949
|
+
layout: page.layout
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
}
|
|
1953
|
+
async writeSessionPage(ctx, payload) {
|
|
1954
|
+
const session = this.resolveSession(ctx);
|
|
1955
|
+
const pid = ctx.params.pid;
|
|
1956
|
+
const endpoint = ctx.params.endpoint;
|
|
1957
|
+
const content = typeof payload.content === "string" ? payload.content : String(payload.content ?? "");
|
|
1958
|
+
const format = payload.meta?.format ?? "html";
|
|
1959
|
+
const layout = payload.meta?.layout ?? void 0;
|
|
1960
|
+
session.setPage(pid, {
|
|
1961
|
+
content,
|
|
1962
|
+
format,
|
|
1963
|
+
layout
|
|
1964
|
+
});
|
|
1965
|
+
return { data: {
|
|
1966
|
+
id: pid,
|
|
1967
|
+
path: joinURL("/", endpoint, "sessions", session.id, "pages", pid),
|
|
1968
|
+
content,
|
|
1969
|
+
meta: {
|
|
1970
|
+
kind: "page",
|
|
1971
|
+
format
|
|
1972
|
+
}
|
|
1973
|
+
} };
|
|
1974
|
+
}
|
|
1975
|
+
async readSessionCaps(ctx) {
|
|
1976
|
+
const session = this.resolveSession(ctx);
|
|
1977
|
+
const endpoint = ctx.params.endpoint;
|
|
1978
|
+
return {
|
|
1979
|
+
id: ".caps",
|
|
1980
|
+
path: joinURL("/", endpoint, "sessions", session.id, ".caps"),
|
|
1981
|
+
content: session.deviceCaps,
|
|
1982
|
+
meta: { kind: "aup:device-caps" }
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
async writeSessionCaps(ctx, payload) {
|
|
1986
|
+
const session = this.resolveSession(ctx);
|
|
1987
|
+
const endpoint = ctx.params.endpoint;
|
|
1988
|
+
const err = session.setDeviceCaps(payload.content);
|
|
1989
|
+
if (err) throw new Error(`Invalid DeviceCaps: ${err}`);
|
|
1990
|
+
const capsPath = joinURL("/", endpoint, "sessions", session.id, ".caps");
|
|
1991
|
+
return { data: {
|
|
1992
|
+
id: capsPath,
|
|
1993
|
+
path: capsPath
|
|
1994
|
+
} };
|
|
1995
|
+
}
|
|
1996
|
+
async readSessionTree(ctx) {
|
|
1997
|
+
const session = this.resolveSession(ctx);
|
|
1998
|
+
const endpoint = ctx.params.endpoint;
|
|
1999
|
+
const root = this.aupStores.get(session.id)?.getRoot() ?? null;
|
|
2000
|
+
return {
|
|
2001
|
+
id: "tree",
|
|
2002
|
+
path: joinURL("/", endpoint, "sessions", session.id, "tree"),
|
|
2003
|
+
content: root,
|
|
2004
|
+
meta: { kind: "aup:tree" }
|
|
2005
|
+
};
|
|
2006
|
+
}
|
|
2007
|
+
async writeSessionTree(ctx, payload) {
|
|
2008
|
+
const session = this.resolveSession(ctx);
|
|
2009
|
+
const endpoint = ctx.params.endpoint;
|
|
2010
|
+
const root = payload.content;
|
|
2011
|
+
if (!root || typeof root !== "object") throw new Error("tree write requires a node object as content");
|
|
2012
|
+
const err = validateNode(root);
|
|
2013
|
+
if (err) throw new Error(`Invalid AUP node: ${err}`);
|
|
2014
|
+
const store = this.getAupStore(session.id);
|
|
2015
|
+
store.setRoot(root);
|
|
2016
|
+
const meta = payload.meta ?? {};
|
|
2017
|
+
const fullPage = meta.fullPage === true;
|
|
2018
|
+
const chrome = meta.chrome === true;
|
|
2019
|
+
const theme = typeof meta.theme === "string" ? meta.theme : void 0;
|
|
2020
|
+
const style = typeof meta.style === "string" ? meta.style : void 0;
|
|
2021
|
+
const locale = typeof meta.locale === "string" ? meta.locale : void 0;
|
|
2022
|
+
if (isAUPTransport(this.backend)) {
|
|
2023
|
+
const msg = {
|
|
2024
|
+
type: "aup",
|
|
2025
|
+
action: "render",
|
|
2026
|
+
root: this.degradeForSession(store.getRoot(), session)
|
|
2027
|
+
};
|
|
2028
|
+
if (fullPage) msg.fullPage = true;
|
|
2029
|
+
if (chrome) msg.chrome = true;
|
|
2030
|
+
if (theme) msg.theme = theme;
|
|
2031
|
+
if (style) msg.style = style;
|
|
2032
|
+
if (locale) msg.locale = locale;
|
|
2033
|
+
this.backend.sendToSession(session.id, msg);
|
|
2034
|
+
}
|
|
2035
|
+
const treePath = joinURL("/", endpoint, "sessions", session.id, "tree");
|
|
2036
|
+
this.emit({
|
|
2037
|
+
type: "afs:write",
|
|
2038
|
+
path: treePath,
|
|
2039
|
+
data: { root: store.getRoot() }
|
|
2040
|
+
});
|
|
2041
|
+
return { data: {
|
|
2042
|
+
id: "tree",
|
|
2043
|
+
path: treePath,
|
|
2044
|
+
content: store.getRoot(),
|
|
2045
|
+
meta: { kind: "aup:tree" }
|
|
2046
|
+
} };
|
|
2047
|
+
}
|
|
2048
|
+
async readSessionWm(ctx) {
|
|
2049
|
+
const session = this.resolveSession(ctx);
|
|
2050
|
+
const endpoint = ctx.params.endpoint;
|
|
2051
|
+
const wm = this.getWmNode(session.id);
|
|
2052
|
+
const wmProps = wm?.wmNode.props ?? {};
|
|
2053
|
+
const wmState = wm?.wmNode.state ?? {};
|
|
2054
|
+
const surfaces = (wm?.wmNode.children ?? []).filter((c) => c.type === "wm-surface");
|
|
2055
|
+
return {
|
|
2056
|
+
id: "wm",
|
|
2057
|
+
path: joinURL("/", endpoint, "sessions", session.id, "wm"),
|
|
2058
|
+
content: {
|
|
2059
|
+
strategy: wmProps.strategy ?? "single",
|
|
2060
|
+
active: wmState.active ?? null,
|
|
2061
|
+
surfaceCount: surfaces.length
|
|
2062
|
+
},
|
|
2063
|
+
meta: { kind: "wm" }
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
async listSessionWm(ctx) {
|
|
2067
|
+
const session = this.resolveSession(ctx);
|
|
2068
|
+
const endpoint = ctx.params.endpoint;
|
|
2069
|
+
const basePath = joinURL("/", endpoint, "sessions", session.id, "wm");
|
|
2070
|
+
return { data: [
|
|
2071
|
+
{
|
|
2072
|
+
id: "strategy",
|
|
2073
|
+
path: joinURL(basePath, "strategy"),
|
|
2074
|
+
meta: { kind: "wm:strategy" }
|
|
2075
|
+
},
|
|
2076
|
+
{
|
|
2077
|
+
id: "layout",
|
|
2078
|
+
path: joinURL(basePath, "layout"),
|
|
2079
|
+
meta: { kind: "wm:layout" }
|
|
2080
|
+
},
|
|
2081
|
+
{
|
|
2082
|
+
id: "surfaces",
|
|
2083
|
+
path: joinURL(basePath, "surfaces"),
|
|
2084
|
+
meta: { kind: "wm:surfaces" }
|
|
2085
|
+
}
|
|
2086
|
+
] };
|
|
2087
|
+
}
|
|
2088
|
+
async readWmStrategy(ctx) {
|
|
2089
|
+
const session = this.resolveSession(ctx);
|
|
2090
|
+
const endpoint = ctx.params.endpoint;
|
|
2091
|
+
const strategy = (this.getWmNode(session.id)?.wmNode.props ?? {}).strategy ?? "single";
|
|
2092
|
+
return {
|
|
2093
|
+
id: "strategy",
|
|
2094
|
+
path: joinURL("/", endpoint, "sessions", session.id, "wm", "strategy"),
|
|
2095
|
+
content: strategy,
|
|
2096
|
+
meta: { kind: "wm:strategy" }
|
|
2097
|
+
};
|
|
2098
|
+
}
|
|
2099
|
+
async writeWmStrategy(ctx, payload) {
|
|
2100
|
+
const session = this.resolveSession(ctx);
|
|
2101
|
+
const endpoint = ctx.params.endpoint;
|
|
2102
|
+
const value = payload.content;
|
|
2103
|
+
if (typeof value !== "string") throw new Error("WM strategy must be a string");
|
|
2104
|
+
const { wmNode } = this.ensureWmNode(session.id, value);
|
|
2105
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2106
|
+
op: "update",
|
|
2107
|
+
id: wmNode.id,
|
|
2108
|
+
props: { strategy: value }
|
|
2109
|
+
}]);
|
|
2110
|
+
return { data: {
|
|
2111
|
+
id: "strategy",
|
|
2112
|
+
path: joinURL("/", endpoint, "sessions", session.id, "wm", "strategy"),
|
|
2113
|
+
content: value,
|
|
2114
|
+
meta: { kind: "wm:strategy" }
|
|
2115
|
+
} };
|
|
2116
|
+
}
|
|
2117
|
+
async readWmLayout(ctx) {
|
|
2118
|
+
const session = this.resolveSession(ctx);
|
|
2119
|
+
const endpoint = ctx.params.endpoint;
|
|
2120
|
+
const wm = this.getWmNode(session.id);
|
|
2121
|
+
const wmProps = wm?.wmNode.props ?? {};
|
|
2122
|
+
const wmState = wm?.wmNode.state ?? {};
|
|
2123
|
+
return {
|
|
2124
|
+
id: "layout",
|
|
2125
|
+
path: joinURL("/", endpoint, "sessions", session.id, "wm", "layout"),
|
|
2126
|
+
content: {
|
|
2127
|
+
active: wmState.active ?? null,
|
|
2128
|
+
panels: wmProps.panels ?? [],
|
|
2129
|
+
preset: wmProps.layoutPreset ?? null,
|
|
2130
|
+
panelActives: wmState.panelActives ?? null
|
|
2131
|
+
},
|
|
2132
|
+
meta: { kind: "wm:layout" }
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
async listWmSurfaces(ctx) {
|
|
2136
|
+
const session = this.resolveSession(ctx);
|
|
2137
|
+
const endpoint = ctx.params.endpoint;
|
|
2138
|
+
const surfaces = (this.getWmNode(session.id)?.wmNode.children ?? []).filter((c) => c.type === "wm-surface");
|
|
2139
|
+
const basePath = joinURL("/", endpoint, "sessions", session.id, "wm", "surfaces");
|
|
2140
|
+
return { data: surfaces.map((c) => {
|
|
2141
|
+
const cp = c.props ?? {};
|
|
2142
|
+
const name = cp.surfaceName || c.id.replace(/^wm-surface-/, "");
|
|
2143
|
+
return {
|
|
2144
|
+
id: name,
|
|
2145
|
+
path: joinURL(basePath, name),
|
|
2146
|
+
meta: {
|
|
2147
|
+
kind: "wm:surface",
|
|
2148
|
+
title: cp.title ?? name,
|
|
2149
|
+
panel: cp.panel || void 0
|
|
2150
|
+
}
|
|
2151
|
+
};
|
|
2152
|
+
}) };
|
|
2153
|
+
}
|
|
2154
|
+
async readWmSurface(ctx) {
|
|
2155
|
+
const session = this.resolveSession(ctx);
|
|
2156
|
+
const endpoint = ctx.params.endpoint;
|
|
2157
|
+
const surfaceName = ctx.params.surfaceName;
|
|
2158
|
+
const wm = this.getWmNode(session.id);
|
|
2159
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, surfaceName) : void 0;
|
|
2160
|
+
if (!surfNode) throw new AFSNotFoundError(`Surface not found: ${surfaceName}`);
|
|
2161
|
+
const cp = surfNode.props ?? {};
|
|
2162
|
+
return {
|
|
2163
|
+
id: surfaceName,
|
|
2164
|
+
path: joinURL("/", endpoint, "sessions", session.id, "wm", "surfaces", surfaceName),
|
|
2165
|
+
content: {
|
|
2166
|
+
session: surfNode.src,
|
|
2167
|
+
panel: cp.panel ?? void 0,
|
|
2168
|
+
title: cp.title ?? cp.surfaceName ?? surfaceName,
|
|
2169
|
+
position: cp.position,
|
|
2170
|
+
size: cp.size,
|
|
2171
|
+
docked: !!cp.docked,
|
|
2172
|
+
originalPanel: cp.originalPanel ?? void 0
|
|
2173
|
+
},
|
|
2174
|
+
meta: {
|
|
2175
|
+
kind: "wm:surface",
|
|
2176
|
+
title: cp.title ?? surfaceName
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
async listWmActions(ctx) {
|
|
2181
|
+
const session = this.resolveSession(ctx);
|
|
2182
|
+
const endpoint = ctx.params.endpoint;
|
|
2183
|
+
const basePath = joinURL("/", endpoint, "sessions", session.id, "wm", ".actions");
|
|
2184
|
+
return { data: [
|
|
2185
|
+
{
|
|
2186
|
+
id: "open-surface",
|
|
2187
|
+
path: joinURL(basePath, "open-surface"),
|
|
2188
|
+
meta: {
|
|
2189
|
+
kind: "afs:executable",
|
|
2190
|
+
description: "Open (or replace) a WM surface. Universal: src can be ws:// URL, AFS path, or omitted for inline content.",
|
|
2191
|
+
inputSchema: {
|
|
2192
|
+
type: "object",
|
|
2193
|
+
required: ["name"],
|
|
2194
|
+
properties: {
|
|
2195
|
+
name: {
|
|
2196
|
+
type: "string",
|
|
2197
|
+
description: "Unique surface name"
|
|
2198
|
+
},
|
|
2199
|
+
src: {
|
|
2200
|
+
type: "string",
|
|
2201
|
+
description: "Source: ws:// URL, http:// URL, or /afs/path"
|
|
2202
|
+
},
|
|
2203
|
+
session: {
|
|
2204
|
+
type: "string",
|
|
2205
|
+
description: "Alias for src (backward compat)"
|
|
2206
|
+
},
|
|
2207
|
+
content: {
|
|
2208
|
+
type: "object",
|
|
2209
|
+
description: "Initial inline AUP tree (when no src)"
|
|
2210
|
+
},
|
|
2211
|
+
panel: {
|
|
2212
|
+
type: "string",
|
|
2213
|
+
description: "Panel assignment"
|
|
2214
|
+
},
|
|
2215
|
+
title: {
|
|
2216
|
+
type: "string",
|
|
2217
|
+
description: "Display title"
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
},
|
|
2223
|
+
{
|
|
2224
|
+
id: "set-surface-content",
|
|
2225
|
+
path: joinURL(basePath, "set-surface-content"),
|
|
2226
|
+
meta: {
|
|
2227
|
+
kind: "afs:executable",
|
|
2228
|
+
description: "Replace the inline AUP content of a surface.",
|
|
2229
|
+
inputSchema: {
|
|
2230
|
+
type: "object",
|
|
2231
|
+
required: ["surface", "content"],
|
|
2232
|
+
properties: {
|
|
2233
|
+
surface: {
|
|
2234
|
+
type: "string",
|
|
2235
|
+
description: "Surface name"
|
|
2236
|
+
},
|
|
2237
|
+
content: {
|
|
2238
|
+
type: "object",
|
|
2239
|
+
description: "AUP node tree to render"
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
},
|
|
2245
|
+
{
|
|
2246
|
+
id: "close-surface",
|
|
2247
|
+
path: joinURL(basePath, "close-surface"),
|
|
2248
|
+
meta: {
|
|
2249
|
+
kind: "afs:executable",
|
|
2250
|
+
description: "Close a WM surface.",
|
|
2251
|
+
inputSchema: {
|
|
2252
|
+
type: "object",
|
|
2253
|
+
required: ["name"],
|
|
2254
|
+
properties: { name: {
|
|
2255
|
+
type: "string",
|
|
2256
|
+
description: "Surface name to close"
|
|
2257
|
+
} }
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
id: "set-strategy",
|
|
2263
|
+
path: joinURL(basePath, "set-strategy"),
|
|
2264
|
+
meta: {
|
|
2265
|
+
kind: "afs:executable",
|
|
2266
|
+
description: "Change WM strategy.",
|
|
2267
|
+
inputSchema: {
|
|
2268
|
+
type: "object",
|
|
2269
|
+
required: ["strategy"],
|
|
2270
|
+
properties: { strategy: {
|
|
2271
|
+
type: "string",
|
|
2272
|
+
enum: [
|
|
2273
|
+
"single",
|
|
2274
|
+
"panels",
|
|
2275
|
+
"floating",
|
|
2276
|
+
"virtual"
|
|
2277
|
+
],
|
|
2278
|
+
description: "WM strategy"
|
|
2279
|
+
} }
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
},
|
|
2283
|
+
{
|
|
2284
|
+
id: "set-active",
|
|
2285
|
+
path: joinURL(basePath, "set-active"),
|
|
2286
|
+
meta: {
|
|
2287
|
+
kind: "afs:executable",
|
|
2288
|
+
description: "Set the active (focused) surface.",
|
|
2289
|
+
inputSchema: {
|
|
2290
|
+
type: "object",
|
|
2291
|
+
required: ["name"],
|
|
2292
|
+
properties: { name: {
|
|
2293
|
+
type: "string",
|
|
2294
|
+
description: "Surface name to activate"
|
|
2295
|
+
} }
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
{
|
|
2300
|
+
id: "pin-to-dock",
|
|
2301
|
+
path: joinURL(basePath, "pin-to-dock"),
|
|
2302
|
+
meta: {
|
|
2303
|
+
kind: "afs:executable",
|
|
2304
|
+
description: "Pin a floating surface to the dock strip.",
|
|
2305
|
+
inputSchema: {
|
|
2306
|
+
type: "object",
|
|
2307
|
+
required: ["name"],
|
|
2308
|
+
properties: { name: {
|
|
2309
|
+
type: "string",
|
|
2310
|
+
description: "Surface name to dock"
|
|
2311
|
+
} }
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
},
|
|
2315
|
+
{
|
|
2316
|
+
id: "unpin-from-dock",
|
|
2317
|
+
path: joinURL(basePath, "unpin-from-dock"),
|
|
2318
|
+
meta: {
|
|
2319
|
+
kind: "afs:executable",
|
|
2320
|
+
description: "Unpin a surface from the dock, restore to floating.",
|
|
2321
|
+
inputSchema: {
|
|
2322
|
+
type: "object",
|
|
2323
|
+
required: ["name"],
|
|
2324
|
+
properties: { name: {
|
|
2325
|
+
type: "string",
|
|
2326
|
+
description: "Surface name to undock"
|
|
2327
|
+
} }
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
},
|
|
2331
|
+
{
|
|
2332
|
+
id: "set-layout",
|
|
2333
|
+
path: joinURL(basePath, "set-layout"),
|
|
2334
|
+
meta: {
|
|
2335
|
+
kind: "afs:executable",
|
|
2336
|
+
description: "Set panel layout preset (explorer, settings, simple, miller).",
|
|
2337
|
+
inputSchema: {
|
|
2338
|
+
type: "object",
|
|
2339
|
+
required: ["preset"],
|
|
2340
|
+
properties: { preset: {
|
|
2341
|
+
type: "string",
|
|
2342
|
+
description: "Panel preset name (explorer, settings, simple, miller). Unknown names fall back to simple."
|
|
2343
|
+
} }
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
},
|
|
2347
|
+
{
|
|
2348
|
+
id: "collapse-panel",
|
|
2349
|
+
path: joinURL(basePath, "collapse-panel"),
|
|
2350
|
+
meta: {
|
|
2351
|
+
kind: "afs:executable",
|
|
2352
|
+
description: "Collapse a panel in the layout.",
|
|
2353
|
+
inputSchema: {
|
|
2354
|
+
type: "object",
|
|
2355
|
+
required: ["panelId"],
|
|
2356
|
+
properties: { panelId: {
|
|
2357
|
+
type: "string",
|
|
2358
|
+
description: "Panel ID to collapse"
|
|
2359
|
+
} }
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
id: "expand-panel",
|
|
2365
|
+
path: joinURL(basePath, "expand-panel"),
|
|
2366
|
+
meta: {
|
|
2367
|
+
kind: "afs:executable",
|
|
2368
|
+
description: "Expand a collapsed panel.",
|
|
2369
|
+
inputSchema: {
|
|
2370
|
+
type: "object",
|
|
2371
|
+
required: ["panelId"],
|
|
2372
|
+
properties: { panelId: {
|
|
2373
|
+
type: "string",
|
|
2374
|
+
description: "Panel ID to expand"
|
|
2375
|
+
} }
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
},
|
|
2379
|
+
{
|
|
2380
|
+
id: "move-surface",
|
|
2381
|
+
path: joinURL(basePath, "move-surface"),
|
|
2382
|
+
meta: {
|
|
2383
|
+
kind: "afs:executable",
|
|
2384
|
+
description: "Move a surface to a different panel.",
|
|
2385
|
+
inputSchema: {
|
|
2386
|
+
type: "object",
|
|
2387
|
+
required: ["name", "panel"],
|
|
2388
|
+
properties: {
|
|
2389
|
+
name: {
|
|
2390
|
+
type: "string",
|
|
2391
|
+
description: "Surface name to move"
|
|
2392
|
+
},
|
|
2393
|
+
panel: {
|
|
2394
|
+
type: "string",
|
|
2395
|
+
description: "Target panel ID"
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
},
|
|
2401
|
+
{
|
|
2402
|
+
id: "add-to-panel",
|
|
2403
|
+
path: joinURL(basePath, "add-to-panel"),
|
|
2404
|
+
meta: {
|
|
2405
|
+
kind: "afs:executable",
|
|
2406
|
+
description: "Add a surface to a panel as a new tab.",
|
|
2407
|
+
inputSchema: {
|
|
2408
|
+
type: "object",
|
|
2409
|
+
required: [
|
|
2410
|
+
"name",
|
|
2411
|
+
"session",
|
|
2412
|
+
"panel"
|
|
2413
|
+
],
|
|
2414
|
+
properties: {
|
|
2415
|
+
name: {
|
|
2416
|
+
type: "string",
|
|
2417
|
+
description: "Surface name"
|
|
2418
|
+
},
|
|
2419
|
+
session: {
|
|
2420
|
+
type: "string",
|
|
2421
|
+
description: "AUP session ID for this surface"
|
|
2422
|
+
},
|
|
2423
|
+
panel: {
|
|
2424
|
+
type: "string",
|
|
2425
|
+
description: "Target panel ID"
|
|
2426
|
+
},
|
|
2427
|
+
title: {
|
|
2428
|
+
type: "string",
|
|
2429
|
+
description: "Display title for the tab"
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
},
|
|
2435
|
+
{
|
|
2436
|
+
id: "set-panel-active",
|
|
2437
|
+
path: joinURL(basePath, "set-panel-active"),
|
|
2438
|
+
meta: {
|
|
2439
|
+
kind: "afs:executable",
|
|
2440
|
+
description: "Set the active tab (surface) within a specific panel.",
|
|
2441
|
+
inputSchema: {
|
|
2442
|
+
type: "object",
|
|
2443
|
+
required: ["panel", "surface"],
|
|
2444
|
+
properties: {
|
|
2445
|
+
panel: {
|
|
2446
|
+
type: "string",
|
|
2447
|
+
description: "Panel ID"
|
|
2448
|
+
},
|
|
2449
|
+
surface: {
|
|
2450
|
+
type: "string",
|
|
2451
|
+
description: "Surface name to activate in the panel"
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
id: "popout",
|
|
2459
|
+
path: joinURL(basePath, "popout"),
|
|
2460
|
+
meta: {
|
|
2461
|
+
kind: "afs:executable",
|
|
2462
|
+
description: "Detach a surface from its panel to a floating overlay.",
|
|
2463
|
+
inputSchema: {
|
|
2464
|
+
type: "object",
|
|
2465
|
+
required: ["name"],
|
|
2466
|
+
properties: { name: {
|
|
2467
|
+
type: "string",
|
|
2468
|
+
description: "Surface name to pop out"
|
|
2469
|
+
} }
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
},
|
|
2473
|
+
{
|
|
2474
|
+
id: "redock",
|
|
2475
|
+
path: joinURL(basePath, "redock"),
|
|
2476
|
+
meta: {
|
|
2477
|
+
kind: "afs:executable",
|
|
2478
|
+
description: "Return a floating overlay surface to its original panel.",
|
|
2479
|
+
inputSchema: {
|
|
2480
|
+
type: "object",
|
|
2481
|
+
required: ["name"],
|
|
2482
|
+
properties: { name: {
|
|
2483
|
+
type: "string",
|
|
2484
|
+
description: "Surface name to redock"
|
|
2485
|
+
} }
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
{
|
|
2490
|
+
id: "auto-arrange",
|
|
2491
|
+
path: joinURL(basePath, "auto-arrange"),
|
|
2492
|
+
meta: {
|
|
2493
|
+
kind: "afs:executable",
|
|
2494
|
+
description: "Tile all floating surfaces evenly within the viewport. Computes non-overlapping positions using an optimal grid layout.",
|
|
2495
|
+
inputSchema: {
|
|
2496
|
+
type: "object",
|
|
2497
|
+
required: ["viewport"],
|
|
2498
|
+
properties: {
|
|
2499
|
+
viewport: {
|
|
2500
|
+
type: "object",
|
|
2501
|
+
description: "Desktop viewport size",
|
|
2502
|
+
properties: {
|
|
2503
|
+
width: {
|
|
2504
|
+
type: "number",
|
|
2505
|
+
description: "Viewport width in px"
|
|
2506
|
+
},
|
|
2507
|
+
height: {
|
|
2508
|
+
type: "number",
|
|
2509
|
+
description: "Viewport height in px"
|
|
2510
|
+
}
|
|
2511
|
+
},
|
|
2512
|
+
required: ["width", "height"]
|
|
2513
|
+
},
|
|
2514
|
+
gap: {
|
|
2515
|
+
type: "number",
|
|
2516
|
+
description: "Gap between windows in px (default 8)"
|
|
2517
|
+
},
|
|
2518
|
+
padding: {
|
|
2519
|
+
type: "number",
|
|
2520
|
+
description: "Padding from viewport edges in px (default 8)"
|
|
2521
|
+
},
|
|
2522
|
+
preserveAspect: {
|
|
2523
|
+
type: "boolean",
|
|
2524
|
+
description: "Preserve each window's aspect ratio (default false)"
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
] };
|
|
2531
|
+
}
|
|
2532
|
+
async execWmOpenSurface(ctx, args) {
|
|
2533
|
+
const session = this.resolveSession(ctx);
|
|
2534
|
+
const name = args.name;
|
|
2535
|
+
if (!name) throw new Error("open-surface action requires 'name' argument");
|
|
2536
|
+
const sessionRef = args.session;
|
|
2537
|
+
const src = args.src || sessionRef;
|
|
2538
|
+
const { wmNode } = this.ensureWmNode(session.id);
|
|
2539
|
+
const surfId = `wm-surface-${name}`;
|
|
2540
|
+
const existing = this.findWmSurface(wmNode, name);
|
|
2541
|
+
const surfProps = {
|
|
2542
|
+
surfaceName: name,
|
|
2543
|
+
title: typeof args.title === "string" ? args.title : name
|
|
2544
|
+
};
|
|
2545
|
+
if (typeof args.panel === "string") surfProps.panel = args.panel;
|
|
2546
|
+
if (args.position) surfProps.position = args.position;
|
|
2547
|
+
if (args.size) surfProps.size = args.size;
|
|
2548
|
+
const surfNode = {
|
|
2549
|
+
id: surfId,
|
|
2550
|
+
type: "wm-surface",
|
|
2551
|
+
props: surfProps
|
|
2552
|
+
};
|
|
2553
|
+
if (src) surfNode.src = src;
|
|
2554
|
+
const content = args.content;
|
|
2555
|
+
if (content && typeof content === "object" && content.type) surfNode.children = [content];
|
|
2556
|
+
const ops = [];
|
|
2557
|
+
if (existing) ops.push({
|
|
2558
|
+
op: "remove",
|
|
2559
|
+
id: existing.id
|
|
2560
|
+
});
|
|
2561
|
+
ops.push({
|
|
2562
|
+
op: "create",
|
|
2563
|
+
id: surfId,
|
|
2564
|
+
parentId: "wm",
|
|
2565
|
+
node: surfNode
|
|
2566
|
+
});
|
|
2567
|
+
ops.push({
|
|
2568
|
+
op: "update",
|
|
2569
|
+
id: "wm",
|
|
2570
|
+
state: { active: name }
|
|
2571
|
+
});
|
|
2572
|
+
this.applyWmPatch(session.id, ctx, ops);
|
|
2573
|
+
return {
|
|
2574
|
+
success: true,
|
|
2575
|
+
data: {
|
|
2576
|
+
name,
|
|
2577
|
+
active: name
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
async execWmSetSurfaceContent(ctx, args) {
|
|
2582
|
+
const session = this.resolveSession(ctx);
|
|
2583
|
+
const surfaceName = args.surface;
|
|
2584
|
+
if (!surfaceName) throw new Error("set-surface-content requires 'surface' argument");
|
|
2585
|
+
const content = args.content;
|
|
2586
|
+
const wm = this.getWmNode(session.id);
|
|
2587
|
+
if (!wm) throw new Error(`No WM node for session ${session.id}`);
|
|
2588
|
+
const surfNode = this.findWmSurface(wm.wmNode, surfaceName);
|
|
2589
|
+
if (!surfNode) throw new Error(`Surface not found: ${surfaceName}`);
|
|
2590
|
+
if (content && typeof content === "object" && content.type) surfNode.children = [content];
|
|
2591
|
+
else surfNode.children = [];
|
|
2592
|
+
const store = this.getAupStore(session.id);
|
|
2593
|
+
const root = store.getRoot();
|
|
2594
|
+
if (root) store.setRoot(root);
|
|
2595
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(session.id, {
|
|
2596
|
+
type: "aup",
|
|
2597
|
+
action: "surface-update",
|
|
2598
|
+
surfaceId: surfNode.id,
|
|
2599
|
+
children: surfNode.children || [],
|
|
2600
|
+
treeVersion: store.version
|
|
2601
|
+
});
|
|
2602
|
+
return {
|
|
2603
|
+
success: true,
|
|
2604
|
+
data: { surface: surfaceName }
|
|
2605
|
+
};
|
|
2606
|
+
}
|
|
2607
|
+
async execWmCloseSurface(ctx, args) {
|
|
2608
|
+
const session = this.resolveSession(ctx);
|
|
2609
|
+
const name = args.name;
|
|
2610
|
+
if (!name) throw new Error("close-surface action requires 'name' argument");
|
|
2611
|
+
const wm = this.getWmNode(session.id);
|
|
2612
|
+
if (!wm) throw new Error(`Surface not found: ${name}`);
|
|
2613
|
+
const surfNode = this.findWmSurface(wm.wmNode, name);
|
|
2614
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2615
|
+
const ops = [{
|
|
2616
|
+
op: "remove",
|
|
2617
|
+
id: surfNode.id
|
|
2618
|
+
}];
|
|
2619
|
+
const wmState = wm.wmNode.state ?? {};
|
|
2620
|
+
if (wmState.active === name) {
|
|
2621
|
+
const remaining = (wm.wmNode.children ?? []).filter((c) => c.type === "wm-surface" && c.id !== surfNode.id);
|
|
2622
|
+
const next = remaining.length > 0 ? remaining[0].props?.surfaceName || remaining[0].id.replace(/^wm-surface-/, "") : null;
|
|
2623
|
+
ops.push({
|
|
2624
|
+
op: "update",
|
|
2625
|
+
id: "wm",
|
|
2626
|
+
state: { active: next }
|
|
2627
|
+
});
|
|
2628
|
+
}
|
|
2629
|
+
const surfPanel = (surfNode.props ?? {}).panel;
|
|
2630
|
+
const panelActives = wmState.panelActives ?? {};
|
|
2631
|
+
if (surfPanel && panelActives[surfPanel] === name) {
|
|
2632
|
+
const remainingInPanel = (wm.wmNode.children ?? []).filter((c) => c.type === "wm-surface" && c.id !== surfNode.id && c.props?.panel === surfPanel);
|
|
2633
|
+
const nextTab = remainingInPanel.length > 0 ? remainingInPanel[0].props?.surfaceName || remainingInPanel[0].id.replace(/^wm-surface-/, "") : void 0;
|
|
2634
|
+
const updatedPanelActives = { ...panelActives };
|
|
2635
|
+
if (nextTab) updatedPanelActives[surfPanel] = nextTab;
|
|
2636
|
+
else delete updatedPanelActives[surfPanel];
|
|
2637
|
+
ops.push({
|
|
2638
|
+
op: "update",
|
|
2639
|
+
id: "wm",
|
|
2640
|
+
state: { panelActives: updatedPanelActives }
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
this.applyWmPatch(session.id, ctx, ops);
|
|
2644
|
+
return {
|
|
2645
|
+
success: true,
|
|
2646
|
+
data: { active: (wm.wmNode.state ?? {}).active ?? null }
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
async execWmSetStrategy(ctx, args) {
|
|
2650
|
+
const session = this.resolveSession(ctx);
|
|
2651
|
+
const strategy = args.strategy;
|
|
2652
|
+
if (!strategy) throw new Error("set-strategy action requires 'strategy' argument");
|
|
2653
|
+
const validStrategies = [
|
|
2654
|
+
"single",
|
|
2655
|
+
"panels",
|
|
2656
|
+
"floating",
|
|
2657
|
+
"virtual"
|
|
2658
|
+
];
|
|
2659
|
+
if (!validStrategies.includes(strategy)) throw new Error(`Invalid WM strategy: ${strategy}. Must be one of: ${validStrategies.join(", ")}`);
|
|
2660
|
+
this.ensureWmNode(session.id, strategy);
|
|
2661
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2662
|
+
op: "update",
|
|
2663
|
+
id: "wm",
|
|
2664
|
+
props: { strategy }
|
|
2665
|
+
}]);
|
|
2666
|
+
return {
|
|
2667
|
+
success: true,
|
|
2668
|
+
data: { strategy }
|
|
2669
|
+
};
|
|
2670
|
+
}
|
|
2671
|
+
async execWmSetActive(ctx, args) {
|
|
2672
|
+
const session = this.resolveSession(ctx);
|
|
2673
|
+
const name = args.name;
|
|
2674
|
+
if (!name) throw new Error("set-active action requires 'name' argument");
|
|
2675
|
+
this.ensureWmNode(session.id);
|
|
2676
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2677
|
+
op: "update",
|
|
2678
|
+
id: "wm",
|
|
2679
|
+
state: { active: name }
|
|
2680
|
+
}]);
|
|
2681
|
+
return {
|
|
2682
|
+
success: true,
|
|
2683
|
+
data: { active: name }
|
|
2684
|
+
};
|
|
2685
|
+
}
|
|
2686
|
+
async execWmPinToDock(ctx, args) {
|
|
2687
|
+
const session = this.resolveSession(ctx);
|
|
2688
|
+
const name = args.name;
|
|
2689
|
+
if (!name) throw new Error("pin-to-dock action requires 'name' argument");
|
|
2690
|
+
const wm = this.getWmNode(session.id);
|
|
2691
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, name) : void 0;
|
|
2692
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2693
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2694
|
+
op: "update",
|
|
2695
|
+
id: surfNode.id,
|
|
2696
|
+
props: { docked: true }
|
|
2697
|
+
}]);
|
|
2698
|
+
return {
|
|
2699
|
+
success: true,
|
|
2700
|
+
data: {
|
|
2701
|
+
name,
|
|
2702
|
+
docked: true
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
}
|
|
2706
|
+
async execWmUnpinFromDock(ctx, args) {
|
|
2707
|
+
const session = this.resolveSession(ctx);
|
|
2708
|
+
const name = args.name;
|
|
2709
|
+
if (!name) throw new Error("unpin-from-dock action requires 'name' argument");
|
|
2710
|
+
const wm = this.getWmNode(session.id);
|
|
2711
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, name) : void 0;
|
|
2712
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2713
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2714
|
+
op: "update",
|
|
2715
|
+
id: surfNode.id,
|
|
2716
|
+
props: { docked: false }
|
|
2717
|
+
}]);
|
|
2718
|
+
return {
|
|
2719
|
+
success: true,
|
|
2720
|
+
data: {
|
|
2721
|
+
name,
|
|
2722
|
+
docked: false
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
async execWmSetLayout(ctx, args) {
|
|
2727
|
+
const session = this.resolveSession(ctx);
|
|
2728
|
+
const preset = args.preset;
|
|
2729
|
+
if (!preset) throw new Error("set-layout action requires 'preset' argument");
|
|
2730
|
+
const resolved = PANEL_PRESETS[preset] ?? PANEL_PRESETS.simple;
|
|
2731
|
+
const effectivePreset = PANEL_PRESETS[preset] ? preset : "simple";
|
|
2732
|
+
this.ensureWmNode(session.id, "panels");
|
|
2733
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2734
|
+
op: "update",
|
|
2735
|
+
id: "wm",
|
|
2736
|
+
props: {
|
|
2737
|
+
strategy: "panels",
|
|
2738
|
+
layoutPreset: effectivePreset,
|
|
2739
|
+
panels: resolved
|
|
2740
|
+
}
|
|
2741
|
+
}]);
|
|
2742
|
+
return {
|
|
2743
|
+
success: true,
|
|
2744
|
+
data: { preset: effectivePreset }
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
async execWmCollapsePanel(ctx, args) {
|
|
2748
|
+
const session = this.resolveSession(ctx);
|
|
2749
|
+
const panelId = args.panelId;
|
|
2750
|
+
if (!panelId) throw new Error("collapse-panel action requires 'panelId' argument");
|
|
2751
|
+
const wm = this.getWmNode(session.id);
|
|
2752
|
+
if (!wm) throw new Error(`Panel not found: ${panelId}`);
|
|
2753
|
+
const panels = (wm.wmNode.props ?? {}).panels ?? [];
|
|
2754
|
+
if (!panels.find((p) => p.id === panelId)) throw new Error(`Panel not found: ${panelId}`);
|
|
2755
|
+
const updated = panels.map((p) => p.id === panelId ? {
|
|
2756
|
+
...p,
|
|
2757
|
+
collapsed: true
|
|
2758
|
+
} : p);
|
|
2759
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2760
|
+
op: "update",
|
|
2761
|
+
id: "wm",
|
|
2762
|
+
props: { panels: updated }
|
|
2763
|
+
}]);
|
|
2764
|
+
return {
|
|
2765
|
+
success: true,
|
|
2766
|
+
data: {
|
|
2767
|
+
panelId,
|
|
2768
|
+
collapsed: true
|
|
2769
|
+
}
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
async execWmExpandPanel(ctx, args) {
|
|
2773
|
+
const session = this.resolveSession(ctx);
|
|
2774
|
+
const panelId = args.panelId;
|
|
2775
|
+
if (!panelId) throw new Error("expand-panel action requires 'panelId' argument");
|
|
2776
|
+
const wm = this.getWmNode(session.id);
|
|
2777
|
+
if (!wm) throw new Error(`Panel not found: ${panelId}`);
|
|
2778
|
+
const panels = (wm.wmNode.props ?? {}).panels ?? [];
|
|
2779
|
+
if (!panels.find((p) => p.id === panelId)) throw new Error(`Panel not found: ${panelId}`);
|
|
2780
|
+
const updated = panels.map((p) => p.id === panelId ? {
|
|
2781
|
+
...p,
|
|
2782
|
+
collapsed: false
|
|
2783
|
+
} : p);
|
|
2784
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2785
|
+
op: "update",
|
|
2786
|
+
id: "wm",
|
|
2787
|
+
props: { panels: updated }
|
|
2788
|
+
}]);
|
|
2789
|
+
return {
|
|
2790
|
+
success: true,
|
|
2791
|
+
data: {
|
|
2792
|
+
panelId,
|
|
2793
|
+
collapsed: false
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2797
|
+
async execWmMoveSurface(ctx, args) {
|
|
2798
|
+
const session = this.resolveSession(ctx);
|
|
2799
|
+
const name = args.name;
|
|
2800
|
+
if (!name) throw new Error("move-surface action requires 'name' argument");
|
|
2801
|
+
const panel = args.panel;
|
|
2802
|
+
if (!panel) throw new Error("move-surface action requires 'panel' argument");
|
|
2803
|
+
const wm = this.getWmNode(session.id);
|
|
2804
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, name) : void 0;
|
|
2805
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2806
|
+
const panels = (wm.wmNode.props ?? {}).panels ?? [];
|
|
2807
|
+
if (panels.length > 0 && !panels.some((p) => p.id === panel)) throw new Error(`Panel not found: ${panel}`);
|
|
2808
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2809
|
+
op: "update",
|
|
2810
|
+
id: surfNode.id,
|
|
2811
|
+
props: { panel }
|
|
2812
|
+
}]);
|
|
2813
|
+
return {
|
|
2814
|
+
success: true,
|
|
2815
|
+
data: {
|
|
2816
|
+
name,
|
|
2817
|
+
panel
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
async execWmAddToPanel(ctx, args) {
|
|
2822
|
+
const session = this.resolveSession(ctx);
|
|
2823
|
+
const name = args.name;
|
|
2824
|
+
if (!name) throw new Error("add-to-panel action requires 'name' argument");
|
|
2825
|
+
const sessionRef = args.session;
|
|
2826
|
+
if (!sessionRef) throw new Error("add-to-panel action requires 'session' argument");
|
|
2827
|
+
const panel = args.panel;
|
|
2828
|
+
if (!panel) throw new Error("add-to-panel action requires 'panel' argument");
|
|
2829
|
+
this.ensureWmNode(session.id, "panels");
|
|
2830
|
+
const panels = (this.getWmNode(session.id).wmNode.props ?? {}).panels ?? [];
|
|
2831
|
+
if (panels.length > 0 && !panels.some((p) => p.id === panel)) throw new Error(`Panel not found: ${panel}`);
|
|
2832
|
+
const surfId = `wm-surface-${name}`;
|
|
2833
|
+
const ops = [{
|
|
2834
|
+
op: "create",
|
|
2835
|
+
id: surfId,
|
|
2836
|
+
parentId: "wm",
|
|
2837
|
+
node: {
|
|
2838
|
+
id: surfId,
|
|
2839
|
+
type: "wm-surface",
|
|
2840
|
+
src: sessionRef,
|
|
2841
|
+
props: {
|
|
2842
|
+
surfaceName: name,
|
|
2843
|
+
title: typeof args.title === "string" ? args.title : name,
|
|
2844
|
+
panel
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
}, {
|
|
2848
|
+
op: "update",
|
|
2849
|
+
id: "wm",
|
|
2850
|
+
state: { active: name }
|
|
2851
|
+
}];
|
|
2852
|
+
this.applyWmPatch(session.id, ctx, ops);
|
|
2853
|
+
return {
|
|
2854
|
+
success: true,
|
|
2855
|
+
data: {
|
|
2856
|
+
name,
|
|
2857
|
+
panel,
|
|
2858
|
+
active: name
|
|
2859
|
+
}
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
async execWmSetPanelActive(ctx, args) {
|
|
2863
|
+
const session = this.resolveSession(ctx);
|
|
2864
|
+
const panel = args.panel;
|
|
2865
|
+
if (!panel) throw new Error("set-panel-active action requires 'panel' argument");
|
|
2866
|
+
const surface = args.surface;
|
|
2867
|
+
if (!surface) throw new Error("set-panel-active action requires 'surface' argument");
|
|
2868
|
+
this.ensureWmNode(session.id);
|
|
2869
|
+
const wm = this.getWmNode(session.id);
|
|
2870
|
+
if (!this.findWmSurface(wm.wmNode, surface)) throw new Error(`Surface not found: ${surface}`);
|
|
2871
|
+
const panelActives = { ...(wm.wmNode.state ?? {}).panelActives ?? {} };
|
|
2872
|
+
panelActives[panel] = surface;
|
|
2873
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2874
|
+
op: "update",
|
|
2875
|
+
id: "wm",
|
|
2876
|
+
state: { panelActives }
|
|
2877
|
+
}]);
|
|
2878
|
+
return {
|
|
2879
|
+
success: true,
|
|
2880
|
+
data: {
|
|
2881
|
+
panel,
|
|
2882
|
+
surface
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
}
|
|
2886
|
+
async execWmPopout(ctx, args) {
|
|
2887
|
+
const session = this.resolveSession(ctx);
|
|
2888
|
+
const name = args.name;
|
|
2889
|
+
if (!name) throw new Error("popout action requires 'name' argument");
|
|
2890
|
+
const wm = this.getWmNode(session.id);
|
|
2891
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, name) : void 0;
|
|
2892
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2893
|
+
const originalPanel = (surfNode.props ?? {}).panel;
|
|
2894
|
+
if (!originalPanel) throw new Error(`Surface "${name}" has no panel to pop out from`);
|
|
2895
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2896
|
+
op: "update",
|
|
2897
|
+
id: surfNode.id,
|
|
2898
|
+
props: {
|
|
2899
|
+
panel: null,
|
|
2900
|
+
originalPanel
|
|
2901
|
+
}
|
|
2902
|
+
}]);
|
|
2903
|
+
return {
|
|
2904
|
+
success: true,
|
|
2905
|
+
data: {
|
|
2906
|
+
name,
|
|
2907
|
+
originalPanel
|
|
2908
|
+
}
|
|
2909
|
+
};
|
|
2910
|
+
}
|
|
2911
|
+
async execWmRedock(ctx, args) {
|
|
2912
|
+
const session = this.resolveSession(ctx);
|
|
2913
|
+
const name = args.name;
|
|
2914
|
+
if (!name) throw new Error("redock action requires 'name' argument");
|
|
2915
|
+
const wm = this.getWmNode(session.id);
|
|
2916
|
+
const surfNode = wm ? this.findWmSurface(wm.wmNode, name) : void 0;
|
|
2917
|
+
if (!surfNode) throw new Error(`Surface not found: ${name}`);
|
|
2918
|
+
const panel = (surfNode.props ?? {}).originalPanel;
|
|
2919
|
+
if (!panel) throw new Error(`Surface "${name}" was not popped out`);
|
|
2920
|
+
this.applyWmPatch(session.id, ctx, [{
|
|
2921
|
+
op: "update",
|
|
2922
|
+
id: surfNode.id,
|
|
2923
|
+
props: {
|
|
2924
|
+
panel,
|
|
2925
|
+
originalPanel: null
|
|
2926
|
+
}
|
|
2927
|
+
}]);
|
|
2928
|
+
return {
|
|
2929
|
+
success: true,
|
|
2930
|
+
data: {
|
|
2931
|
+
name,
|
|
2932
|
+
panel
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
async execWmAutoArrange(ctx, args) {
|
|
2937
|
+
const session = this.resolveSession(ctx);
|
|
2938
|
+
const viewport = args.viewport;
|
|
2939
|
+
if (!viewport || typeof viewport.width !== "number" || typeof viewport.height !== "number") throw new Error("auto-arrange action requires 'viewport' with width and height");
|
|
2940
|
+
const wm = this.getWmNode(session.id);
|
|
2941
|
+
if (!wm) return {
|
|
2942
|
+
success: true,
|
|
2943
|
+
data: { arranged: [] }
|
|
2944
|
+
};
|
|
2945
|
+
const arranged = autoArrange((wm.wmNode.children ?? []).filter((c) => c.type === "wm-surface" && !c.props?.docked).map((c) => {
|
|
2946
|
+
const cp = c.props ?? {};
|
|
2947
|
+
return {
|
|
2948
|
+
name: cp.surfaceName || c.id.replace(/^wm-surface-/, ""),
|
|
2949
|
+
size: cp.size
|
|
2950
|
+
};
|
|
2951
|
+
}), viewport, {
|
|
2952
|
+
gap: typeof args.gap === "number" ? args.gap : void 0,
|
|
2953
|
+
padding: typeof args.padding === "number" ? args.padding : void 0,
|
|
2954
|
+
preserveAspect: typeof args.preserveAspect === "boolean" ? args.preserveAspect : void 0
|
|
2955
|
+
});
|
|
2956
|
+
const ops = [];
|
|
2957
|
+
for (const a of arranged) {
|
|
2958
|
+
const surfNode = this.findWmSurface(wm.wmNode, a.name);
|
|
2959
|
+
if (surfNode) ops.push({
|
|
2960
|
+
op: "update",
|
|
2961
|
+
id: surfNode.id,
|
|
2962
|
+
props: {
|
|
2963
|
+
position: {
|
|
2964
|
+
x: a.x,
|
|
2965
|
+
y: a.y
|
|
2966
|
+
},
|
|
2967
|
+
size: {
|
|
2968
|
+
width: a.width,
|
|
2969
|
+
height: a.height
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
if (ops.length > 0) this.applyWmPatch(session.id, ctx, ops);
|
|
2975
|
+
return {
|
|
2976
|
+
success: true,
|
|
2977
|
+
data: { arranged }
|
|
2978
|
+
};
|
|
2979
|
+
}
|
|
2980
|
+
async explainSession(ctx) {
|
|
2981
|
+
const session = this.resolveSession(ctx);
|
|
2982
|
+
return {
|
|
2983
|
+
format: "markdown",
|
|
2984
|
+
content: [
|
|
2985
|
+
`# Session ${session.id}`,
|
|
2986
|
+
"",
|
|
2987
|
+
`Endpoint: ${session.endpoint}`,
|
|
2988
|
+
`Messages: ${session.listMessages().length}`,
|
|
2989
|
+
`Pages: ${session.listPages().length}`,
|
|
2990
|
+
"",
|
|
2991
|
+
"## Paths",
|
|
2992
|
+
"- `messages/` — Message stream (read/write)",
|
|
2993
|
+
"- `pages/:id` — Managed pages (CRUD)",
|
|
2994
|
+
"- `.actions/*` — Interactive actions"
|
|
2995
|
+
].join("\n")
|
|
2996
|
+
};
|
|
2997
|
+
}
|
|
2998
|
+
/** Assert web endpoint and return channelId from params */
|
|
2999
|
+
assertLiveChannel(ctx) {
|
|
3000
|
+
const endpoint = ctx.params.endpoint;
|
|
3001
|
+
if (endpoint !== "web") throw new AFSNotFoundError(`/${endpoint}/live`);
|
|
3002
|
+
this.assertEndpoint(ctx);
|
|
3003
|
+
return ctx.params.channelId;
|
|
3004
|
+
}
|
|
3005
|
+
async listLiveChannels(ctx) {
|
|
3006
|
+
const endpoint = ctx.params.endpoint;
|
|
3007
|
+
if (endpoint !== "web") throw new AFSNotFoundError(`/${endpoint}/live`);
|
|
3008
|
+
this.assertEndpoint(ctx);
|
|
3009
|
+
return { data: this.getActiveChannelIds().map((id) => ({
|
|
3010
|
+
id,
|
|
3011
|
+
path: joinURL("/", endpoint, "live", id),
|
|
3012
|
+
meta: {
|
|
3013
|
+
kind: "live-channel",
|
|
3014
|
+
childrenCount: 1
|
|
3015
|
+
}
|
|
3016
|
+
})) };
|
|
3017
|
+
}
|
|
3018
|
+
async readLiveDirectory(ctx) {
|
|
3019
|
+
const endpoint = ctx.params.endpoint;
|
|
3020
|
+
if (endpoint !== "web") throw new AFSNotFoundError(`/${endpoint}/live`);
|
|
3021
|
+
this.assertEndpoint(ctx);
|
|
3022
|
+
const ids = this.getActiveChannelIds();
|
|
3023
|
+
return {
|
|
3024
|
+
id: "live",
|
|
3025
|
+
path: joinURL("/", endpoint, "live"),
|
|
3026
|
+
content: `${ids.length} active live channel(s)`,
|
|
3027
|
+
meta: {
|
|
3028
|
+
kind: "live-directory",
|
|
3029
|
+
childrenCount: ids.length
|
|
3030
|
+
}
|
|
3031
|
+
};
|
|
3032
|
+
}
|
|
3033
|
+
async metaLiveDirectory(ctx) {
|
|
3034
|
+
const endpoint = ctx.params.endpoint;
|
|
3035
|
+
if (endpoint !== "web") throw new AFSNotFoundError(`/${endpoint}/live`);
|
|
3036
|
+
this.assertEndpoint(ctx);
|
|
3037
|
+
const ids = this.getActiveChannelIds();
|
|
3038
|
+
return {
|
|
3039
|
+
id: "live",
|
|
3040
|
+
path: joinURL("/", endpoint, "live", ".meta"),
|
|
3041
|
+
meta: {
|
|
3042
|
+
kind: "live-directory",
|
|
3043
|
+
childrenCount: ids.length
|
|
3044
|
+
}
|
|
3045
|
+
};
|
|
3046
|
+
}
|
|
3047
|
+
async readLiveChannel(ctx) {
|
|
3048
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3049
|
+
const endpoint = ctx.params.endpoint;
|
|
3050
|
+
const hasTree = !!this.aupStores.get(this.channelAupKey(channelId))?.getRoot();
|
|
3051
|
+
return {
|
|
3052
|
+
id: channelId,
|
|
3053
|
+
path: joinURL("/", endpoint, "live", channelId),
|
|
3054
|
+
content: `Live channel: ${channelId}`,
|
|
3055
|
+
meta: {
|
|
3056
|
+
kind: "live-channel",
|
|
3057
|
+
hasTree,
|
|
3058
|
+
childrenCount: 1
|
|
3059
|
+
}
|
|
3060
|
+
};
|
|
3061
|
+
}
|
|
3062
|
+
async metaLiveChannel(ctx) {
|
|
3063
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3064
|
+
const endpoint = ctx.params.endpoint;
|
|
3065
|
+
return {
|
|
3066
|
+
id: channelId,
|
|
3067
|
+
path: joinURL("/", endpoint, "live", channelId, ".meta"),
|
|
3068
|
+
meta: {
|
|
3069
|
+
kind: "live-channel",
|
|
3070
|
+
childrenCount: 1
|
|
3071
|
+
}
|
|
3072
|
+
};
|
|
3073
|
+
}
|
|
3074
|
+
async listLiveChannel(ctx) {
|
|
3075
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3076
|
+
const endpoint = ctx.params.endpoint;
|
|
3077
|
+
return { data: [{
|
|
3078
|
+
id: "tree",
|
|
3079
|
+
path: joinURL("/", endpoint, "live", channelId, "tree"),
|
|
3080
|
+
meta: {
|
|
3081
|
+
kind: "aup:tree",
|
|
3082
|
+
childrenCount: 0
|
|
3083
|
+
}
|
|
3084
|
+
}] };
|
|
3085
|
+
}
|
|
3086
|
+
async readLiveChannelTree(ctx) {
|
|
3087
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3088
|
+
const endpoint = ctx.params.endpoint;
|
|
3089
|
+
const root = this.aupStores.get(this.channelAupKey(channelId))?.getRoot() ?? null;
|
|
3090
|
+
return {
|
|
3091
|
+
id: "tree",
|
|
3092
|
+
path: joinURL("/", endpoint, "live", channelId, "tree"),
|
|
3093
|
+
content: root,
|
|
3094
|
+
meta: { kind: "aup:tree" }
|
|
3095
|
+
};
|
|
3096
|
+
}
|
|
3097
|
+
async metaLiveChannelTree(ctx) {
|
|
3098
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3099
|
+
const endpoint = ctx.params.endpoint;
|
|
3100
|
+
return {
|
|
3101
|
+
id: "tree",
|
|
3102
|
+
path: joinURL("/", endpoint, "live", channelId, "tree", ".meta"),
|
|
3103
|
+
meta: { kind: "aup:tree" }
|
|
3104
|
+
};
|
|
3105
|
+
}
|
|
3106
|
+
async writeLiveChannelTree(ctx, payload) {
|
|
3107
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3108
|
+
const endpoint = ctx.params.endpoint;
|
|
3109
|
+
const root = payload.content;
|
|
3110
|
+
if (!root || typeof root !== "object") throw new Error("tree write requires a node object as content");
|
|
3111
|
+
const err = validateNode(root);
|
|
3112
|
+
if (err) throw new Error(`Invalid AUP node: ${err}`);
|
|
3113
|
+
const store = this.getChannelAupStore(channelId);
|
|
3114
|
+
store.setRoot(root);
|
|
3115
|
+
const meta = payload.meta ?? {};
|
|
3116
|
+
const fullPage = meta.fullPage !== false;
|
|
3117
|
+
const chrome = meta.chrome === true;
|
|
3118
|
+
const theme = typeof meta.theme === "string" ? meta.theme : void 0;
|
|
3119
|
+
const style = typeof meta.style === "string" ? meta.style : void 0;
|
|
3120
|
+
const locale = typeof meta.locale === "string" ? meta.locale : void 0;
|
|
3121
|
+
if (isAUPTransport(this.backend)) {
|
|
3122
|
+
const msg = {
|
|
3123
|
+
type: "aup",
|
|
3124
|
+
action: "render",
|
|
3125
|
+
root: store.getRoot()
|
|
3126
|
+
};
|
|
3127
|
+
if (fullPage) msg.fullPage = true;
|
|
3128
|
+
if (chrome) msg.chrome = true;
|
|
3129
|
+
if (theme) msg.theme = theme;
|
|
3130
|
+
if (style) msg.style = style;
|
|
3131
|
+
if (locale) msg.locale = locale;
|
|
3132
|
+
this.backend.sendToLiveChannel(channelId, msg);
|
|
3133
|
+
}
|
|
3134
|
+
const treePath = joinURL("/", endpoint, "live", channelId, "tree");
|
|
3135
|
+
this.emit({
|
|
3136
|
+
type: "afs:write",
|
|
3137
|
+
path: treePath,
|
|
3138
|
+
data: { root: store.getRoot() }
|
|
3139
|
+
});
|
|
3140
|
+
return { data: {
|
|
3141
|
+
id: "tree",
|
|
3142
|
+
path: treePath,
|
|
3143
|
+
content: store.getRoot(),
|
|
3144
|
+
meta: { kind: "aup:tree" }
|
|
3145
|
+
} };
|
|
3146
|
+
}
|
|
3147
|
+
async listLiveChannelActions(ctx) {
|
|
3148
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3149
|
+
const endpoint = ctx.params.endpoint;
|
|
3150
|
+
const basePath = joinURL("/", endpoint, "live", channelId, ".actions");
|
|
3151
|
+
return { data: [{
|
|
3152
|
+
id: "aup_render",
|
|
3153
|
+
path: joinURL(basePath, "aup_render"),
|
|
3154
|
+
meta: {
|
|
3155
|
+
kind: "afs:executable",
|
|
3156
|
+
description: "Render AUP tree to all live channel viewers",
|
|
3157
|
+
inputSchema: {
|
|
3158
|
+
type: "object",
|
|
3159
|
+
required: ["root"],
|
|
3160
|
+
properties: {
|
|
3161
|
+
root: {
|
|
3162
|
+
type: "object",
|
|
3163
|
+
description: "AUP node tree"
|
|
3164
|
+
},
|
|
3165
|
+
fullPage: {
|
|
3166
|
+
type: "boolean",
|
|
3167
|
+
default: true
|
|
3168
|
+
},
|
|
3169
|
+
chrome: {
|
|
3170
|
+
type: "boolean",
|
|
3171
|
+
description: "Show lang/theme/mode toolbar"
|
|
3172
|
+
},
|
|
3173
|
+
theme: {
|
|
3174
|
+
type: "string",
|
|
3175
|
+
enum: [
|
|
3176
|
+
"dark",
|
|
3177
|
+
"light",
|
|
3178
|
+
"auto"
|
|
3179
|
+
]
|
|
3180
|
+
},
|
|
3181
|
+
style: { type: "string" },
|
|
3182
|
+
locale: { type: "string" }
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
}, {
|
|
3187
|
+
id: "aup_patch",
|
|
3188
|
+
path: joinURL(basePath, "aup_patch"),
|
|
3189
|
+
meta: {
|
|
3190
|
+
kind: "afs:executable",
|
|
3191
|
+
description: "Patch AUP tree for all live channel viewers",
|
|
3192
|
+
inputSchema: {
|
|
3193
|
+
type: "object",
|
|
3194
|
+
required: ["ops"],
|
|
3195
|
+
properties: { ops: {
|
|
3196
|
+
type: "array",
|
|
3197
|
+
description: "Patch operations to apply",
|
|
3198
|
+
items: {
|
|
3199
|
+
type: "object",
|
|
3200
|
+
required: ["op", "id"],
|
|
3201
|
+
properties: {
|
|
3202
|
+
op: {
|
|
3203
|
+
type: "string",
|
|
3204
|
+
enum: [
|
|
3205
|
+
"create",
|
|
3206
|
+
"update",
|
|
3207
|
+
"remove",
|
|
3208
|
+
"reorder"
|
|
3209
|
+
],
|
|
3210
|
+
description: "Operation type"
|
|
3211
|
+
},
|
|
3212
|
+
id: {
|
|
3213
|
+
type: "string",
|
|
3214
|
+
description: "Target node id (for update/remove/reorder) or new node id (for create)"
|
|
3215
|
+
},
|
|
3216
|
+
props: {
|
|
3217
|
+
type: "object",
|
|
3218
|
+
description: "Props to merge into node (for update)"
|
|
3219
|
+
},
|
|
3220
|
+
parentId: {
|
|
3221
|
+
type: "string",
|
|
3222
|
+
description: "Parent node id (for create)"
|
|
3223
|
+
},
|
|
3224
|
+
node: {
|
|
3225
|
+
type: "object",
|
|
3226
|
+
description: "Full AUP node { id, type, props, children } (for create)"
|
|
3227
|
+
},
|
|
3228
|
+
index: {
|
|
3229
|
+
type: "number",
|
|
3230
|
+
description: "Position index (for create/reorder)"
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
} }
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
}] };
|
|
3238
|
+
}
|
|
3239
|
+
async execLiveChannelAupRender(ctx, args) {
|
|
3240
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3241
|
+
const endpoint = ctx.params.endpoint;
|
|
3242
|
+
const root = args.root;
|
|
3243
|
+
if (!root) throw new Error("aup_render action requires 'root' argument");
|
|
3244
|
+
const fullPage = args.fullPage !== false;
|
|
3245
|
+
const chrome = args.chrome === true;
|
|
3246
|
+
const theme = typeof args.theme === "string" ? args.theme : void 0;
|
|
3247
|
+
const style = typeof args.style === "string" ? args.style : void 0;
|
|
3248
|
+
const locale = typeof args.locale === "string" ? args.locale : void 0;
|
|
3249
|
+
const err = validateNode(root);
|
|
3250
|
+
if (err) throw new Error(`Invalid AUP node: ${err}`);
|
|
3251
|
+
const store = this.getChannelAupStore(channelId);
|
|
3252
|
+
store.setRoot(root);
|
|
3253
|
+
store.setRenderOptions({
|
|
3254
|
+
fullPage,
|
|
3255
|
+
chrome,
|
|
3256
|
+
theme,
|
|
3257
|
+
style,
|
|
3258
|
+
locale
|
|
3259
|
+
});
|
|
3260
|
+
if (isAUPTransport(this.backend)) {
|
|
3261
|
+
const msg = {
|
|
3262
|
+
type: "aup",
|
|
3263
|
+
action: "render",
|
|
3264
|
+
root: store.getRoot(),
|
|
3265
|
+
treeVersion: store.version
|
|
3266
|
+
};
|
|
3267
|
+
if (fullPage) msg.fullPage = true;
|
|
3268
|
+
if (chrome) msg.chrome = true;
|
|
3269
|
+
if (theme) msg.theme = theme;
|
|
3270
|
+
if (style) msg.style = style;
|
|
3271
|
+
if (locale) msg.locale = locale;
|
|
3272
|
+
this.backend.sendToLiveChannel(channelId, msg);
|
|
3273
|
+
}
|
|
3274
|
+
const treePath = joinURL("/", endpoint, "live", channelId, "tree");
|
|
3275
|
+
this.emit({
|
|
3276
|
+
type: "afs:write",
|
|
3277
|
+
path: treePath,
|
|
3278
|
+
data: { root: store.getRoot() }
|
|
3279
|
+
});
|
|
3280
|
+
return { success: true };
|
|
3281
|
+
}
|
|
3282
|
+
async execLiveChannelAupPatch(ctx, args) {
|
|
3283
|
+
const channelId = this.assertLiveChannel(ctx);
|
|
3284
|
+
const endpoint = ctx.params.endpoint;
|
|
3285
|
+
const ops = args.ops;
|
|
3286
|
+
if (!ops || !Array.isArray(ops)) throw new Error("aup_patch action requires 'ops' array argument");
|
|
3287
|
+
const store = this.getChannelAupStore(channelId);
|
|
3288
|
+
store.applyPatch(ops);
|
|
3289
|
+
if (isAUPTransport(this.backend)) this.backend.sendToLiveChannel(channelId, {
|
|
3290
|
+
type: "aup",
|
|
3291
|
+
action: "patch",
|
|
3292
|
+
ops,
|
|
3293
|
+
treeVersion: store.version
|
|
3294
|
+
});
|
|
3295
|
+
const treePath = joinURL("/", endpoint, "live", channelId, "tree");
|
|
3296
|
+
this.emit({
|
|
3297
|
+
type: "afs:write",
|
|
3298
|
+
path: treePath,
|
|
3299
|
+
data: { root: store.getRoot() }
|
|
3300
|
+
});
|
|
3301
|
+
return { success: true };
|
|
3302
|
+
}
|
|
3303
|
+
sessionActionEntries(basePath) {
|
|
3304
|
+
return [
|
|
3305
|
+
{
|
|
3306
|
+
id: "prompt",
|
|
3307
|
+
path: joinURL(basePath, "prompt"),
|
|
3308
|
+
meta: {
|
|
3309
|
+
kind: "afs:executable",
|
|
3310
|
+
description: "Ask user a question",
|
|
3311
|
+
inputSchema: {
|
|
3312
|
+
type: "object",
|
|
3313
|
+
required: ["message"],
|
|
3314
|
+
properties: {
|
|
3315
|
+
message: { type: "string" },
|
|
3316
|
+
type: {
|
|
3317
|
+
type: "string",
|
|
3318
|
+
enum: [
|
|
3319
|
+
"text",
|
|
3320
|
+
"password",
|
|
3321
|
+
"confirm",
|
|
3322
|
+
"select",
|
|
3323
|
+
"multiselect"
|
|
3324
|
+
],
|
|
3325
|
+
default: "text"
|
|
3326
|
+
},
|
|
3327
|
+
options: {
|
|
3328
|
+
type: "array",
|
|
3329
|
+
items: { type: "string" }
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
},
|
|
3335
|
+
{
|
|
3336
|
+
id: "clear",
|
|
3337
|
+
path: joinURL(basePath, "clear"),
|
|
3338
|
+
meta: {
|
|
3339
|
+
kind: "afs:executable",
|
|
3340
|
+
description: "Clear screen",
|
|
3341
|
+
inputSchema: {
|
|
3342
|
+
type: "object",
|
|
3343
|
+
properties: {}
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
},
|
|
3347
|
+
{
|
|
3348
|
+
id: "notify",
|
|
3349
|
+
path: joinURL(basePath, "notify"),
|
|
3350
|
+
meta: {
|
|
3351
|
+
kind: "afs:executable",
|
|
3352
|
+
description: "Send notification",
|
|
3353
|
+
inputSchema: {
|
|
3354
|
+
type: "object",
|
|
3355
|
+
required: ["message"],
|
|
3356
|
+
properties: { message: { type: "string" } }
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
},
|
|
3360
|
+
{
|
|
3361
|
+
id: "navigate",
|
|
3362
|
+
path: joinURL(basePath, "navigate"),
|
|
3363
|
+
meta: {
|
|
3364
|
+
kind: "afs:executable",
|
|
3365
|
+
description: "Navigate to a managed page",
|
|
3366
|
+
inputSchema: {
|
|
3367
|
+
type: "object",
|
|
3368
|
+
required: ["page"],
|
|
3369
|
+
properties: { page: { type: "string" } }
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
},
|
|
3373
|
+
{
|
|
3374
|
+
id: "dialog",
|
|
3375
|
+
path: joinURL(basePath, "dialog"),
|
|
3376
|
+
meta: {
|
|
3377
|
+
kind: "afs:executable",
|
|
3378
|
+
description: "Show dialog with custom buttons",
|
|
3379
|
+
inputSchema: {
|
|
3380
|
+
type: "object",
|
|
3381
|
+
required: [
|
|
3382
|
+
"title",
|
|
3383
|
+
"content",
|
|
3384
|
+
"buttons"
|
|
3385
|
+
],
|
|
3386
|
+
properties: {
|
|
3387
|
+
title: { type: "string" },
|
|
3388
|
+
content: { type: "string" },
|
|
3389
|
+
buttons: {
|
|
3390
|
+
type: "array",
|
|
3391
|
+
items: { type: "string" }
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3396
|
+
},
|
|
3397
|
+
{
|
|
3398
|
+
id: "progress",
|
|
3399
|
+
path: joinURL(basePath, "progress"),
|
|
3400
|
+
meta: {
|
|
3401
|
+
kind: "afs:executable",
|
|
3402
|
+
description: "Display/update progress indicator",
|
|
3403
|
+
inputSchema: {
|
|
3404
|
+
type: "object",
|
|
3405
|
+
required: [
|
|
3406
|
+
"label",
|
|
3407
|
+
"value",
|
|
3408
|
+
"max"
|
|
3409
|
+
],
|
|
3410
|
+
properties: {
|
|
3411
|
+
label: { type: "string" },
|
|
3412
|
+
value: { type: "number" },
|
|
3413
|
+
max: { type: "number" }
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
},
|
|
3418
|
+
{
|
|
3419
|
+
id: "form",
|
|
3420
|
+
path: joinURL(basePath, "form"),
|
|
3421
|
+
meta: {
|
|
3422
|
+
kind: "afs:executable",
|
|
3423
|
+
description: "Collect structured input via form",
|
|
3424
|
+
inputSchema: {
|
|
3425
|
+
type: "object",
|
|
3426
|
+
required: ["fields"],
|
|
3427
|
+
properties: {
|
|
3428
|
+
title: { type: "string" },
|
|
3429
|
+
fields: {
|
|
3430
|
+
type: "array",
|
|
3431
|
+
items: { type: "object" }
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
},
|
|
3437
|
+
{
|
|
3438
|
+
id: "table",
|
|
3439
|
+
path: joinURL(basePath, "table"),
|
|
3440
|
+
meta: {
|
|
3441
|
+
kind: "afs:executable",
|
|
3442
|
+
description: "Display tabular data",
|
|
3443
|
+
inputSchema: {
|
|
3444
|
+
type: "object",
|
|
3445
|
+
required: ["headers", "rows"],
|
|
3446
|
+
properties: {
|
|
3447
|
+
headers: {
|
|
3448
|
+
type: "array",
|
|
3449
|
+
items: { type: "string" }
|
|
3450
|
+
},
|
|
3451
|
+
rows: {
|
|
3452
|
+
type: "array",
|
|
3453
|
+
items: {
|
|
3454
|
+
type: "array",
|
|
3455
|
+
items: { type: "string" }
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
},
|
|
3462
|
+
{
|
|
3463
|
+
id: "toast",
|
|
3464
|
+
path: joinURL(basePath, "toast"),
|
|
3465
|
+
meta: {
|
|
3466
|
+
kind: "afs:executable",
|
|
3467
|
+
description: "Show lightweight toast notification",
|
|
3468
|
+
inputSchema: {
|
|
3469
|
+
type: "object",
|
|
3470
|
+
required: ["message"],
|
|
3471
|
+
properties: {
|
|
3472
|
+
message: { type: "string" },
|
|
3473
|
+
toastType: {
|
|
3474
|
+
type: "string",
|
|
3475
|
+
enum: [
|
|
3476
|
+
"info",
|
|
3477
|
+
"success",
|
|
3478
|
+
"warning",
|
|
3479
|
+
"error"
|
|
3480
|
+
],
|
|
3481
|
+
default: "info"
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
},
|
|
3487
|
+
{
|
|
3488
|
+
id: "aup_render",
|
|
3489
|
+
path: joinURL(basePath, "aup_render"),
|
|
3490
|
+
meta: {
|
|
3491
|
+
kind: "afs:executable",
|
|
3492
|
+
description: "Render an AUP node graph (full replacement)",
|
|
3493
|
+
inputSchema: {
|
|
3494
|
+
type: "object",
|
|
3495
|
+
required: ["root"],
|
|
3496
|
+
properties: {
|
|
3497
|
+
root: {
|
|
3498
|
+
type: "object",
|
|
3499
|
+
description: "AUP node tree (id, type, props, children)"
|
|
3500
|
+
},
|
|
3501
|
+
fullPage: {
|
|
3502
|
+
type: "boolean",
|
|
3503
|
+
description: "When true, renders AUP as full-page (no chat chrome)"
|
|
3504
|
+
},
|
|
3505
|
+
chrome: {
|
|
3506
|
+
type: "boolean",
|
|
3507
|
+
description: "When true, shows lang/theme/mode toolbar. Hidden by default."
|
|
3508
|
+
},
|
|
3509
|
+
theme: {
|
|
3510
|
+
type: "string",
|
|
3511
|
+
enum: [
|
|
3512
|
+
"dark",
|
|
3513
|
+
"light",
|
|
3514
|
+
"auto"
|
|
3515
|
+
],
|
|
3516
|
+
description: "Color mode (default: auto, follows system preference)"
|
|
3517
|
+
},
|
|
3518
|
+
style: {
|
|
3519
|
+
type: "string",
|
|
3520
|
+
enum: [
|
|
3521
|
+
"midnight",
|
|
3522
|
+
"clean",
|
|
3523
|
+
"glass",
|
|
3524
|
+
"brutalist",
|
|
3525
|
+
"soft",
|
|
3526
|
+
"cyber"
|
|
3527
|
+
],
|
|
3528
|
+
description: "Structural style (default: midnight)"
|
|
3529
|
+
},
|
|
3530
|
+
locale: {
|
|
3531
|
+
type: "string",
|
|
3532
|
+
description: "Display locale for UI chrome (e.g. 'en', 'zh', 'ja')"
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
},
|
|
3538
|
+
{
|
|
3539
|
+
id: "aup_patch",
|
|
3540
|
+
path: joinURL(basePath, "aup_patch"),
|
|
3541
|
+
meta: {
|
|
3542
|
+
kind: "afs:executable",
|
|
3543
|
+
description: "Apply incremental patches to the AUP graph",
|
|
3544
|
+
inputSchema: {
|
|
3545
|
+
type: "object",
|
|
3546
|
+
required: ["ops"],
|
|
3547
|
+
properties: { ops: {
|
|
3548
|
+
type: "array",
|
|
3549
|
+
description: "Patch operations to apply",
|
|
3550
|
+
items: {
|
|
3551
|
+
type: "object",
|
|
3552
|
+
required: ["op", "id"],
|
|
3553
|
+
properties: {
|
|
3554
|
+
op: {
|
|
3555
|
+
type: "string",
|
|
3556
|
+
enum: [
|
|
3557
|
+
"create",
|
|
3558
|
+
"update",
|
|
3559
|
+
"remove",
|
|
3560
|
+
"reorder"
|
|
3561
|
+
],
|
|
3562
|
+
description: "Operation type"
|
|
3563
|
+
},
|
|
3564
|
+
id: {
|
|
3565
|
+
type: "string",
|
|
3566
|
+
description: "Target node id (for update/remove/reorder) or new node id (for create)"
|
|
3567
|
+
},
|
|
3568
|
+
props: {
|
|
3569
|
+
type: "object",
|
|
3570
|
+
description: "Props to merge into node (for update)"
|
|
3571
|
+
},
|
|
3572
|
+
parentId: {
|
|
3573
|
+
type: "string",
|
|
3574
|
+
description: "Parent node id (for create)"
|
|
3575
|
+
},
|
|
3576
|
+
node: {
|
|
3577
|
+
type: "object",
|
|
3578
|
+
description: "Full AUP node { id, type, props, children } (for create)"
|
|
3579
|
+
},
|
|
3580
|
+
index: {
|
|
3581
|
+
type: "number",
|
|
3582
|
+
description: "Position index (for create/reorder)"
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
} }
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
},
|
|
3590
|
+
{
|
|
3591
|
+
id: "aup_save",
|
|
3592
|
+
path: joinURL(basePath, "aup_save"),
|
|
3593
|
+
meta: {
|
|
3594
|
+
kind: "afs:executable",
|
|
3595
|
+
description: "Save current AUP graph to a session page",
|
|
3596
|
+
inputSchema: {
|
|
3597
|
+
type: "object",
|
|
3598
|
+
required: ["pageId"],
|
|
3599
|
+
properties: { pageId: {
|
|
3600
|
+
type: "string",
|
|
3601
|
+
description: "Page ID to save the graph under"
|
|
3602
|
+
} }
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
},
|
|
3606
|
+
{
|
|
3607
|
+
id: "aup_load",
|
|
3608
|
+
path: joinURL(basePath, "aup_load"),
|
|
3609
|
+
meta: {
|
|
3610
|
+
kind: "afs:executable",
|
|
3611
|
+
description: "Load a previously saved AUP graph from a session page",
|
|
3612
|
+
inputSchema: {
|
|
3613
|
+
type: "object",
|
|
3614
|
+
required: ["pageId"],
|
|
3615
|
+
properties: {
|
|
3616
|
+
pageId: {
|
|
3617
|
+
type: "string",
|
|
3618
|
+
description: "Page ID to load the graph from"
|
|
3619
|
+
},
|
|
3620
|
+
fullPage: {
|
|
3621
|
+
type: "boolean",
|
|
3622
|
+
description: "When true, renders AUP as full-page (no chat chrome)"
|
|
3623
|
+
},
|
|
3624
|
+
chrome: {
|
|
3625
|
+
type: "boolean",
|
|
3626
|
+
description: "When true, shows lang/theme/mode toolbar. Hidden by default."
|
|
3627
|
+
},
|
|
3628
|
+
theme: {
|
|
3629
|
+
type: "string",
|
|
3630
|
+
enum: [
|
|
3631
|
+
"dark",
|
|
3632
|
+
"light",
|
|
3633
|
+
"auto"
|
|
3634
|
+
],
|
|
3635
|
+
description: "Color mode (default: auto, follows system preference)"
|
|
3636
|
+
},
|
|
3637
|
+
style: {
|
|
3638
|
+
type: "string",
|
|
3639
|
+
enum: [
|
|
3640
|
+
"midnight",
|
|
3641
|
+
"clean",
|
|
3642
|
+
"glass",
|
|
3643
|
+
"brutalist",
|
|
3644
|
+
"soft",
|
|
3645
|
+
"cyber"
|
|
3646
|
+
],
|
|
3647
|
+
description: "Structural style (default: midnight)"
|
|
3648
|
+
},
|
|
3649
|
+
locale: {
|
|
3650
|
+
type: "string",
|
|
3651
|
+
description: "Display locale for UI chrome (e.g. 'en', 'zh', 'ja')"
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
},
|
|
3657
|
+
{
|
|
3658
|
+
id: "aup_stage",
|
|
3659
|
+
path: joinURL(basePath, "aup_stage"),
|
|
3660
|
+
meta: {
|
|
3661
|
+
kind: "afs:executable",
|
|
3662
|
+
description: "Stage an AUP scene (pre-render off-screen). Use aup_take to swap it live.",
|
|
3663
|
+
inputSchema: {
|
|
3664
|
+
type: "object",
|
|
3665
|
+
required: ["sceneId", "root"],
|
|
3666
|
+
properties: {
|
|
3667
|
+
sceneId: {
|
|
3668
|
+
type: "string",
|
|
3669
|
+
description: "Unique scene identifier"
|
|
3670
|
+
},
|
|
3671
|
+
root: {
|
|
3672
|
+
type: "object",
|
|
3673
|
+
description: "AUP node tree"
|
|
3674
|
+
},
|
|
3675
|
+
fullPage: { type: "boolean" },
|
|
3676
|
+
chrome: { type: "boolean" },
|
|
3677
|
+
theme: { type: "string" },
|
|
3678
|
+
style: { type: "string" },
|
|
3679
|
+
locale: { type: "string" }
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
},
|
|
3684
|
+
{
|
|
3685
|
+
id: "aup_take",
|
|
3686
|
+
path: joinURL(basePath, "aup_take"),
|
|
3687
|
+
meta: {
|
|
3688
|
+
kind: "afs:executable",
|
|
3689
|
+
description: "Take a staged scene live (CSS swap, zero DOM teardown).",
|
|
3690
|
+
inputSchema: {
|
|
3691
|
+
type: "object",
|
|
3692
|
+
required: ["sceneId"],
|
|
3693
|
+
properties: {
|
|
3694
|
+
sceneId: {
|
|
3695
|
+
type: "string",
|
|
3696
|
+
description: "Scene to take live"
|
|
3697
|
+
},
|
|
3698
|
+
transition: {
|
|
3699
|
+
type: "string",
|
|
3700
|
+
enum: ["cut", "dissolve"],
|
|
3701
|
+
description: "Transition type (default: cut)"
|
|
3702
|
+
},
|
|
3703
|
+
duration: {
|
|
3704
|
+
type: "number",
|
|
3705
|
+
description: "Transition duration in ms"
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
}
|
|
3710
|
+
},
|
|
3711
|
+
{
|
|
3712
|
+
id: "aup_release",
|
|
3713
|
+
path: joinURL(basePath, "aup_release"),
|
|
3714
|
+
meta: {
|
|
3715
|
+
kind: "afs:executable",
|
|
3716
|
+
description: "Release a staged scene's resources. Cannot release the active scene.",
|
|
3717
|
+
inputSchema: {
|
|
3718
|
+
type: "object",
|
|
3719
|
+
required: ["sceneId"],
|
|
3720
|
+
properties: { sceneId: {
|
|
3721
|
+
type: "string",
|
|
3722
|
+
description: "Scene to release"
|
|
3723
|
+
} }
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
];
|
|
3728
|
+
}
|
|
3729
|
+
async listSessionActions(ctx) {
|
|
3730
|
+
const session = this.resolveSession(ctx);
|
|
3731
|
+
const endpoint = ctx.params.endpoint;
|
|
3732
|
+
const basePath = joinURL("/", endpoint, "sessions", session.id, ".actions");
|
|
3733
|
+
return { data: this.sessionActionEntries(basePath) };
|
|
3734
|
+
}
|
|
3735
|
+
async execSessionPrompt(ctx, args) {
|
|
3736
|
+
const session = this.resolveSession(ctx);
|
|
3737
|
+
const message = args.message;
|
|
3738
|
+
if (!message) throw new Error("prompt action requires 'message' argument");
|
|
3739
|
+
const type = args.type ?? "text";
|
|
3740
|
+
const options = args.options;
|
|
3741
|
+
const promptMsg = session.addMessage({
|
|
3742
|
+
type: "prompt",
|
|
3743
|
+
from: "agent",
|
|
3744
|
+
message,
|
|
3745
|
+
promptType: type,
|
|
3746
|
+
...options ? { options } : {}
|
|
3747
|
+
});
|
|
3748
|
+
const result = await this.backend.prompt({
|
|
3749
|
+
message,
|
|
3750
|
+
type,
|
|
3751
|
+
options
|
|
3752
|
+
});
|
|
3753
|
+
session.addMessage({
|
|
3754
|
+
type: "prompt.response",
|
|
3755
|
+
from: "user",
|
|
3756
|
+
ref: promptMsg.id,
|
|
3757
|
+
value: result
|
|
3758
|
+
});
|
|
3759
|
+
return {
|
|
3760
|
+
success: true,
|
|
3761
|
+
data: { response: result }
|
|
3762
|
+
};
|
|
3763
|
+
}
|
|
3764
|
+
async execSessionForm(ctx, args) {
|
|
3765
|
+
const session = this.resolveSession(ctx);
|
|
3766
|
+
const fields = args.fields;
|
|
3767
|
+
if (!fields || fields.length === 0) throw new Error("form action requires non-empty 'fields' argument");
|
|
3768
|
+
const title = args.title;
|
|
3769
|
+
const formMsg = session.addMessage({
|
|
3770
|
+
type: "form",
|
|
3771
|
+
from: "agent",
|
|
3772
|
+
...title ? { title } : {},
|
|
3773
|
+
fields
|
|
3774
|
+
});
|
|
3775
|
+
const values = {};
|
|
3776
|
+
for (const field of fields) {
|
|
3777
|
+
const promptType = field.type === "password" ? "password" : "text";
|
|
3778
|
+
const result = await this.backend.prompt({
|
|
3779
|
+
message: `${field.label}:`,
|
|
3780
|
+
type: promptType
|
|
3781
|
+
});
|
|
3782
|
+
values[field.name] = result;
|
|
3783
|
+
}
|
|
3784
|
+
session.addMessage({
|
|
3785
|
+
type: "form.response",
|
|
3786
|
+
from: "user",
|
|
3787
|
+
ref: formMsg.id,
|
|
3788
|
+
data: values
|
|
3789
|
+
});
|
|
3790
|
+
return {
|
|
3791
|
+
success: true,
|
|
3792
|
+
data: { values }
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
async execSessionDialog(ctx, args) {
|
|
3796
|
+
const session = this.resolveSession(ctx);
|
|
3797
|
+
const title = args.title;
|
|
3798
|
+
if (!title) throw new Error("dialog action requires 'title' argument");
|
|
3799
|
+
const content = args.content ?? "";
|
|
3800
|
+
const buttons = args.buttons;
|
|
3801
|
+
if (!buttons || buttons.length === 0) throw new Error("dialog action requires 'buttons' argument");
|
|
3802
|
+
const dialogMsg = session.addMessage({
|
|
3803
|
+
type: "dialog",
|
|
3804
|
+
from: "agent",
|
|
3805
|
+
title,
|
|
3806
|
+
content,
|
|
3807
|
+
buttons
|
|
3808
|
+
});
|
|
3809
|
+
const result = await this.backend.prompt({
|
|
3810
|
+
message: `${title}\n${content}`,
|
|
3811
|
+
type: "select",
|
|
3812
|
+
options: buttons
|
|
3813
|
+
});
|
|
3814
|
+
session.addMessage({
|
|
3815
|
+
type: "dialog.response",
|
|
3816
|
+
from: "user",
|
|
3817
|
+
ref: dialogMsg.id,
|
|
3818
|
+
selected: result
|
|
3819
|
+
});
|
|
3820
|
+
return {
|
|
3821
|
+
success: true,
|
|
3822
|
+
data: { selection: result }
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
async execSessionTable(ctx, args) {
|
|
3826
|
+
const session = this.resolveSession(ctx);
|
|
3827
|
+
const headers = args.headers;
|
|
3828
|
+
if (!headers || headers.length === 0) throw new Error("table action requires 'headers' argument");
|
|
3829
|
+
const rows = args.rows ?? [];
|
|
3830
|
+
session.addMessage({
|
|
3831
|
+
type: "table",
|
|
3832
|
+
from: "agent",
|
|
3833
|
+
headers,
|
|
3834
|
+
rows
|
|
3835
|
+
});
|
|
3836
|
+
const colWidths = headers.map((h, i) => {
|
|
3837
|
+
let max = h.length;
|
|
3838
|
+
for (const row of rows) if (row[i] && row[i].length > max) max = row[i].length;
|
|
3839
|
+
return max;
|
|
3840
|
+
});
|
|
3841
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
3842
|
+
const headerLine = headers.map((h, i) => pad(h, colWidths[i])).join(" | ");
|
|
3843
|
+
const separator = colWidths.map((w) => "-".repeat(w)).join("-+-");
|
|
3844
|
+
const dataLines = rows.map((row) => headers.map((_, i) => pad(row[i] ?? "", colWidths[i])).join(" | "));
|
|
3845
|
+
await this.backend.write([
|
|
3846
|
+
headerLine,
|
|
3847
|
+
separator,
|
|
3848
|
+
...dataLines
|
|
3849
|
+
].join("\n"));
|
|
3850
|
+
return { success: true };
|
|
3851
|
+
}
|
|
3852
|
+
async execSessionToast(ctx, args) {
|
|
3853
|
+
const session = this.resolveSession(ctx);
|
|
3854
|
+
const message = args.message;
|
|
3855
|
+
if (!message) throw new Error("toast action requires 'message' argument");
|
|
3856
|
+
session.addMessage({
|
|
3857
|
+
type: "notification",
|
|
3858
|
+
from: "agent",
|
|
3859
|
+
message
|
|
3860
|
+
});
|
|
3861
|
+
await this.backend.notify(message);
|
|
3862
|
+
return { success: true };
|
|
3863
|
+
}
|
|
3864
|
+
async execSessionProgress(ctx, args) {
|
|
3865
|
+
const session = this.resolveSession(ctx);
|
|
3866
|
+
const label = args.label ?? "";
|
|
3867
|
+
const value = args.value;
|
|
3868
|
+
if (value === void 0 || value === null) throw new Error("progress action requires 'value' argument");
|
|
3869
|
+
const max = args.max ?? 100;
|
|
3870
|
+
const pct = Math.round(value / max * 100);
|
|
3871
|
+
session.addMessage({
|
|
3872
|
+
type: "progress",
|
|
3873
|
+
from: "agent",
|
|
3874
|
+
label,
|
|
3875
|
+
value,
|
|
3876
|
+
max
|
|
3877
|
+
});
|
|
3878
|
+
await this.backend.notify(`[${pct}%] ${label}`);
|
|
3879
|
+
return {
|
|
3880
|
+
success: true,
|
|
3881
|
+
data: {
|
|
3882
|
+
value,
|
|
3883
|
+
max,
|
|
3884
|
+
percent: pct
|
|
3885
|
+
}
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
async execSessionClear(ctx, _args) {
|
|
3889
|
+
this.resolveSession(ctx);
|
|
3890
|
+
await this.backend.clear();
|
|
3891
|
+
return { success: true };
|
|
3892
|
+
}
|
|
3893
|
+
async execSessionNavigate(ctx, args) {
|
|
3894
|
+
const session = this.resolveSession(ctx);
|
|
3895
|
+
const pageId = args.page;
|
|
3896
|
+
if (!pageId) throw new Error("navigate action requires 'page' argument");
|
|
3897
|
+
const page = session.getPage(pageId) ?? this.pages.get(pageId);
|
|
3898
|
+
if (!page) throw new Error(`Page not found: ${pageId}`);
|
|
3899
|
+
if (this.backend.navigate) await this.backend.navigate(pageId, page.content, page.format, page.layout);
|
|
3900
|
+
else await this.backend.write(page.content);
|
|
3901
|
+
return { success: true };
|
|
3902
|
+
}
|
|
3903
|
+
async execSessionNotify(ctx, args) {
|
|
3904
|
+
const session = this.resolveSession(ctx);
|
|
3905
|
+
const message = args.message;
|
|
3906
|
+
if (!message) throw new Error("notify action requires 'message' argument");
|
|
3907
|
+
session.addMessage({
|
|
3908
|
+
type: "notification",
|
|
3909
|
+
from: "agent",
|
|
3910
|
+
message
|
|
3911
|
+
});
|
|
3912
|
+
await this.backend.notify(message);
|
|
3913
|
+
return { success: true };
|
|
3914
|
+
}
|
|
3915
|
+
async execSessionAupRender(ctx, args) {
|
|
3916
|
+
const session = this.resolveSession(ctx);
|
|
3917
|
+
const root = args.root;
|
|
3918
|
+
if (!root) throw new Error("aup_render action requires 'root' argument");
|
|
3919
|
+
const fullPage = args.fullPage === true;
|
|
3920
|
+
const chrome = args.chrome === true;
|
|
3921
|
+
const theme = typeof args.theme === "string" ? args.theme : void 0;
|
|
3922
|
+
const style = typeof args.style === "string" ? args.style : void 0;
|
|
3923
|
+
const locale = typeof args.locale === "string" ? args.locale : void 0;
|
|
3924
|
+
const err = validateNode(root);
|
|
3925
|
+
if (err) throw new Error(`Invalid AUP node: ${err}`);
|
|
3926
|
+
const store = this.getAupStore(session.id);
|
|
3927
|
+
store.setRoot(root);
|
|
3928
|
+
store.setRenderOptions({
|
|
3929
|
+
fullPage,
|
|
3930
|
+
chrome,
|
|
3931
|
+
theme,
|
|
3932
|
+
style,
|
|
3933
|
+
locale
|
|
3934
|
+
});
|
|
3935
|
+
if (isAUPTransport(this.backend)) {
|
|
3936
|
+
const msg = {
|
|
3937
|
+
type: "aup",
|
|
3938
|
+
action: "render",
|
|
3939
|
+
root: this.degradeForSession(store.getRoot(), session),
|
|
3940
|
+
treeVersion: store.version
|
|
3941
|
+
};
|
|
3942
|
+
if (fullPage) msg.fullPage = true;
|
|
3943
|
+
if (chrome) msg.chrome = true;
|
|
3944
|
+
if (theme) msg.theme = theme;
|
|
3945
|
+
if (style) msg.style = style;
|
|
3946
|
+
if (locale) msg.locale = locale;
|
|
3947
|
+
this.backend.sendToSession(session.id, msg);
|
|
3948
|
+
}
|
|
3949
|
+
const endpoint = ctx.params.endpoint;
|
|
3950
|
+
const treePath = joinURL("/", endpoint, "sessions", session.id, "tree");
|
|
3951
|
+
this.emit({
|
|
3952
|
+
type: "afs:write",
|
|
3953
|
+
path: treePath,
|
|
3954
|
+
data: { root: store.getRoot() }
|
|
3955
|
+
});
|
|
3956
|
+
return { success: true };
|
|
3957
|
+
}
|
|
3958
|
+
async execSessionAupPatch(ctx, args) {
|
|
3959
|
+
const session = this.resolveSession(ctx);
|
|
3960
|
+
const ops = args.ops;
|
|
3961
|
+
if (!ops || !Array.isArray(ops)) throw new Error("aup_patch action requires 'ops' array argument");
|
|
3962
|
+
const store = this.getAupStore(session.id);
|
|
3963
|
+
store.applyPatch(ops);
|
|
3964
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(session.id, {
|
|
3965
|
+
type: "aup",
|
|
3966
|
+
action: "patch",
|
|
3967
|
+
ops,
|
|
3968
|
+
treeVersion: store.version
|
|
3969
|
+
});
|
|
3970
|
+
const endpoint = ctx.params.endpoint;
|
|
3971
|
+
const treePath = joinURL("/", endpoint, "sessions", session.id, "tree");
|
|
3972
|
+
this.emit({
|
|
3973
|
+
type: "afs:write",
|
|
3974
|
+
path: treePath,
|
|
3975
|
+
data: { root: store.getRoot() }
|
|
3976
|
+
});
|
|
3977
|
+
return { success: true };
|
|
3978
|
+
}
|
|
3979
|
+
async execSessionAupSave(ctx, args) {
|
|
3980
|
+
const session = this.resolveSession(ctx);
|
|
3981
|
+
const pageId = args.pageId;
|
|
3982
|
+
if (!pageId) throw new Error("aup_save action requires 'pageId' argument");
|
|
3983
|
+
const root = this.aupStores.get(session.id)?.getRoot();
|
|
3984
|
+
if (!root) throw new Error("No active AUP graph to save");
|
|
3985
|
+
const graphJson = JSON.stringify(root);
|
|
3986
|
+
session.setPage(pageId, {
|
|
3987
|
+
content: graphJson,
|
|
3988
|
+
format: "application/json"
|
|
3989
|
+
});
|
|
3990
|
+
return { success: true };
|
|
3991
|
+
}
|
|
3992
|
+
async execSessionAupLoad(ctx, args) {
|
|
3993
|
+
const session = this.resolveSession(ctx);
|
|
3994
|
+
const pageId = args.pageId;
|
|
3995
|
+
if (!pageId) throw new Error("aup_load action requires 'pageId' argument");
|
|
3996
|
+
const fullPage = args.fullPage === true;
|
|
3997
|
+
const chrome = args.chrome === true;
|
|
3998
|
+
const theme = typeof args.theme === "string" ? args.theme : void 0;
|
|
3999
|
+
const style = typeof args.style === "string" ? args.style : void 0;
|
|
4000
|
+
const locale = typeof args.locale === "string" ? args.locale : void 0;
|
|
4001
|
+
const page = session.getPage(pageId);
|
|
4002
|
+
if (!page) throw new Error(`AUP page not found: ${pageId}`);
|
|
4003
|
+
const root = JSON.parse(page.content);
|
|
4004
|
+
const err = validateNode(root);
|
|
4005
|
+
if (err) throw new Error(`Invalid stored AUP graph: ${err}`);
|
|
4006
|
+
const store = this.getAupStore(session.id);
|
|
4007
|
+
store.setRoot(root);
|
|
4008
|
+
if (isAUPTransport(this.backend)) {
|
|
4009
|
+
const msg = {
|
|
4010
|
+
type: "aup",
|
|
4011
|
+
action: "render",
|
|
4012
|
+
root: this.degradeForSession(store.getRoot(), session)
|
|
4013
|
+
};
|
|
4014
|
+
if (fullPage) msg.fullPage = true;
|
|
4015
|
+
if (chrome) msg.chrome = true;
|
|
4016
|
+
if (theme) msg.theme = theme;
|
|
4017
|
+
if (style) msg.style = style;
|
|
4018
|
+
if (locale) msg.locale = locale;
|
|
4019
|
+
this.backend.sendToSession(session.id, msg);
|
|
4020
|
+
}
|
|
4021
|
+
return { success: true };
|
|
4022
|
+
}
|
|
4023
|
+
async execSessionAupStage(ctx, args) {
|
|
4024
|
+
const session = this.resolveSession(ctx);
|
|
4025
|
+
const sceneId = args.sceneId;
|
|
4026
|
+
if (!sceneId) throw new Error("aup_stage action requires 'sceneId' argument");
|
|
4027
|
+
const root = args.root;
|
|
4028
|
+
if (!root) throw new Error("aup_stage action requires 'root' argument");
|
|
4029
|
+
const fullPage = args.fullPage === true;
|
|
4030
|
+
const chrome = args.chrome === true;
|
|
4031
|
+
const theme = typeof args.theme === "string" ? args.theme : void 0;
|
|
4032
|
+
const style = typeof args.style === "string" ? args.style : void 0;
|
|
4033
|
+
const locale = typeof args.locale === "string" ? args.locale : void 0;
|
|
4034
|
+
const err = validateNode(root);
|
|
4035
|
+
if (err) throw new Error(`Invalid AUP node: ${err}`);
|
|
4036
|
+
const store = this.getAupManager(session.id).stage(sceneId, root, {
|
|
4037
|
+
fullPage,
|
|
4038
|
+
chrome,
|
|
4039
|
+
theme,
|
|
4040
|
+
style,
|
|
4041
|
+
locale
|
|
4042
|
+
});
|
|
4043
|
+
if (isAUPTransport(this.backend)) {
|
|
4044
|
+
const msg = {
|
|
4045
|
+
type: "aup",
|
|
4046
|
+
action: "stage",
|
|
4047
|
+
sceneId,
|
|
4048
|
+
root: this.degradeForSession(store.getRoot(), session),
|
|
4049
|
+
treeVersion: store.version
|
|
4050
|
+
};
|
|
4051
|
+
if (fullPage) msg.fullPage = true;
|
|
4052
|
+
if (chrome) msg.chrome = true;
|
|
4053
|
+
if (theme) msg.theme = theme;
|
|
4054
|
+
if (style) msg.style = style;
|
|
4055
|
+
if (locale) msg.locale = locale;
|
|
4056
|
+
this.backend.sendToSession(session.id, msg);
|
|
4057
|
+
}
|
|
4058
|
+
return { success: true };
|
|
4059
|
+
}
|
|
4060
|
+
async execSessionAupTake(ctx, args) {
|
|
4061
|
+
const session = this.resolveSession(ctx);
|
|
4062
|
+
const sceneId = args.sceneId;
|
|
4063
|
+
if (!sceneId) throw new Error("aup_take action requires 'sceneId' argument");
|
|
4064
|
+
const transition = typeof args.transition === "string" ? args.transition : void 0;
|
|
4065
|
+
const duration = typeof args.duration === "number" ? args.duration : void 0;
|
|
4066
|
+
this.getAupManager(session.id).take(sceneId);
|
|
4067
|
+
if (isAUPTransport(this.backend)) {
|
|
4068
|
+
const msg = {
|
|
4069
|
+
type: "aup",
|
|
4070
|
+
action: "take",
|
|
4071
|
+
sceneId
|
|
4072
|
+
};
|
|
4073
|
+
if (transition) msg.transition = transition;
|
|
4074
|
+
if (duration) msg.duration = duration;
|
|
4075
|
+
this.backend.sendToSession(session.id, msg);
|
|
4076
|
+
}
|
|
4077
|
+
return { success: true };
|
|
4078
|
+
}
|
|
4079
|
+
async execSessionAupRelease(ctx, args) {
|
|
4080
|
+
const session = this.resolveSession(ctx);
|
|
4081
|
+
const sceneId = args.sceneId;
|
|
4082
|
+
if (!sceneId) throw new Error("aup_release action requires 'sceneId' argument");
|
|
4083
|
+
this.getAupManager(session.id).release(sceneId);
|
|
4084
|
+
if (isAUPTransport(this.backend)) this.backend.sendToSession(session.id, {
|
|
4085
|
+
type: "aup",
|
|
4086
|
+
action: "release",
|
|
4087
|
+
sceneId
|
|
4088
|
+
});
|
|
4089
|
+
return { success: true };
|
|
4090
|
+
}
|
|
4091
|
+
async listActions(_ctx) {
|
|
4092
|
+
return { data: [
|
|
4093
|
+
{
|
|
4094
|
+
id: "prompt",
|
|
4095
|
+
path: "/.actions/prompt",
|
|
4096
|
+
meta: {
|
|
4097
|
+
kind: "afs:executable",
|
|
4098
|
+
description: "Ask user a question",
|
|
4099
|
+
inputSchema: {
|
|
4100
|
+
type: "object",
|
|
4101
|
+
required: ["message"],
|
|
4102
|
+
properties: {
|
|
4103
|
+
message: {
|
|
4104
|
+
type: "string",
|
|
4105
|
+
description: "The question to ask"
|
|
4106
|
+
},
|
|
4107
|
+
type: {
|
|
4108
|
+
type: "string",
|
|
4109
|
+
enum: [
|
|
4110
|
+
"text",
|
|
4111
|
+
"password",
|
|
4112
|
+
"confirm",
|
|
4113
|
+
"select",
|
|
4114
|
+
"multiselect"
|
|
4115
|
+
],
|
|
4116
|
+
default: "text",
|
|
4117
|
+
description: "Input type"
|
|
4118
|
+
},
|
|
4119
|
+
options: {
|
|
4120
|
+
type: "array",
|
|
4121
|
+
items: { type: "string" },
|
|
4122
|
+
description: "Options for select/multiselect"
|
|
4123
|
+
}
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
},
|
|
4128
|
+
{
|
|
4129
|
+
id: "clear",
|
|
4130
|
+
path: "/.actions/clear",
|
|
4131
|
+
meta: {
|
|
4132
|
+
kind: "afs:executable",
|
|
4133
|
+
description: "Clear screen",
|
|
4134
|
+
inputSchema: {
|
|
4135
|
+
type: "object",
|
|
4136
|
+
properties: {}
|
|
4137
|
+
}
|
|
4138
|
+
}
|
|
4139
|
+
},
|
|
4140
|
+
{
|
|
4141
|
+
id: "notify",
|
|
4142
|
+
path: "/.actions/notify",
|
|
4143
|
+
meta: {
|
|
4144
|
+
kind: "afs:executable",
|
|
4145
|
+
description: "Send notification",
|
|
4146
|
+
inputSchema: {
|
|
4147
|
+
type: "object",
|
|
4148
|
+
required: ["message"],
|
|
4149
|
+
properties: { message: {
|
|
4150
|
+
type: "string",
|
|
4151
|
+
description: "Notification message"
|
|
4152
|
+
} }
|
|
4153
|
+
}
|
|
4154
|
+
}
|
|
4155
|
+
},
|
|
4156
|
+
{
|
|
4157
|
+
id: "navigate",
|
|
4158
|
+
path: "/.actions/navigate",
|
|
4159
|
+
meta: {
|
|
4160
|
+
kind: "afs:executable",
|
|
4161
|
+
description: "Navigate to a managed page",
|
|
4162
|
+
inputSchema: {
|
|
4163
|
+
type: "object",
|
|
4164
|
+
required: ["page"],
|
|
4165
|
+
properties: { page: {
|
|
4166
|
+
type: "string",
|
|
4167
|
+
description: "Page ID to navigate to"
|
|
4168
|
+
} }
|
|
4169
|
+
}
|
|
4170
|
+
}
|
|
4171
|
+
},
|
|
4172
|
+
{
|
|
4173
|
+
id: "dialog",
|
|
4174
|
+
path: "/.actions/dialog",
|
|
4175
|
+
meta: {
|
|
4176
|
+
kind: "afs:executable",
|
|
4177
|
+
description: "Show dialog with custom buttons",
|
|
4178
|
+
inputSchema: {
|
|
4179
|
+
type: "object",
|
|
4180
|
+
required: [
|
|
4181
|
+
"title",
|
|
4182
|
+
"content",
|
|
4183
|
+
"buttons"
|
|
4184
|
+
],
|
|
4185
|
+
properties: {
|
|
4186
|
+
title: {
|
|
4187
|
+
type: "string",
|
|
4188
|
+
description: "Dialog title"
|
|
4189
|
+
},
|
|
4190
|
+
content: {
|
|
4191
|
+
type: "string",
|
|
4192
|
+
description: "Dialog content"
|
|
4193
|
+
},
|
|
4194
|
+
buttons: {
|
|
4195
|
+
type: "array",
|
|
4196
|
+
items: { type: "string" },
|
|
4197
|
+
description: "Button labels"
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
},
|
|
4203
|
+
{
|
|
4204
|
+
id: "progress",
|
|
4205
|
+
path: "/.actions/progress",
|
|
4206
|
+
meta: {
|
|
4207
|
+
kind: "afs:executable",
|
|
4208
|
+
description: "Display/update progress indicator",
|
|
4209
|
+
inputSchema: {
|
|
4210
|
+
type: "object",
|
|
4211
|
+
required: [
|
|
4212
|
+
"label",
|
|
4213
|
+
"value",
|
|
4214
|
+
"max"
|
|
4215
|
+
],
|
|
4216
|
+
properties: {
|
|
4217
|
+
label: {
|
|
4218
|
+
type: "string",
|
|
4219
|
+
description: "Progress label"
|
|
4220
|
+
},
|
|
4221
|
+
value: {
|
|
4222
|
+
type: "number",
|
|
4223
|
+
description: "Current value"
|
|
4224
|
+
},
|
|
4225
|
+
max: {
|
|
4226
|
+
type: "number",
|
|
4227
|
+
description: "Maximum value"
|
|
4228
|
+
}
|
|
4229
|
+
}
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
},
|
|
4233
|
+
{
|
|
4234
|
+
id: "form",
|
|
4235
|
+
path: "/.actions/form",
|
|
4236
|
+
meta: {
|
|
4237
|
+
kind: "afs:executable",
|
|
4238
|
+
description: "Collect structured input via form",
|
|
4239
|
+
inputSchema: {
|
|
4240
|
+
type: "object",
|
|
4241
|
+
required: ["fields"],
|
|
4242
|
+
properties: {
|
|
4243
|
+
title: {
|
|
4244
|
+
type: "string",
|
|
4245
|
+
description: "Form title"
|
|
4246
|
+
},
|
|
4247
|
+
fields: {
|
|
4248
|
+
type: "array",
|
|
4249
|
+
items: {
|
|
4250
|
+
type: "object",
|
|
4251
|
+
required: [
|
|
4252
|
+
"name",
|
|
4253
|
+
"label",
|
|
4254
|
+
"type"
|
|
4255
|
+
],
|
|
4256
|
+
properties: {
|
|
4257
|
+
name: { type: "string" },
|
|
4258
|
+
label: { type: "string" },
|
|
4259
|
+
type: {
|
|
4260
|
+
type: "string",
|
|
4261
|
+
enum: [
|
|
4262
|
+
"text",
|
|
4263
|
+
"number",
|
|
4264
|
+
"password",
|
|
4265
|
+
"select",
|
|
4266
|
+
"checkbox",
|
|
4267
|
+
"textarea"
|
|
4268
|
+
]
|
|
4269
|
+
},
|
|
4270
|
+
options: {
|
|
4271
|
+
type: "array",
|
|
4272
|
+
items: { type: "string" }
|
|
4273
|
+
},
|
|
4274
|
+
required: { type: "boolean" }
|
|
4275
|
+
}
|
|
4276
|
+
},
|
|
4277
|
+
description: "Form field definitions"
|
|
4278
|
+
}
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
}
|
|
4282
|
+
},
|
|
4283
|
+
{
|
|
4284
|
+
id: "table",
|
|
4285
|
+
path: "/.actions/table",
|
|
4286
|
+
meta: {
|
|
4287
|
+
kind: "afs:executable",
|
|
4288
|
+
description: "Display tabular data",
|
|
4289
|
+
inputSchema: {
|
|
4290
|
+
type: "object",
|
|
4291
|
+
required: ["headers", "rows"],
|
|
4292
|
+
properties: {
|
|
4293
|
+
headers: {
|
|
4294
|
+
type: "array",
|
|
4295
|
+
items: { type: "string" },
|
|
4296
|
+
description: "Column headers"
|
|
4297
|
+
},
|
|
4298
|
+
rows: {
|
|
4299
|
+
type: "array",
|
|
4300
|
+
items: {
|
|
4301
|
+
type: "array",
|
|
4302
|
+
items: { type: "string" }
|
|
4303
|
+
},
|
|
4304
|
+
description: "Table rows"
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
4309
|
+
},
|
|
4310
|
+
{
|
|
4311
|
+
id: "toast",
|
|
4312
|
+
path: "/.actions/toast",
|
|
4313
|
+
meta: {
|
|
4314
|
+
kind: "afs:executable",
|
|
4315
|
+
description: "Show lightweight toast notification",
|
|
4316
|
+
inputSchema: {
|
|
4317
|
+
type: "object",
|
|
4318
|
+
required: ["message"],
|
|
4319
|
+
properties: {
|
|
4320
|
+
message: {
|
|
4321
|
+
type: "string",
|
|
4322
|
+
description: "Toast message"
|
|
4323
|
+
},
|
|
4324
|
+
toastType: {
|
|
4325
|
+
type: "string",
|
|
4326
|
+
enum: [
|
|
4327
|
+
"info",
|
|
4328
|
+
"success",
|
|
4329
|
+
"warning",
|
|
4330
|
+
"error"
|
|
4331
|
+
],
|
|
4332
|
+
default: "info",
|
|
4333
|
+
description: "Toast type"
|
|
4334
|
+
}
|
|
4335
|
+
}
|
|
4336
|
+
}
|
|
4337
|
+
}
|
|
4338
|
+
}
|
|
4339
|
+
] };
|
|
4340
|
+
}
|
|
4341
|
+
async execPrompt(_ctx, args) {
|
|
4342
|
+
const message = args.message;
|
|
4343
|
+
if (!message) throw new Error("prompt action requires 'message' argument");
|
|
4344
|
+
const type = args.type ?? "text";
|
|
4345
|
+
const validTypes = [
|
|
4346
|
+
"text",
|
|
4347
|
+
"password",
|
|
4348
|
+
"confirm",
|
|
4349
|
+
"select",
|
|
4350
|
+
"multiselect"
|
|
4351
|
+
];
|
|
4352
|
+
if (!validTypes.includes(type)) throw new Error(`Invalid prompt type: ${type}. Must be one of: ${validTypes.join(", ")}`);
|
|
4353
|
+
const options = args.options;
|
|
4354
|
+
return {
|
|
4355
|
+
success: true,
|
|
4356
|
+
data: { response: await this.backend.prompt({
|
|
4357
|
+
message,
|
|
4358
|
+
type,
|
|
4359
|
+
options
|
|
4360
|
+
}) }
|
|
4361
|
+
};
|
|
4362
|
+
}
|
|
4363
|
+
async execClear(_ctx, _args) {
|
|
4364
|
+
await this.backend.clear();
|
|
4365
|
+
return { success: true };
|
|
4366
|
+
}
|
|
4367
|
+
async execNotify(_ctx, args) {
|
|
4368
|
+
const message = args.message;
|
|
4369
|
+
if (!message) throw new Error("notify action requires 'message' argument");
|
|
4370
|
+
await this.backend.notify(message);
|
|
4371
|
+
return { success: true };
|
|
4372
|
+
}
|
|
4373
|
+
async execNavigate(_ctx, args) {
|
|
4374
|
+
const pageId = args.page;
|
|
4375
|
+
if (!pageId) throw new Error("navigate action requires 'page' argument");
|
|
4376
|
+
const page = this.pages.get(pageId);
|
|
4377
|
+
if (!page) throw new Error(`Page not found: ${pageId}`);
|
|
4378
|
+
if (this.backend.navigate) await this.backend.navigate(pageId, page.content, page.format, page.layout);
|
|
4379
|
+
else await this.backend.write(page.content);
|
|
4380
|
+
return { success: true };
|
|
4381
|
+
}
|
|
4382
|
+
async execDialog(_ctx, args) {
|
|
4383
|
+
const title = args.title;
|
|
4384
|
+
if (!title) throw new Error("dialog action requires 'title' argument");
|
|
4385
|
+
const content = args.content ?? "";
|
|
4386
|
+
const buttons = args.buttons;
|
|
4387
|
+
if (!buttons || buttons.length === 0) throw new Error("dialog action requires 'buttons' argument");
|
|
4388
|
+
return {
|
|
4389
|
+
success: true,
|
|
4390
|
+
data: { selection: await this.backend.prompt({
|
|
4391
|
+
message: `${title}\n${content}`,
|
|
4392
|
+
type: "select",
|
|
4393
|
+
options: buttons
|
|
4394
|
+
}) }
|
|
4395
|
+
};
|
|
4396
|
+
}
|
|
4397
|
+
async execProgress(_ctx, args) {
|
|
4398
|
+
const label = args.label ?? "";
|
|
4399
|
+
const value = args.value;
|
|
4400
|
+
if (value === void 0 || value === null) throw new Error("progress action requires 'value' argument");
|
|
4401
|
+
const max = args.max ?? 100;
|
|
4402
|
+
const pct = Math.round(value / max * 100);
|
|
4403
|
+
await this.backend.notify(`[${pct}%] ${label}`);
|
|
4404
|
+
return {
|
|
4405
|
+
success: true,
|
|
4406
|
+
data: {
|
|
4407
|
+
value,
|
|
4408
|
+
max,
|
|
4409
|
+
percent: pct
|
|
4410
|
+
}
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
async execForm(_ctx, args) {
|
|
4414
|
+
const fields = args.fields;
|
|
4415
|
+
if (!fields || fields.length === 0) throw new Error("form action requires non-empty 'fields' argument");
|
|
4416
|
+
const values = {};
|
|
4417
|
+
for (const field of fields) {
|
|
4418
|
+
const promptType = field.type === "password" ? "password" : "text";
|
|
4419
|
+
const result = await this.backend.prompt({
|
|
4420
|
+
message: `${field.label}:`,
|
|
4421
|
+
type: promptType
|
|
4422
|
+
});
|
|
4423
|
+
values[field.name] = result;
|
|
4424
|
+
}
|
|
4425
|
+
return {
|
|
4426
|
+
success: true,
|
|
4427
|
+
data: { values }
|
|
4428
|
+
};
|
|
4429
|
+
}
|
|
4430
|
+
async execTable(_ctx, args) {
|
|
4431
|
+
const headers = args.headers;
|
|
4432
|
+
if (!headers || headers.length === 0) throw new Error("table action requires 'headers' argument");
|
|
4433
|
+
const rows = args.rows ?? [];
|
|
4434
|
+
const colWidths = headers.map((h, i) => {
|
|
4435
|
+
let max = h.length;
|
|
4436
|
+
for (const row of rows) if (row[i] && row[i].length > max) max = row[i].length;
|
|
4437
|
+
return max;
|
|
4438
|
+
});
|
|
4439
|
+
const pad = (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
4440
|
+
const tableText = [
|
|
4441
|
+
headers.map((h, i) => pad(h, colWidths[i])).join(" | "),
|
|
4442
|
+
colWidths.map((w) => "-".repeat(w)).join("-+-"),
|
|
4443
|
+
...rows.map((row) => headers.map((_, i) => pad(row[i] ?? "", colWidths[i])).join(" | "))
|
|
4444
|
+
].join("\n");
|
|
4445
|
+
await this.backend.write(tableText);
|
|
4446
|
+
return { success: true };
|
|
4447
|
+
}
|
|
4448
|
+
async execToast(_ctx, args) {
|
|
4449
|
+
const message = args.message;
|
|
4450
|
+
if (!message) throw new Error("toast action requires 'message' argument");
|
|
4451
|
+
await this.backend.notify(message);
|
|
4452
|
+
return { success: true };
|
|
4453
|
+
}
|
|
4454
|
+
};
|
|
4455
|
+
__decorate([Read("/.meta/.capabilities")], AFSUIProvider.prototype, "readCapabilities", null);
|
|
4456
|
+
__decorate([Meta("/")], AFSUIProvider.prototype, "metaRoot", null);
|
|
4457
|
+
__decorate([Meta("/input")], AFSUIProvider.prototype, "metaInput", null);
|
|
4458
|
+
__decorate([Meta("/output")], AFSUIProvider.prototype, "metaOutput", null);
|
|
4459
|
+
__decorate([Meta("/pages")], AFSUIProvider.prototype, "metaPages", null);
|
|
4460
|
+
__decorate([Meta("/pages/:id")], AFSUIProvider.prototype, "metaPage", null);
|
|
4461
|
+
__decorate([List("/input")], AFSUIProvider.prototype, "listInput", null);
|
|
4462
|
+
__decorate([List("/output")], AFSUIProvider.prototype, "listOutput", null);
|
|
4463
|
+
__decorate([List("/pages")], AFSUIProvider.prototype, "listPages", null);
|
|
4464
|
+
__decorate([List("/")], AFSUIProvider.prototype, "listRoot", null);
|
|
4465
|
+
__decorate([Read("/")], AFSUIProvider.prototype, "readRoot", null);
|
|
4466
|
+
__decorate([Read("/input")], AFSUIProvider.prototype, "readInput", null);
|
|
4467
|
+
__decorate([Read("/output")], AFSUIProvider.prototype, "readOutput", null);
|
|
4468
|
+
__decorate([Read("/pages")], AFSUIProvider.prototype, "readPages", null);
|
|
4469
|
+
__decorate([Read("/pages/:id")], AFSUIProvider.prototype, "readPage", null);
|
|
4470
|
+
__decorate([List("/primitives")], AFSUIProvider.prototype, "listPrimitives", null);
|
|
4471
|
+
__decorate([Read("/primitives")], AFSUIProvider.prototype, "readPrimitivesDir", null);
|
|
4472
|
+
__decorate([Stat("/primitives")], AFSUIProvider.prototype, "statPrimitivesDir", null);
|
|
4473
|
+
__decorate([Meta("/primitives")], AFSUIProvider.prototype, "metaPrimitivesDir", null);
|
|
4474
|
+
__decorate([Read("/primitives/:name")], AFSUIProvider.prototype, "readPrimitive", null);
|
|
4475
|
+
__decorate([Stat("/primitives/:name")], AFSUIProvider.prototype, "statPrimitive", null);
|
|
4476
|
+
__decorate([Meta("/primitives/:name")], AFSUIProvider.prototype, "metaPrimitive", null);
|
|
4477
|
+
__decorate([List("/themes")], AFSUIProvider.prototype, "listThemes", null);
|
|
4478
|
+
__decorate([Read("/themes")], AFSUIProvider.prototype, "readThemesDir", null);
|
|
4479
|
+
__decorate([Stat("/themes")], AFSUIProvider.prototype, "statThemesDir", null);
|
|
4480
|
+
__decorate([Meta("/themes")], AFSUIProvider.prototype, "metaThemesDir", null);
|
|
4481
|
+
__decorate([Read("/themes/:name")], AFSUIProvider.prototype, "readTheme", null);
|
|
4482
|
+
__decorate([Stat("/themes/:name")], AFSUIProvider.prototype, "statTheme", null);
|
|
4483
|
+
__decorate([Meta("/themes/:name")], AFSUIProvider.prototype, "metaTheme", null);
|
|
4484
|
+
__decorate([List("/overlay-themes")], AFSUIProvider.prototype, "listOverlayThemes", null);
|
|
4485
|
+
__decorate([Read("/overlay-themes")], AFSUIProvider.prototype, "readOverlayThemesDir", null);
|
|
4486
|
+
__decorate([Stat("/overlay-themes")], AFSUIProvider.prototype, "statOverlayThemesDir", null);
|
|
4487
|
+
__decorate([Meta("/overlay-themes")], AFSUIProvider.prototype, "metaOverlayThemesDir", null);
|
|
4488
|
+
__decorate([Read("/overlay-themes/:name")], AFSUIProvider.prototype, "readOverlayTheme", null);
|
|
4489
|
+
__decorate([Stat("/overlay-themes/:name")], AFSUIProvider.prototype, "statOverlayTheme", null);
|
|
4490
|
+
__decorate([Meta("/overlay-themes/:name")], AFSUIProvider.prototype, "metaOverlayTheme", null);
|
|
4491
|
+
__decorate([List("/overlay-themes/:name/examples")], AFSUIProvider.prototype, "listOverlayExamples", null);
|
|
4492
|
+
__decorate([Read("/overlay-themes/:name/examples/:example")], AFSUIProvider.prototype, "readOverlayExample", null);
|
|
4493
|
+
__decorate([Read("/spec")], AFSUIProvider.prototype, "readSpec", null);
|
|
4494
|
+
__decorate([Stat("/spec")], AFSUIProvider.prototype, "statSpec", null);
|
|
4495
|
+
__decorate([List("/spec")], AFSUIProvider.prototype, "listSpec", null);
|
|
4496
|
+
__decorate([Meta("/spec")], AFSUIProvider.prototype, "metaSpec", null);
|
|
4497
|
+
__decorate([Explain("/spec")], AFSUIProvider.prototype, "explainSpec", null);
|
|
4498
|
+
__decorate([List("/examples")], AFSUIProvider.prototype, "listExamples", null);
|
|
4499
|
+
__decorate([Read("/examples")], AFSUIProvider.prototype, "readExamplesDir", null);
|
|
4500
|
+
__decorate([Stat("/examples")], AFSUIProvider.prototype, "statExamplesDir", null);
|
|
4501
|
+
__decorate([Meta("/examples")], AFSUIProvider.prototype, "metaExamplesDir", null);
|
|
4502
|
+
__decorate([Read("/examples/:name")], AFSUIProvider.prototype, "readExample", null);
|
|
4503
|
+
__decorate([Stat("/examples/:name")], AFSUIProvider.prototype, "statExample", null);
|
|
4504
|
+
__decorate([Meta("/examples/:name")], AFSUIProvider.prototype, "metaExample", null);
|
|
4505
|
+
__decorate([Write("/output")], AFSUIProvider.prototype, "writeOutput", null);
|
|
4506
|
+
__decorate([Write("/pages/:id")], AFSUIProvider.prototype, "writePage", null);
|
|
4507
|
+
__decorate([Delete("/pages/:id")], AFSUIProvider.prototype, "deletePage", null);
|
|
4508
|
+
__decorate([Write("/sharing/:slug")], AFSUIProvider.prototype, "writeSharingEntry", null);
|
|
4509
|
+
__decorate([Read("/sharing/:slug")], AFSUIProvider.prototype, "readSharingEntry", null);
|
|
4510
|
+
__decorate([Read("/sharing")], AFSUIProvider.prototype, "readSharingDir", null);
|
|
4511
|
+
__decorate([List("/sharing")], AFSUIProvider.prototype, "listSharingEntries", null);
|
|
4512
|
+
__decorate([Delete("/sharing/:slug")], AFSUIProvider.prototype, "deleteSharingEntry", null);
|
|
4513
|
+
__decorate([Meta("/sharing")], AFSUIProvider.prototype, "metaSharingDir", null);
|
|
4514
|
+
__decorate([Meta("/sharing/:slug")], AFSUIProvider.prototype, "metaSharingEntry", null);
|
|
4515
|
+
__decorate([Stat("/sharing")], AFSUIProvider.prototype, "statSharingDir", null);
|
|
4516
|
+
__decorate([Stat("/sharing/:slug")], AFSUIProvider.prototype, "statSharingEntry", null);
|
|
4517
|
+
__decorate([Actions("/sharing/:slug")], AFSUIProvider.prototype, "listSharingActions", null);
|
|
4518
|
+
__decorate([Actions.Exec("/sharing/:slug", "snapshot")], AFSUIProvider.prototype, "execSharingSnapshot", null);
|
|
4519
|
+
__decorate([Stat("/")], AFSUIProvider.prototype, "statRoot", null);
|
|
4520
|
+
__decorate([Stat("/input")], AFSUIProvider.prototype, "statInput", null);
|
|
4521
|
+
__decorate([Stat("/output")], AFSUIProvider.prototype, "statOutput", null);
|
|
4522
|
+
__decorate([Stat("/pages")], AFSUIProvider.prototype, "statPages", null);
|
|
4523
|
+
__decorate([Stat("/pages/:id")], AFSUIProvider.prototype, "statPage", null);
|
|
4524
|
+
__decorate([Explain("/")], AFSUIProvider.prototype, "explainRoot", null);
|
|
4525
|
+
__decorate([Explain("/input")], AFSUIProvider.prototype, "explainInput", null);
|
|
4526
|
+
__decorate([Explain("/output")], AFSUIProvider.prototype, "explainOutput", null);
|
|
4527
|
+
__decorate([Explain("/pages")], AFSUIProvider.prototype, "explainPages", null);
|
|
4528
|
+
__decorate([Read("/:endpoint")], AFSUIProvider.prototype, "readEndpoint", null);
|
|
4529
|
+
__decorate([Stat("/:endpoint")], AFSUIProvider.prototype, "statEndpoint", null);
|
|
4530
|
+
__decorate([List("/:endpoint")], AFSUIProvider.prototype, "listEndpoint", null);
|
|
4531
|
+
__decorate([Meta("/:endpoint")], AFSUIProvider.prototype, "metaEndpoint", null);
|
|
4532
|
+
__decorate([Read("/:endpoint/sessions")], AFSUIProvider.prototype, "readSessions", null);
|
|
4533
|
+
__decorate([Meta("/:endpoint/sessions")], AFSUIProvider.prototype, "metaSessions", null);
|
|
4534
|
+
__decorate([List("/:endpoint/sessions")], AFSUIProvider.prototype, "listSessions", null);
|
|
4535
|
+
__decorate([List("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "listSession", null);
|
|
4536
|
+
__decorate([Read("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "readSession", null);
|
|
4537
|
+
__decorate([Meta("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "metaSession", null);
|
|
4538
|
+
__decorate([Stat("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "statSession", null);
|
|
4539
|
+
__decorate([List("/:endpoint/sessions/:sid/messages")], AFSUIProvider.prototype, "listSessionMessages", null);
|
|
4540
|
+
__decorate([Read("/:endpoint/sessions/:sid/messages/:mid")], AFSUIProvider.prototype, "readSessionMessage", null);
|
|
4541
|
+
__decorate([Write("/:endpoint/sessions/:sid/messages")], AFSUIProvider.prototype, "writeSessionMessage", null);
|
|
4542
|
+
__decorate([List("/:endpoint/sessions/:sid/pages")], AFSUIProvider.prototype, "listSessionPages", null);
|
|
4543
|
+
__decorate([Read("/:endpoint/sessions/:sid/pages/:pid")], AFSUIProvider.prototype, "readSessionPage", null);
|
|
4544
|
+
__decorate([Write("/:endpoint/sessions/:sid/pages/:pid")], AFSUIProvider.prototype, "writeSessionPage", null);
|
|
4545
|
+
__decorate([Read("/:endpoint/sessions/:sid/.caps")], AFSUIProvider.prototype, "readSessionCaps", null);
|
|
4546
|
+
__decorate([Write("/:endpoint/sessions/:sid/.caps")], AFSUIProvider.prototype, "writeSessionCaps", null);
|
|
4547
|
+
__decorate([Read("/:endpoint/sessions/:sid/tree")], AFSUIProvider.prototype, "readSessionTree", null);
|
|
4548
|
+
__decorate([Write("/:endpoint/sessions/:sid/tree")], AFSUIProvider.prototype, "writeSessionTree", null);
|
|
4549
|
+
__decorate([Read("/:endpoint/sessions/:sid/wm")], AFSUIProvider.prototype, "readSessionWm", null);
|
|
4550
|
+
__decorate([List("/:endpoint/sessions/:sid/wm")], AFSUIProvider.prototype, "listSessionWm", null);
|
|
4551
|
+
__decorate([Read("/:endpoint/sessions/:sid/wm/strategy")], AFSUIProvider.prototype, "readWmStrategy", null);
|
|
4552
|
+
__decorate([Write("/:endpoint/sessions/:sid/wm/strategy")], AFSUIProvider.prototype, "writeWmStrategy", null);
|
|
4553
|
+
__decorate([Read("/:endpoint/sessions/:sid/wm/layout")], AFSUIProvider.prototype, "readWmLayout", null);
|
|
4554
|
+
__decorate([List("/:endpoint/sessions/:sid/wm/surfaces")], AFSUIProvider.prototype, "listWmSurfaces", null);
|
|
4555
|
+
__decorate([Read("/:endpoint/sessions/:sid/wm/surfaces/:surfaceName")], AFSUIProvider.prototype, "readWmSurface", null);
|
|
4556
|
+
__decorate([Actions("/:endpoint/sessions/:sid/wm")], AFSUIProvider.prototype, "listWmActions", null);
|
|
4557
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "open-surface")], AFSUIProvider.prototype, "execWmOpenSurface", null);
|
|
4558
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "set-surface-content")], AFSUIProvider.prototype, "execWmSetSurfaceContent", null);
|
|
4559
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "close-surface")], AFSUIProvider.prototype, "execWmCloseSurface", null);
|
|
4560
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "set-strategy")], AFSUIProvider.prototype, "execWmSetStrategy", null);
|
|
4561
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "set-active")], AFSUIProvider.prototype, "execWmSetActive", null);
|
|
4562
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "pin-to-dock")], AFSUIProvider.prototype, "execWmPinToDock", null);
|
|
4563
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "unpin-from-dock")], AFSUIProvider.prototype, "execWmUnpinFromDock", null);
|
|
4564
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "set-layout")], AFSUIProvider.prototype, "execWmSetLayout", null);
|
|
4565
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "collapse-panel")], AFSUIProvider.prototype, "execWmCollapsePanel", null);
|
|
4566
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "expand-panel")], AFSUIProvider.prototype, "execWmExpandPanel", null);
|
|
4567
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "move-surface")], AFSUIProvider.prototype, "execWmMoveSurface", null);
|
|
4568
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "add-to-panel")], AFSUIProvider.prototype, "execWmAddToPanel", null);
|
|
4569
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "set-panel-active")], AFSUIProvider.prototype, "execWmSetPanelActive", null);
|
|
4570
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "popout")], AFSUIProvider.prototype, "execWmPopout", null);
|
|
4571
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "redock")], AFSUIProvider.prototype, "execWmRedock", null);
|
|
4572
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid/wm", "auto-arrange")], AFSUIProvider.prototype, "execWmAutoArrange", null);
|
|
4573
|
+
__decorate([Explain("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "explainSession", null);
|
|
4574
|
+
__decorate([List("/:endpoint/live")], AFSUIProvider.prototype, "listLiveChannels", null);
|
|
4575
|
+
__decorate([Read("/:endpoint/live")], AFSUIProvider.prototype, "readLiveDirectory", null);
|
|
4576
|
+
__decorate([Meta("/:endpoint/live")], AFSUIProvider.prototype, "metaLiveDirectory", null);
|
|
4577
|
+
__decorate([Read("/:endpoint/live/:channelId")], AFSUIProvider.prototype, "readLiveChannel", null);
|
|
4578
|
+
__decorate([Meta("/:endpoint/live/:channelId")], AFSUIProvider.prototype, "metaLiveChannel", null);
|
|
4579
|
+
__decorate([List("/:endpoint/live/:channelId")], AFSUIProvider.prototype, "listLiveChannel", null);
|
|
4580
|
+
__decorate([Read("/:endpoint/live/:channelId/tree")], AFSUIProvider.prototype, "readLiveChannelTree", null);
|
|
4581
|
+
__decorate([Meta("/:endpoint/live/:channelId/tree")], AFSUIProvider.prototype, "metaLiveChannelTree", null);
|
|
4582
|
+
__decorate([Write("/:endpoint/live/:channelId/tree")], AFSUIProvider.prototype, "writeLiveChannelTree", null);
|
|
4583
|
+
__decorate([Actions("/:endpoint/live/:channelId")], AFSUIProvider.prototype, "listLiveChannelActions", null);
|
|
4584
|
+
__decorate([Actions.Exec("/:endpoint/live/:channelId", "aup_render")], AFSUIProvider.prototype, "execLiveChannelAupRender", null);
|
|
4585
|
+
__decorate([Actions.Exec("/:endpoint/live/:channelId", "aup_patch")], AFSUIProvider.prototype, "execLiveChannelAupPatch", null);
|
|
4586
|
+
__decorate([Actions("/:endpoint/sessions/:sid")], AFSUIProvider.prototype, "listSessionActions", null);
|
|
4587
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "prompt")], AFSUIProvider.prototype, "execSessionPrompt", null);
|
|
4588
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "form")], AFSUIProvider.prototype, "execSessionForm", null);
|
|
4589
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "dialog")], AFSUIProvider.prototype, "execSessionDialog", null);
|
|
4590
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "table")], AFSUIProvider.prototype, "execSessionTable", null);
|
|
4591
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "toast")], AFSUIProvider.prototype, "execSessionToast", null);
|
|
4592
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "progress")], AFSUIProvider.prototype, "execSessionProgress", null);
|
|
4593
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "clear")], AFSUIProvider.prototype, "execSessionClear", null);
|
|
4594
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "navigate")], AFSUIProvider.prototype, "execSessionNavigate", null);
|
|
4595
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "notify")], AFSUIProvider.prototype, "execSessionNotify", null);
|
|
4596
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_render")], AFSUIProvider.prototype, "execSessionAupRender", null);
|
|
4597
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_patch")], AFSUIProvider.prototype, "execSessionAupPatch", null);
|
|
4598
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_save")], AFSUIProvider.prototype, "execSessionAupSave", null);
|
|
4599
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_load")], AFSUIProvider.prototype, "execSessionAupLoad", null);
|
|
4600
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_stage")], AFSUIProvider.prototype, "execSessionAupStage", null);
|
|
4601
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_take")], AFSUIProvider.prototype, "execSessionAupTake", null);
|
|
4602
|
+
__decorate([Actions.Exec("/:endpoint/sessions/:sid", "aup_release")], AFSUIProvider.prototype, "execSessionAupRelease", null);
|
|
4603
|
+
__decorate([Actions("/")], AFSUIProvider.prototype, "listActions", null);
|
|
4604
|
+
__decorate([Actions.Exec("/", "prompt")], AFSUIProvider.prototype, "execPrompt", null);
|
|
4605
|
+
__decorate([Actions.Exec("/", "clear")], AFSUIProvider.prototype, "execClear", null);
|
|
4606
|
+
__decorate([Actions.Exec("/", "notify")], AFSUIProvider.prototype, "execNotify", null);
|
|
4607
|
+
__decorate([Actions.Exec("/", "navigate")], AFSUIProvider.prototype, "execNavigate", null);
|
|
4608
|
+
__decorate([Actions.Exec("/", "dialog")], AFSUIProvider.prototype, "execDialog", null);
|
|
4609
|
+
__decorate([Actions.Exec("/", "progress")], AFSUIProvider.prototype, "execProgress", null);
|
|
4610
|
+
__decorate([Actions.Exec("/", "form")], AFSUIProvider.prototype, "execForm", null);
|
|
4611
|
+
__decorate([Actions.Exec("/", "table")], AFSUIProvider.prototype, "execTable", null);
|
|
4612
|
+
__decorate([Actions.Exec("/", "toast")], AFSUIProvider.prototype, "execToast", null);
|
|
4613
|
+
|
|
4614
|
+
//#endregion
|
|
4615
|
+
export { AFSUIProvider };
|
|
4616
|
+
//# sourceMappingURL=ui-provider.mjs.map
|