@iniesta8888/agent-live-pi-adapter 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/adapter.js +2375 -0
  4. package/package.json +23 -0
  5. package/web/app.d.ts +1 -0
  6. package/web/app.js +1317 -0
  7. package/web/index.html +45 -0
  8. package/web/office.js +350 -0
  9. package/web/sprites.js +185 -0
  10. package/web/style.css +288 -0
  11. package/web/v2/bootstrap.js +276 -0
  12. package/web/v2/content/README.md +17 -0
  13. package/web/v2/content/agent-skins/studio-team.json +33 -0
  14. package/web/v2/content/agent-skins/tiny-developers.json +33 -0
  15. package/web/v2/content/atmospheres/default.json +13 -0
  16. package/web/v2/content/atmospheres/rainy-night.json +37 -0
  17. package/web/v2/content/catalog.json +14 -0
  18. package/web/v2/content/component-library/activity-implementations.json +873 -0
  19. package/web/v2/content/component-library/activity-recipes.json +25 -0
  20. package/web/v2/content/component-library/agent-profile-templates.json +18 -0
  21. package/web/v2/content/component-library/catalog.json +38 -0
  22. package/web/v2/content/component-library/npc-templates.json +42 -0
  23. package/web/v2/content/component-library/props.json +41 -0
  24. package/web/v2/content/environments/local-office.json +31 -0
  25. package/web/v2/content/environments/rainy-night.json +28 -0
  26. package/web/v2/content/environments/static-office.json +27 -0
  27. package/web/v2/content/layouts/boardroom-office.json +122 -0
  28. package/web/v2/content/layouts/demo-office.json +88 -0
  29. package/web/v2/content/layouts/lively-office.json +96 -0
  30. package/web/v2/content/layouts/old-school-office.json +154 -0
  31. package/web/v2/content/layouts/tech-open-office.json +132 -0
  32. package/web/v2/content/life-activities/boardroom-routines.json +67 -0
  33. package/web/v2/content/life-activities/none.json +10 -0
  34. package/web/v2/content/life-activities/office-basics.json +39 -0
  35. package/web/v2/content/life-activities/old-school-routines.json +74 -0
  36. package/web/v2/content/life-activities/tech-office-routines.json +148 -0
  37. package/web/v2/content/npcs/boardroom-staff.json +54 -0
  38. package/web/v2/content/npcs/none.json +10 -0
  39. package/web/v2/content/npcs/office-staff.json +25 -0
  40. package/web/v2/content/npcs/old-school-staff.json +39 -0
  41. package/web/v2/content/npcs/tech-office-staff.json +40 -0
  42. package/web/v2/content/official-offices/boardroom-office.json +25 -0
  43. package/web/v2/content/official-offices/old-school-office.json +25 -0
  44. package/web/v2/content/official-offices/tech-open-office.json +25 -0
  45. package/web/v2/content/presets/boardroom-office.json +21 -0
  46. package/web/v2/content/presets/cozy-studio.json +19 -0
  47. package/web/v2/content/presets/demo-office.json +19 -0
  48. package/web/v2/content/presets/lively-office.json +19 -0
  49. package/web/v2/content/presets/night-shift.json +19 -0
  50. package/web/v2/content/presets/old-school-office.json +20 -0
  51. package/web/v2/content/presets/refined-demo.json +20 -0
  52. package/web/v2/content/presets/tech-open-office.json +21 -0
  53. package/web/v2/content/props/default-office.json +23 -0
  54. package/web/v2/content/props/extended-office.json +39 -0
  55. package/web/v2/content/props/tech-office.json +28 -0
  56. package/web/v2/content/styles/pixel-classic.json +160 -0
  57. package/web/v2/content/styles/warm-studio.json +150 -0
  58. package/web/v2/environment-runtime.d.ts +1 -0
  59. package/web/v2/environment-runtime.js +138 -0
  60. package/web/v2/graph-validator.d.ts +55 -0
  61. package/web/v2/graph-validator.js +225 -0
  62. package/web/v2/i18n.js +54 -0
  63. package/web/v2/locales/en.json +209 -0
  64. package/web/v2/locales/zh-CN.json +52 -0
  65. package/web/v2/office-engine.d.ts +1 -0
  66. package/web/v2/office-engine.js +145 -0
  67. package/web/v2/office-renderer.d.ts +1 -0
  68. package/web/v2/office-renderer.js +843 -0
  69. package/web/v2/parity.html +10 -0
  70. package/web/v2/parity.js +82 -0
  71. package/web/v2/sprite-renderer.d.ts +1 -0
  72. package/web/v2/sprite-renderer.js +242 -0
  73. package/web/v2/static-content.js +48 -0
  74. package/web/v2/style.css +140 -0
  75. package/web/v2.html +68 -0
@@ -0,0 +1,2375 @@
1
+ // ../../plugins/agent-live/src/core/mapping.ts
2
+ var DELEGATION_TOOLS = /^(subagent|task|dispatch_agent|spawn_agent|agent)$/i;
3
+ var ACTION_BY_TOOL = [
4
+ [/^(read|ls|glob|grep|find|rg|search_files|list)/i, "archive"],
5
+ [/^(write|edit|multi_edit|apply_patch|create|notebook)/i, "type"],
6
+ [/^(bash|shell|exec|run|terminal)/i, "server"],
7
+ [/^(todo|plan|task_list|note)/i, "whiteboard"],
8
+ [/(browser|web|fetch|http|curl|search)/i, "phone"],
9
+ [/^(ask_user|question|confirm)/i, "phone"]
10
+ ];
11
+ function isDelegationTool(toolName) {
12
+ return DELEGATION_TOOLS.test(toolName);
13
+ }
14
+ function actionForTool(toolName) {
15
+ if (isDelegationTool(toolName)) return "delegate";
16
+ for (const [pattern, action] of ACTION_BY_TOOL) {
17
+ if (pattern.test(toolName)) return action;
18
+ }
19
+ return "type";
20
+ }
21
+ function short(value, max = 64) {
22
+ if (value === void 0 || value === null) return "";
23
+ const text = typeof value === "string" ? value : JSON.stringify(value);
24
+ const clean = text.replace(/\s+/g, " ").trim();
25
+ return clean.length > max ? `${clean.slice(0, max)}\u2026` : clean;
26
+ }
27
+ function baseName(p) {
28
+ const text = short(p, 120);
29
+ if (!text) return "";
30
+ const parts = text.split("/");
31
+ return parts.length > 2 ? `\u2026/${parts.slice(-2).join("/")}` : text;
32
+ }
33
+ function labelForTool(toolName, args = {}) {
34
+ const a = args ?? {};
35
+ switch (true) {
36
+ case /^read/i.test(toolName):
37
+ return `\u67E5\u9605 ${baseName(a.path ?? a.file ?? a.filePath)}`;
38
+ case /^(ls|list|glob|find)/i.test(toolName):
39
+ return `\u7FFB\u627E ${baseName(a.path ?? a.pattern ?? a.glob_pattern ?? ".")}`;
40
+ case /^(grep|rg|search)/i.test(toolName):
41
+ return `\u68C0\u7D22 /${short(a.pattern ?? a.query, 32)}/`;
42
+ case /^(write|create)/i.test(toolName):
43
+ return `\u65B0\u5EFA ${baseName(a.path)}`;
44
+ case /^(edit|multi_edit|apply_patch)/i.test(toolName):
45
+ return `\u4FEE\u6539 ${baseName(a.path ?? a.file)}`;
46
+ case /^(bash|shell|exec|run|terminal)/i.test(toolName):
47
+ return `\u6267\u884C ${short(a.command ?? a.cmd, 56)}`;
48
+ case /^(todo|plan|task_list)/i.test(toolName):
49
+ return "\u66F4\u65B0\u4EFB\u52A1\u677F";
50
+ case /browser/i.test(toolName):
51
+ return `\u8054\u7F51 ${short(a.url ?? a.args ?? a.action ?? "browser", 40)}`;
52
+ case /^(web|fetch|http|curl)/i.test(toolName):
53
+ return `\u8054\u7F51 ${short(a.url ?? a.search_term ?? a.query, 40)}`;
54
+ case /^(ask_user|question|confirm)/i.test(toolName):
55
+ return `\u8BF7\u793A ${short(a.question, 40)}`;
56
+ case isDelegationTool(toolName):
57
+ return `\u6D3E\u6D3B ${short(describeDelegation(a).map((t) => t.agent).join(", "), 40)}`;
58
+ default: {
59
+ const hint = short(a.path ?? a.command ?? a.query ?? a.name, 40);
60
+ return hint ? `${toolName} ${hint}` : toolName;
61
+ }
62
+ }
63
+ }
64
+ function describeDelegation(args = {}) {
65
+ const a = args ?? {};
66
+ const out = [];
67
+ const push = (agent, task, slot) => {
68
+ out.push({
69
+ agent: String(agent ?? "worker"),
70
+ task: short(task ?? "", 200) || "(\u672A\u63CF\u8FF0\u7684\u4EFB\u52A1)",
71
+ slot
72
+ });
73
+ };
74
+ if (Array.isArray(a.tasks)) {
75
+ a.tasks.forEach((t, i) => push(t?.agent, t?.task, i));
76
+ } else if (Array.isArray(a.chain)) {
77
+ a.chain.forEach((t, i) => push(t?.agent, t?.task, i));
78
+ } else if (a.agent || a.task || a.prompt || a.description) {
79
+ push(a.agent ?? a.subagent_type, a.task ?? a.prompt ?? a.description, 0);
80
+ }
81
+ return out;
82
+ }
83
+
84
+ // ../../plugins/agent-live/src/core/limits.ts
85
+ var SCENE_LIMITS = Object.freeze({
86
+ agents: 16,
87
+ seats: 8,
88
+ npcs: 12,
89
+ props: 80,
90
+ animatedProps: 24,
91
+ activities: 16,
92
+ effects: 40,
93
+ visibleBubbles: 4,
94
+ queuedBubbles: 8
95
+ });
96
+
97
+ // ../../plugins/agent-live/src/core/state.ts
98
+ var MAX_LOG = 200;
99
+ var MAX_HISTORY = 4e3;
100
+ var THOUGHT_FLUSH_MS = 180;
101
+ var MIN_THOUGHT_CHARS = 12;
102
+ var OfficeState = class {
103
+ agents = /* @__PURE__ */ new Map();
104
+ log = [];
105
+ history = [];
106
+ listeners = /* @__PURE__ */ new Set();
107
+ session;
108
+ thoughtBuffers = /* @__PURE__ */ new Map();
109
+ thoughtTimer;
110
+ seats = /* @__PURE__ */ new Set();
111
+ constructor(cwd) {
112
+ this.session = {
113
+ cwd,
114
+ busy: false,
115
+ turns: 0,
116
+ startedAt: Date.now()
117
+ };
118
+ }
119
+ subscribe(listener) {
120
+ this.listeners.add(listener);
121
+ return () => this.listeners.delete(listener);
122
+ }
123
+ emit(event) {
124
+ this.history.push({ at: Date.now(), event: structuredClone(event) });
125
+ if (this.history.length > MAX_HISTORY) this.history.shift();
126
+ for (const listener of this.listeners) {
127
+ try {
128
+ listener(structuredClone(event));
129
+ } catch {
130
+ }
131
+ }
132
+ }
133
+ snapshot(includeHistory = true) {
134
+ return structuredClone({
135
+ type: "snapshot",
136
+ agents: [...this.agents.values()],
137
+ log: this.log.slice(-60),
138
+ session: this.session,
139
+ history: includeHistory ? this.history.slice() : []
140
+ });
141
+ }
142
+ getAgent(id) {
143
+ return this.agents.get(id);
144
+ }
145
+ sessionBusy() {
146
+ return this.session.busy;
147
+ }
148
+ hasClients() {
149
+ return this.listeners.size > 0;
150
+ }
151
+ updateSession(patch) {
152
+ this.session = { ...this.session, ...patch };
153
+ this.emit({ type: "session", session: this.session });
154
+ }
155
+ claimSeat() {
156
+ for (let i = 0; i < SCENE_LIMITS.seats; i++) {
157
+ if (!this.seats.has(i)) {
158
+ this.seats.add(i);
159
+ return i;
160
+ }
161
+ }
162
+ return -1;
163
+ }
164
+ join(id, init) {
165
+ const existing = this.agents.get(id);
166
+ if (existing) {
167
+ Object.assign(existing, init);
168
+ this.emit({ type: "agent_join", agent: existing });
169
+ return existing;
170
+ }
171
+ if (this.agents.size >= SCENE_LIMITS.agents) return void 0;
172
+ const seat = this.claimSeat();
173
+ const agent = {
174
+ id,
175
+ state: "idle",
176
+ tokens: 0,
177
+ cost: 0,
178
+ toolCalls: 0,
179
+ joinedAt: Date.now(),
180
+ ...seat >= 0 ? { seat } : {},
181
+ ...init
182
+ };
183
+ this.agents.set(id, agent);
184
+ this.emit({ type: "agent_join", agent });
185
+ this.addLog(id, "join", `${agent.name} \u4E0A\u73ED\u4E86`);
186
+ return agent;
187
+ }
188
+ leave(id, ok) {
189
+ const agent = this.agents.get(id);
190
+ if (!agent) return;
191
+ if (agent.seat !== void 0) this.seats.delete(agent.seat);
192
+ this.agents.delete(id);
193
+ this.emit({ type: "agent_leave", id, ok });
194
+ this.addLog(id, "leave", `${agent.name} ${ok === true ? "\u4EA4\u4ED8\u5B8C\u6210\uFF0C\u4E0B\u73ED" : ok === false ? "\u5F02\u5E38\u9000\u51FA" : "\u7ED3\u675F\u5DE5\u4F5C"}`);
195
+ }
196
+ setState(id, state, detail) {
197
+ const agent = this.agents.get(id);
198
+ if (!agent) return;
199
+ agent.state = state;
200
+ agent.detail = detail;
201
+ this.emit({ type: "agent_state", id, state, detail });
202
+ }
203
+ setTask(id, task) {
204
+ const agent = this.agents.get(id);
205
+ if (!agent) return;
206
+ agent.task = task;
207
+ this.emit({ type: "task", id, task });
208
+ }
209
+ /** Thinking tokens arrive fast; batch them so the renderer gets readable chunks. */
210
+ pushThought(id, delta) {
211
+ if (!delta) return;
212
+ this.thoughtBuffers.set(id, (this.thoughtBuffers.get(id) ?? "") + delta);
213
+ if (this.thoughtTimer) return;
214
+ this.thoughtTimer = setTimeout(() => {
215
+ this.thoughtTimer = void 0;
216
+ this.flushThoughts(false);
217
+ }, THOUGHT_FLUSH_MS);
218
+ if (typeof this.thoughtTimer.unref === "function") this.thoughtTimer.unref();
219
+ }
220
+ flushThoughts(force = true) {
221
+ for (const [id, text] of this.thoughtBuffers) {
222
+ const clean = text.replace(/\s+/g, " ").trim();
223
+ if (!force && clean.length < MIN_THOUGHT_CHARS) continue;
224
+ this.thoughtBuffers.delete(id);
225
+ if (!clean) continue;
226
+ const agent = this.agents.get(id);
227
+ if (agent) agent.thought = clean.slice(-160);
228
+ this.emit({ type: "thought", id, text: clean });
229
+ this.addLog(id, "thought", clean, false);
230
+ }
231
+ }
232
+ say(id, text) {
233
+ const clean = text.replace(/\s+/g, " ").trim();
234
+ if (!clean) return;
235
+ this.emit({ type: "say", id, text: clean.slice(0, 400) });
236
+ this.addLog(id, "say", clean.slice(0, 400), false);
237
+ }
238
+ startAction(id, toolCallId, action, label) {
239
+ const agent = this.agents.get(id);
240
+ if (agent) {
241
+ agent.action = action;
242
+ agent.toolCalls += 1;
243
+ agent.state = "working";
244
+ agent.detail = label;
245
+ }
246
+ this.emit({ type: "action", id, action, label, toolCallId });
247
+ this.addLog(id, "tool", label, false);
248
+ }
249
+ endAction(id, toolCallId, ok) {
250
+ const agent = this.agents.get(id);
251
+ if (agent) agent.action = void 0;
252
+ this.emit({ type: "action_end", id, toolCallId, ok });
253
+ }
254
+ delegate(from, to, task) {
255
+ this.emit({ type: "delegate", from, to, task });
256
+ const fromName = this.agents.get(from)?.name ?? from;
257
+ const toName = this.agents.get(to)?.name ?? to;
258
+ this.addLog(from, "delegate", `${fromName} \u2192 ${toName}: ${task.slice(0, 120)}`);
259
+ }
260
+ addUsage(id, tokens, cost) {
261
+ const agent = this.agents.get(id);
262
+ if (!agent) return;
263
+ if (Number.isFinite(tokens) && tokens >= 0) agent.tokens = tokens;
264
+ if (Number.isFinite(cost) && cost >= 0) agent.cost = cost;
265
+ this.emit({ type: "usage", id, tokens: agent.tokens, cost: agent.cost });
266
+ }
267
+ addLog(agentId, kind, text, broadcast = true) {
268
+ const item = { at: Date.now(), agentId, kind, text };
269
+ this.log.push(item);
270
+ if (this.log.length > MAX_LOG) this.log.shift();
271
+ if (broadcast) this.emit({ type: "log", item });
272
+ }
273
+ dispose() {
274
+ if (this.thoughtTimer) clearTimeout(this.thoughtTimer);
275
+ this.thoughtTimer = void 0;
276
+ this.thoughtBuffers.clear();
277
+ this.listeners.clear();
278
+ this.agents.clear();
279
+ this.seats.clear();
280
+ this.log = [];
281
+ this.history = [];
282
+ }
283
+ };
284
+
285
+ // ../../plugins/agent-live/src/runtime/server.ts
286
+ import { spawn } from "node:child_process";
287
+ import * as fs from "node:fs";
288
+ import * as http from "node:http";
289
+ import * as path from "node:path";
290
+ import { fileURLToPath } from "node:url";
291
+ var MIME = {
292
+ ".html": "text/html; charset=utf-8",
293
+ ".js": "text/javascript; charset=utf-8",
294
+ ".css": "text/css; charset=utf-8",
295
+ ".json": "application/json; charset=utf-8",
296
+ ".png": "image/png",
297
+ ".svg": "image/svg+xml"
298
+ };
299
+ function moduleDir() {
300
+ try {
301
+ return path.dirname(fileURLToPath(import.meta.url));
302
+ } catch {
303
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
304
+ }
305
+ }
306
+ var WEB_ROOT = [
307
+ path.resolve(moduleDir(), "..", "..", "web"),
308
+ path.resolve(moduleDir(), "..", "web")
309
+ ].find((candidate) => fs.existsSync(candidate)) ?? path.resolve(moduleDir(), "..", "..", "web");
310
+ function resolveViewerUrl(baseUrl, target = "") {
311
+ if (!target) return baseUrl;
312
+ const normalizedTarget = target.startsWith("/") ? target : `/${target}`;
313
+ return new URL(normalizedTarget, baseUrl).toString();
314
+ }
315
+ async function startServer(state, options) {
316
+ const host = options.host ?? "127.0.0.1";
317
+ const clients = /* @__PURE__ */ new Set();
318
+ const sockets = /* @__PURE__ */ new Set();
319
+ const accessToken = options.controls?.token ?? options.creatorToken;
320
+ let closePromise = null;
321
+ const server = http.createServer((req, res) => {
322
+ const url2 = new URL(req.url ?? "/", `http://${host}`);
323
+ const controls = options.controls;
324
+ if (!isLocalRequest(req)) {
325
+ json(res, 403, { error: "local requests only" });
326
+ return;
327
+ }
328
+ if (accessToken && (url2.pathname === "/events" || url2.pathname.startsWith("/api/") && url2.pathname !== "/api/scene-limits") && !hasToken(req, url2, accessToken)) {
329
+ json(res, 403, { error: "forbidden" });
330
+ return;
331
+ }
332
+ if (url2.pathname === "/events") {
333
+ res.writeHead(200, {
334
+ "content-type": "text/event-stream",
335
+ "cache-control": "no-cache, no-transform",
336
+ connection: "keep-alive",
337
+ "x-accel-buffering": "no"
338
+ });
339
+ res.write(": connected\n\n");
340
+ write(res, state.snapshot(false));
341
+ clients.add(res);
342
+ options.onViewerCountChange?.(clients.size);
343
+ const unsubscribe = state.subscribe((event) => write(res, event));
344
+ const heartbeat = setInterval(() => res.write(": ping\n\n"), 25e3);
345
+ heartbeat.unref?.();
346
+ req.on("close", () => {
347
+ clearInterval(heartbeat);
348
+ unsubscribe();
349
+ clients.delete(res);
350
+ options.onViewerCountChange?.(clients.size);
351
+ });
352
+ return;
353
+ }
354
+ if (url2.pathname === "/api/state") {
355
+ res.writeHead(200, { "content-type": MIME[".json"] });
356
+ res.end(JSON.stringify(state.snapshot()));
357
+ return;
358
+ }
359
+ if (url2.pathname === "/api/scene-limits") {
360
+ json(res, 200, SCENE_LIMITS);
361
+ return;
362
+ }
363
+ if (options.content && url2.pathname === "/api/offices" && req.method === "GET") {
364
+ void options.content.list().then((value) => json(res, 200, value), (error) => json(res, 500, { error: error.message }));
365
+ return;
366
+ }
367
+ if (options.content?.select && url2.pathname === "/api/office-selection" && req.method === "POST") {
368
+ if (!accessToken || req.headers["x-agent-live-token"] !== accessToken) {
369
+ json(res, 403, { error: "forbidden" });
370
+ return;
371
+ }
372
+ void readJson(req).then((body) => options.content.select(String(body.id ?? ""))).then(
373
+ (value) => json(res, 200, value ?? { ok: true }),
374
+ (error) => json(res, 400, { error: error.message })
375
+ );
376
+ return;
377
+ }
378
+ if (options.content && url2.pathname === "/api/office-content" && req.method === "GET") {
379
+ void options.content.resolve(url2.searchParams.get("id") ?? void 0).then((value) => json(res, 200, value), (error) => json(res, 404, { error: error.message }));
380
+ return;
381
+ }
382
+ if (options.content?.subscribe && url2.pathname === "/api/content-events" && req.method === "GET") {
383
+ res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache, no-transform", connection: "keep-alive", "x-accel-buffering": "no" });
384
+ res.write(": connected\n\n");
385
+ const unsubscribe = options.content.subscribe((change) => res.write(`data: ${JSON.stringify(change)}
386
+
387
+ `));
388
+ const heartbeat = setInterval(() => res.write(": ping\n\n"), 25e3);
389
+ heartbeat.unref?.();
390
+ req.on("close", () => {
391
+ clearInterval(heartbeat);
392
+ unsubscribe();
393
+ });
394
+ return;
395
+ }
396
+ if (controls && url2.pathname === "/api/client/status" && req.method === "GET") {
397
+ json(res, 200, controls.status());
398
+ return;
399
+ }
400
+ if (controls && url2.pathname.startsWith("/api/client/") && req.method === "POST") {
401
+ if (req.headers["x-agent-live-token"] !== controls.token) {
402
+ json(res, 403, { error: "forbidden" });
403
+ return;
404
+ }
405
+ void readJson(req).then(async (body) => {
406
+ if (url2.pathname === "/api/client/model") return controls.selectModel(String(body.model ?? ""));
407
+ if (url2.pathname === "/api/client/prompt") {
408
+ return controls.prompt(String(body.text ?? ""), typeof body.model === "string" ? body.model : void 0);
409
+ }
410
+ if (url2.pathname === "/api/client/interrupt") return controls.interrupt();
411
+ if (url2.pathname === "/api/client/approval") {
412
+ return controls.resolveApproval(
413
+ body.id,
414
+ Boolean(body.allow),
415
+ Boolean(body.forSession)
416
+ );
417
+ }
418
+ throw new Error("unknown client endpoint");
419
+ }).then(
420
+ (result) => json(res, 200, result ?? { ok: true }),
421
+ (error) => json(res, 400, { error: error.message })
422
+ );
423
+ return;
424
+ }
425
+ if (options.creator && url2.pathname === "/api/creator" && req.method === "POST") {
426
+ const expectedToken = options.creatorToken ?? controls?.token;
427
+ if (!expectedToken || req.headers["x-agent-live-token"] !== expectedToken) {
428
+ json(res, 403, { error: "forbidden" });
429
+ return;
430
+ }
431
+ void readJson(req).then((command) => options.creator.execute(command)).then(
432
+ (result) => json(res, 200, result),
433
+ (error) => json(res, 400, { error: error.message })
434
+ );
435
+ return;
436
+ }
437
+ serveStatic(url2.pathname, res);
438
+ });
439
+ server.on("connection", (socket) => {
440
+ sockets.add(socket);
441
+ socket.once("close", () => sockets.delete(socket));
442
+ });
443
+ const port = await listen(server, host, options.port);
444
+ const url = `http://localhost:${port}`;
445
+ return {
446
+ url,
447
+ port,
448
+ open(target = "") {
449
+ openInBrowser(resolveViewerUrl(url, target));
450
+ },
451
+ async close() {
452
+ if (closePromise) return closePromise;
453
+ closePromise = new Promise((resolve2) => {
454
+ const forceClose = setTimeout(() => {
455
+ for (const socket of sockets) socket.destroy();
456
+ }, 2e3);
457
+ for (const client of clients) client.end();
458
+ clients.clear();
459
+ server.close(() => {
460
+ clearTimeout(forceClose);
461
+ sockets.clear();
462
+ resolve2();
463
+ });
464
+ });
465
+ return closePromise;
466
+ }
467
+ };
468
+ }
469
+ function json(res, status, value) {
470
+ res.writeHead(status, { "content-type": MIME[".json"], "cache-control": "no-store" });
471
+ res.end(JSON.stringify(value));
472
+ }
473
+ function hasToken(req, url, expected) {
474
+ return req.headers["x-agent-live-token"] === expected || url.searchParams.get("token") === expected;
475
+ }
476
+ function isLocalRequest(req) {
477
+ const local = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
478
+ try {
479
+ const hostname = new URL(`http://${req.headers.host ?? ""}`).hostname;
480
+ if (!local.has(hostname)) return false;
481
+ const origin = req.headers.origin;
482
+ if (origin && !local.has(new URL(origin).hostname)) return false;
483
+ return true;
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
488
+ function readJson(req) {
489
+ return new Promise((resolve2, reject) => {
490
+ let body = "";
491
+ req.setEncoding("utf8");
492
+ req.on("data", (chunk) => {
493
+ body += chunk;
494
+ if (body.length > 1e6) reject(new Error("request body is too large"));
495
+ });
496
+ req.on("end", () => {
497
+ try {
498
+ resolve2(body ? JSON.parse(body) : {});
499
+ } catch {
500
+ reject(new Error("invalid JSON"));
501
+ }
502
+ });
503
+ req.on("error", reject);
504
+ });
505
+ }
506
+ function write(res, event) {
507
+ try {
508
+ res.write(`data: ${JSON.stringify(event)}
509
+
510
+ `);
511
+ } catch {
512
+ }
513
+ }
514
+ function serveStatic(pathname, res) {
515
+ const rel = pathname === "/" ? "index.html" : pathname.replace(/^\/+/, "");
516
+ const filePath = path.resolve(WEB_ROOT, rel);
517
+ const relative2 = path.relative(WEB_ROOT, filePath);
518
+ if (relative2.startsWith("..") || path.isAbsolute(relative2)) {
519
+ res.writeHead(403).end("forbidden");
520
+ return;
521
+ }
522
+ fs.readFile(filePath, (err, data) => {
523
+ if (err) {
524
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
525
+ res.end(`not found: ${rel}`);
526
+ return;
527
+ }
528
+ res.writeHead(200, {
529
+ "content-type": MIME[path.extname(filePath)] ?? "application/octet-stream",
530
+ "cache-control": "no-cache"
531
+ });
532
+ res.end(data);
533
+ });
534
+ }
535
+ function listen(server, host, startPort, attempts = 12) {
536
+ return new Promise((resolve2, reject) => {
537
+ let port = startPort;
538
+ let left = attempts;
539
+ const onError = (err) => {
540
+ if (err.code === "EADDRINUSE" && left-- > 0) {
541
+ port += 1;
542
+ server.listen(port, host);
543
+ return;
544
+ }
545
+ server.off("error", onError);
546
+ reject(err);
547
+ };
548
+ server.on("error", onError);
549
+ server.once("listening", () => {
550
+ server.off("error", onError);
551
+ resolve2(server.address().port);
552
+ });
553
+ server.listen(port, host);
554
+ });
555
+ }
556
+ function openInBrowser(url) {
557
+ const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "rundll32" : "xdg-open";
558
+ const args = process.platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
559
+ const child = spawn(command, args, { stdio: "ignore", detached: true });
560
+ child.once("error", () => {
561
+ });
562
+ child.unref();
563
+ }
564
+
565
+ // ../../plugins/agent-live/src/runtime/content-service.ts
566
+ import os from "node:os";
567
+ import path5 from "node:path";
568
+ import { existsSync as existsSync2 } from "node:fs";
569
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
570
+
571
+ // ../../plugins/agent-live/src/content/library.ts
572
+ import { readFile as readFile2 } from "node:fs/promises";
573
+ import path2 from "node:path";
574
+ async function readJson2(file) {
575
+ return JSON.parse(await readFile2(file, "utf8"));
576
+ }
577
+ async function loadComponentLibrary(contentRoot) {
578
+ const root = path2.join(contentRoot, "component-library");
579
+ const [catalog, props, npcTemplates, agentProfileTemplates, activityRecipes, activityImplementations] = await Promise.all([
580
+ readJson2(path2.join(root, "catalog.json")),
581
+ readJson2(path2.join(root, "props.json")),
582
+ readJson2(path2.join(root, "npc-templates.json")),
583
+ readJson2(path2.join(root, "agent-profile-templates.json")),
584
+ readJson2(path2.join(root, "activity-recipes.json")),
585
+ readJson2(path2.join(root, "activity-implementations.json"))
586
+ ]);
587
+ const layouts = /* @__PURE__ */ new Map();
588
+ for (const entry of catalog.layouts) layouts.set(entry.id, await readJson2(path2.join(contentRoot, "layouts", `${entry.id.replace(/^builtin\//, "")}.json`)));
589
+ return {
590
+ descriptors: {
591
+ styles: catalog.styles,
592
+ layouts: catalog.layouts,
593
+ agentSkins: catalog.agentSkins,
594
+ atmospheres: catalog.atmospheres,
595
+ environments: catalog.environments
596
+ },
597
+ styles: new Set(catalog.styles.map((entry) => entry.id)),
598
+ layouts,
599
+ agentSkins: new Set(catalog.agentSkins.map((entry) => entry.id)),
600
+ props: new Map(props.entries.map((entry) => [entry.id, entry])),
601
+ npcTemplates: new Map(npcTemplates.entries.map((entry) => [entry.id, entry])),
602
+ agentProfileTemplates: new Map(agentProfileTemplates.entries.map((entry) => [entry.id, entry])),
603
+ activityRecipes: new Map(activityRecipes.entries.map((entry) => [entry.id, entry])),
604
+ activityImplementations: new Map(activityImplementations.entries.map((entry) => [`${entry.layout}|${entry.recipe}`, entry])),
605
+ atmospheres: new Set(catalog.atmospheres.map((entry) => entry.id)),
606
+ environments: new Set(catalog.environments.map((entry) => entry.id)),
607
+ defaultNpcTemplate: npcTemplates.defaultTemplate,
608
+ npcProfilePolicy: npcTemplates.defaultInstancePolicy
609
+ };
610
+ }
611
+ async function loadOfficialOffices(contentRoot) {
612
+ const catalog = await readJson2(path2.join(contentRoot, "catalog.json"));
613
+ const publicIds = catalog.presets.filter((entry) => entry.visibility !== "internal").map((entry) => entry.id);
614
+ return Promise.all(publicIds.map((id) => readJson2(path2.join(contentRoot, "official-offices", `${id}.json`))));
615
+ }
616
+
617
+ // ../../plugins/agent-live/src/content/registry.ts
618
+ import { EventEmitter } from "node:events";
619
+ import { mkdir, readFile as readFile3, readdir, rename, rm, writeFile } from "node:fs/promises";
620
+ import path3 from "node:path";
621
+
622
+ // ../../plugins/agent-live/src/content/schema.ts
623
+ var OFFICE_SPEC_SCHEMA_VERSION = 1;
624
+ var GENDER_VALUES = ["female", "male", "nonbinary", "unspecified"];
625
+ var POSE_VALUES = ["stand", "sit"];
626
+ var ORIENTATION_VALUES = ["horizontal", "vertical"];
627
+ var WEATHER_VALUES = ["clear", "cloudy", "rain", "snow"];
628
+ var OFFICE_SPEC_DEFAULTS = Object.freeze({
629
+ style: "builtin/pixel-classic",
630
+ agentSkin: "builtin/tiny-developers",
631
+ atmosphere: "builtin/default-atmosphere",
632
+ environment: "builtin/local-office-environment",
633
+ agentProfile: Object.freeze({ template: "builtin/host-agent" }),
634
+ placements: Object.freeze([]),
635
+ npcs: Object.freeze([]),
636
+ activities: Object.freeze([])
637
+ });
638
+ var SPEC_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "kind", "id", "name", "origin", "basePreset", "layout", "style", "agentSkin", "placements", "npcs", "activities", "atmosphere", "environment", "environmentOverrides", "agentProfile", "texts"]);
639
+ var PATCH_KEYS = /* @__PURE__ */ new Set(["schemaVersion", "kind", "id", "base", "name", "components", "placements", "npcs", "activities", "environmentOverrides", "agentProfile", "texts"]);
640
+ var COMPONENT_KEYS = /* @__PURE__ */ new Set(["layout", "style", "agentSkin", "atmosphere", "environment"]);
641
+ var PLACEMENT_KEYS = /* @__PURE__ */ new Set(["id", "component", "slot", "orientation"]);
642
+ var NPC_KEYS = /* @__PURE__ */ new Set(["id", "template", "profile", "name", "title", "gender", "appearance", "spawn", "shift", "pose"]);
643
+ var APPEARANCE_KEYS = /* @__PURE__ */ new Set(["skin", "hair", "shirt", "trim", "badge"]);
644
+ var AGENT_PROFILE_KEYS = /* @__PURE__ */ new Set(["template", "name", "title", "appearance"]);
645
+ var SHIFT_KEYS = /* @__PURE__ */ new Set(["start", "end", "endLatest"]);
646
+ var ENVIRONMENT_KEYS = /* @__PURE__ */ new Set(["clock", "weather", "lighting", "npcSchedule"]);
647
+ var CLOCK_KEYS = /* @__PURE__ */ new Set(["mode", "fixedTime"]);
648
+ var WEATHER_KEYS = /* @__PURE__ */ new Set(["fallback"]);
649
+ var LIGHTING_KEYS = /* @__PURE__ */ new Set(["auto"]);
650
+ var NPC_SCHEDULE_KEYS = /* @__PURE__ */ new Set(["defaultShift", "roleOverrides"]);
651
+ var COLLECTION_PATCH_KEYS = /* @__PURE__ */ new Set(["upsert", "remove"]);
652
+ var ACTIVITY_PATCH_KEYS = /* @__PURE__ */ new Set(["enable", "disable"]);
653
+ function object(value) {
654
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
655
+ }
656
+ function issue(issues, path6, message) {
657
+ issues.push({ path: path6, message });
658
+ }
659
+ function exactKeys(value, allowed, path6, issues) {
660
+ for (const key of Object.keys(value)) if (!allowed.has(key)) issue(issues, `${path6}.${key}`, "unknown field");
661
+ }
662
+ function requiredString(value, path6, issues) {
663
+ if (typeof value !== "string" || value.trim() === "") issue(issues, path6, "must be a non-empty string");
664
+ }
665
+ function optionalString(value, path6, issues) {
666
+ if (value !== void 0) requiredString(value, path6, issues);
667
+ }
668
+ function stringArray(value, path6, issues) {
669
+ if (!Array.isArray(value)) return issue(issues, path6, "must be an array");
670
+ for (let index = 0; index < value.length; index += 1) requiredString(value[index], `${path6}[${index}]`, issues);
671
+ }
672
+ function enumValue(value, allowed, path6, issues, optional = false) {
673
+ if (optional && value === void 0) return;
674
+ if (typeof value !== "string" || !allowed.includes(value)) issue(issues, path6, `must be one of: ${allowed.join(", ")}`);
675
+ }
676
+ function validateShift(value, path6, issues) {
677
+ if (!object(value)) return issue(issues, path6, "must be an object");
678
+ exactKeys(value, SHIFT_KEYS, path6, issues);
679
+ requiredString(value.start, `${path6}.start`, issues);
680
+ requiredString(value.end, `${path6}.end`, issues);
681
+ optionalString(value.endLatest, `${path6}.endLatest`, issues);
682
+ }
683
+ function validateAppearance(value, path6, issues) {
684
+ if (!object(value)) return issue(issues, path6, "must be an object");
685
+ exactKeys(value, APPEARANCE_KEYS, path6, issues);
686
+ for (const [key, color] of Object.entries(value)) optionalString(color, `${path6}.${key}`, issues);
687
+ }
688
+ function validatePlacement(value, path6, issues) {
689
+ if (!object(value)) return issue(issues, path6, "must be an object");
690
+ exactKeys(value, PLACEMENT_KEYS, path6, issues);
691
+ requiredString(value.id, `${path6}.id`, issues);
692
+ requiredString(value.component, `${path6}.component`, issues);
693
+ requiredString(value.slot, `${path6}.slot`, issues);
694
+ enumValue(value.orientation, ORIENTATION_VALUES, `${path6}.orientation`, issues, true);
695
+ }
696
+ function validateNpc(value, path6, issues) {
697
+ if (!object(value)) return issue(issues, path6, "must be an object");
698
+ exactKeys(value, NPC_KEYS, path6, issues);
699
+ requiredString(value.id, `${path6}.id`, issues);
700
+ for (const key of ["template", "profile", "name", "title", "spawn"]) optionalString(value[key], `${path6}.${key}`, issues);
701
+ enumValue(value.gender, GENDER_VALUES, `${path6}.gender`, issues, true);
702
+ enumValue(value.pose, POSE_VALUES, `${path6}.pose`, issues, true);
703
+ if (value.appearance !== void 0) validateAppearance(value.appearance, `${path6}.appearance`, issues);
704
+ if (value.shift !== void 0) validateShift(value.shift, `${path6}.shift`, issues);
705
+ }
706
+ function validateAgentProfile(value, path6, issues) {
707
+ if (!object(value)) return issue(issues, path6, "must be an object");
708
+ exactKeys(value, AGENT_PROFILE_KEYS, path6, issues);
709
+ requiredString(value.template, `${path6}.template`, issues);
710
+ optionalString(value.name, `${path6}.name`, issues);
711
+ optionalString(value.title, `${path6}.title`, issues);
712
+ if (value.appearance !== void 0) validateAppearance(value.appearance, `${path6}.appearance`, issues);
713
+ }
714
+ function validateEnvironment(value, path6, issues) {
715
+ if (!object(value)) return issue(issues, path6, "must be an object");
716
+ exactKeys(value, ENVIRONMENT_KEYS, path6, issues);
717
+ if (value.clock !== void 0) {
718
+ if (!object(value.clock)) issue(issues, `${path6}.clock`, "must be an object");
719
+ else {
720
+ exactKeys(value.clock, CLOCK_KEYS, `${path6}.clock`, issues);
721
+ enumValue(value.clock.mode, ["local", "fixed"], `${path6}.clock.mode`, issues);
722
+ optionalString(value.clock.fixedTime, `${path6}.clock.fixedTime`, issues);
723
+ }
724
+ }
725
+ if (value.weather !== void 0) {
726
+ if (!object(value.weather)) issue(issues, `${path6}.weather`, "must be an object");
727
+ else {
728
+ exactKeys(value.weather, WEATHER_KEYS, `${path6}.weather`, issues);
729
+ enumValue(value.weather.fallback, WEATHER_VALUES, `${path6}.weather.fallback`, issues);
730
+ }
731
+ }
732
+ if (value.lighting !== void 0) {
733
+ if (!object(value.lighting)) issue(issues, `${path6}.lighting`, "must be an object");
734
+ else {
735
+ exactKeys(value.lighting, LIGHTING_KEYS, `${path6}.lighting`, issues);
736
+ if (typeof value.lighting.auto !== "boolean") issue(issues, `${path6}.lighting.auto`, "must be a boolean");
737
+ }
738
+ }
739
+ if (value.npcSchedule !== void 0) {
740
+ if (!object(value.npcSchedule)) issue(issues, `${path6}.npcSchedule`, "must be an object");
741
+ else {
742
+ exactKeys(value.npcSchedule, NPC_SCHEDULE_KEYS, `${path6}.npcSchedule`, issues);
743
+ if (value.npcSchedule.defaultShift !== void 0) validateShift(value.npcSchedule.defaultShift, `${path6}.npcSchedule.defaultShift`, issues);
744
+ if (value.npcSchedule.roleOverrides !== void 0) {
745
+ if (!object(value.npcSchedule.roleOverrides)) issue(issues, `${path6}.npcSchedule.roleOverrides`, "must be an object");
746
+ else for (const [role, shift] of Object.entries(value.npcSchedule.roleOverrides)) validateShift(shift, `${path6}.npcSchedule.roleOverrides.${role}`, issues);
747
+ }
748
+ }
749
+ }
750
+ }
751
+ function validateTexts(value, issues) {
752
+ if (!object(value)) return issue(issues, "$.texts", "must be an object");
753
+ if (Object.keys(value).length > 8) issue(issues, "$.texts", "maximum 8 text areas");
754
+ for (const [key, text] of Object.entries(value)) {
755
+ if (!/^[a-z][a-z0-9-]*$/.test(key)) issue(issues, `$.texts.${key}`, "invalid text area id");
756
+ if (typeof text !== "string" || [...text].length > 120 || /[\r\n\u0000-\u001f]/.test(text)) issue(issues, `$.texts.${key}`, "must be single-line plain text of at most 120 characters");
757
+ }
758
+ }
759
+ function validateOfficeSpecShape(input) {
760
+ const issues = [];
761
+ if (!object(input)) return [{ path: "$", message: "must be an object" }];
762
+ exactKeys(input, SPEC_KEYS, "$", issues);
763
+ if (input.schemaVersion !== OFFICE_SPEC_SCHEMA_VERSION) issue(issues, "$.schemaVersion", `must equal ${OFFICE_SPEC_SCHEMA_VERSION}`);
764
+ if (input.kind !== "office-spec") issue(issues, "$.kind", "must equal office-spec");
765
+ for (const key of ["id", "name", "layout", "style", "agentSkin", "atmosphere", "environment"]) requiredString(input[key], `$.${key}`, issues);
766
+ enumValue(input.origin, ["official", "custom"], "$.origin", issues);
767
+ optionalString(input.basePreset, "$.basePreset", issues);
768
+ if (!Array.isArray(input.placements)) issue(issues, "$.placements", "must be an array");
769
+ else input.placements.forEach((value, index) => validatePlacement(value, `$.placements[${index}]`, issues));
770
+ if (!Array.isArray(input.npcs)) issue(issues, "$.npcs", "must be an array");
771
+ else input.npcs.forEach((value, index) => validateNpc(value, `$.npcs[${index}]`, issues));
772
+ stringArray(input.activities, "$.activities", issues);
773
+ if (input.environmentOverrides !== void 0) validateEnvironment(input.environmentOverrides, "$.environmentOverrides", issues);
774
+ if (input.agentProfile !== void 0) validateAgentProfile(input.agentProfile, "$.agentProfile", issues);
775
+ if (input.texts !== void 0) validateTexts(input.texts, issues);
776
+ return issues;
777
+ }
778
+ function validateCollectionPatch(value, path6, issues, itemValidator) {
779
+ if (!object(value)) return issue(issues, path6, "must be an object");
780
+ exactKeys(value, COLLECTION_PATCH_KEYS, path6, issues);
781
+ if (value.upsert !== void 0) {
782
+ if (!Array.isArray(value.upsert)) issue(issues, `${path6}.upsert`, "must be an array");
783
+ else value.upsert.forEach((item, index) => itemValidator(item, `${path6}.upsert[${index}]`, issues));
784
+ }
785
+ if (value.remove !== void 0) stringArray(value.remove, `${path6}.remove`, issues);
786
+ }
787
+ function validateOfficePatchShape(input) {
788
+ const issues = [];
789
+ if (!object(input)) return [{ path: "$", message: "must be an object" }];
790
+ exactKeys(input, PATCH_KEYS, "$", issues);
791
+ if (input.schemaVersion !== OFFICE_SPEC_SCHEMA_VERSION) issue(issues, "$.schemaVersion", `must equal ${OFFICE_SPEC_SCHEMA_VERSION}`);
792
+ if (input.kind !== "office-patch") issue(issues, "$.kind", "must equal office-patch");
793
+ requiredString(input.base, "$.base", issues);
794
+ optionalString(input.id, "$.id", issues);
795
+ optionalString(input.name, "$.name", issues);
796
+ if (input.components !== void 0) {
797
+ if (!object(input.components)) issue(issues, "$.components", "must be an object");
798
+ else {
799
+ exactKeys(input.components, COMPONENT_KEYS, "$.components", issues);
800
+ if (input.components.layout !== void 0) {
801
+ issue(issues, "$.components.layout", "an Office keeps its room; edit the Office that already uses that layout instead");
802
+ }
803
+ for (const [key, value] of Object.entries(input.components)) optionalString(value, `$.components.${key}`, issues);
804
+ }
805
+ }
806
+ if (input.placements !== void 0) validateCollectionPatch(input.placements, "$.placements", issues, validatePlacement);
807
+ if (input.npcs !== void 0) validateCollectionPatch(input.npcs, "$.npcs", issues, validateNpc);
808
+ if (input.activities !== void 0) {
809
+ if (!object(input.activities)) issue(issues, "$.activities", "must be an object");
810
+ else {
811
+ exactKeys(input.activities, ACTIVITY_PATCH_KEYS, "$.activities", issues);
812
+ if (input.activities.enable !== void 0) stringArray(input.activities.enable, "$.activities.enable", issues);
813
+ if (input.activities.disable !== void 0) stringArray(input.activities.disable, "$.activities.disable", issues);
814
+ }
815
+ }
816
+ if (input.environmentOverrides !== void 0 && input.environmentOverrides !== null) validateEnvironment(input.environmentOverrides, "$.environmentOverrides", issues);
817
+ if (input.agentProfile !== void 0 && input.agentProfile !== null) validateAgentProfile(input.agentProfile, "$.agentProfile", issues);
818
+ if (input.texts !== void 0 && input.texts !== null) validateTexts(input.texts, issues);
819
+ return issues;
820
+ }
821
+
822
+ // ../../plugins/agent-live/src/content/graph-validator.ts
823
+ var WORK_CAPABILITIES = ["research", "create", "compute", "plan", "communicate", "collaborate"];
824
+ var DYNAMIC_ACTIVITY_TARGETS = /* @__PURE__ */ new Set(["near-colleague"]);
825
+ function namedInstance(requirement) {
826
+ if (typeof requirement === "string") return requirement;
827
+ if (requirement && typeof requirement.prop === "string") return requirement.prop;
828
+ return void 0;
829
+ }
830
+ function resolveActivityRequirements(requires, instances, capabilitiesOf, activity) {
831
+ const table = [...instances];
832
+ const bindings = [];
833
+ const issues = [];
834
+ for (const requirement of requires ?? []) {
835
+ const named = namedInstance(requirement);
836
+ if (named !== void 0) {
837
+ const present = table.some(([instanceId]) => instanceId === named);
838
+ bindings.push(present ? named : null);
839
+ if (!present) issues.push({ code: "missing-activity-prop", path: "$", message: `${activity} \u7F3A\u5C11 Prop\uFF1A${named}` });
840
+ continue;
841
+ }
842
+ const capability = requirement && typeof requirement.capability === "string" ? requirement.capability : void 0;
843
+ if (!capability) {
844
+ bindings.push(null);
845
+ issues.push({ code: "invalid-activity-requirement", path: "$", message: `${activity} \u7684\u4F9D\u8D56\u5FC5\u987B\u662F\u5177\u540D\u9053\u5177\u6216\u80FD\u529B\u9700\u6C42` });
846
+ continue;
847
+ }
848
+ const match = table.find(([, type]) => capabilitiesOf(type).includes(capability));
849
+ bindings.push(match ? match[0] : null);
850
+ if (!match) issues.push({ code: "missing-activity-capability", path: "$", message: `${activity} \u9700\u8981 ${capability} \u80FD\u529B\uFF0C\u5F53\u524D\u529E\u516C\u5BA4\u6CA1\u6709\u63D0\u4F9B\u8BE5\u80FD\u529B\u7684\u9053\u5177` });
851
+ }
852
+ return { bindings, issues };
853
+ }
854
+ function assertManifest(value, kind) {
855
+ const manifest = value;
856
+ if (!manifest || manifest.schemaVersion !== 1 || manifest.kind !== kind || !manifest.id || !manifest.version) {
857
+ throw new Error(`\u65E0\u6548\u7684 ${kind} \u5185\u5BB9\u6E05\u5355`);
858
+ }
859
+ }
860
+ function manifestKindFor(key) {
861
+ if (key === "agentSkin") return "agent-skin";
862
+ if (key === "lifeActivities") return "life-activities";
863
+ return key;
864
+ }
865
+ function validClock(value) {
866
+ if (typeof value !== "string" || !/^\d{2}:\d{2}$/.test(value)) return false;
867
+ const [hour, minute] = value.split(":").map(Number);
868
+ return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59;
869
+ }
870
+ function shiftIssue(value, code, path6, label) {
871
+ if (!value || !validClock(value.start) || !validClock(value.end)) return { code, path: path6, message: `${label} \u5FC5\u987B\u63D0\u4F9B\u6709\u6548\u7684 HH:MM \u8D77\u6B62\u65F6\u95F4` };
872
+ if (value.endLatest !== void 0 && !validClock(value.endLatest)) return { code, path: `${path6}.endLatest`, message: `${label} \u7684\u6700\u665A\u4E0B\u73ED\u65F6\u95F4\u5FC5\u987B\u662F\u6709\u6548\u7684 HH:MM` };
873
+ return null;
874
+ }
875
+ function layoutIssues(layout, propTypeNames) {
876
+ if (!layout || typeof layout !== "object") return [{ code: "missing-layout", path: "$.layout", message: "Layout \u7F3A\u5931" }];
877
+ const issues = [];
878
+ if (layout.contract !== "single-office-v1") issues.push({ code: "invalid-layout-contract", path: "$.layout", message: `\u4E0D\u652F\u6301\u7684 Layout \u5408\u540C\uFF1A${layout.contract}` });
879
+ if (layout.canvas?.width !== 384 || layout.canvas?.height !== 216) issues.push({ code: "invalid-layout-canvas", path: "$.layout", message: "single-office-v1 \u5FC5\u987B\u4F7F\u7528 384\xD7216 \u903B\u8F91\u753B\u5E03" });
880
+ if (!Array.isArray(layout.seats) || layout.seats.length !== 8) issues.push({ code: "invalid-layout-seats", path: "$.layout", message: "Demo Layout \u5FC5\u987B\u63D0\u4F9B 8 \u4E2A\u5EA7\u4F4D" });
881
+ if (!Array.isArray(layout.navigation?.lanes) || !layout.navigation.lanes.length) issues.push({ code: "invalid-layout-navigation", path: "$.layout", message: "Layout \u7F3A\u5C11\u5BFC\u822A\u901A\u9053" });
882
+ const propTypes = new Set(propTypeNames);
883
+ const textIds = /* @__PURE__ */ new Set();
884
+ if (layout.textSlots !== void 0 && (!Array.isArray(layout.textSlots) || layout.textSlots.length > 8)) {
885
+ issues.push({ code: "invalid-text-slots", path: "$.layout.textSlots", message: "maximum 8 text areas" });
886
+ } else for (const slot of layout.textSlots ?? []) {
887
+ const valid = slot && typeof slot.id === "string" && /^[a-z][a-z0-9-]*$/.test(slot.id) && !textIds.has(slot.id) && [slot.x, slot.y, slot.width, slot.height, slot.maxLength].every(Number.isFinite) && slot.x >= 0 && slot.y >= 0 && slot.width >= 12 && slot.height >= 9 && slot.x + slot.width <= 384 && slot.y + slot.height <= 216 && slot.maxLength >= 1 && slot.maxLength <= 120 && [slot.text, slot.defaultText].every((text) => text === void 0 || typeof text === "string" && [...text].length <= slot.maxLength);
888
+ if (!valid) issues.push({ code: "invalid-text-slot", path: "$.layout.textSlots", message: "invalid, duplicate or out-of-bounds text area" });
889
+ if (slot?.id) textIds.add(slot.id);
890
+ }
891
+ const propInstances = /* @__PURE__ */ new Set();
892
+ for (const instance of layout.propInstances ?? []) {
893
+ if (!propTypes.has(instance?.type)) issues.push({ code: "unknown-layout-prop", path: "$.layout.propInstances", message: `\u672A\u77E5 Prop Type\uFF1A${instance?.type}` });
894
+ if (!instance?.id || propInstances.has(instance.id)) issues.push({ code: "invalid-layout-prop", path: "$.layout.propInstances", message: `\u91CD\u590D\u6216\u65E0\u6548\u7684 Prop \u5B9E\u4F8B\uFF1A${instance?.id ?? "\u2014"}` });
895
+ propInstances.add(instance?.id);
896
+ }
897
+ for (const capability of WORK_CAPABILITIES) {
898
+ if (!layout.stations?.[capability]) issues.push({ code: "missing-layout-station", path: `$.layout.stations.${capability}`, message: `Layout \u7F3A\u5C11\u5DE5\u4F5C\u80FD\u529B\uFF1A${capability}` });
899
+ }
900
+ return issues;
901
+ }
902
+ function graphIssues(content) {
903
+ const issues = [];
904
+ for (const [key, value] of Object.entries(content ?? {})) {
905
+ if (key === "preset" || key === "agentProfile") continue;
906
+ try {
907
+ assertManifest(value, manifestKindFor(key));
908
+ } catch (error) {
909
+ issues.push({ code: "invalid-manifest", path: `$.${key}`, message: error.message });
910
+ }
911
+ }
912
+ const profile = content?.agentProfile;
913
+ if (!profile || typeof profile !== "object" || typeof profile.template !== "string" || !profile.template || !profile.appearance || typeof profile.appearance !== "object" || Array.isArray(profile.appearance)) {
914
+ issues.push({ code: "invalid-agent-profile", path: "$.agentProfile", message: "\u65E0\u6548\u7684 Agent Profile" });
915
+ }
916
+ const layout = content?.layout;
917
+ issues.push(...layoutIssues(layout, Object.keys(content?.props?.types ?? {})));
918
+ const instances = (layout?.propInstances ?? []).map((instance) => [instance?.id, instance?.type]);
919
+ const capabilitiesOf = (type) => content?.props?.types?.[type]?.capabilities ?? [];
920
+ ;
921
+ (content?.npcs?.entries ?? []).forEach((npc, index) => {
922
+ const path6 = `$.npcs.entries[${index}]`;
923
+ if (!npc?.id || !npc.role || !layout?.targets?.[npc.spawn]) issues.push({ code: "invalid-npc", path: path6, message: `\u65E0\u6548\u7684 NPC\uFF1A${npc?.id ?? "\u2014"}` });
924
+ const shift = npc?.shift ? shiftIssue(npc.shift, "invalid-npc-shift", path6, `NPC ${npc.id} \u7684 shift`) : null;
925
+ if (shift) issues.push(shift);
926
+ });
927
+ ;
928
+ (content?.lifeActivities?.entries ?? []).forEach((activity, index) => {
929
+ const path6 = `$.lifeActivities.entries[${index}]`;
930
+ if (!activity?.id || !["agent", "npc", "person"].includes(activity.participant?.kind) || !activity.steps?.length) {
931
+ issues.push({ code: "invalid-activity", path: path6, message: `\u65E0\u6548\u7684 Life Activity\uFF1A${activity?.id ?? "\u2014"}` });
932
+ return;
933
+ }
934
+ if (activity.participant?.minAgents != null && (!Number.isInteger(activity.participant.minAgents) || activity.participant.minAgents < 2)) {
935
+ issues.push({ code: "invalid-activity-participant", path: path6, message: `${activity.id} \u7684 minAgents \u5FC5\u987B\u662F\u81F3\u5C11 2 \u7684\u6574\u6570` });
936
+ }
937
+ const resolved = resolveActivityRequirements(activity.requires, instances, capabilitiesOf, activity.id);
938
+ issues.push(...resolved.issues.map((issue2) => ({ ...issue2, path: path6 })));
939
+ for (const step of activity.steps ?? []) {
940
+ if (!layout?.targets?.[step?.target] && !DYNAMIC_ACTIVITY_TARGETS.has(step?.target)) issues.push({ code: "missing-activity-target", path: path6, message: `${activity.id} \u7F3A\u5C11 Target\uFF1A${step?.target}` });
941
+ for (const target of step?.targets ?? []) {
942
+ if (!layout?.targets?.[target]) issues.push({ code: "missing-activity-target", path: path6, message: `${activity.id} \u7F3A\u5C11 Group Target\uFF1A${target}` });
943
+ }
944
+ }
945
+ });
946
+ const environment = content?.environment;
947
+ if (!environment || !["local", "fixed"].includes(environment.clock?.mode)) {
948
+ issues.push({ code: "invalid-environment", path: "$.environment", message: "Environment \u7684 clock.mode \u5FC5\u987B\u662F local \u6216 fixed" });
949
+ } else {
950
+ if (environment.clock.mode === "fixed" && !validClock(environment.clock.fixedTime)) issues.push({ code: "invalid-environment", path: "$.environment.clock.fixedTime", message: "Environment \u7684 fixedTime \u65E0\u6548" });
951
+ if (!Array.isArray(environment.clock?.phases) || !environment.clock.phases.length) issues.push({ code: "invalid-environment", path: "$.environment.clock", message: "Environment \u7F3A\u5C11 day phases" });
952
+ for (const phase of environment.clock?.phases ?? []) {
953
+ if (!phase?.id || !validClock(phase.start)) issues.push({ code: "invalid-environment", path: "$.environment.clock.phases", message: "Environment \u5305\u542B\u65E0\u6548\u7684 day phase" });
954
+ }
955
+ if (!Array.isArray(environment.weather?.allowedConditions) || !environment.weather.allowedConditions.length) {
956
+ issues.push({ code: "invalid-environment", path: "$.environment.weather", message: "Environment \u7F3A\u5C11\u5929\u6C14\u7C7B\u578B" });
957
+ }
958
+ const allowedWeather = new Set(environment.weather?.allowedConditions ?? []);
959
+ for (const condition of [environment.weather?.condition, environment.weather?.fallback]) {
960
+ if (condition != null && !allowedWeather.has(condition)) {
961
+ issues.push({ code: "invalid-environment", path: "$.environment.weather", message: `Environment \u7684\u5929\u6C14 ${condition} \u4E0D\u5728 allowedConditions \u4E2D` });
962
+ }
963
+ }
964
+ const shift = environment.npcSchedule?.defaultShift ? shiftIssue(environment.npcSchedule.defaultShift, "invalid-environment", "$.environment.npcSchedule", "Environment \u7684 NPC \u9ED8\u8BA4\u73ED\u6B21") : null;
965
+ if (shift) issues.push(shift);
966
+ for (const [role, value] of Object.entries(environment.npcSchedule?.roleOverrides ?? {})) {
967
+ const roleIssue = shiftIssue(value, "invalid-environment", `$.environment.npcSchedule.roleOverrides.${role}`, `Environment \u7684 ${role} \u73ED\u6B21`);
968
+ if (roleIssue) issues.push(roleIssue);
969
+ }
970
+ }
971
+ return issues;
972
+ }
973
+ function graphIssueMessages(content) {
974
+ return graphIssues(content).map((issue2) => issue2.message);
975
+ }
976
+
977
+ // ../../plugins/agent-live/src/content/validator.ts
978
+ function add(issues, code, path6, message) {
979
+ issues.push({ code, path: path6, message });
980
+ }
981
+ function validClock2(value) {
982
+ if (typeof value !== "string" || !/^\d{2}:\d{2}$/.test(value)) return false;
983
+ const [hour, minute] = value.split(":").map(Number);
984
+ return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59;
985
+ }
986
+ function validateShift2(value, path6, issues) {
987
+ if (!validClock2(value?.start) || !validClock2(value?.end)) add(issues, "invalid-shift", path6, "shift must contain valid HH:MM start and end values");
988
+ if (value?.endLatest !== void 0 && !validClock2(value.endLatest)) add(issues, "invalid-shift", `${path6}.endLatest`, "endLatest must be a valid HH:MM value");
989
+ }
990
+ function validateLayoutContract(layout, library, issues) {
991
+ const propTypeNames = [...library.props.keys()].map((id) => id.replace(/^(builtin|local)\//, ""));
992
+ for (const issue2 of layoutIssues(layout, propTypeNames)) add(issues, issue2.code, issue2.path, issue2.message);
993
+ }
994
+ var OFFICE_ID = /^(builtin|local)\/[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/;
995
+ var INSTANCE_ID = /^[a-z0-9][a-z0-9-]*$/;
996
+ var COLOR = /^#[0-9a-f]{6}$/i;
997
+ function validateOfficeSpec(spec, library) {
998
+ const issues = validateOfficeSpecShape(spec).map((entry) => ({ ...entry, code: "invalid-shape" }));
999
+ if (issues.length || !spec || typeof spec !== "object") return { valid: false, issues };
1000
+ const value = spec;
1001
+ if (!OFFICE_ID.test(value.id)) add(issues, "invalid-office-id", "$.id", "office id must use a safe builtin/ or local/ identifier");
1002
+ if (value.origin === "official" && !value.id.startsWith("builtin/")) add(issues, "invalid-office-origin", "$.origin", "official offices require a builtin/ id");
1003
+ if (value.origin === "custom" && !value.id.startsWith("local/")) add(issues, "invalid-office-origin", "$.origin", "custom offices require a local/ id");
1004
+ if (!library.layouts.has(value.layout)) add(issues, "unknown-layout", "$.layout", `unknown layout ${value.layout}`);
1005
+ if (!library.styles.has(value.style)) add(issues, "unknown-style", "$.style", `unknown style ${value.style}`);
1006
+ if (!library.agentSkins.has(value.agentSkin)) add(issues, "unknown-agent-skin", "$.agentSkin", `unknown agent skin ${value.agentSkin}`);
1007
+ if (!library.atmospheres.has(value.atmosphere)) add(issues, "unknown-atmosphere", "$.atmosphere", `unknown atmosphere ${value.atmosphere}`);
1008
+ if (!library.environments.has(value.environment)) add(issues, "unknown-environment", "$.environment", `unknown environment ${value.environment}`);
1009
+ if (value.agentProfile) {
1010
+ if (!library.agentProfileTemplates.has(value.agentProfile.template)) add(issues, "unknown-agent-profile-template", "$.agentProfile.template", `unknown Agent Profile template ${value.agentProfile.template}`);
1011
+ for (const [field, color] of Object.entries(value.agentProfile.appearance ?? {})) if (!COLOR.test(String(color))) add(issues, "invalid-color", `$.agentProfile.appearance.${field}`, "appearance colors must use #RRGGBB");
1012
+ }
1013
+ const layout = library.layouts.get(value.layout);
1014
+ if (!layout) return { valid: false, issues };
1015
+ validateLayoutContract(layout, library, issues);
1016
+ for (const [id, text] of Object.entries(value.texts ?? {})) {
1017
+ const slot = (layout.textSlots ?? []).find((entry) => entry.id === id);
1018
+ if (!slot) add(issues, "unknown-text-area", `$.texts.${id}`, `unknown text area ${id}`);
1019
+ else if ([...text].length > slot.maxLength) add(issues, "text-too-long", `$.texts.${id}`, `maximum ${slot.maxLength} characters`);
1020
+ }
1021
+ const slots = new Map((layout.placementSlots ?? []).map((slot) => [slot.id, slot]));
1022
+ const placementIds = /* @__PURE__ */ new Set();
1023
+ const occupiedSlots = /* @__PURE__ */ new Set();
1024
+ for (let index = 0; index < value.placements.length; index += 1) {
1025
+ const placement = value.placements[index];
1026
+ const path6 = `$.placements[${index}]`;
1027
+ if (!INSTANCE_ID.test(placement.id)) add(issues, "invalid-placement-id", `${path6}.id`, "placement id must be a safe lowercase identifier");
1028
+ if (placementIds.has(placement.id)) add(issues, "duplicate-placement", `${path6}.id`, `duplicate placement id ${placement.id}`);
1029
+ placementIds.add(placement.id);
1030
+ const component = library.props.get(placement.component);
1031
+ if (!component) add(issues, "unknown-prop", `${path6}.component`, `unknown prop ${placement.component}`);
1032
+ const slot = slots.get(placement.slot);
1033
+ if (!slot) add(issues, "unknown-slot", `${path6}.slot`, `unknown slot ${placement.slot}`);
1034
+ else {
1035
+ if (occupiedSlots.has(placement.slot)) add(issues, "occupied-slot", `${path6}.slot`, `slot ${placement.slot} is already occupied`);
1036
+ occupiedSlots.add(placement.slot);
1037
+ const propType = placement.component.replace(/^builtin\//, "");
1038
+ if (component && !slot.accepts?.includes(propType)) add(issues, "incompatible-slot", path6, `${placement.component} is not accepted by ${placement.slot}`);
1039
+ if (component && (component.size.width > slot.maxSize?.width || component.size.height > slot.maxSize?.height)) add(issues, "prop-too-large", path6, `${placement.component} exceeds ${placement.slot}`);
1040
+ }
1041
+ }
1042
+ if (value.placements.length > SCENE_LIMITS.props) add(issues, "too-many-props", "$.placements", `maximum ${SCENE_LIMITS.props} props`);
1043
+ const npcIds = /* @__PURE__ */ new Set();
1044
+ for (let index = 0; index < value.npcs.length; index += 1) {
1045
+ const npc = value.npcs[index];
1046
+ const path6 = `$.npcs[${index}]`;
1047
+ if (!INSTANCE_ID.test(npc.id)) add(issues, "invalid-npc-id", `${path6}.id`, "NPC id must be a safe lowercase identifier");
1048
+ if (npcIds.has(npc.id)) add(issues, "duplicate-npc", `${path6}.id`, `duplicate NPC id ${npc.id}`);
1049
+ npcIds.add(npc.id);
1050
+ const template = npc.template ? library.npcTemplates.get(npc.template) : void 0;
1051
+ if (!npc.template || !template) add(issues, "unknown-npc-template", `${path6}.template`, `unknown NPC template ${npc.template ?? "(missing)"}`);
1052
+ if (npc.profile && (!template?.defaultProfiles || !template.defaultProfiles.some((profile) => profile.id === npc.profile))) add(issues, "unknown-npc-profile", `${path6}.profile`, `unknown profile ${npc.profile}`);
1053
+ if (npc.gender && !GENDER_VALUES.includes(npc.gender)) add(issues, "invalid-gender", `${path6}.gender`, `unsupported gender ${npc.gender}`);
1054
+ if (npc.pose && !POSE_VALUES.includes(npc.pose)) add(issues, "invalid-pose", `${path6}.pose`, `unsupported pose ${npc.pose}`);
1055
+ for (const [field, color] of Object.entries(npc.appearance ?? {})) if (!COLOR.test(String(color))) add(issues, "invalid-color", `${path6}.appearance.${field}`, "appearance colors must use #RRGGBB");
1056
+ if (!npc.spawn || !layout.npcSpawns?.includes(npc.spawn) || !layout.targets?.[npc.spawn]) add(issues, "invalid-npc-spawn", `${path6}.spawn`, `invalid NPC spawn ${npc.spawn ?? "(missing)"}`);
1057
+ if (npc.shift) validateShift2(npc.shift, `${path6}.shift`, issues);
1058
+ }
1059
+ if (value.npcs.length > SCENE_LIMITS.npcs) add(issues, "too-many-npcs", "$.npcs", `maximum ${SCENE_LIMITS.npcs} NPCs`);
1060
+ const activities = /* @__PURE__ */ new Set();
1061
+ const propInstances = /* @__PURE__ */ new Map();
1062
+ const replaceableIds = new Set((layout.placementSlots ?? []).map((slot) => slot.occupiedBy).filter(Boolean));
1063
+ for (const instance of layout.propInstances ?? []) if (!replaceableIds.has(instance.id)) propInstances.set(instance.id, instance.type);
1064
+ for (const placement of value.placements) propInstances.set(placement.id, placement.component.replace(/^(builtin|local)\//, ""));
1065
+ const capabilitiesOf = (type) => {
1066
+ const prop = library.props.get(`builtin/${type}`) ?? library.props.get(`local/${type}`) ?? library.props.get(type);
1067
+ return prop?.capabilities ?? [];
1068
+ };
1069
+ const preparedSlots = /* @__PURE__ */ new Map();
1070
+ for (const slot of layout.placementSlots ?? []) {
1071
+ const builtin = (layout.propInstances ?? []).find((instance) => instance.id === slot.occupiedBy);
1072
+ if (!builtin) continue;
1073
+ for (const capability of capabilitiesOf(builtin.type)) if (!preparedSlots.has(capability)) preparedSlots.set(capability, slot.id);
1074
+ }
1075
+ const npcRoles = new Set(value.npcs.map((npc) => library.npcTemplates.get(npc.template ?? "")?.role).filter(Boolean));
1076
+ for (let index = 0; index < value.activities.length; index += 1) {
1077
+ const activityId = value.activities[index];
1078
+ const path6 = `$.activities[${index}]`;
1079
+ if (activities.has(activityId)) add(issues, "duplicate-activity", path6, `duplicate activity ${activityId}`);
1080
+ activities.add(activityId);
1081
+ const recipe = library.activityRecipes.get(activityId);
1082
+ if (!recipe) add(issues, "unknown-activity", path6, `unknown activity ${activityId}`);
1083
+ else if (!library.activityImplementations.has(`${value.layout}|${activityId}`)) add(issues, "unsupported-activity-layout", path6, `${activityId} has no implementation for ${value.layout}`);
1084
+ else {
1085
+ const implementation = library.activityImplementations.get(`${value.layout}|${activityId}`);
1086
+ const resolved = resolveActivityRequirements(implementation?.definition?.requires, propInstances, capabilitiesOf, activityId);
1087
+ for (const issue2 of resolved.issues) add(issues, issue2.code, path6, issue2.message);
1088
+ if (!resolved.issues.length) {
1089
+ (implementation?.definition?.requires ?? []).forEach((requirement, requirementIndex) => {
1090
+ const capability = requirement && typeof requirement === "object" && typeof requirement.capability === "string" ? requirement.capability : void 0;
1091
+ const bound = resolved.bindings[requirementIndex];
1092
+ const placement = capability && bound ? value.placements.find((entry) => entry.id === bound) : void 0;
1093
+ if (!placement) return;
1094
+ const prepared = preparedSlots.get(capability);
1095
+ if (prepared === placement.slot) return;
1096
+ add(issues, "capability-prop-slot", path6, `${placement.id} provides ${capability} from ${placement.slot}, but ${activityId} performs where this layout prepared ${capability}${prepared ? ` (${prepared})` : ""}; keep the prop where it is instead of relocating it`);
1097
+ });
1098
+ }
1099
+ if (!resolved.issues.length && recipe.participantKinds?.includes("npc") && recipe.participantKinds.length === 1 && recipe.participantRoles?.length && !recipe.participantRoles.some((role) => npcRoles.has(role))) {
1100
+ add(issues, "missing-activity-participant", path6, `${activityId} has no compatible NPC in this office`);
1101
+ }
1102
+ }
1103
+ }
1104
+ if (value.activities.length > SCENE_LIMITS.activities) add(issues, "too-many-activities", "$.activities", `maximum ${SCENE_LIMITS.activities} activities`);
1105
+ const overrides = value.environmentOverrides;
1106
+ if (overrides?.clock?.mode === "fixed" && !validClock2(overrides.clock.fixedTime)) add(issues, "invalid-fixed-time", "$.environmentOverrides.clock.fixedTime", "fixed clock requires a valid HH:MM value");
1107
+ if (overrides?.clock?.mode === "local" && overrides.clock.fixedTime !== void 0) add(issues, "unused-fixed-time", "$.environmentOverrides.clock.fixedTime", "local clock cannot include fixedTime");
1108
+ if (overrides?.weather && !WEATHER_VALUES.includes(overrides.weather.fallback)) add(issues, "invalid-weather", "$.environmentOverrides.weather.fallback", "unsupported weather");
1109
+ if (overrides?.npcSchedule?.defaultShift) validateShift2(overrides.npcSchedule.defaultShift, "$.environmentOverrides.npcSchedule.defaultShift", issues);
1110
+ for (const [role, shift] of Object.entries(overrides?.npcSchedule?.roleOverrides ?? {})) {
1111
+ if (![...library.npcTemplates.values()].some((template) => template.role === role)) add(issues, "unknown-npc-role", `$.environmentOverrides.npcSchedule.roleOverrides.${role}`, `unknown NPC role ${role}`);
1112
+ validateShift2(shift, `$.environmentOverrides.npcSchedule.roleOverrides.${role}`, issues);
1113
+ }
1114
+ return { valid: issues.length === 0, issues };
1115
+ }
1116
+
1117
+ // ../../plugins/agent-live/src/content/registry.ts
1118
+ var SAFE_LOCAL_ID = /^local\/[a-z0-9][a-z0-9-]*(?:\/[a-z0-9][a-z0-9-]*)*$/;
1119
+ var OfficeRegistry = class {
1120
+ root;
1121
+ #library;
1122
+ #official = /* @__PURE__ */ new Map();
1123
+ #events = new EventEmitter();
1124
+ #fallbackOffice;
1125
+ constructor(options) {
1126
+ this.root = path3.resolve(options.root);
1127
+ this.#library = options.library;
1128
+ for (const office of options.officialOffices) this.#official.set(office.id, structuredClone(office));
1129
+ this.#fallbackOffice = options.fallbackOffice ?? "builtin/tech-open-office";
1130
+ if (!this.#official.has(this.#fallbackOffice)) throw new Error(`unknown fallback office ${this.#fallbackOffice}`);
1131
+ }
1132
+ onChange(listener) {
1133
+ this.#events.on("change", listener);
1134
+ return () => this.#events.off("change", listener);
1135
+ }
1136
+ async initialize() {
1137
+ await mkdir(this.#officeDir(), { recursive: true });
1138
+ }
1139
+ async list() {
1140
+ await this.initialize();
1141
+ const selected = await this.selectedId();
1142
+ const entries = [...this.#official.values()].map((office) => ({ id: office.id, name: office.name, origin: "official", selected: office.id === selected }));
1143
+ for (const file of await readdir(this.#officeDir())) {
1144
+ if (!file.endsWith(".json")) continue;
1145
+ try {
1146
+ const office = await this.#readCustomFile(path3.join(this.#officeDir(), file));
1147
+ entries.push({ id: office.id, name: office.name, origin: "custom", selected: office.id === selected });
1148
+ } catch (error) {
1149
+ console.warn(`Agent Live ignored invalid custom office ${file}: ${error instanceof Error ? error.message : String(error)}`);
1150
+ }
1151
+ }
1152
+ return [
1153
+ ...entries.filter((entry) => entry.origin === "official"),
1154
+ ...entries.filter((entry) => entry.origin === "custom").sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))
1155
+ ];
1156
+ }
1157
+ async get(id) {
1158
+ const official = this.#official.get(id);
1159
+ if (official) return structuredClone(official);
1160
+ if (!SAFE_LOCAL_ID.test(id)) return void 0;
1161
+ try {
1162
+ return await this.#readCustomFile(this.#fileFor(id));
1163
+ } catch {
1164
+ return void 0;
1165
+ }
1166
+ }
1167
+ async save(spec) {
1168
+ const validation = validateOfficeSpec(spec, this.#library);
1169
+ if (!validation.valid) return { saved: false, issues: validation.issues };
1170
+ if (spec.origin !== "custom" || !SAFE_LOCAL_ID.test(spec.id)) return { saved: false, issues: [{ code: "official-read-only", path: "$.id", message: "only local/ custom offices can be saved" }] };
1171
+ await this.initialize();
1172
+ const destination = this.#fileFor(spec.id);
1173
+ const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
1174
+ await writeFile(temporary, `${JSON.stringify(spec, null, " ")}
1175
+ `, { encoding: "utf8", mode: 384 });
1176
+ await rename(temporary, destination);
1177
+ this.#events.emit("change", spec.id);
1178
+ return { saved: true, issues: [] };
1179
+ }
1180
+ async remove(id) {
1181
+ if (!SAFE_LOCAL_ID.test(id)) return false;
1182
+ const selected = await this.selectedId();
1183
+ try {
1184
+ await rm(this.#fileFor(id));
1185
+ } catch (error) {
1186
+ if (error?.code === "ENOENT") return false;
1187
+ throw error;
1188
+ }
1189
+ if (selected === id) await this.select(this.#fallbackOffice);
1190
+ this.#events.emit("change", id);
1191
+ return true;
1192
+ }
1193
+ async select(id) {
1194
+ if (!await this.get(id)) throw new Error(`unknown or invalid office ${id}`);
1195
+ await this.initialize();
1196
+ const state = { schemaVersion: 1, selectedOffice: id };
1197
+ const destination = this.#stateFile();
1198
+ const temporary = `${destination}.${process.pid}.${Date.now()}.tmp`;
1199
+ await writeFile(temporary, `${JSON.stringify(state, null, " ")}
1200
+ `, { encoding: "utf8", mode: 384 });
1201
+ await rename(temporary, destination);
1202
+ this.#events.emit("change", id);
1203
+ }
1204
+ async selectedId() {
1205
+ try {
1206
+ const state = JSON.parse(await readFile3(this.#stateFile(), "utf8"));
1207
+ if (state.schemaVersion === 1 && state.selectedOffice && await this.get(state.selectedOffice)) return state.selectedOffice;
1208
+ } catch {
1209
+ }
1210
+ return this.#fallbackOffice;
1211
+ }
1212
+ async selected() {
1213
+ return await this.get(await this.selectedId()) ?? structuredClone(this.#official.get(this.#fallbackOffice));
1214
+ }
1215
+ #officeDir() {
1216
+ return path3.join(this.root, "offices");
1217
+ }
1218
+ #stateFile() {
1219
+ return path3.join(this.root, "registry.json");
1220
+ }
1221
+ #fileFor(id) {
1222
+ return path3.join(this.#officeDir(), `${encodeURIComponent(id.slice("local/".length))}.json`);
1223
+ }
1224
+ async #readCustomFile(file) {
1225
+ const spec = JSON.parse(await readFile3(file, "utf8"));
1226
+ const validation = validateOfficeSpec(spec, this.#library);
1227
+ if (!validation.valid || spec.origin !== "custom" || !SAFE_LOCAL_ID.test(spec.id)) throw new Error(`invalid custom office ${file}`);
1228
+ if (this.#fileFor(spec.id) !== file) throw new Error(`custom office filename does not match id ${spec.id}`);
1229
+ return spec;
1230
+ }
1231
+ };
1232
+
1233
+ // ../../plugins/agent-live/src/content/runtime-content.ts
1234
+ import { readFile as readFile4 } from "node:fs/promises";
1235
+ import path4 from "node:path";
1236
+ var readJson3 = async (file) => JSON.parse(await readFile4(file, "utf8"));
1237
+ var short2 = (id) => id.replace(/^builtin\//, "");
1238
+ async function asset(contentRoot, family, id, suffix = "") {
1239
+ const name = suffix && short2(id).endsWith(suffix) ? short2(id).slice(0, -suffix.length) : short2(id);
1240
+ return readJson3(path4.join(contentRoot, family, `${name}.json`));
1241
+ }
1242
+ async function resolveRuntimeContent(spec, contentRoot, library) {
1243
+ const layout = structuredClone(library.layouts.get(spec.layout));
1244
+ if (!layout) throw new Error(`unknown layout ${spec.layout}`);
1245
+ layout.textSlots = (layout.textSlots ?? []).map((slot) => ({ ...slot, text: spec.texts?.[slot.id] ?? slot.defaultText ?? "" }));
1246
+ const officialId = short2(spec.layout);
1247
+ const scaffold = await readJson3(path4.join(contentRoot, "presets", `${officialId}.json`));
1248
+ const [style, agentSkin, atmosphere, environment] = await Promise.all([
1249
+ asset(contentRoot, "styles", spec.style),
1250
+ asset(contentRoot, "agent-skins", spec.agentSkin),
1251
+ asset(contentRoot, "atmospheres", spec.atmosphere, "-atmosphere"),
1252
+ asset(contentRoot, "environments", spec.environment, "-environment")
1253
+ ]);
1254
+ const slottedIds = new Set((layout.placementSlots ?? []).map((slot) => slot.occupiedBy).filter(Boolean));
1255
+ for (const placement of spec.placements) slottedIds.add(placement.id);
1256
+ layout.propInstances = (layout.propInstances ?? []).filter((entry) => !slottedIds.has(entry.id));
1257
+ for (const placement of spec.placements) {
1258
+ const slot = layout.placementSlots.find((entry) => entry.id === placement.slot);
1259
+ layout.propInstances.push({ id: placement.id, type: short2(placement.component), x: slot.x, y: slot.y, ...placement.orientation ? { orientation: placement.orientation } : {} });
1260
+ }
1261
+ const propTypes = Object.fromEntries([...library.props.values()].map((entry) => [short2(entry.id), { size: entry.size, capabilities: entry.capabilities, renderer: entry.renderer }]));
1262
+ const props = { schemaVersion: 1, kind: "props", id: `local/${short2(spec.id)}-props`, name: `${spec.name} props`, version: "1.0.0", contract: "single-office-v1", types: propTypes };
1263
+ const npcs = {
1264
+ schemaVersion: 1,
1265
+ kind: "npcs",
1266
+ id: `local/${short2(spec.id)}-npcs`,
1267
+ name: `${spec.name} NPCs`,
1268
+ version: "1.0.0",
1269
+ contract: "single-office-v1",
1270
+ entries: spec.npcs.map((npc) => ({ ...structuredClone(npc), role: library.npcTemplates.get(npc.template)?.role }))
1271
+ };
1272
+ const capabilitiesOf = (type) => propTypes[type]?.capabilities ?? [];
1273
+ const activityInstances = (layout.propInstances ?? []).map((entry) => [entry.id, entry.type]);
1274
+ const activityIds = [...spec.activities];
1275
+ const entries = activityIds.map((id) => {
1276
+ const implementation = library.activityImplementations.get(`${spec.layout}|${id}`);
1277
+ if (!implementation) throw new Error(`activity ${id} has no implementation compatible with ${spec.layout}`);
1278
+ const definition = structuredClone(implementation.definition);
1279
+ return { ...definition, bindings: resolveActivityRequirements(definition.requires, activityInstances, capabilitiesOf, id).bindings };
1280
+ });
1281
+ const lifeActivities = { schemaVersion: 1, kind: "life-activities", id: `local/${short2(spec.id)}-activities`, name: `${spec.name} activities`, version: "1.0.0", contract: "single-office-v1", entries };
1282
+ const agentProfileTemplate = library.agentProfileTemplates.get(spec.agentProfile?.template ?? "builtin/host-agent");
1283
+ const agentProfile = {
1284
+ template: agentProfileTemplate?.id ?? "builtin/host-agent",
1285
+ ...spec.agentProfile?.name ? { name: spec.agentProfile.name } : {},
1286
+ ...spec.agentProfile?.title ? { title: spec.agentProfile.title } : {},
1287
+ appearance: { ...agentProfileTemplate?.defaultAppearance ?? {}, ...spec.agentProfile?.appearance ?? {} }
1288
+ };
1289
+ if (spec.environmentOverrides?.clock) environment.clock = { ...environment.clock, ...spec.environmentOverrides.clock };
1290
+ if (spec.environmentOverrides?.weather) environment.weather = { ...environment.weather, ...spec.environmentOverrides.weather };
1291
+ if (spec.environmentOverrides?.lighting) environment.lighting = { ...environment.lighting, enabled: spec.environmentOverrides.lighting.auto };
1292
+ if (spec.environmentOverrides?.npcSchedule) environment.npcSchedule = {
1293
+ ...environment.npcSchedule,
1294
+ ...spec.environmentOverrides.npcSchedule,
1295
+ roleOverrides: { ...environment.npcSchedule?.roleOverrides ?? {}, ...spec.environmentOverrides.npcSchedule.roleOverrides ?? {} }
1296
+ };
1297
+ const preset = { ...scaffold, id: spec.id, name: spec.name, officeSpec: spec.id };
1298
+ delete preset.content;
1299
+ return {
1300
+ preset,
1301
+ style,
1302
+ layout,
1303
+ agentSkin,
1304
+ agentProfile,
1305
+ props,
1306
+ npcs,
1307
+ lifeActivities,
1308
+ atmosphere,
1309
+ environment
1310
+ };
1311
+ }
1312
+
1313
+ // ../../plugins/agent-live/src/content/compiler.ts
1314
+ function compileOfficeSpec(input, library) {
1315
+ const validation = validateOfficeSpec(input, library);
1316
+ if (!validation.valid) return { errors: validation.issues, adjustments: [] };
1317
+ return { draft: structuredClone(input), errors: [], adjustments: [] };
1318
+ }
1319
+ function hash(value) {
1320
+ let result = 2166136261;
1321
+ for (let index = 0; index < value.length; index += 1) {
1322
+ result ^= value.charCodeAt(index);
1323
+ result = Math.imul(result, 16777619);
1324
+ }
1325
+ return result >>> 0;
1326
+ }
1327
+ function upsertById(base, updates, removals, path6, adjustments, merge = (previous, update) => ({ ...previous ?? {}, ...structuredClone(update) })) {
1328
+ const values = new Map(base.map((entry) => [entry.id, structuredClone(entry)]));
1329
+ for (const id of removals) {
1330
+ if (!values.delete(id)) adjustments.push({ code: "remove-missing", path: path6, message: `${id} did not exist and was ignored` });
1331
+ }
1332
+ for (const update of updates) values.set(update.id, merge(values.get(update.id), update));
1333
+ return [...values.values()];
1334
+ }
1335
+ function truncate(values, maximum, path6, adjustments) {
1336
+ if (values.length <= maximum) return values;
1337
+ adjustments.push({ code: "capacity-truncated", path: path6, message: `${values.length - maximum} extra item(s) were ignored; maximum is ${maximum}` });
1338
+ return values.slice(0, maximum);
1339
+ }
1340
+ function resolveNpc(npc, library, layout) {
1341
+ const templateId = npc.template ?? library.defaultNpcTemplate;
1342
+ const template = library.npcTemplates.get(templateId);
1343
+ if (!template) return { ...npc, template: templateId };
1344
+ const profiles = template.defaultProfiles ?? [];
1345
+ const selected = npc.profile ? profiles.find((profile) => profile.id === npc.profile) : profiles.length ? profiles[hash(npc.id) % profiles.length] : void 0;
1346
+ const role = template.role;
1347
+ const spawnCandidates = (layout.npcSpawns ?? []).filter((spawn2) => {
1348
+ if (role === "boss") return /boss|director|manager/.test(spawn2);
1349
+ if (role === "cleaner") return /clean|service|staff|entry/.test(spawn2) && !/boss/.test(spawn2);
1350
+ if (role === "receptionist") return /reception|staff|entry/.test(spawn2) && !/boss/.test(spawn2);
1351
+ if (role === "secretary" || role === "attendant") return /secretary|service|staff|entry/.test(spawn2) && !/boss/.test(spawn2);
1352
+ return /staff|entry/.test(spawn2) && !/boss|clean|service|reception|secretary/.test(spawn2);
1353
+ });
1354
+ const requestedSpawn = npc.spawn;
1355
+ const requestedIsRoleSafe = requestedSpawn && (spawnCandidates.includes(requestedSpawn) || role !== "colleague");
1356
+ const fallbackSpawn = spawnCandidates[hash(`${npc.id}:spawn`) % Math.max(1, spawnCandidates.length)] ?? layout.npcSpawns?.[hash(`${npc.id}:spawn`) % Math.max(1, layout.npcSpawns?.length ?? 0)];
1357
+ return {
1358
+ id: npc.id,
1359
+ template: templateId,
1360
+ ...selected?.id ? { profile: selected.id } : {},
1361
+ name: npc.name ?? selected?.name ?? template.name,
1362
+ title: npc.title ?? template.defaultTitle,
1363
+ gender: npc.gender ?? selected?.gender ?? template.defaultGender,
1364
+ appearance: { ...template.defaultAppearance, ...selected?.appearance ?? {}, ...npc.appearance ?? {} },
1365
+ spawn: requestedIsRoleSafe ? requestedSpawn : fallbackSpawn,
1366
+ ...npc.shift ? { shift: structuredClone(npc.shift) } : {},
1367
+ pose: npc.pose ?? template.defaultPose
1368
+ };
1369
+ }
1370
+ function mergeNpc(previous, update) {
1371
+ return {
1372
+ ...previous ?? {},
1373
+ ...structuredClone(update),
1374
+ ...previous?.appearance || update.appearance ? { appearance: { ...previous?.appearance ?? {}, ...update.appearance ?? {} } } : {},
1375
+ ...previous?.shift || update.shift ? { shift: { ...previous?.shift ?? {}, ...update.shift ?? {} } } : {}
1376
+ };
1377
+ }
1378
+ function mergeEnvironment(base, patch) {
1379
+ const clock = patch.clock ? { ...base?.clock ?? {}, ...patch.clock } : base?.clock;
1380
+ if (clock?.mode === "local") delete clock.fixedTime;
1381
+ const npcSchedule = patch.npcSchedule ? {
1382
+ ...base?.npcSchedule ?? {},
1383
+ ...patch.npcSchedule,
1384
+ roleOverrides: { ...base?.npcSchedule?.roleOverrides ?? {}, ...patch.npcSchedule.roleOverrides ?? {} }
1385
+ } : base?.npcSchedule;
1386
+ return {
1387
+ ...clock ? { clock } : {},
1388
+ ...patch.weather || base?.weather ? { weather: { ...base?.weather ?? {}, ...patch.weather ?? {} } } : {},
1389
+ ...patch.lighting || base?.lighting ? { lighting: { ...base?.lighting ?? {}, ...patch.lighting ?? {} } } : {},
1390
+ ...npcSchedule ? { npcSchedule } : {}
1391
+ };
1392
+ }
1393
+ function duplicates(values) {
1394
+ const seen = /* @__PURE__ */ new Set();
1395
+ return values.filter((value) => seen.has(value) || !seen.add(value));
1396
+ }
1397
+ function patchOfficeId(base) {
1398
+ return base.origin === "custom" ? base.id : `local/${base.id.replace(/^builtin\//, "")}`;
1399
+ }
1400
+ function presetIdForLocalId(localId) {
1401
+ return localId.startsWith("local/") ? `builtin/${localId.slice("local/".length)}` : void 0;
1402
+ }
1403
+ function ownerPresetId(base) {
1404
+ return base.origin === "official" ? base.id : base.basePreset;
1405
+ }
1406
+ function compileOfficePatch(base, patchInput, library) {
1407
+ const shapeIssues = validateOfficePatchShape(patchInput).map((entry) => ({ ...entry, code: "invalid-patch-shape" }));
1408
+ if (shapeIssues.length) return { errors: shapeIssues, adjustments: [] };
1409
+ const patch = patchInput;
1410
+ if (patch.base !== base.id) return { errors: [{ code: "base-mismatch", path: "$.base", message: `patch base ${patch.base} does not match ${base.id}` }], adjustments: [] };
1411
+ const baseValidation = validateOfficeSpec(base, library);
1412
+ if (!baseValidation.valid) return { errors: baseValidation.issues.map((entry) => ({ ...entry, path: `$.base${entry.path.slice(1)}` })), adjustments: [] };
1413
+ const operationErrors = [];
1414
+ for (const [path6, values] of [
1415
+ ["$.placements.upsert", (patch.placements?.upsert ?? []).map((entry) => entry.id)],
1416
+ ["$.placements.remove", patch.placements?.remove ?? []],
1417
+ ["$.npcs.upsert", (patch.npcs?.upsert ?? []).map((entry) => entry.id)],
1418
+ ["$.npcs.remove", patch.npcs?.remove ?? []],
1419
+ ["$.activities.enable", patch.activities?.enable ?? []],
1420
+ ["$.activities.disable", patch.activities?.disable ?? []]
1421
+ ]) {
1422
+ for (const id of new Set(duplicates(values))) operationErrors.push({ code: "duplicate-operation", path: path6, message: `${id} appears more than once` });
1423
+ }
1424
+ const enabled = new Set(patch.activities?.enable ?? []);
1425
+ for (const id of patch.activities?.disable ?? []) if (enabled.has(id)) operationErrors.push({ code: "conflicting-operation", path: "$.activities", message: `${id} cannot be enabled and disabled together` });
1426
+ if (operationErrors.length) return { errors: operationErrors, adjustments: [] };
1427
+ const adjustments = [];
1428
+ const components = patch.components ?? {};
1429
+ const layoutId = base.layout;
1430
+ const layout = library.layouts.get(layoutId);
1431
+ if (!layout) return { errors: [{ code: "unknown-layout", path: "$.layout", message: `unknown layout ${layoutId}` }], adjustments: [] };
1432
+ const placements = upsertById(base.placements, patch.placements?.upsert ?? [], patch.placements?.remove ?? [], "$.placements", adjustments);
1433
+ const npcs = upsertById(base.npcs, patch.npcs?.upsert ?? [], patch.npcs?.remove ?? [], "$.npcs", adjustments, mergeNpc);
1434
+ const activities = new Set(base.activities);
1435
+ for (const id of patch.activities?.disable ?? []) {
1436
+ if (!activities.delete(id)) adjustments.push({ code: "disable-missing", path: "$.activities.disable", message: `${id} was not enabled and was ignored` });
1437
+ }
1438
+ for (const id of patch.activities?.enable ?? []) activities.add(id);
1439
+ const draft = {
1440
+ schemaVersion: OFFICE_SPEC_SCHEMA_VERSION,
1441
+ kind: "office-spec",
1442
+ id: patch.id ?? patchOfficeId(base),
1443
+ name: patch.name ?? base.name,
1444
+ origin: "custom",
1445
+ basePreset: base.origin === "official" ? base.id : base.basePreset,
1446
+ layout: layoutId,
1447
+ style: components.style ?? base.style,
1448
+ agentSkin: components.agentSkin ?? base.agentSkin,
1449
+ placements: truncate(placements, SCENE_LIMITS.props, "$.placements", adjustments),
1450
+ npcs: truncate(npcs, SCENE_LIMITS.npcs, "$.npcs", adjustments).map((npc) => resolveNpc(npc, library, layout)),
1451
+ activities: truncate([...activities], SCENE_LIMITS.activities, "$.activities", adjustments),
1452
+ atmosphere: components.atmosphere ?? base.atmosphere,
1453
+ environment: components.environment ?? base.environment,
1454
+ ...patch.texts === null ? {} : base.texts || patch.texts ? { texts: { ...base.texts, ...patch.texts } } : {},
1455
+ ...patch.environmentOverrides === null ? {} : patch.environmentOverrides || base.environmentOverrides ? { environmentOverrides: mergeEnvironment(base.environmentOverrides, patch.environmentOverrides ?? {}) } : {},
1456
+ ...patch.agentProfile === null ? {} : patch.agentProfile || base.agentProfile ? { agentProfile: { ...base.agentProfile ?? OFFICE_SPEC_DEFAULTS.agentProfile, ...patch.agentProfile ?? {}, appearance: { ...base.agentProfile?.appearance ?? {}, ...patch.agentProfile?.appearance ?? {} } } } : {}
1457
+ };
1458
+ const compiled = compileOfficeSpec(draft, library);
1459
+ return { draft: compiled.draft, errors: compiled.errors, adjustments: [...adjustments, ...compiled.adjustments] };
1460
+ }
1461
+
1462
+ // ../../plugins/agent-live/src/runtime/content-service.ts
1463
+ function pluginRoot() {
1464
+ return path5.resolve(path5.dirname(fileURLToPath2(import.meta.url)), "../..");
1465
+ }
1466
+ function bundledContentRoot() {
1467
+ const directory = path5.dirname(fileURLToPath2(import.meta.url));
1468
+ const candidates = [
1469
+ path5.resolve(directory, "../../web/v2/content"),
1470
+ path5.resolve(directory, "../web/v2/content")
1471
+ ];
1472
+ return candidates.find((candidate) => existsSync2(candidate)) ?? path5.join(pluginRoot(), "web/v2/content");
1473
+ }
1474
+ var OfficeContentService = class _OfficeContentService {
1475
+ registry;
1476
+ #contentRoot;
1477
+ library;
1478
+ constructor(contentRoot, library, registry) {
1479
+ this.#contentRoot = contentRoot;
1480
+ this.library = library;
1481
+ this.registry = registry;
1482
+ }
1483
+ static async create(options = {}) {
1484
+ const contentRoot = options.contentRoot ?? bundledContentRoot();
1485
+ const library = await loadComponentLibrary(contentRoot);
1486
+ const officialOffices = await loadOfficialOffices(contentRoot);
1487
+ const dataRoot = options.dataRoot ?? process.env.AGENT_LIVE_DATA_DIR ?? path5.join(os.homedir(), ".agent-live");
1488
+ const registry = new OfficeRegistry({ root: dataRoot, library, officialOffices });
1489
+ return new _OfficeContentService(contentRoot, library, registry);
1490
+ }
1491
+ list() {
1492
+ return this.registry.list();
1493
+ }
1494
+ select(id) {
1495
+ return this.registry.select(id);
1496
+ }
1497
+ async resolve(id) {
1498
+ const office = id ? await this.registry.get(id) : await this.registry.selected();
1499
+ if (!office) throw new Error(`unknown or invalid office ${id}`);
1500
+ const compiled = compileOfficeSpec(office, this.library);
1501
+ if (!compiled.draft) throw new Error(`office ${id} failed compilation: ${compiled.errors.map((issue2) => issue2.message).join("; ")}`);
1502
+ const graph = await resolveRuntimeContent(compiled.draft, this.#contentRoot, this.library);
1503
+ const issues = graphIssueMessages(graph);
1504
+ if (issues.length) throw new Error(`office ${office.id} produced content the viewer cannot render: ${issues.join("; ")}`);
1505
+ return graph;
1506
+ }
1507
+ subscribe(listener) {
1508
+ const stopRegistry = this.registry.onChange((officeId) => listener({ type: "office", officeId }));
1509
+ return stopRegistry;
1510
+ }
1511
+ };
1512
+
1513
+ // ../../plugins/agent-live/src/runtime/resource-guard.ts
1514
+ var DEFAULT_CLEANUP_TIMEOUT_MS = 5e3;
1515
+ var ResourceGuard = class {
1516
+ resources = [];
1517
+ closePromise = null;
1518
+ closed = false;
1519
+ track(name, dispose, options = {}) {
1520
+ if (this.closed || this.closePromise) throw new Error(`Cannot track ${name}: resource guard is closing`);
1521
+ const resource = { name, dispose, timeoutMs: options.timeoutMs ?? DEFAULT_CLEANUP_TIMEOUT_MS };
1522
+ this.resources.push(resource);
1523
+ return () => {
1524
+ const index = this.resources.indexOf(resource);
1525
+ if (index >= 0) this.resources.splice(index, 1);
1526
+ };
1527
+ }
1528
+ close() {
1529
+ if (this.closePromise) return this.closePromise;
1530
+ if (this.closed) return Promise.resolve();
1531
+ this.closePromise = this.disposeAll().finally(() => {
1532
+ this.closed = true;
1533
+ this.closePromise = null;
1534
+ });
1535
+ return this.closePromise;
1536
+ }
1537
+ async disposeAll() {
1538
+ const failures = [];
1539
+ for (const resource of this.resources.splice(0).reverse()) {
1540
+ try {
1541
+ await withTimeout(Promise.resolve().then(resource.dispose), resource.timeoutMs, resource.name);
1542
+ } catch (error) {
1543
+ failures.push(error instanceof Error ? error : new Error(String(error)));
1544
+ }
1545
+ }
1546
+ if (failures.length) throw new AggregateError(failures, "One or more Agent Live resources failed to close");
1547
+ }
1548
+ };
1549
+ function withTimeout(operation, timeoutMs, name) {
1550
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return operation;
1551
+ return new Promise((resolve2, reject) => {
1552
+ const timer = setTimeout(() => reject(new Error(`Timed out closing ${name} after ${timeoutMs}ms`)), timeoutMs);
1553
+ operation.then(
1554
+ (value) => {
1555
+ clearTimeout(timer);
1556
+ resolve2(value);
1557
+ },
1558
+ (error) => {
1559
+ clearTimeout(timer);
1560
+ reject(error);
1561
+ }
1562
+ );
1563
+ });
1564
+ }
1565
+
1566
+ // ../../plugins/agent-live/src/runtime/agent-live-runtime.ts
1567
+ var AgentLiveRuntime = class {
1568
+ cwd;
1569
+ state;
1570
+ server = null;
1571
+ dataRoot;
1572
+ resources = new ResourceGuard();
1573
+ closed = false;
1574
+ constructor(cwd, options = {}) {
1575
+ this.cwd = cwd;
1576
+ this.state = new OfficeState(cwd);
1577
+ this.dataRoot = options.dataRoot;
1578
+ this.resources.track("office-state", () => this.state.dispose());
1579
+ }
1580
+ async start(options) {
1581
+ if (this.closed) throw new Error("Agent Live Runtime is closed");
1582
+ if (this.server) return this.server;
1583
+ let server = null;
1584
+ try {
1585
+ const contentService = options.content ? void 0 : await OfficeContentService.create({ dataRoot: this.dataRoot });
1586
+ server = await startServer(this.state, { ...options, content: options.content ?? contentService });
1587
+ if (this.closed) throw new Error("Agent Live Runtime is closed");
1588
+ this.server = server;
1589
+ this.resources.track("office-server", () => server.close());
1590
+ return server;
1591
+ } catch (error) {
1592
+ this.closed = true;
1593
+ await server?.close().catch(() => void 0);
1594
+ await this.resources.close().catch(() => void 0);
1595
+ throw error;
1596
+ }
1597
+ }
1598
+ async close() {
1599
+ this.closed = true;
1600
+ this.server = null;
1601
+ await this.resources.close();
1602
+ }
1603
+ };
1604
+
1605
+ // ../../plugins/agent-live/src/core/agents.ts
1606
+ var DEFAULT_LEAVE_DELAY_MS = 2600;
1607
+ var ROLE_NAMES = {
1608
+ scout: "\u4FA6\u5BDF\u5458",
1609
+ planner: "\u89C4\u5212\u5E08",
1610
+ reviewer: "\u8BC4\u5BA1\u5458",
1611
+ worker: "\u5DE5\u7A0B\u5E08",
1612
+ tester: "\u6D4B\u8BD5\u5458",
1613
+ writer: "\u6587\u6848"
1614
+ };
1615
+ function roleForAgent(name, fallback = "\u5916\u63F4") {
1616
+ return ROLE_NAMES[name.trim().toLowerCase()] ?? fallback;
1617
+ }
1618
+ var AgentRegistry = class {
1619
+ state;
1620
+ mainId;
1621
+ leaveDelayMs;
1622
+ children = /* @__PURE__ */ new Set();
1623
+ completed = /* @__PURE__ */ new Set();
1624
+ leaveTimers = /* @__PURE__ */ new Map();
1625
+ constructor(state, options = {}) {
1626
+ this.state = state;
1627
+ this.mainId = options.mainId ?? "main";
1628
+ this.leaveDelayMs = options.leaveDelayMs ?? DEFAULT_LEAVE_DELAY_MS;
1629
+ }
1630
+ join(id, init) {
1631
+ return this.state.join(id, init);
1632
+ }
1633
+ spawn(input) {
1634
+ const parent = input.parent ?? this.mainId;
1635
+ const existing = this.state.getAgent(input.id);
1636
+ if (existing && this.isSettling(input.id)) return existing;
1637
+ const agent = this.state.join(input.id, {
1638
+ name: input.name,
1639
+ role: input.role ?? roleForAgent(input.name),
1640
+ parent,
1641
+ task: input.task
1642
+ });
1643
+ if (!agent) return void 0;
1644
+ this.children.add(input.id);
1645
+ this.completed.delete(input.id);
1646
+ if (!existing) this.state.delegate(parent, input.id, input.task);
1647
+ this.state.setState(input.id, "thinking", "\u63A5\u5230\u534F\u4F5C\u4EFB\u52A1");
1648
+ this.syncParentState();
1649
+ return agent;
1650
+ }
1651
+ updateIdentity(id, patch) {
1652
+ const current = this.state.getAgent(id);
1653
+ if (!current) return;
1654
+ this.state.join(id, {
1655
+ name: patch.name,
1656
+ role: patch.role ?? roleForAgent(patch.name, current.role),
1657
+ parent: current.parent,
1658
+ model: current.model,
1659
+ task: patch.task ?? current.task
1660
+ });
1661
+ }
1662
+ setState(id, state, detail) {
1663
+ this.state.setState(id, state, detail);
1664
+ }
1665
+ setTask(id, task) {
1666
+ this.state.setTask(id, task);
1667
+ }
1668
+ thought(id, text) {
1669
+ this.state.pushThought(id, text);
1670
+ }
1671
+ say(id, text) {
1672
+ this.state.say(id, text);
1673
+ }
1674
+ startAction(id, toolCallId, action, label) {
1675
+ this.state.startAction(id, toolCallId, action, label);
1676
+ }
1677
+ endAction(id, toolCallId, ok) {
1678
+ this.state.endAction(id, toolCallId, ok);
1679
+ }
1680
+ usage(id, tokens, cost) {
1681
+ this.state.addUsage(id, tokens, cost);
1682
+ }
1683
+ complete(id, ok, detail = ok ? "\u5DF2\u4EA4\u4ED8" : "\u672A\u5B8C\u6210") {
1684
+ this.finish(id, ok, detail);
1685
+ }
1686
+ cancel(id, detail = "\u5DF2\u505C\u6B62") {
1687
+ this.finish(id, void 0, detail);
1688
+ }
1689
+ finish(id, outcome, detail) {
1690
+ if (!this.state.getAgent(id) || this.leaveTimers.has(id)) return;
1691
+ this.completed.add(id);
1692
+ this.state.setState(id, outcome === false ? "error" : "done", detail);
1693
+ this.syncParentState();
1694
+ const timer = setTimeout(() => {
1695
+ this.leaveTimers.delete(id);
1696
+ this.children.delete(id);
1697
+ this.completed.delete(id);
1698
+ this.state.leave(id, outcome);
1699
+ this.syncParentState();
1700
+ }, this.leaveDelayMs);
1701
+ timer.unref?.();
1702
+ this.leaveTimers.set(id, timer);
1703
+ }
1704
+ settleChildren(ok) {
1705
+ for (const id of this.children) this.complete(id, ok);
1706
+ }
1707
+ cancelChildren() {
1708
+ for (const id of this.children) this.cancel(id);
1709
+ }
1710
+ has(id) {
1711
+ return this.children.has(id);
1712
+ }
1713
+ activeChildren() {
1714
+ let count = 0;
1715
+ for (const id of this.children) if (!this.completed.has(id)) count += 1;
1716
+ return count;
1717
+ }
1718
+ isSettling(id) {
1719
+ return this.completed.has(id) || this.leaveTimers.has(id);
1720
+ }
1721
+ dispose() {
1722
+ for (const timer of this.leaveTimers.values()) clearTimeout(timer);
1723
+ this.leaveTimers.clear();
1724
+ this.children.clear();
1725
+ this.completed.clear();
1726
+ }
1727
+ syncParentState() {
1728
+ const count = this.activeChildren();
1729
+ if (count > 0) {
1730
+ this.state.setState(this.mainId, "waiting", `\u7B49\u5F85 ${count} \u4F4D\u540C\u4E8B`);
1731
+ } else if (this.state.sessionBusy()) {
1732
+ this.state.setState(this.mainId, "thinking", "\u7EE7\u7EED\u63A8\u8FDB");
1733
+ }
1734
+ }
1735
+ };
1736
+
1737
+ // src/adapter.ts
1738
+ import { randomBytes } from "node:crypto";
1739
+
1740
+ // ../../plugins/agent-live/src/creator/service.ts
1741
+ var COMPONENT_CATEGORIES = ["summary", "room", "npcs", "props", "activities", "appearance", "environment", "all"];
1742
+ function roomView(library, office) {
1743
+ const layout = library.layouts.get(office.layout);
1744
+ if (!layout) return null;
1745
+ const occupants = /* @__PURE__ */ new Map();
1746
+ for (const slot of layout.placementSlots ?? []) occupants.set(slot.id, null);
1747
+ for (const placement of office.placements) occupants.set(placement.slot, placement.id);
1748
+ return {
1749
+ name: layout.name,
1750
+ zones: (layout.zones ?? []).map((zone) => ({ id: zone.id, name: zone.name, x: zone.x, y: zone.y, width: zone.width, height: zone.height })),
1751
+ slots: (layout.placementSlots ?? []).map((slot) => ({ id: slot.id, zone: slot.zone, accepts: slot.accepts ?? [], maxSize: slot.maxSize, occupiedBy: occupants.get(slot.id) ?? null })),
1752
+ npcSpawns: layout.npcSpawns ?? [],
1753
+ textSlots: (layout.textSlots ?? []).map((slot) => ({ id: slot.id, name: slot.name, maxLength: slot.maxLength, text: office.texts?.[slot.id] ?? slot.defaultText ?? "" })),
1754
+ placements: office.placements.map((placement) => ({ id: placement.id, component: placement.component, slot: placement.slot, ...placement.orientation ? { orientation: placement.orientation } : {} }))
1755
+ };
1756
+ }
1757
+ var CreatorService = class {
1758
+ #registry;
1759
+ #library;
1760
+ constructor(registry, library) {
1761
+ this.#registry = registry;
1762
+ this.#library = library;
1763
+ }
1764
+ async listOffices() {
1765
+ return this.#registry.list();
1766
+ }
1767
+ async selectOffice(id) {
1768
+ const office = await this.#registry.get(id);
1769
+ if (!office) return { selected: false, error: `unknown or invalid office ${id}` };
1770
+ await this.#registry.select(id);
1771
+ return { selected: true, office };
1772
+ }
1773
+ /**
1774
+ * Capabilities the model may map a request onto. `room` describes the room of
1775
+ * the currently selected Office only — zones, placement slots and NPC spawns —
1776
+ * because "add a plant" or "put a water cooler in the lounge" is only reliable
1777
+ * when the model can see what this Office actually offers. Rooms are never
1778
+ * presented as a choice.
1779
+ */
1780
+ async listComponents(category = "summary") {
1781
+ const office = await this.#registry.selected();
1782
+ const full = {
1783
+ room: roomView(this.#library, office),
1784
+ styles: structuredClone(this.#library.descriptors.styles),
1785
+ agentSkins: structuredClone(this.#library.descriptors.agentSkins),
1786
+ props: structuredClone([...this.#library.props.values()]),
1787
+ npcTemplates: structuredClone([...this.#library.npcTemplates.values()]),
1788
+ agentProfileTemplates: structuredClone([...this.#library.agentProfileTemplates.values()]),
1789
+ activities: [...this.#library.activityRecipes.values()].map((entry) => ({ ...structuredClone(entry), rooms: [...this.#library.activityImplementations.values()].filter((implementation) => implementation.recipe === entry.id).map((implementation) => implementation.layout) })),
1790
+ atmospheres: structuredClone(this.#library.descriptors.atmospheres),
1791
+ environments: structuredClone(this.#library.descriptors.environments)
1792
+ };
1793
+ if (category === "all") return full;
1794
+ if (category === "room") return { room: full.room };
1795
+ if (category === "npcs") return { npcTemplates: full.npcTemplates };
1796
+ if (category === "props") return { room: full.room, props: full.props };
1797
+ if (category === "activities") return { activities: full.activities };
1798
+ if (category === "appearance") return { styles: full.styles, agentSkins: full.agentSkins, agentProfileTemplates: full.agentProfileTemplates };
1799
+ if (category === "environment") return { atmospheres: full.atmospheres, environments: full.environments };
1800
+ return {
1801
+ // The compact view is also the model's edit baseline. Supplying the
1802
+ // bounded, user-editable state here avoids filesystem inspection and
1803
+ // repeated catalog calls just to discover an NPC id or text slot.
1804
+ office: {
1805
+ id: office.id,
1806
+ name: office.name,
1807
+ origin: office.origin,
1808
+ agentProfile: structuredClone(office.agentProfile ?? null),
1809
+ texts: structuredClone(office.texts ?? {}),
1810
+ npcs: office.npcs.map((npc) => ({
1811
+ id: npc.id,
1812
+ template: npc.template,
1813
+ name: npc.name,
1814
+ title: npc.title,
1815
+ gender: npc.gender,
1816
+ spawn: npc.spawn
1817
+ }))
1818
+ },
1819
+ counts: {
1820
+ styles: full.styles.length,
1821
+ agentSkins: full.agentSkins.length,
1822
+ props: full.props.length,
1823
+ npcTemplates: full.npcTemplates.length,
1824
+ activities: full.activities.length,
1825
+ atmospheres: full.atmospheres.length,
1826
+ environments: full.environments.length
1827
+ },
1828
+ categories: COMPONENT_CATEGORIES.filter((entry) => entry !== "summary")
1829
+ };
1830
+ }
1831
+ /** Validate, persist and select one customization without exposing draft state. */
1832
+ async customize(patchInput, baseOffice) {
1833
+ if (!patchInput || typeof patchInput !== "object" || Array.isArray(patchInput)) {
1834
+ return { saved: false, errors: [{ code: "invalid-patch-shape", path: "$", message: "patch must be an object" }], adjustments: [] };
1835
+ }
1836
+ const base = baseOffice ? await this.#registry.get(baseOffice) : await this.#registry.selected();
1837
+ if (!base) return { saved: false, errors: [{ code: "unknown-base", path: "$.base", message: `unknown or invalid base office ${baseOffice}` }], adjustments: [] };
1838
+ const patch = {
1839
+ ...patchInput,
1840
+ schemaVersion: 1,
1841
+ kind: "office-patch",
1842
+ base: base.id
1843
+ };
1844
+ const compiled = compileOfficePatch(base, patch, this.#library);
1845
+ if (!compiled.draft) return { saved: false, errors: compiled.errors, adjustments: compiled.adjustments };
1846
+ const owner = ownerPresetId(base);
1847
+ const targetId = patchOfficeId(base);
1848
+ const refuse = (code, message) => ({
1849
+ saved: false,
1850
+ errors: [{ code, path: "$.id", message }],
1851
+ adjustments: compiled.adjustments
1852
+ });
1853
+ if (compiled.draft.id !== targetId) {
1854
+ if (await this.#registry.get(compiled.draft.id)) {
1855
+ return refuse("id-conflict", `${compiled.draft.id} already exists; a patch cannot overwrite another Office`);
1856
+ }
1857
+ const reserved = presetIdForLocalId(compiled.draft.id);
1858
+ if (reserved && reserved !== owner && await this.#registry.get(reserved)) {
1859
+ return refuse("reserved-office-id", `${compiled.draft.id} is the editable copy of ${reserved}`);
1860
+ }
1861
+ } else {
1862
+ const existing = await this.#registry.get(targetId);
1863
+ if (existing && existing.basePreset !== owner) {
1864
+ const occupant = existing.basePreset ? `an Office based on ${existing.basePreset}` : "an Office without a base Preset";
1865
+ return refuse("id-conflict", `${targetId} already holds ${occupant}; it will not be overwritten`);
1866
+ }
1867
+ }
1868
+ const saved = await this.#registry.save(compiled.draft);
1869
+ if (!saved.saved) return { saved: false, errors: saved.issues, adjustments: compiled.adjustments };
1870
+ await this.#registry.select(compiled.draft.id);
1871
+ return { saved: true, office: structuredClone(compiled.draft), errors: [], adjustments: compiled.adjustments };
1872
+ }
1873
+ };
1874
+
1875
+ // ../../plugins/agent-live/src/creator/commands.ts
1876
+ function object2(value) {
1877
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1878
+ }
1879
+ function exact(input, fields) {
1880
+ const allowed = /* @__PURE__ */ new Set(["command", ...fields]);
1881
+ const unknown = Object.keys(input).filter((key) => !allowed.has(key));
1882
+ if (unknown.length) throw new Error(`unknown command field(s): ${unknown.join(", ")}`);
1883
+ }
1884
+ var CreatorCommandRouter = class {
1885
+ #creator;
1886
+ constructor(creator) {
1887
+ this.#creator = creator;
1888
+ }
1889
+ async execute(value) {
1890
+ try {
1891
+ if (!object2(value) || typeof value.command !== "string") throw new Error("command is required");
1892
+ switch (value.command) {
1893
+ case "list_offices":
1894
+ exact(value, []);
1895
+ return { ok: true, data: await this.#creator.listOffices() };
1896
+ case "list_components":
1897
+ exact(value, ["category"]);
1898
+ if (value.category !== void 0 && !COMPONENT_CATEGORIES.includes(value.category)) throw new Error(`unknown component category ${String(value.category)}`);
1899
+ return { ok: true, data: await this.#creator.listComponents(value.category) };
1900
+ case "customize": {
1901
+ exact(value, ["base", "patch"]);
1902
+ if (value.base !== void 0 && (typeof value.base !== "string" || !value.base)) throw new Error("base must be a non-empty string");
1903
+ const result = await this.#creator.customize(value.patch, value.base);
1904
+ return result.saved ? { ok: true, data: result, adjustments: result.adjustments } : { ok: false, error: "Office customization was rejected; the current office was not changed", issues: result.errors, adjustments: result.adjustments };
1905
+ }
1906
+ default:
1907
+ throw new Error(`unknown Creator command ${value.command}`);
1908
+ }
1909
+ } catch (error) {
1910
+ return { ok: false, error: error.message };
1911
+ }
1912
+ }
1913
+ };
1914
+
1915
+ // ../../plugins/agent-live/src/creator/mode.ts
1916
+ var CREATOR_MODE_CONTEXT = `Agent Live Creator Mode is active for this session.
1917
+ Treat office-related natural language as a request to modify the currently selected Custom Office and use the agent_live_creator tool.
1918
+ The currently selected Office is the only edit target. Do not inspect files, search for another copy, deliberate about replacement, invent a new Office id, or pass base/id unless the user explicitly selected another listed Office.
1919
+ On the first edit, call list_components with the default compact summary at most once to obtain the current Agent Profile, text areas, and NPC ids. Then call customize immediately. Do not call list_offices or broader component categories unless the request genuinely needs an unknown choice.
1920
+ For common edits, use these internal Patch shapes: agentProfile { template: "builtin/host-agent", name?, title? }; texts { company?, notice?, slogan? }; npcs { upsert: [{ id, template?, name?, title?, gender?, spawn?, pose? }], remove?: [id] }. Rename an existing NPC by its summary id. Add an ordinary colleague with a unique id and template "builtin/colleague"; omitted profile and appearance are resolved deterministically.
1921
+ Do not expose schemas, patches, or component ids unless explicitly asked for implementation details.
1922
+ If a request is unrelated to the office or ambiguous, do not perform it. Explain that Creator Mode is active and offer exactly these choices: continue editing, /agent-live list presets, /agent-live preset <number or name>, /agent-live custom, or /agent-live exit.
1923
+ Map a request like "make me a police station" onto the closest complete Preset Office, then change its name, people, identities, furniture, style and activities. If the request needs a brand-new room structure (walls, areas, lanes, seats or work stations), say that it requires adding a new Office Preset and therefore a source change; never offer to swap a room in place.
1924
+ An Office keeps its room. Moving to another room means selecting that Preset Office and editing a copy of it.
1925
+ After every response, state that Creator Mode remains active and mention /agent-live exit.`;
1926
+ var CreatorModeRegistry = class {
1927
+ #sessions = /* @__PURE__ */ new Set();
1928
+ enter(sessionId) {
1929
+ if (!sessionId) throw new TypeError("Creator Mode requires a session id");
1930
+ this.#sessions.add(sessionId);
1931
+ }
1932
+ exit(sessionId) {
1933
+ return this.#sessions.delete(sessionId);
1934
+ }
1935
+ isActive(sessionId) {
1936
+ return this.#sessions.has(sessionId);
1937
+ }
1938
+ clear() {
1939
+ this.#sessions.clear();
1940
+ }
1941
+ };
1942
+
1943
+ // src/adapter.ts
1944
+ var MAIN = "main";
1945
+ var PI_CREATOR_SESSION = "pi-current-session";
1946
+ var DEFAULT_PORT = Number(process.env.AGENT_LIVE_PI_PORT ?? 7788);
1947
+ var VIEWER_CLOSE_GRACE_MS = Number(process.env.AGENT_LIVE_VIEWER_CLOSE_GRACE_MS ?? 12e3);
1948
+ var VIEWER_PATH = "v2.html";
1949
+ var CREATOR_TOOL_PARAMETERS = {
1950
+ type: "object",
1951
+ additionalProperties: false,
1952
+ properties: {
1953
+ command: {
1954
+ type: "string",
1955
+ enum: ["list_offices", "list_components", "customize"]
1956
+ },
1957
+ base: { type: "string" },
1958
+ category: { type: "string", enum: ["summary", "room", "npcs", "props", "activities", "appearance", "environment", "all"] },
1959
+ patch: { type: "object" }
1960
+ },
1961
+ required: ["command"]
1962
+ };
1963
+ var CREATOR_TOOL_GUIDELINES = [
1964
+ "Use agent_live_creator for office changes only while Agent Live Creator Mode is active. The user enters with /agent-live custom and exits with /agent-live exit.",
1965
+ "The selected Office is authoritative. Never inspect files, search for another copy, invent an Office id, or pass base/id unless the user explicitly selected another listed Office.",
1966
+ "For the first common edit, query the default compact list_components summary at most once for current Agent, text, and NPC ids, then customize immediately. Use a narrower component category only when a requested choice is unknown; avoid all unless the user explicitly asks for the complete catalog.",
1967
+ "Common internal Patch shapes are agentProfile { template: 'builtin/host-agent', name?, title? }, texts { company?, notice?, slogan? }, and npcs { upsert: [{ id, template?, name?, title?, gender?, spawn?, pose? }], remove?: [id] }. Rename by the current summary id; add a colleague with a unique id and template 'builtin/colleague'.",
1968
+ "Keep schemas, component IDs and patches internal. After applying, summarize defaults, substitutions, ignored requests and source-code-only boundaries."
1969
+ ];
1970
+ function joinBlocks(content, kind) {
1971
+ if (typeof content === "string") return kind === "text" ? content : "";
1972
+ if (!Array.isArray(content)) return "";
1973
+ return content.filter((block) => block?.type === kind).map((block) => (kind === "thinking" ? block.thinking : block.text) ?? "").join("\n");
1974
+ }
1975
+ function adapter_default(pi) {
1976
+ let state = null;
1977
+ let server = null;
1978
+ let runtime = null;
1979
+ let agents = null;
1980
+ let creator = null;
1981
+ let creatorService = null;
1982
+ let accessToken = "";
1983
+ let closePromise = null;
1984
+ let bootPromise = null;
1985
+ let viewerCloseTimer = null;
1986
+ let hasSeenViewer = false;
1987
+ let thinkingCursor = 0;
1988
+ let textCursor = 0;
1989
+ let totalTokens = 0;
1990
+ let totalCost = 0;
1991
+ const delegated = /* @__PURE__ */ new Map();
1992
+ const activeToolCalls = /* @__PURE__ */ new Set();
1993
+ const creatorModes = new CreatorModeRegistry();
1994
+ const mainName = (ctx) => {
1995
+ const model = ctx?.model?.id ?? ctx?.model?.name ?? "pi";
1996
+ return { name: "\u554A\u6D3E", role: String(model) };
1997
+ };
1998
+ async function bootOnce(ctx) {
1999
+ if (closePromise) await closePromise.catch(() => void 0);
2000
+ if (state && server) return;
2001
+ try {
2002
+ const branch = ctx.sessionManager?.getBranch?.();
2003
+ if (Array.isArray(branch)) {
2004
+ totalTokens = 0;
2005
+ totalCost = 0;
2006
+ for (const entry of branch) {
2007
+ const message = entry?.message;
2008
+ if (message?.role !== "assistant") continue;
2009
+ totalTokens += Number(message.usage?.totalTokens ?? 0);
2010
+ totalCost += Number(message.usage?.cost?.total ?? 0);
2011
+ }
2012
+ }
2013
+ const nextRuntime = new AgentLiveRuntime(ctx.cwd ?? process.cwd());
2014
+ runtime = nextRuntime;
2015
+ state = nextRuntime.state;
2016
+ agents = new AgentRegistry(state);
2017
+ hasSeenViewer = false;
2018
+ const content = await OfficeContentService.create();
2019
+ creatorService = new CreatorService(content.registry, content.library);
2020
+ creator = new CreatorCommandRouter(creatorService);
2021
+ const creatorToken = randomBytes(24).toString("hex");
2022
+ accessToken = creatorToken;
2023
+ state.updateSession({
2024
+ cwd: ctx.cwd ?? process.cwd(),
2025
+ model: ctx?.model?.id,
2026
+ thinkingLevel: ctx?.thinkingLevel
2027
+ });
2028
+ const info = mainName(ctx);
2029
+ agents.join(MAIN, { ...info, model: ctx?.model?.id });
2030
+ state.addUsage(MAIN, totalTokens, totalCost);
2031
+ server = await nextRuntime.start({
2032
+ port: DEFAULT_PORT,
2033
+ content,
2034
+ creator,
2035
+ creatorToken,
2036
+ onViewerCountChange(count) {
2037
+ if (runtime !== nextRuntime) return;
2038
+ if (count > 0) {
2039
+ hasSeenViewer = true;
2040
+ if (viewerCloseTimer) clearTimeout(viewerCloseTimer);
2041
+ viewerCloseTimer = null;
2042
+ return;
2043
+ }
2044
+ if (!hasSeenViewer || viewerCloseTimer) return;
2045
+ viewerCloseTimer = setTimeout(() => void shutdown(), VIEWER_CLOSE_GRACE_MS);
2046
+ viewerCloseTimer.unref?.();
2047
+ }
2048
+ });
2049
+ if (ctx.hasUI) {
2050
+ ctx.ui.setStatus("agent-live", `agent-live: ${viewerUrl(server.url)}`);
2051
+ ctx.ui.notify(`Agent Live: ${viewerUrl(server.url)} (/agent-live \u6253\u5F00)`, "info");
2052
+ }
2053
+ if (process.env.AGENT_LIVE_AUTO_OPEN === "1") server.open(viewerTarget());
2054
+ } catch (err) {
2055
+ agents?.dispose();
2056
+ await runtime?.close().catch(() => void 0);
2057
+ server = null;
2058
+ state = null;
2059
+ runtime = null;
2060
+ agents = null;
2061
+ creator = null;
2062
+ creatorService = null;
2063
+ const message = `Agent Live \u542F\u52A8\u5931\u8D25: ${err.message}`;
2064
+ if (ctx.hasUI) ctx.ui.notify(message, "error");
2065
+ else console.error(message);
2066
+ }
2067
+ }
2068
+ async function boot(ctx) {
2069
+ if (server && state) return;
2070
+ if (bootPromise) return bootPromise;
2071
+ bootPromise = bootOnce(ctx).finally(() => {
2072
+ bootPromise = null;
2073
+ });
2074
+ return bootPromise;
2075
+ }
2076
+ async function shutdown() {
2077
+ creatorModes.clear();
2078
+ if (closePromise) return closePromise;
2079
+ if (viewerCloseTimer) clearTimeout(viewerCloseTimer);
2080
+ viewerCloseTimer = null;
2081
+ hasSeenViewer = false;
2082
+ const runtimeToClose = runtime;
2083
+ const agentsToDispose = agents;
2084
+ server = null;
2085
+ state = null;
2086
+ runtime = null;
2087
+ agents = null;
2088
+ creator = null;
2089
+ creatorService = null;
2090
+ accessToken = "";
2091
+ delegated.clear();
2092
+ activeToolCalls.clear();
2093
+ closePromise = (async () => {
2094
+ agentsToDispose?.dispose();
2095
+ await runtimeToClose?.close();
2096
+ })().finally(() => {
2097
+ closePromise = null;
2098
+ });
2099
+ return closePromise;
2100
+ }
2101
+ function viewerTarget(params = {}) {
2102
+ const query = new URLSearchParams({ ...params, ...accessToken ? { token: accessToken } : {} });
2103
+ return `${VIEWER_PATH}${query.size ? `?${query}` : ""}`;
2104
+ }
2105
+ function viewerUrl(base, params = {}) {
2106
+ return `${base}/${viewerTarget(params)}`;
2107
+ }
2108
+ pi.on("session_start", async (_event, ctx) => {
2109
+ totalTokens = 0;
2110
+ totalCost = 0;
2111
+ await boot(ctx);
2112
+ });
2113
+ pi.on("session_shutdown", async () => {
2114
+ creatorModes.clear();
2115
+ await shutdown();
2116
+ });
2117
+ pi.on("model_select", async (event) => {
2118
+ if (!state) return;
2119
+ const model = String(event?.model?.id ?? event?.model?.name ?? "pi");
2120
+ state.updateSession({ model });
2121
+ agents?.join(MAIN, { name: "\u554A\u6D3E", role: model, model });
2122
+ });
2123
+ pi.on("thinking_level_select", async (event) => {
2124
+ state?.updateSession({ thinkingLevel: event?.level });
2125
+ });
2126
+ pi.on("before_agent_start", async (event) => {
2127
+ if (!state) return;
2128
+ state.updateSession({ busy: true });
2129
+ state.setTask(MAIN, String(event?.prompt ?? "").replace(/\s+/g, " ").slice(0, 300));
2130
+ state.setState(MAIN, "thinking", "\u63A5\u5230\u65B0\u9700\u6C42");
2131
+ if (creatorModes.isActive(PI_CREATOR_SESSION)) {
2132
+ return { systemPrompt: `${event?.systemPrompt ?? ""}
2133
+
2134
+ ${CREATOR_MODE_CONTEXT}`.trim() };
2135
+ }
2136
+ });
2137
+ pi.registerTool({
2138
+ name: "agent_live_creator",
2139
+ label: "Agent Live Creator",
2140
+ description: "Inspect Agent Live capabilities or directly apply a validated Custom Office change.",
2141
+ promptSnippet: "Directly customize an Agent Live office from a natural-language request",
2142
+ promptGuidelines: CREATOR_TOOL_GUIDELINES,
2143
+ parameters: CREATOR_TOOL_PARAMETERS,
2144
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
2145
+ if (!creator || !server) await boot(ctx);
2146
+ if (!creator) {
2147
+ return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: "Agent Live is not running." }) }], details: { ok: false } };
2148
+ }
2149
+ const result = await creator.execute(params);
2150
+ return { content: [{ type: "text", text: JSON.stringify(result) }], details: result };
2151
+ }
2152
+ });
2153
+ pi.on("message_start", async (event) => {
2154
+ if (!state) return;
2155
+ if (event?.message?.role !== "assistant") return;
2156
+ thinkingCursor = 0;
2157
+ textCursor = 0;
2158
+ state.setState(MAIN, "thinking", "\u6784\u601D\u4E2D");
2159
+ });
2160
+ pi.on("message_update", async (event) => {
2161
+ if (!state) return;
2162
+ const message = event?.message;
2163
+ if (message?.role !== "assistant") return;
2164
+ const thinking = joinBlocks(message.content, "thinking");
2165
+ if (thinking.length > thinkingCursor) {
2166
+ state.pushThought(MAIN, thinking.slice(thinkingCursor));
2167
+ thinkingCursor = thinking.length;
2168
+ if (state.getAgent(MAIN)?.state !== "working") {
2169
+ state.setState(MAIN, "thinking", "\u601D\u8003\u4E2D");
2170
+ }
2171
+ }
2172
+ const text = joinBlocks(message.content, "text");
2173
+ if (text.length > textCursor) {
2174
+ textCursor = text.length;
2175
+ if (state.getAgent(MAIN)?.state === "thinking") {
2176
+ state.setState(MAIN, "talking", "\u6C47\u62A5\u4E2D");
2177
+ }
2178
+ }
2179
+ });
2180
+ pi.on("message_end", async (event) => {
2181
+ const message = event?.message;
2182
+ if (message?.role !== "assistant") return;
2183
+ const usage = message.usage;
2184
+ if (usage) {
2185
+ totalTokens += Number(usage.totalTokens ?? 0);
2186
+ totalCost += Number(usage.cost?.total ?? 0);
2187
+ }
2188
+ if (!state) return;
2189
+ state.flushThoughts();
2190
+ const text = joinBlocks(message.content, "text");
2191
+ if (text.trim()) state.say(MAIN, text);
2192
+ if (usage) {
2193
+ state.addUsage(MAIN, totalTokens, totalCost);
2194
+ }
2195
+ });
2196
+ pi.on("turn_start", async () => {
2197
+ if (!state) return;
2198
+ state.updateSession({ busy: true });
2199
+ });
2200
+ pi.on("turn_end", async (event) => {
2201
+ if (!state) return;
2202
+ state.updateSession({ turns: Number(event?.turnIndex ?? 0) + 1 });
2203
+ });
2204
+ pi.on("tool_execution_start", async (event) => {
2205
+ if (!state) return;
2206
+ const toolName = String(event?.toolName ?? "tool");
2207
+ const toolCallId = String(event?.toolCallId ?? Math.random());
2208
+ const args = event?.args ?? {};
2209
+ activeToolCalls.add(toolCallId);
2210
+ state.startAction(MAIN, toolCallId, actionForTool(toolName), labelForTool(toolName, args));
2211
+ if (!isDelegationTool(toolName)) return;
2212
+ const children = [];
2213
+ for (const item of describeDelegation(args)) {
2214
+ const childId = `${toolCallId}:${item.slot}`;
2215
+ const child = agents?.spawn({
2216
+ id: childId,
2217
+ name: item.agent,
2218
+ task: item.task
2219
+ });
2220
+ if (child) children.push(childId);
2221
+ }
2222
+ delegated.set(toolCallId, children);
2223
+ });
2224
+ pi.on("tool_execution_update", async (event) => {
2225
+ if (!state) return;
2226
+ const toolCallId = String(event?.toolCallId ?? "");
2227
+ if (!delegated.has(toolCallId)) return;
2228
+ syncDelegationProgress(toolCallId, event?.partialResult?.details);
2229
+ });
2230
+ pi.on("tool_execution_end", async (event) => {
2231
+ if (!state) return;
2232
+ const toolCallId = String(event?.toolCallId ?? "");
2233
+ const ok = !event?.isError;
2234
+ activeToolCalls.delete(toolCallId);
2235
+ const children = delegated.get(toolCallId);
2236
+ if (children) {
2237
+ syncDelegationProgress(toolCallId, event?.result?.details);
2238
+ for (const childId of children) {
2239
+ const settled = state.getAgent(childId)?.state;
2240
+ if (settled === "done" || settled === "error") continue;
2241
+ agents?.complete(childId, ok, ok ? "\u5DF2\u4EA4\u4ED8" : "\u5931\u8D25");
2242
+ }
2243
+ delegated.delete(toolCallId);
2244
+ }
2245
+ state.endAction(MAIN, toolCallId, ok);
2246
+ state.setState(MAIN, "thinking", ok ? "\u7EE7\u7EED\u63A8\u8FDB" : "\u5904\u7406\u62A5\u9519");
2247
+ });
2248
+ pi.on("agent_end", async () => {
2249
+ state?.flushThoughts();
2250
+ });
2251
+ pi.on("agent_settled", async () => {
2252
+ if (!state) return;
2253
+ state.flushThoughts();
2254
+ for (const toolCallId of activeToolCalls) state.endAction(MAIN, toolCallId, false);
2255
+ activeToolCalls.clear();
2256
+ agents?.settleChildren(false);
2257
+ delegated.clear();
2258
+ state.updateSession({ busy: false });
2259
+ state.setState(MAIN, "idle", "\u5F85\u547D");
2260
+ });
2261
+ function syncDelegationProgress(toolCallId, details) {
2262
+ if (!state || !details) return;
2263
+ const results = Array.isArray(details.results) ? details.results : Array.isArray(details.tasks) ? details.tasks : [];
2264
+ const children = delegated.get(toolCallId) ?? [];
2265
+ results.forEach((result, index) => {
2266
+ const childId = children[index] ?? `${toolCallId}:${index}`;
2267
+ if (!state?.getAgent(childId)) return;
2268
+ const usage = result?.usage;
2269
+ if (usage) {
2270
+ const tokens = Number(usage.contextTokens ?? 0) || Number(usage.input ?? 0) + Number(usage.output ?? 0);
2271
+ agents?.usage(childId, tokens, Number(usage.cost ?? 0));
2272
+ }
2273
+ const exitCode = Number(result?.exitCode ?? -1);
2274
+ if (exitCode !== -1) {
2275
+ const failed = exitCode !== 0 || result?.stopReason === "error" || result?.stopReason === "aborted";
2276
+ agents?.complete(childId, !failed, failed ? "\u5931\u8D25" : "\u5DF2\u4EA4\u4ED8");
2277
+ return;
2278
+ }
2279
+ const messages = Array.isArray(result?.messages) ? result.messages : [];
2280
+ const last = [...messages].reverse().find((m) => m?.role === "assistant");
2281
+ const toolCall = last?.content?.find?.((c) => c?.type === "toolCall");
2282
+ const thinking = joinBlocks(last?.content, "thinking");
2283
+ const text = joinBlocks(last?.content, "text");
2284
+ if (toolCall) {
2285
+ const label = labelForTool(String(toolCall.name), toolCall.arguments ?? {});
2286
+ agents?.startAction(childId, `${childId}:${messages.length}`, actionForTool(String(toolCall.name)), label);
2287
+ } else if (thinking.trim()) {
2288
+ agents?.setState(childId, "thinking", "\u601D\u8003\u4E2D");
2289
+ agents?.thought(childId, thinking.slice(-200));
2290
+ } else if (text.trim()) {
2291
+ agents?.setState(childId, "talking", "\u6574\u7406\u7ED3\u8BBA");
2292
+ }
2293
+ });
2294
+ }
2295
+ pi.registerCommand("agent-live", {
2296
+ description: "\u6253\u5F00 Agent Live \u53EF\u89C6\u5316 (demo | status | close | list presets | preset <number or exact name> | custom | exit)",
2297
+ handler: async (args, ctx) => {
2298
+ const raw = (args ?? "").trim().replace(/\s+/g, " ");
2299
+ const sub = raw.toLowerCase();
2300
+ const [command, value, ...rest] = sub.split(/\s+/).filter(Boolean);
2301
+ if (command === "close") {
2302
+ await shutdown();
2303
+ ctx.ui.notify("Agent Live \u5DF2\u5173\u95ED", "info");
2304
+ return;
2305
+ }
2306
+ if (command === "exit") {
2307
+ const exited = creatorModes.exit(PI_CREATOR_SESSION);
2308
+ ctx.ui.notify(exited ? "Creator Mode \u5DF2\u9000\u51FA\u3002" : "\u5F53\u524D\u672A\u5904\u4E8E Creator Mode\u3002", "info");
2309
+ return;
2310
+ }
2311
+ await boot(ctx);
2312
+ if (!server) {
2313
+ ctx.ui.notify("Agent Live \u670D\u52A1\u672A\u542F\u52A8", "error");
2314
+ return;
2315
+ }
2316
+ if (command === "status") {
2317
+ ctx.ui.notify(`Agent Live: ${viewerUrl(server.url)}`, "info");
2318
+ return;
2319
+ }
2320
+ if (command === "demo") {
2321
+ server.open(viewerTarget({ demo: "1" }));
2322
+ ctx.ui.notify("\u5DF2\u6253\u5F00\u6F14\u793A\u573A\u666F", "info");
2323
+ return;
2324
+ }
2325
+ const entries = await creatorService.listOffices();
2326
+ const listPresets = () => {
2327
+ ctx.ui.notify(entries.map((office, index) => `${index + 1}. ${office.name}${office.selected ? " (selected)" : ""}
2328
+ /agent-live preset ${index + 1}`).join("\n") + "\n\u4E5F\u53EF\u4EE5\u4F7F\u7528\u5B8C\u6574\u540D\u79F0\uFF0C\u4F8B\u5982 /agent-live preset tech", "info");
2329
+ };
2330
+ const openPreset = async (selector) => {
2331
+ if (!selector) {
2332
+ listPresets();
2333
+ return;
2334
+ }
2335
+ const index = /^\d+$/.test(selector) ? Number(selector) - 1 : -1;
2336
+ const matches = entries.filter((office) => office.name.toLowerCase() === selector);
2337
+ const selected = index >= 0 ? entries[index] : matches.length === 1 ? matches[0] : void 0;
2338
+ if (!selected) {
2339
+ ctx.ui.notify(`\u672A\u77E5 Preset\uFF1A${selector}\u3002\u8F93\u5165 /agent-live list presets \u67E5\u770B\u53EF\u7528\u9009\u9879\u3002`, "error");
2340
+ return;
2341
+ }
2342
+ const result = await creatorService?.selectOffice(selected.id);
2343
+ if (!result?.selected) {
2344
+ ctx.ui.notify(`\u65E0\u6CD5\u9009\u62E9 ${selected.name}\uFF1A${result?.error ?? "Creator \u672A\u5C31\u7EEA"}`, "error");
2345
+ return;
2346
+ }
2347
+ server.open(viewerTarget());
2348
+ creatorModes.exit(PI_CREATOR_SESSION);
2349
+ ctx.ui.notify(`\u5DF2\u9009\u62E9 ${selected.name}\u3002Creator Mode \u5DF2\u9000\u51FA\u3002`, "info");
2350
+ };
2351
+ if (command === "list" && (value === "preset" || value === "presets")) {
2352
+ listPresets();
2353
+ return;
2354
+ }
2355
+ if (command === "preset") {
2356
+ await openPreset([value, ...rest].filter(Boolean).join(" ") || void 0);
2357
+ return;
2358
+ }
2359
+ if (command === "custom") {
2360
+ creatorModes.enter(PI_CREATOR_SESSION);
2361
+ ctx.ui.notify("Creator Mode \u5DF2\u5F00\u542F\u3002\u73B0\u5728\u76F4\u63A5\u63CF\u8FF0\u529E\u516C\u5BA4\u4FEE\u6539\uFF1B\u8F93\u5165 /agent-live exit \u9000\u51FA\u3002", "info");
2362
+ return;
2363
+ }
2364
+ if (command && command !== "open") {
2365
+ ctx.ui.notify("\u7528\u6CD5\uFF1A/agent-live [demo | status | close | list presets | preset <number or exact name> | custom | exit]", "error");
2366
+ return;
2367
+ }
2368
+ server.open(viewerTarget());
2369
+ ctx.ui.notify(`\u5DF2\u6253\u5F00 ${viewerUrl(server.url)}`, "info");
2370
+ }
2371
+ });
2372
+ }
2373
+ export {
2374
+ adapter_default as default
2375
+ };