@iniesta8888/agent-live-dsh-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.
- package/README.md +23 -0
- package/cordis.patch.yml +3 -0
- package/lib/client.js +539 -0
- package/lib/index.js +1342 -0
- package/lib/types/dsh/src/adapter.d.ts +53 -0
- package/lib/types/dsh/src/client.d.ts +4 -0
- package/lib/types/dsh/src/creator.d.ts +14 -0
- package/lib/types/dsh/src/frame-runtime.d.ts +1046 -0
- package/lib/types/dsh/src/index.d.ts +4 -0
- package/lib/types/src/content/compiler.d.ts +30 -0
- package/lib/types/src/content/graph-validator.d.ts +55 -0
- package/lib/types/src/content/library.d.ts +3 -0
- package/lib/types/src/content/registry.d.ts +33 -0
- package/lib/types/src/content/runtime-content.d.ts +62 -0
- package/lib/types/src/content/schema.d.ts +141 -0
- package/lib/types/src/content/validator.d.ts +34 -0
- package/lib/types/src/core/limits.d.ts +14 -0
- package/lib/types/src/core/mapping.d.ts +16 -0
- package/lib/types/src/core/protocol.d.ts +104 -0
- package/lib/types/src/creator/commands.d.ts +18 -0
- package/lib/types/src/creator/mode.d.ts +9 -0
- package/lib/types/src/creator/service.d.ts +198 -0
- package/lib/types/src/runtime/content-service.d.ts +74 -0
- package/package.json +89 -0
package/lib/client.js
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({ id: "agent-live-dsh-adapter", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/client.tsx
|
|
32
|
+
var client_exports = {};
|
|
33
|
+
__export(client_exports, {
|
|
34
|
+
apply: () => apply,
|
|
35
|
+
inject: () => inject
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(client_exports);
|
|
38
|
+
var import_react = __toESM(require("react"), 1);
|
|
39
|
+
|
|
40
|
+
// agent-live:agent-live-frame-document
|
|
41
|
+
var document = '<!doctype html>\n<html lang="en">\n<head>\n <meta charset="utf-8">\n <meta name="viewport" content="width=device-width,initial-scale=1">\n <style>:root {\n --bg: #0d1017;\n --panel: #151a24;\n --panel-2: #1c2230;\n --line: #2a3243;\n --ink: #d7dee9;\n --dim: #7c8798;\n --accent: #ffcc4d;\n --ok: #6ee7a8;\n --warn: #ff8f6b;\n --think: #8fb8ff;\n font-family: ui-monospace, SFMono-Regular, Menlo, "PingFang SC", monospace;\n}\n\n* {\n box-sizing: border-box;\n}\n\nhtml,\nbody {\n margin: 0;\n height: 100%;\n background: var(--bg);\n color: var(--ink);\n overflow: hidden;\n}\n\n#app {\n display: flex;\n flex-direction: column;\n height: 100%;\n}\n\n#bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n background: var(--panel);\n border-bottom: 2px solid var(--line);\n font-size: 12px;\n}\n\n.brand {\n color: var(--accent);\n letter-spacing: 2px;\n font-weight: 700;\n}\n\n.grow {\n flex: 1;\n}\n\n.pill {\n padding: 2px 8px;\n border: 1px solid var(--line);\n border-radius: 2px;\n color: var(--dim);\n white-space: nowrap;\n}\n\n.pill.online {\n color: var(--ok);\n border-color: #2c5c45;\n}\n\n.pill.offline {\n color: var(--warn);\n border-color: #5c3a2c;\n}\n\n.btn {\n background: var(--panel-2);\n color: var(--ink);\n border: 1px solid var(--line);\n border-radius: 2px;\n padding: 3px 10px;\n font: inherit;\n font-size: 12px;\n cursor: pointer;\n}\n\n.btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\nmain {\n display: flex;\n flex: 1;\n min-height: 0;\n}\n\n#stageWrap {\n position: relative;\n flex: 1;\n min-width: 0;\n background: #07090d;\n}\n\n#stage {\n display: block;\n width: 100%;\n height: 100%;\n image-rendering: pixelated;\n}\n\n#taskbar {\n position: absolute;\n left: 12px;\n right: 12px;\n bottom: 10px;\n display: flex;\n gap: 8px;\n align-items: baseline;\n padding: 6px 10px;\n background: rgba(10, 13, 19, 0.86);\n border: 1px solid var(--line);\n font-size: 12px;\n}\n\n#taskbar .k {\n color: var(--accent);\n white-space: nowrap;\n}\n\n#task {\n color: var(--ink);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n#side {\n width: 330px;\n display: flex;\n flex-direction: column;\n border-left: 2px solid var(--line);\n background: var(--panel);\n min-height: 0;\n}\n\n.panel {\n display: flex;\n flex-direction: column;\n min-height: 0;\n border-bottom: 1px solid var(--line);\n}\n\n.panel.grow {\n flex: 1;\n}\n\n.panel h2 {\n margin: 0;\n padding: 7px 10px;\n font-size: 11px;\n letter-spacing: 1px;\n color: var(--dim);\n text-transform: uppercase;\n background: var(--panel-2);\n}\n\n.crew {\n overflow-y: auto;\n max-height: 40vh;\n}\n\n.card {\n display: flex;\n gap: 8px;\n align-items: flex-start;\n padding: 8px 10px;\n border-bottom: 1px solid #1e2431;\n font-size: 12px;\n}\n\n.card .avatar {\n width: 14px;\n height: 14px;\n border-radius: 2px;\n flex: none;\n margin-top: 2px;\n}\n\n.card .body {\n min-width: 0;\n flex: 1;\n}\n\n.card .name {\n color: var(--ink);\n font-weight: 700;\n}\n\n.card .role {\n color: var(--dim);\n}\n\n.card .detail {\n color: var(--think);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.card .meta {\n color: #5c6675;\n font-size: 11px;\n}\n\n.badge {\n font-size: 10px;\n padding: 1px 5px;\n border: 1px solid var(--line);\n color: var(--dim);\n}\n\n.badge.thinking {\n color: var(--think);\n border-color: #34456b;\n}\n\n.badge.working {\n color: var(--accent);\n border-color: #5c4c22;\n}\n\n.badge.waiting {\n color: #c79bff;\n border-color: #453163;\n}\n\n.badge.done {\n color: var(--ok);\n border-color: #2c5c45;\n}\n\n.badge.error {\n color: var(--warn);\n border-color: #5c3a2c;\n}\n\n.log {\n overflow-y: auto;\n flex: 1;\n padding: 4px 0;\n font-size: 12px;\n line-height: 1.45;\n}\n\n.line {\n display: flex;\n gap: 6px;\n padding: 2px 10px;\n}\n\n.line .who {\n flex: none;\n color: var(--dim);\n}\n\n.line .txt {\n min-width: 0;\n word-break: break-word;\n}\n\n.line.thought .txt {\n color: var(--think);\n}\n\n.line.tool .txt {\n color: var(--accent);\n}\n\n.line.delegate .txt {\n color: #c79bff;\n}\n\n.line.say .txt {\n color: var(--ink);\n}\n\n.line.join .txt,\n.line.leave .txt,\n.line.system .txt {\n color: var(--dim);\n}\n\n.dev-controls {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.dev-controls[hidden] {\n display: none;\n}\n\n.panel-title {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n}\n\n.panel-title h2 {\n margin: 0;\n}\n\n.replay-btn {\n max-width: 190px;\n padding: 6px 10px;\n overflow: hidden;\n color: #17130a;\n background: var(--accent);\n border: 1px solid #ffe59a;\n border-radius: 3px;\n box-shadow: 0 2px 0 #8d6b14;\n font: inherit;\n font-size: 11px;\n font-weight: 800;\n text-overflow: ellipsis;\n white-space: nowrap;\n cursor: pointer;\n}\n\n.replay-btn:hover:not(:disabled) {\n filter: brightness(1.08);\n transform: translateY(-1px);\n}\n\n.replay-btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n\n.replay-status {\n position: absolute;\n top: 12px;\n left: 50%;\n z-index: 4;\n padding: 7px 12px;\n transform: translateX(-50%);\n color: #17130a;\n background: var(--accent);\n border: 2px solid #fff0b8;\n box-shadow: 0 3px 0 #76570c;\n font-size: 12px;\n font-weight: 800;\n pointer-events: none;\n}\n\n.replay-status[hidden] {\n display: none;\n}\n\n.client-controls {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n gap: 8px;\n padding: 9px 12px;\n background: var(--panel);\n border-top: 2px solid var(--line);\n}\n\n.client-controls .model-select {\n min-width: 0;\n text-overflow: ellipsis;\n}\n\n.client-controls[hidden] {\n display: none;\n}\n\n.client-controls textarea {\n min-width: 0;\n resize: none;\n padding: 7px 9px;\n background: var(--bg);\n color: var(--ink);\n border: 1px solid var(--line);\n font: inherit;\n font-size: 12px;\n}\n\n.client-controls textarea:focus {\n outline: none;\n border-color: var(--accent);\n}\n\n.client-controls .primary {\n color: var(--accent);\n}\n\n.approval {\n grid-column: 1 / -1;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 9px;\n background: #211b2c;\n border: 1px solid #594070;\n font-size: 12px;\n}\n\n.approval[hidden] {\n display: none;\n}\n\n.approval > div {\n min-width: 0;\n flex: 1;\n}\n\n.approval strong,\n.approval span {\n display: block;\n}\n\n.approval span {\n margin-top: 3px;\n color: var(--dim);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n</head>\n<body>\n <div id="app">\n <header id="bar">\n <span class="brand">AGENT LIVE</span>\n <span id="conn" class="pill online">Connected</span>\n <span id="model" class="pill">model: \u2014</span>\n <span id="turns" class="pill">turn 0</span>\n <span id="usage" class="pill">0 tok</span>\n <span class="grow"></span>\n <button id="language" class="btn" type="button" aria-label="Switch language">\u4E2D\u6587</button>\n <button id="mute" class="btn" type="button">Sound: off</button>\n <button id="demo" class="btn" type="button" data-i18n="nav.demo" hidden>Demo</button>\n </header>\n <main>\n <div id="stageWrap">\n <canvas id="stage"></canvas>\n <div id="replayStatus" class="replay-status" hidden></div>\n <div id="taskbar"><span class="k" data-i18n="task.label">Current task</span><span id="task" data-i18n="task.empty">Waiting for a task\u2026</span></div>\n </div>\n <aside id="side">\n <section class="panel">\n <div class="panel-title"><h2 data-i18n="panel.crew">Team</h2><button id="replay" class="replay-btn" type="button" hidden></button></div>\n <div id="crew" class="crew"></div>\n </section>\n <section class="panel grow"><h2 data-i18n="panel.activity">Activity</h2><div id="log" class="log"></div></section>\n </aside>\n </main>\n </div>\n <script>window.AgentLiveInitialContent=JSON.parse(decodeURIComponent("__AGENT_LIVE_CONTENT_URI__"));window.AgentLiveInitialLocale="__AGENT_LIVE_LOCALE__";<\/script>\n <script>"use strict";\n(() => {\n var __getOwnPropNames = Object.getOwnPropertyNames;\n var __esm = (fn, res) => function __init() {\n return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;\n };\n\n // ../web/app.js\n var app_exports = {};\n var init_app = __esm({\n "../web/app.js"() {\n (() => {\n const { Office, Sprites } = window;\n const I18n = window.AgentLiveI18n ?? { t: (key) => key, text: (value) => value };\n const t = (key, vars) => I18n.t(key, vars);\n const tx = (value) => I18n.text(value);\n const clientKind = window.AgentLiveClientKind ?? new URLSearchParams(location.search).get("client");\n const accessToken = new URLSearchParams(location.search).get("token") ?? "";\n const authenticatedFetch = (path) => fetch(path, {\n cache: "no-store",\n headers: accessToken ? { "x-agent-live-token": accessToken } : {}\n });\n const hideCost = clientKind === "codex" || clientKind === "dsh";\n const officeContent = window.OfficeContent ?? null;\n const lifeActivities = officeContent?.lifeActivities?.entries ?? [];\n const npcEntries = officeContent?.npcs?.entries ?? [];\n function displayAgentName(view, isLead) {\n const profileName = officeContent?.agentProfile?.name;\n if (isLead) return tx(profileName ?? view.name);\n const fallback = /^Teammate(?:\\s+(\\d+))?$/.exec(String(view.name ?? ""));\n if (profileName && fallback) return t("agent.teammate", { name: profileName, index: fallback[1] ? " " + fallback[1] : "" });\n return tx(view.name);\n }\n const canvas = document.getElementById("stage");\n const ctx = canvas.getContext("2d", { alpha: false, desynchronized: true });\n const SPEED = 44;\n const REPLAY_RATE = 3;\n const BUBBLE_MS = { think: 4200, say: 6e3, do: 2800, life: 3200 };\n let dpr = 1;\n let scale = 3;\n let offX = 0;\n let offY = 0;\n let resizeFrame = 0;\n let lastFrameAt = performance.now();\n let contextLost = false;\n let lastFrameError = null;\n function resize() {\n const rect = canvas.parentElement.getBoundingClientRect();\n if (rect.width < 2 || rect.height < 2) return;\n dpr = Math.min(window.devicePixelRatio || 1, 2);\n const dw = Math.max(1, Math.floor(rect.width * dpr));\n const dh = Math.max(1, Math.floor(rect.height * dpr));\n if (canvas.width !== dw) canvas.width = dw;\n if (canvas.height !== dh) canvas.height = dh;\n scale = Math.max(1, Math.floor(Math.min(dw / Office.W, dh / Office.H)));\n offX = Math.floor((dw - Office.W * scale) / 2);\n offY = Math.floor((dh - Office.H * scale) / 2);\n }\n function scheduleResize() {\n if (resizeFrame) return;\n resizeFrame = requestAnimationFrame(() => {\n resizeFrame = 0;\n resize();\n });\n }\n window.addEventListener("resize", scheduleResize);\n document.addEventListener("visibilitychange", () => {\n if (!document.hidden) {\n lastFrameAt = performance.now();\n scheduleResize();\n }\n });\n window.addEventListener("pageshow", () => {\n lastFrameAt = performance.now();\n scheduleResize();\n });\n const sizeObserver = typeof ResizeObserver === "function" ? new ResizeObserver(scheduleResize) : null;\n sizeObserver?.observe(canvas.parentElement);\n canvas.addEventListener("contextlost", (event) => {\n event.preventDefault();\n contextLost = true;\n });\n canvas.addEventListener("contextrestored", () => {\n contextLost = false;\n lastFrameAt = performance.now();\n scheduleResize();\n });\n const toScreen = (wx, wy) => ({ x: offX + wx * scale, y: offY + wy * scale });\n const actors = /* @__PURE__ */ new Map();\n const particles = [];\n const hot = /* @__PURE__ */ new Set();\n const lifeHot = /* @__PURE__ */ new Set();\n let session = { model: "\\u2014", turns: 0, busy: false, cwd: "" };\n let meeting = null;\n const meetQueue = [];\n let sound = false;\n let nextNpcShiftCheck = 0;\n let eventHistory = [];\n let replay = null;\n const replayButton = document.getElementById("replay");\n const replayStatus = document.getElementById("replayStatus");\n const MAX_EVENT_HISTORY = 4e3;\n const isReplayable = (entry) => ["task", "thought", "say", "action", "delegate"].includes(entry?.event?.type);\n function recordEvent(event) {\n eventHistory.push({ at: Date.now(), event });\n if (eventHistory.length > MAX_EVENT_HISTORY) eventHistory.splice(0, eventHistory.length - MAX_EVENT_HISTORY);\n }\n function makeActor(view) {\n const isLead = !view.parent;\n const profile = isLead ? officeContent?.agentProfile : null;\n const seat = view.seat;\n const placementKey = Sprites.hash(view.id);\n const spawn2 = isLead && seat != null ? Office.seatAnchor(seat) : Office.TARGETS.entry;\n return {\n id: view.id,\n name: displayAgentName(view, isLead),\n role: tx(profile?.title ?? view.role),\n title: tx(profile?.title ?? view.role),\n model: view.model,\n isLead,\n isNpc: false,\n seat,\n placementKey,\n palette: { ...Sprites.paletteFor(view.id, view.name, isLead), ...profile?.appearance ?? {} },\n seed: Sprites.hash(view.id) % 100 / 10,\n x: spawn2.x,\n y: spawn2.y,\n lane: spawn2.lane,\n zone: spawn2.zone,\n dir: spawn2.dir,\n pose: isLead ? "sit" : "stand",\n walkPhase: 0,\n path: [],\n dest: spawn2,\n state: view.state ?? "idle",\n detail: tx(view.detail ?? ""),\n task: view.task ?? "",\n action: view.action ?? null,\n tokens: view.tokens ?? 0,\n cost: view.cost ?? 0,\n bubble: null,\n inMeeting: false,\n leaving: false,\n actions: /* @__PURE__ */ new Map(),\n life: null,\n lifeCycle: 0,\n nextLifeAt: 0,\n spawnAt: performance.now()\n };\n }\n function goTo(actor, target) {\n if (!target) return;\n actor.dest = target;\n actor.path = Office.path(actor, target);\n }\n function retarget(actor) {\n if (actor.inMeeting || actor.leaving) return;\n if (actor.isNpc) {\n goTo(actor, actor.homeTarget ?? Office.TARGETS.entry);\n return;\n }\n goTo(actor, Office.anchorFor(actor.action, actor.seat, actor.placementKey));\n }\n function restPose(actor) {\n if (actor.life && !actor.path.length) return actor.life.step?.pose ?? "stand";\n if (actor.isNpc) return actor.idlePose ?? "stand";\n const a = actor.action;\n if (a === "archive" || a === "server") return "reach";\n if (a === "whiteboard") return "reach";\n if (a === "phone") return "talk";\n if (a === "coffee") return "stand";\n if (actor.inMeeting) return "talk";\n if (actor.state === "working" && (!a || a === "type")) return "type";\n if (!a || a === "type" || a === "delegate") return actor.seat == null ? "stand" : "sit";\n return "stand";\n }\n function bubble(actor, kind, text) {\n if (!text) return;\n const queued = [...actors.values()].filter((candidate) => candidate.bubble).length;\n if (!actor.bubble && queued >= (window.SceneLimits?.queuedBubbles ?? 8)) return;\n actor.bubble = {\n kind,\n text: String(text),\n shown: 0,\n remainingMs: BUBBLE_MS[kind] ?? 3e3\n };\n }\n function spawn(kind, x, y, opts = {}) {\n const maxEffects = window.SceneLimits?.effects ?? 40;\n if (particles.length >= maxEffects) particles.shift();\n particles.push({\n kind,\n x,\n y,\n vx: opts.vx ?? 0,\n vy: opts.vy ?? -14,\n life: opts.life ?? 1,\n maxLife: opts.life ?? 1,\n target: opts.target ?? null,\n t: 0,\n color: opts.color\n });\n }\n function rangedDelay(range, seed) {\n if (typeof range === "number") return Math.max(0, range);\n const min = Math.max(0, Number(range?.[0] ?? 0));\n const max = Math.max(min, Number(range?.[1] ?? min));\n if (max === min) return min;\n return min + Sprites.hash(seed) % (max - min + 1);\n }\n function matchesParticipant(actor, participant) {\n if (participant.kind !== "person" && participant.kind === "npc" !== actor.isNpc) return false;\n if (actor.isNpc && participant.roles?.length && !participant.roles.includes(actor.role)) return false;\n if (participant.states?.length && !participant.states.includes(actor.state)) return false;\n return true;\n }\n function activitiesFor(actor) {\n return lifeActivities.filter((activity) => {\n const participant = activity.participant ?? {};\n if (!matchesParticipant(actor, participant)) return false;\n if (activity.onlyWhenSessionIdle && session.busy) return false;\n if (Number(participant.minAgents ?? 1) > 1) {\n const available = [...actors.values()].filter(\n (candidate) => !candidate.leaving && matchesParticipant(candidate, participant)\n );\n if (available.length < Number(participant.minAgents)) return false;\n }\n if (activity.steps?.some((step) => step.target === "near-colleague")) {\n const hasAvailableColleague = [...actors.values()].some(\n (candidate) => candidate.id !== actor.id && candidate.isNpc && candidate.role === "colleague" && !candidate.leaving\n );\n if (!hasAvailableColleague) return false;\n }\n return true;\n });\n }\n function scheduleLife(actor, activity, now, initial = false) {\n actor.lifeCycle += 1;\n const range = initial ? activity.initialDelayMs : activity.cooldownMs;\n actor.nextLifeAt = now + rangedDelay(range, `${actor.id}:${activity.id}:${actor.lifeCycle}`);\n }\n function clearLifeStep(actor) {\n const hotKey = actor.life?.step?.hot;\n if (hotKey) lifeHot.delete(hotKey);\n }\n function cancelLife(actor, now = performance.now()) {\n if (!actor?.life) return;\n clearLifeStep(actor);\n const activity = actor.life.activity;\n actor.life = null;\n if (actor.bubble?.kind === "life") actor.bubble = null;\n scheduleLife(actor, activity, now);\n }\n function beginLifeStep(actor, activity, index, now, slot = actor.life?.slot ?? 0) {\n const configuredStep = activity.steps[index];\n if (!configuredStep) {\n actor.life = null;\n scheduleLife(actor, activity, now);\n retarget(actor);\n return;\n }\n const targetName = configuredStep.targets?.[slot % configuredStep.targets.length] ?? configuredStep.target;\n const step = {\n ...configuredStep,\n target: targetName,\n bubble: configuredStep.bubbles?.[slot % configuredStep.bubbles.length] ?? configuredStep.bubble\n };\n let target = Office.TARGETS[step.target];\n if (step.target === "near-colleague") {\n const colleagues = [...actors.values()].filter(\n (candidate) => candidate.id !== actor.id && candidate.isNpc && candidate.role === "colleague" && !candidate.leaving && !candidate.life && !candidate.path.length\n );\n const colleague = colleagues[Number(step.personIndex ?? index) % Math.max(1, colleagues.length)];\n if (colleague) {\n const side = colleague.x > Office.W / 2 ? -1 : 1;\n target = {\n x: Math.max(8, Math.min(Office.W - 8, colleague.x + side * 16)),\n y: colleague.y,\n dir: side > 0 ? "left" : "right",\n lane: colleague.lane,\n zone: colleague.zone\n };\n }\n }\n actor._lifeAcc = 0;\n actor.life = { activity, index, step, slot, phase: "walk", until: 0 };\n goTo(actor, target);\n if (index === 0 && activity.startBubble) bubble(actor, "life", tx(activity.startBubble));\n }\n function startLife(actor, activity, now) {\n const participant = activity.participant ?? {};\n const minAgents = Math.max(1, Number(participant.minAgents ?? 1));\n if (minAgents === 1) {\n beginLifeStep(actor, activity, 0, now);\n return;\n }\n const partners = [...actors.values()].filter(\n (candidate) => candidate.id !== actor.id && !candidate.life && !candidate.path.length && !candidate.action && !candidate.inMeeting && !candidate.leaving && matchesParticipant(candidate, participant)\n );\n const group = [actor, ...partners.slice(0, minAgents - 1)];\n if (group.length < minAgents) {\n scheduleLife(actor, activity, now);\n return;\n }\n for (let slot = 0; slot < group.length; slot++) beginLifeStep(group[slot], activity, 0, now, slot);\n }\n function updateActorLife(actor, now, dt) {\n if (!actor.isNpc && ["done", "error"].includes(actor.state)) return;\n if (!actor.life) {\n if (actor.path.length || actor.inMeeting || actor.leaving || actor.action) return;\n const choices = activitiesFor(actor);\n if (!choices.length) {\n actor.nextLifeAt = 0;\n return;\n }\n const activity = choices[(Sprites.hash(`${actor.id}:${actor.lifeCycle}`) >>> 4) % choices.length];\n if (!actor.nextLifeAt) scheduleLife(actor, activity, now, true);\n if (now >= actor.nextLifeAt) startLife(actor, activity, now);\n return;\n }\n const life = actor.life;\n if (life.phase === "walk" && !actor.path.length) {\n life.phase = "dwell";\n life.remainingMs = Number(life.step.durationMs ?? 1500);\n if (life.step.hot) lifeHot.add(life.step.hot);\n if (life.step.bubble) bubble(actor, "life", tx(life.step.bubble));\n actor.pose = life.step.pose ?? "stand";\n }\n if (life.phase !== "dwell") return;\n actor._lifeAcc = (actor._lifeAcc ?? 0) + dt;\n if (life.step.particle && actor._lifeAcc >= 0.42) {\n actor._lifeAcc = 0;\n spawn(life.step.particle, actor.x + (Math.random() - 0.5) * 8, actor.y - 14, {\n life: 0.55,\n vy: -10,\n color: Office.colors.waterActive\n });\n }\n life.remainingMs -= dt * 1e3;\n if (life.remainingMs > 0) return;\n clearLifeStep(actor);\n beginLifeStep(actor, life.activity, life.index + 1, now, life.slot);\n }\n function makeNpc(entry, fromEntry = false) {\n const colleagueEntries = npcEntries.filter((candidate) => candidate.role === "colleague");\n const colleagueIndex = colleagueEntries.findIndex((candidate) => candidate.id === entry.id);\n const seatCount = Array.isArray(Office.SEATS) ? Office.SEATS.length : 0;\n const colleagueSeat = entry.role === "colleague" && seatCount > 1 ? 1 + Math.max(0, colleagueIndex) % (seatCount - 1) : -1;\n const workPoint = Office.npcHomeTarget(entry, colleagueIndex);\n const spawnPoint = fromEntry ? Office.TARGETS.entry : workPoint;\n return {\n id: `npc:${entry.id}`,\n name: tx(entry.name),\n role: entry.role,\n title: tx(entry.title ?? entry.role),\n isLead: false,\n isNpc: true,\n seat: colleagueSeat,\n palette: { ...Sprites.paletteFor(entry.id, entry.role, false), ...entry.appearance ?? {} },\n seed: Sprites.hash(entry.id) % 100 / 10,\n x: spawnPoint.x,\n y: spawnPoint.y,\n lane: spawnPoint.lane,\n zone: spawnPoint.zone,\n dir: spawnPoint.dir,\n pose: entry.pose ?? "stand",\n idlePose: entry.pose ?? "stand",\n // `entry.spawn` describes the configured role location/entrance. It is\n // not necessarily where this concrete NPC should return after an\n // activity: ordinary colleagues are assigned a real desk above.\n homeTarget: { ...workPoint },\n shift: entry.shift,\n walkPhase: 0,\n path: [],\n dest: spawnPoint,\n state: "idle",\n detail: "",\n task: "",\n action: null,\n tokens: 0,\n cost: 0,\n bubble: null,\n inMeeting: false,\n leaving: false,\n actions: /* @__PURE__ */ new Map(),\n life: null,\n lifeCycle: 0,\n nextLifeAt: 0,\n spawnAt: performance.now()\n };\n }\n function restoreNpcs(fromEntry = false) {\n for (const entry of npcEntries) {\n if (!isNpcOnDuty(entry)) continue;\n const npc = makeNpc(entry, fromEntry);\n actors.set(npc.id, npc);\n if (fromEntry) {\n bubble(npc, "life", tx(Office.npcShiftLabel?.("arrival") ?? t("npc.arrival")));\n retarget(npc);\n }\n }\n }\n function isNpcOnDuty(entry) {\n return Office.isNpcOnDuty?.(entry.role, entry.shift, entry.id) ?? true;\n }\n function sendNpcHome(actor) {\n if (!actor?.isNpc || actor.leaving) return;\n cancelLife(actor);\n actor.leaving = true;\n actor.pose = "stand";\n actor.nextLifeAt = 0;\n bubble(actor, "life", tx(Office.npcShiftLabel?.("departure") ?? t("npc.departure")));\n goTo(actor, Office.TARGETS.entry);\n }\n function bringNpcToWork(entry) {\n const id = `npc:${entry.id}`;\n if (actors.has(id)) return;\n const npc = makeNpc(entry, true);\n actors.set(id, npc);\n bubble(npc, "life", tx(Office.npcShiftLabel?.("arrival") ?? t("npc.arrival")));\n retarget(npc);\n }\n function syncNpcShift(now) {\n if (now < nextNpcShiftCheck) return;\n nextNpcShiftCheck = now + 250;\n for (const entry of npcEntries) {\n const actor = actors.get(`npc:${entry.id}`);\n if (isNpcOnDuty(entry)) {\n if (!actor) bringNpcToWork(entry);\n } else if (actor) {\n sendNpcHome(actor);\n }\n }\n }\n function feed(agentId, kind, text) {\n if (!text) return;\n logLine({ at: Date.now(), agentId, kind, text });\n }\n function apply(ev) {\n switch (ev.type) {\n case "snapshot": {\n eventHistory = Array.isArray(ev.history) ? ev.history.slice(-MAX_EVENT_HISTORY) : eventHistory;\n actors.clear();\n particles.length = 0;\n hot.clear();\n lifeHot.clear();\n nextNpcShiftCheck = 0;\n document.getElementById("log").innerHTML = "";\n for (const view of ev.agents) actors.set(view.id, makeActor(view));\n restoreNpcs();\n for (const actor of actors.values()) {\n if (actor.isNpc) continue;\n if (actor.action) hot.add(Office.stationKey(actor.action, actor.seat));\n retarget(actor);\n }\n for (const item of ev.log) logLine(item);\n session = ev.session;\n if (ev.agents[0]?.task) setTask(ev.agents[0].task);\n renderCrew();\n break;\n }\n case "session":\n session = ev.session;\n if (session.busy) {\n for (const actor of actors.values()) if (!actor.isNpc) cancelLife(actor);\n }\n break;\n case "agent_join": {\n const existing = actors.get(ev.agent.id);\n if (existing) {\n existing.name = displayAgentName({ ...ev.agent, name: ev.agent.name ?? existing.name }, existing.isLead);\n existing.role = tx(existing.isLead && officeContent?.agentProfile?.title ? officeContent.agentProfile.title : ev.agent.role ?? existing.role);\n existing.title = existing.role;\n existing.model = ev.agent.model ?? existing.model;\n existing.task = ev.agent.task ?? existing.task;\n existing.detail = tx(ev.agent.detail ?? existing.detail);\n renderCrew();\n } else {\n const actor = makeActor(ev.agent);\n actors.set(ev.agent.id, actor);\n if (!actor.isLead) {\n bubble(actor, "say", t("agent.joined"));\n beep(660, 0.06);\n }\n retarget(actor);\n renderCrew();\n }\n break;\n }\n case "agent_leave": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n cancelLife(actor);\n actor.actions.clear();\n hot.clear();\n for (const current of actors.values()) for (const action of current.actions.values()) hot.add(Office.stationKey(action, current.seat));\n actor.leaving = true;\n actor.inMeeting = false;\n if (ev.ok !== void 0) spawn(ev.ok ? "check" : "cross", actor.x, actor.y - 26, { life: 1.1 });\n goTo(actor, Office.TARGETS.entry);\n if (!actor.path.length) {\n actors.delete(actor.id);\n renderCrew();\n }\n break;\n }\n case "agent_state": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n if (ev.state !== "idle") cancelLife(actor);\n actor.state = ev.state;\n actor.detail = tx(ev.detail ?? "");\n if (ev.state === "done") spawn("check", actor.x, actor.y - 26, { life: 1.2 });\n if (ev.state === "error") spawn("cross", actor.x, actor.y - 26, { life: 1.2 });\n if (!actor.path.length) actor.pose = restPose(actor);\n renderCrew();\n break;\n }\n case "task": {\n const actor = actors.get(ev.id);\n if (actor) {\n cancelLife(actor);\n actor.task = ev.task;\n }\n if (actor?.isLead) setTask(ev.task);\n break;\n }\n case "thought": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n cancelLife(actor);\n bubble(actor, "think", tail(ev.text, 52));\n feed(ev.id, "thought", ev.text);\n break;\n }\n case "say": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n cancelLife(actor);\n bubble(actor, "say", head(ev.text, 90));\n feed(ev.id, "say", ev.text);\n break;\n }\n case "action": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n cancelLife(actor);\n actor.action = ev.action;\n actor.actions.set(ev.toolCallId, ev.action);\n actor.state = "working";\n actor.detail = ev.label;\n hot.add(Office.stationKey(ev.action, actor.seat));\n bubble(actor, "do", tx(ev.label));\n feed(ev.id, "tool", tx(ev.label));\n retarget(actor);\n renderCrew();\n break;\n }\n case "action_end": {\n const actor = actors.get(ev.id);\n if (!actor) break;\n actor.actions.delete(ev.toolCallId);\n actor.action = [...actor.actions.values()].at(-1) ?? null;\n hot.clear();\n for (const current of actors.values()) for (const action of current.actions.values()) hot.add(Office.stationKey(action, current.seat));\n if (!ev.ok) spawn("cross", actor.x, actor.y - 26, { life: 1 });\n retarget(actor);\n break;\n }\n case "delegate": {\n cancelLife(actors.get(ev.from));\n cancelLife(actors.get(ev.to));\n if (Office.interactions?.handoff === "seated") {\n const from = actors.get(ev.from);\n const to = actors.get(ev.to);\n const director = [...actors.values()].find(\n (actor) => actor.isNpc && actor.role === Office.interactions.directorNpcRole\n );\n if (director) bubble(director, "say", t("delegate.owner", { name: to?.name ?? "Teammate" }));\n if (from && to) {\n spawn("paper", from.x + 6, from.y - 14, {\n life: 0.9,\n target: { x: to.x - 6, y: to.y - 14 }\n });\n spawn("bang", to.x, to.y - 30, { life: 1.1, vy: -8 });\n bubble(to, "say", head(ev.task, 60));\n }\n beep(520, 0.07);\n } else {\n meetQueue.push({ from: ev.from, to: ev.to, task: ev.task });\n }\n break;\n }\n case "usage": {\n const actor = actors.get(ev.id);\n if (actor) {\n actor.tokens = ev.tokens;\n actor.cost = ev.cost;\n }\n renderCrew();\n break;\n }\n case "log":\n logLine(ev.item);\n break;\n }\n renderBar();\n }\n function updateMeeting(now, dt) {\n if (!meeting && meetQueue.length) {\n const next = meetQueue.shift();\n const from2 = actors.get(next.from);\n const to2 = actors.get(next.to);\n if (!from2 || !to2) return;\n meeting = { ...next, phase: "walk", t: 0 };\n from2.inMeeting = true;\n to2.inMeeting = true;\n goTo(from2, Office.TARGETS.meetA);\n goTo(to2, Office.TARGETS.meetB);\n bubble(from2, "do", t("delegate.assign", { task: head(next.task, 40) }));\n }\n if (!meeting) return;\n const from = actors.get(meeting.from);\n const to = actors.get(meeting.to);\n if (!from || !to) {\n endMeeting();\n return;\n }\n meeting.t += dt;\n if (meeting.phase === "walk") {\n const ready = !from.path.length && !to.path.length;\n if (ready || meeting.t > 5) {\n meeting.phase = "hand";\n meeting.t = 0;\n from.pose = "talk";\n to.pose = "talk";\n spawn("paper", from.x + 6, from.y - 14, {\n life: 0.9,\n target: { x: to.x - 6, y: to.y - 14 }\n });\n spawn("bang", to.x, to.y - 30, { life: 1.1, vy: -8 });\n bubble(to, "say", head(meeting.task, 60));\n beep(520, 0.07);\n setTimeout(() => beep(700, 0.07), 90);\n }\n return;\n }\n if (meeting.phase === "hand" && meeting.t > 1.5) endMeeting();\n }\n function endMeeting() {\n if (!meeting) return;\n for (const id of [meeting.from, meeting.to]) {\n const actor = actors.get(id);\n if (!actor) continue;\n actor.inMeeting = false;\n retarget(actor);\n }\n meeting = null;\n }\n function update(dt, now) {\n if (replay) dt *= REPLAY_RATE;\n syncNpcShift(now);\n updateMeeting(now, dt);\n for (const actor of [...actors.values()]) {\n if (actor.path.length) {\n const next = actor.path[0];\n const dx = next.x - actor.x;\n const dy = next.y - actor.y;\n const dist = Math.abs(dx) + Math.abs(dy);\n const move = SPEED * dt;\n if (dist <= move) {\n actor.x = next.x;\n actor.y = next.y;\n actor.path.shift();\n if (next.zone) actor.zone = next.zone;\n if (!actor.path.length) {\n actor.dir = actor.dest.dir ?? actor.dir;\n actor.lane = actor.dest.lane ?? actor.lane;\n actor.zone = actor.dest.zone ?? actor.zone;\n actor.pose = restPose(actor);\n if (actor.leaving) {\n actors.delete(actor.id);\n renderCrew();\n continue;\n }\n }\n } else if (Math.abs(dx) > 0.01) {\n actor.x += Math.sign(dx) * move;\n actor.dir = dx > 0 ? "right" : "left";\n actor.pose = "walk";\n } else {\n actor.y += Math.sign(dy) * move;\n actor.dir = dy > 0 ? "down" : "up";\n actor.pose = "walk";\n }\n actor.walkPhase += dt;\n } else if (actor.pose !== restPose(actor)) {\n actor.pose = restPose(actor);\n }\n updateActorLife(actor, now, dt);\n if (actor.bubble) {\n actor.bubble.shown = Math.min(\n actor.bubble.text.length,\n actor.bubble.shown + dt * 46\n );\n actor.bubble.remainingMs -= dt * 1e3;\n if (actor.bubble.remainingMs <= 0) actor.bubble = null;\n }\n emitWorkParticles(actor, dt);\n }\n for (let i = particles.length - 1; i >= 0; i--) {\n const p = particles[i];\n p.t += dt;\n p.life -= dt;\n if (p.target) {\n const k = Math.min(1, p.t / p.maxLife);\n p.x += (p.target.x - p.x) * Math.min(1, k * 0.28);\n p.y += (p.target.y - p.y) * Math.min(1, k * 0.28) - dt * 6;\n } else {\n p.x += p.vx * dt;\n p.y += p.vy * dt;\n }\n if (p.life <= 0) particles.splice(i, 1);\n }\n }\n function emitWorkParticles(actor, dt) {\n if (actor.path.length || actor.state !== "working") return;\n actor._acc = (actor._acc ?? 0) + dt;\n const rate = actor.action === "server" ? 0.18 : 0.26;\n if (actor._acc < rate) return;\n actor._acc = 0;\n const jitter = (Math.random() - 0.5) * 10;\n switch (actor.action) {\n case "archive":\n spawn("paper", actor.x + jitter, actor.y - 22, { life: 0.8, vy: -18 });\n break;\n case "server":\n spawn("spark", actor.x + jitter, actor.y - 20, { life: 0.6, vy: -22 });\n break;\n case "phone":\n case "coffee":\n break;\n default:\n spawn("key", actor.x + jitter, actor.y - 24, { life: 0.7, vy: -16 });\n }\n }\n function render(now) {\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.fillStyle = "#07090d";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.setTransform(scale, 0, 0, scale, offX, offY);\n const occupied = /* @__PURE__ */ new Set();\n for (const actor of actors.values()) {\n if (actor.seat != null && !actor.path.length && !actor.action && !actor.inMeeting) occupied.add(actor.seat);\n }\n Office.drawRoom(ctx, now, /* @__PURE__ */ new Set([...hot, ...lifeHot]), occupied);\n const ordered = [...actors.values()].sort((a, b) => a.y - b.y);\n for (const actor of ordered) Sprites.drawCharacter(ctx, actor, now);\n for (const p of particles) Sprites.drawParticle(ctx, p);\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n for (const actor of ordered) drawLabel(actor);\n for (const actor of ordered.filter((actor2) => actor2.bubble).slice(0, window.SceneLimits?.visibleBubbles ?? 4)) {\n drawBubble(actor);\n }\n }\n function drawLabel(actor) {\n const p = toScreen(actor.x, actor.y - 22);\n const size = Math.max(10 * dpr, Math.round(scale * 3));\n ctx.font = `${size}px ui-monospace, Menlo, monospace`;\n ctx.textAlign = "center";\n ctx.textBaseline = "alphabetic";\n const text = actor.name;\n const w = ctx.measureText(text).width;\n ctx.fillStyle = "rgba(8,11,16,0.72)";\n ctx.fillRect(p.x - w / 2 - 3 * dpr, p.y - size, w + 6 * dpr, size + 4 * dpr);\n ctx.fillStyle = actor.isLead ? "#ffcc4d" : actor.isNpc ? "#9fd6bd" : "#cbd4e2";\n ctx.fillText(text, p.x, p.y);\n }\n function drawBubble(actor) {\n const b = actor.bubble;\n if (!b) return;\n const style = b.kind === "think" ? { bg: "rgba(20,30,52,0.94)", border: "#3d5580", fg: "#a9c6ff" } : b.kind === "do" ? { bg: "rgba(38,30,12,0.94)", border: "#7a6224", fg: "#ffd98a" } : b.kind === "life" ? { bg: "rgba(16,38,32,0.95)", border: "#38715e", fg: "#b7eadb" } : { bg: "rgba(16,22,32,0.95)", border: "#4a5668", fg: "#e6ecf6" };\n const size = Math.max(11 * dpr, Math.round(scale * 3.2));\n ctx.font = `${size}px ui-monospace, Menlo, "PingFang SC", monospace`;\n ctx.textAlign = "left";\n ctx.textBaseline = "top";\n const visible = b.text.slice(0, Math.ceil(b.shown));\n const maxW = Math.min(canvas.width * 0.42, 260 * dpr);\n const lines = wrap(visible, maxW);\n const lineH = Math.round(size * 1.35);\n const padX = 6 * dpr;\n const padY = 5 * dpr;\n const boxW = Math.max(...lines.map((l) => ctx.measureText(l).width)) + padX * 2;\n const boxH = lines.length * lineH + padY * 2;\n const anchor = toScreen(actor.x, actor.y - 30);\n let x = anchor.x - boxW / 2;\n let y = anchor.y - boxH;\n x = Math.max(4 * dpr, Math.min(canvas.width - boxW - 4 * dpr, x));\n y = Math.max(4 * dpr, y);\n ctx.fillStyle = style.bg;\n ctx.fillRect(x, y, boxW, boxH);\n ctx.strokeStyle = style.border;\n ctx.lineWidth = Math.max(1, dpr);\n ctx.strokeRect(x + 0.5, y + 0.5, boxW - 1, boxH - 1);\n const tipX = Math.max(x + 6 * dpr, Math.min(x + boxW - 10 * dpr, anchor.x - 3 * dpr));\n ctx.fillStyle = style.bg;\n ctx.fillRect(tipX, y + boxH, 6 * dpr, 4 * dpr);\n if (b.kind === "think") {\n ctx.fillRect(tipX + 1 * dpr, y + boxH + 6 * dpr, 3 * dpr, 3 * dpr);\n }\n ctx.fillStyle = style.fg;\n lines.forEach((line, i) => ctx.fillText(line, x + padX, y + padY + i * lineH));\n }\n function wrap(text, maxW) {\n const out = [];\n let line = "";\n for (const ch of text) {\n const test = line + ch;\n if (ctx.measureText(test).width > maxW && line) {\n out.push(line);\n line = ch;\n } else {\n line = test;\n }\n if (out.length >= 3) break;\n }\n if (line && out.length < 3) out.push(line);\n return out.length ? out : [""];\n }\n const stateLabel = (state) => t(`state.${state}`);\n function renderCrew() {\n const host = document.getElementById("crew");\n const list = [...actors.values()].filter((actor) => !actor.isNpc).sort(\n (a, b) => Number(b.isLead) - Number(a.isLead) || a.spawnAt - b.spawnAt\n );\n host.innerHTML = list.map((a) => {\n const role = Sprites.roleFor(a.name);\n return `<div class="card">\n <div class="avatar" style="background:${a.palette.shirt}"></div>\n <div class="body">\n <div><span class="name">${esc(a.name)}</span>\n <span class="role">${esc(tx(role ? role.title : a.role ?? ""))}</span>\n <span class="badge ${a.state}">${stateLabel(a.state)}</span></div>\n <div class="detail">${esc(a.detail || a.task || "\\u2014")}</div>\n <div class="meta">${hideCost ? `${fmtTokens(a.tokens)} tok` : `${fmtTokens(a.tokens)} tok \\xB7 ${(a.cost ?? 0).toFixed(4)}`}</div>\n </div>\n </div>`;\n }).join("");\n const lead = list.find((actor) => actor.isLead);\n if (!replayButton) return;\n replayButton.hidden = !lead;\n const hasActivity = eventHistory.some(isReplayable) || (session.turns ?? 0) > 0;\n if (lead) replayButton.textContent = replay ? t("replay.playing") : hasActivity ? `\\u25B6 ${t("replay.day", { name: lead.name })}` : t("replay.noActivity");\n replayButton.disabled = !lead || session.busy || Boolean(replay) || !hasActivity;\n }\n function logLine(item) {\n const host = document.getElementById("log");\n const actor = actors.get(item.agentId);\n const el = document.createElement("div");\n el.className = `line ${item.kind}`;\n el.innerHTML = `<span class="who">${esc(actor?.name ?? "\\xB7")}</span><span class="txt">${esc(tx(item.text))}</span>`;\n host.appendChild(el);\n while (host.childNodes.length > 220) host.removeChild(host.firstChild);\n host.scrollTop = host.scrollHeight;\n }\n function renderBar() {\n let tokens = 0;\n let cost = 0;\n for (const a of actors.values()) {\n if (a.isNpc) continue;\n tokens += a.tokens ?? 0;\n cost += a.cost ?? 0;\n }\n const effort = session.thinkingLevel ? ` \\xB7 ${session.thinkingLevel}` : "";\n document.getElementById("model").textContent = `model: ${session.model ?? "\\u2014"}${effort}`;\n document.getElementById("turns").textContent = `turn ${session.turns ?? 0}${session.busy ? ` \\xB7 ${t("status.running")}` : ""}`;\n document.getElementById("usage").textContent = hideCost ? `${fmtTokens(tokens)} tok` : `${fmtTokens(tokens)} tok \\xB7 ${cost.toFixed(4)}`;\n }\n function setTask(text) {\n document.getElementById("task").textContent = text || t("task.empty");\n }\n const esc = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", \'"\': """ })[c]);\n const head = (s, n) => s.length > n ? `${s.slice(0, n)}\\u2026` : s;\n const tail = (s, n) => s.length > n ? `\\u2026${s.slice(-n)}` : s;\n const fmtTokens = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n | 0);\n let audio = null;\n function beep(freq, dur) {\n if (!sound) return;\n try {\n audio = audio ?? new (window.AudioContext ?? window.webkitAudioContext)();\n const osc = audio.createOscillator();\n const gain = audio.createGain();\n osc.type = "square";\n osc.frequency.value = freq;\n gain.gain.value = 0.03;\n osc.connect(gain).connect(audio.destination);\n osc.start();\n osc.stop(audio.currentTime + dur);\n } catch {\n sound = false;\n }\n }\n function isPriorityLiveEvent(event) {\n if (event.type === "session") return Boolean(event.session?.busy);\n return ["task", "thought", "say", "action", "delegate", "agent_join", "agent_leave"].includes(event.type) || event.type === "agent_state" && !["idle", "done"].includes(event.state);\n }\n function waitForReplay(ms, run) {\n return new Promise((resolve) => {\n run.resolveWait = resolve;\n run.timer = setTimeout(resolve, Math.min(3e3, Math.max(220, ms)));\n });\n }\n async function restoreLiveSnapshot() {\n if (typeof window.AgentLiveGetSnapshot === "function") {\n const latest = await window.AgentLiveGetSnapshot();\n if (latest) {\n apply(latest);\n }\n return;\n }\n try {\n const response = await authenticatedFetch("/api/state");\n if (response.ok) apply(await response.json());\n } catch {\n }\n }\n function cancelReplay(restore = false) {\n const run = replay;\n if (!run) return;\n run.cancelled = true;\n if (run.timer) clearTimeout(run.timer);\n run.resolveWait?.();\n replay = null;\n if (replayStatus) replayStatus.hidden = true;\n renderCrew();\n if (restore) void restoreLiveSnapshot();\n }\n function finishReplay() {\n for (const actor of actors.values()) {\n if (actor.isNpc) continue;\n cancelLife(actor);\n actor.action = null;\n actor.state = "idle";\n actor.detail = t("state.idle");\n retarget(actor);\n }\n session = { ...session, busy: false };\n replay = null;\n if (replayStatus) {\n replayStatus.textContent = t("replay.complete");\n replayStatus.hidden = false;\n setTimeout(() => {\n if (!replay) replayStatus.hidden = true;\n }, 1400);\n }\n renderCrew();\n renderBar();\n }\n async function runHistoryReplay() {\n if (replay || session.busy) return;\n const latest = typeof window.AgentLiveGetSnapshot === "function" ? await window.AgentLiveGetSnapshot() : await authenticatedFetch("/api/state").then((response) => response.ok ? response.json() : null);\n if (!latest) return;\n const history = Array.isArray(latest.history) ? latest.history.slice() : [];\n if (!history.some(isReplayable)) {\n if (replayButton) replayButton.textContent = t("replay.empty");\n return;\n }\n const run = { cancelled: false, timer: null, resolveWait: null };\n replay = run;\n const replayLeads = (latest.agents ?? []).filter((agent) => !agent.parent).map((agent) => ({ ...agent, state: "idle", action: void 0, detail: t("state.idle") }));\n apply({\n type: "snapshot",\n agents: replayLeads,\n log: [],\n session: { ...latest.session, busy: false, turns: 0 },\n history\n });\n renderCrew();\n let previousAt = history[0].at;\n for (let index = 0; index < history.length; index++) {\n const entry = history[index];\n if (run.cancelled) return;\n await waitForReplay((entry.at - previousAt) / REPLAY_RATE, run);\n if (run.cancelled) return;\n if (replayStatus) {\n replayStatus.textContent = t("replay.progress", { current: index + 1, total: history.length });\n replayStatus.hidden = false;\n }\n apply(entry.event);\n previousAt = entry.at;\n }\n if (!run.cancelled) finishReplay();\n }\n function connect() {\n const pill = document.getElementById("conn");\n if (typeof window.AgentLiveSubscribe === "function") {\n pill.textContent = t("connection.connected");\n pill.className = "pill online";\n window.AgentLiveSubscribe((event) => {\n if (event.type !== "snapshot") recordEvent(event);\n if (replay) {\n if (isPriorityLiveEvent(event)) cancelReplay(true);\n return;\n }\n apply(event);\n });\n return;\n }\n const es = new EventSource(accessToken ? `/events?token=${encodeURIComponent(accessToken)}` : "/events");\n es.onopen = () => {\n pill.textContent = t("connection.connected");\n pill.className = "pill online";\n };\n es.onerror = () => {\n pill.textContent = t("connection.reconnecting");\n pill.className = "pill offline";\n };\n es.onmessage = (e) => {\n try {\n const event = JSON.parse(e.data);\n if (event.type !== "snapshot") recordEvent(event);\n if (replay) {\n if (isPriorityLiveEvent(event)) cancelReplay(true);\n return;\n }\n apply(event);\n } catch (err) {\n console.error("bad event", err);\n }\n };\n }\n async function runDemo() {\n const wait = (ms) => new Promise((r) => setTimeout(r, ms));\n const now = () => Date.now();\n const log = (agentId, kind, text) => apply({ type: "log", item: { at: now(), agentId, kind, text } });\n Office.startDayPreview?.(24e3);\n apply({\n type: "snapshot",\n agents: [\n {\n id: "main",\n name: "\\u554A\\u6D3E",\n role: "\\u4E3B\\u7BA1",\n state: "idle",\n tokens: 0,\n cost: 0,\n toolCalls: 0,\n joinedAt: now(),\n seat: 0\n }\n ],\n log: [],\n session: { cwd: "demo", model: "demo-model", busy: true, turns: 1, startedAt: now() }\n });\n apply({ type: "task", id: "main", task: "\\u7ED9\\u4F1A\\u8BDD\\u5B58\\u50A8\\u52A0\\u4E0A Redis \\u7F13\\u5B58" });\n apply({ type: "agent_state", id: "main", state: "thinking", detail: "\\u62C6\\u89E3\\u9700\\u6C42" });\n for (const t2 of [\n "\\u5148\\u770B\\u770B session store \\u73B0\\u5728\\u600E\\u4E48\\u5199\\u7684",\n "\\u7F13\\u5B58\\u8981\\u80FD\\u5931\\u6548\\uFF0C\\u4E0D\\u80FD\\u53EA\\u505A\\u8BFB\\u7F13\\u5B58",\n "\\u53EF\\u4EE5\\u5E76\\u884C\\uFF1A\\u4E00\\u4E2A\\u4EBA\\u6478\\u4EE3\\u7801\\uFF0C\\u4E00\\u4E2A\\u4EBA\\u51FA\\u65B9\\u6848"\n ]) {\n apply({ type: "thought", id: "main", text: t2 });\n await wait(1500);\n }\n apply({ type: "action", id: "main", action: "archive", label: "\\u67E5\\u9605 \\u2026/core/session-manager.ts", toolCallId: "d1" });\n await wait(2600);\n apply({ type: "action_end", id: "main", toolCallId: "d1", ok: true });\n for (const [id, name, seat, task] of [\n ["scout", "scout", 1, "\\u5B9A\\u4F4D session store \\u7684\\u8BFB\\u5199\\u5165\\u53E3"],\n ["planner", "planner", 2, "\\u57FA\\u4E8E\\u4FA6\\u5BDF\\u7ED3\\u679C\\u7ED9\\u51FA Redis \\u7F13\\u5B58\\u65B9\\u6848"]\n ]) {\n apply({\n type: "agent_join",\n agent: { id, name, role: "\\u5916\\u63F4", parent: "main", state: "idle", tokens: 0, cost: 0, toolCalls: 0, joinedAt: now(), seat }\n });\n apply({ type: "delegate", from: "main", to: id, task });\n log("main", "delegate", `\\u554A\\u6D3E \\u2192 ${name}: ${task}`);\n await wait(4200);\n }\n apply({ type: "agent_state", id: "main", state: "waiting", detail: "\\u7B49\\u5F85 2 \\u4F4D\\u540C\\u4E8B" });\n apply({ type: "action", id: "scout", action: "server", label: "\\u6267\\u884C rg -n \'getSession\'", toolCallId: "d2" });\n apply({ type: "agent_state", id: "planner", state: "thinking", detail: "\\u5BF9\\u6BD4\\u65B9\\u6848" });\n apply({ type: "thought", id: "planner", text: "\\u5199\\u7A7F vs \\u65C1\\u8DEF\\u7F13\\u5B58\\uFF0C\\u5148\\u770B\\u4E00\\u81F4\\u6027\\u8981\\u6C42" });\n await wait(2600);\n apply({ type: "action_end", id: "scout", toolCallId: "d2", ok: true });\n apply({ type: "action", id: "scout", action: "archive", label: "\\u67E5\\u9605 \\u2026/store/redis.ts", toolCallId: "d3" });\n await wait(2400);\n apply({ type: "action_end", id: "scout", toolCallId: "d3", ok: true });\n apply({ type: "say", id: "scout", text: "\\u8BFB\\u5199\\u90FD\\u8D70 SessionManager.load/save\\uFF0C\\u7F13\\u5B58\\u6302\\u8FD9\\u91CC\\u6700\\u7701\\u4E8B" });\n apply({ type: "usage", id: "scout", tokens: 8200, cost: 31e-4 });\n await wait(1800);\n apply({ type: "agent_state", id: "scout", state: "done", detail: "\\u5DF2\\u4EA4\\u4ED8" });\n apply({ type: "action", id: "planner", action: "whiteboard", label: "\\u66F4\\u65B0\\u4EFB\\u52A1\\u677F", toolCallId: "d4" });\n await wait(2600);\n apply({ type: "action_end", id: "planner", toolCallId: "d4", ok: true });\n apply({ type: "say", id: "planner", text: "\\u65B9\\u6848\\uFF1A\\u65C1\\u8DEF\\u7F13\\u5B58 + \\u5199\\u65F6\\u5931\\u6548\\uFF0C30s TTL \\u515C\\u5E95" });\n apply({ type: "usage", id: "planner", tokens: 12400, cost: 88e-4 });\n apply({ type: "agent_state", id: "planner", state: "done", detail: "\\u5DF2\\u4EA4\\u4ED8" });\n await wait(1400);\n apply({ type: "action", id: "main", action: "type", label: "\\u4FEE\\u6539 \\u2026/core/session-manager.ts", toolCallId: "d5" });\n await wait(3200);\n apply({ type: "action_end", id: "main", toolCallId: "d5", ok: true });\n apply({ type: "action", id: "main", action: "server", label: "\\u6267\\u884C npm test -- session", toolCallId: "d6" });\n await wait(3e3);\n apply({ type: "action_end", id: "main", toolCallId: "d6", ok: true });\n apply({ type: "usage", id: "main", tokens: 46200, cost: 0.184 });\n apply({ type: "say", id: "main", text: "\\u7F13\\u5B58\\u63A5\\u597D\\u4E86\\uFF0C\\u6D4B\\u8BD5\\u901A\\u8FC7\\u3002\\u8981\\u4E0D\\u8981\\u6211\\u518D\\u8865\\u4E00\\u4E2A\\u538B\\u6D4B\\u811A\\u672C\\uFF1F" });\n await wait(1500);\n apply({ type: "agent_state", id: "main", state: "idle", detail: "\\u5F85\\u547D" });\n apply({ type: "session", session: { cwd: "demo", model: "demo-model", busy: false, turns: 3, startedAt: now() } });\n await wait(2e4);\n apply({ type: "agent_leave", id: "scout", ok: true });\n apply({ type: "agent_leave", id: "planner", ok: true });\n }\n document.getElementById("demo").addEventListener("click", () => void runDemo());\n replayButton?.addEventListener("click", () => void runHistoryReplay());\n const muteButton = document.getElementById("mute");\n const renderSoundLabel = () => {\n if (!muteButton) return;\n muteButton.textContent = window.AgentLiveI18n ? t(sound ? "nav.soundOn" : "nav.soundOff") : sound ? "\\u97F3\\u6548: \\u5F00" : "\\u97F3\\u6548: \\u5173";\n };\n muteButton?.addEventListener("click", () => {\n sound = !sound;\n renderSoundLabel();\n });\n window.addEventListener("agent-live:locale", renderSoundLabel);\n renderSoundLabel();\n resize();\n renderBar();\n function frame(now) {\n requestAnimationFrame(frame);\n const dt = Math.min(0.05, Math.max(0, (now - lastFrameAt) / 1e3));\n lastFrameAt = now;\n if (contextLost || document.hidden) return;\n try {\n update(dt, now);\n render(now);\n lastFrameError = null;\n } catch (error) {\n const signature = String(error?.stack ?? error);\n if (signature !== lastFrameError) {\n lastFrameError = signature;\n console.error("Agent Live frame failed; the renderer will recover on the next frame.", error);\n }\n }\n }\n requestAnimationFrame(frame);\n if (new URLSearchParams(location.search).has("demo")) {\n const pill = document.getElementById("conn");\n pill.textContent = t("nav.demo");\n pill.className = "pill online";\n void runDemo();\n } else connect();\n })();\n }\n });\n\n // ../web/v2/environment-runtime.js\n var MINUTES_PER_DAY = 24 * 60;\n var DEFAULT_PHASES = [\n { id: "morning", start: "06:00" },\n { id: "noon", start: "11:00" },\n { id: "evening", start: "17:00" },\n { id: "night", start: "20:00" }\n ];\n var DEFAULT_WEATHER = ["clear", "cloudy", "rain", "snow"];\n function parseClock(value, fallback = 0) {\n if (typeof value !== "string" || !/^\\d{1,2}:\\d{2}$/.test(value)) return fallback;\n const [hour, minute] = value.split(":").map(Number);\n if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return fallback;\n return hour * 60 + minute;\n }\n function phaseAt(minutes, phases) {\n const ordered = phases.map((phase) => ({ ...phase, minute: parseClock(phase.start) })).sort((a, b) => a.minute - b.minute);\n let selected = ordered[ordered.length - 1]?.id ?? "noon";\n for (const phase of ordered) {\n if (minutes < phase.minute) break;\n selected = phase.id;\n }\n return selected;\n }\n function stableHash(value) {\n let hash = 2166136261;\n for (let index = 0; index < value.length; index += 1) {\n hash ^= value.charCodeAt(index);\n hash = Math.imul(hash, 16777619);\n }\n return hash >>> 0;\n }\n function createEnvironmentRuntime(config) {\n const clock = config.clock ?? {};\n const weather = config.weather ?? {};\n const lighting = config.lighting ?? {};\n const npcSchedule = config.npcSchedule ?? {};\n const allowedWeather = new Set(weather.allowedConditions ?? DEFAULT_WEATHER);\n let preview = null;\n let timeOverride = null;\n let weatherOverride = null;\n function officeDate() {\n const now = /* @__PURE__ */ new Date();\n let minutes = now.getHours() * 60 + now.getMinutes();\n if (preview) {\n const elapsed = (performance.now() - preview.startedAt) % preview.durationMs;\n minutes = (preview.startMinute + elapsed / preview.durationMs * MINUTES_PER_DAY) % MINUTES_PER_DAY;\n } else if (timeOverride != null) {\n minutes = timeOverride;\n } else if (clock.mode === "fixed") {\n minutes = parseClock(clock.fixedTime, minutes);\n }\n now.setHours(Math.floor(minutes / 60), Math.floor(minutes % 60), 0, 0);\n return now;\n }\n function snapshot() {\n const now = officeDate();\n const minutes = now.getHours() * 60 + now.getMinutes();\n const phase = phaseAt(minutes, clock.phases ?? DEFAULT_PHASES);\n const condition = weatherOverride ?? weather.condition ?? weather.fallback ?? "clear";\n return Object.freeze({\n now,\n hour: now.getHours(),\n minute: now.getMinutes(),\n minutes,\n phase,\n weather: condition,\n dynamicTime: config.render?.dynamicTime === true,\n dynamicWeather: config.render?.dynamicWeather === true,\n lightingOn: lighting.enabled === true && (lighting.activePhases ?? ["evening", "night"]).includes(phase),\n previewing: Boolean(preview)\n });\n }\n function startPreview(durationMs) {\n const settings = clock.preview ?? {};\n if (settings.enabled !== true || timeOverride != null) return false;\n preview = {\n startedAt: performance.now(),\n durationMs: Math.max(8e3, Number(durationMs ?? settings.durationMs ?? 24e3)),\n startMinute: parseClock(settings.startTime, 6 * 60)\n };\n return true;\n }\n function update(overrides = {}) {\n if (Object.hasOwn(overrides, "time")) {\n if (overrides.time == null || overrides.time === "") {\n timeOverride = null;\n } else {\n const parsed = parseClock(String(overrides.time), -1);\n if (parsed < 0) throw new Error(`\\u65E0\\u6548\\u7684\\u65F6\\u95F4\\uFF1A${overrides.time}`);\n timeOverride = parsed;\n }\n preview = null;\n }\n if (Object.hasOwn(overrides, "weather")) {\n const condition = overrides.weather == null || overrides.weather === "" ? null : String(overrides.weather);\n if (condition != null && !allowedWeather.has(condition)) throw new Error(`\\u4E0D\\u652F\\u6301\\u7684\\u5929\\u6C14\\uFF1A${condition}`);\n weatherOverride = condition;\n }\n return snapshot();\n }\n function isNpcOnDuty(role, entryShift, identity = role) {\n if (npcSchedule.enabled === false) return true;\n const shift = {\n ...npcSchedule.defaultShift ?? { start: "06:00", end: "18:00" },\n ...npcSchedule.roleOverrides?.[role] ?? {},\n ...entryShift ?? {}\n };\n if (shift.enabled === false) return true;\n const start = parseClock(shift.start, 6 * 60);\n const earliestEnd = parseClock(shift.end, 18 * 60);\n const latestEnd = parseClock(shift.endLatest, earliestEnd);\n const currentSnapshot = snapshot();\n const dayKey = `${currentSnapshot.now.getFullYear()}-${currentSnapshot.now.getMonth() + 1}-${currentSnapshot.now.getDate()}`;\n const spread = Math.max(0, latestEnd - earliestEnd);\n const end = earliestEnd + (spread ? stableHash(`${dayKey}:${identity}`) % (spread + 1) : 0);\n const current = currentSnapshot.minutes;\n if (start === end) return true;\n return start < end ? current >= start && current < end : current >= start || current < end;\n }\n function shiftLabel(kind) {\n return kind === "arrival" ? npcSchedule.labels?.arrival ?? "\\u4E0A\\u73ED\\u5566" : npcSchedule.labels?.departure ?? "\\u4E0B\\u73ED\\u5566";\n }\n return Object.freeze({ config, snapshot, startPreview, update, isNpcOnDuty, shiftLabel });\n }\n\n // ../web/v2/office-engine.js\n function createOfficeEngine(content2, environment = null) {\n const layout = content2.layout;\n const ambientEffects = new Set(content2.atmosphere.ambientEffects ?? []);\n const targets = Object.fromEntries(Object.entries(layout.targets).map(([key, value]) => [key, { ...value }]));\n const seats = layout.seats.map((seat) => ({\n index: seat.index,\n cx: seat.cx,\n renderer: seat.renderer ?? "workstation",\n deskX: seat.desk.x,\n deskY: seat.desk.y,\n deskW: seat.desk.width,\n deskH: seat.desk.height,\n anchor: { ...seat.anchor }\n }));\n const lanes = [...layout.navigation.lanes];\n const connectors = [...layout.navigation.connectors];\n const perimeter = layout.navigation.perimeter ?? null;\n function currentEnvironment() {\n if (environment?.snapshot) return environment.snapshot();\n const now = /* @__PURE__ */ new Date();\n const hour = now.getHours();\n const phase = hour >= 6 && hour < 11 ? "morning" : hour >= 11 && hour < 17 ? "noon" : hour >= 17 && hour < 20 ? "evening" : "night";\n return { now, hour, minute: now.getMinutes(), phase, weather: ambientEffects.has("rain-window") ? "rain" : "clear", dynamicTime: false, dynamicWeather: ambientEffects.size > 0, lightingOn: false };\n }\n function seatAnchor(index) {\n return seats[(index ?? 0) % seats.length].anchor;\n }\n function standingAnchor(key = 0) {\n const slots = layout.standingAnchors;\n if (!Array.isArray(slots) || slots.length === 0) return targets.entry;\n return slots[Math.abs(Number(key) || 0) % slots.length];\n }\n function npcHomeTarget(entry, roleIndex = 0) {\n if (entry?.role === "colleague" && seats.length > 1) {\n return seatAnchor(1 + Math.max(0, roleIndex) % (seats.length - 1));\n }\n return targets[entry?.spawn] ?? targets.entry;\n }\n function anchorFor(action, seatIndex, fallbackKey = 0) {\n const capability = layout.legacyActions[action ?? "type"] ?? "create";\n const station = layout.stations[capability];\n if (!station || station.kind === "seat") return seatIndex == null ? standingAnchor(fallbackKey) : seatAnchor(seatIndex);\n return targets[station.target] ?? (seatIndex == null ? standingAnchor(fallbackKey) : seatAnchor(seatIndex));\n }\n function perimeterPath(from, target) {\n const { leftX, rightX, topY, bottomY } = perimeter;\n const zoneFor = (point) => point.zone ?? [\n ["left", Math.abs(point.x - leftX)],\n ["right", Math.abs(point.x - rightX)],\n ["top", Math.abs(point.y - topY)],\n ["bottom", Math.abs(point.y - bottomY)]\n ].reduce((best, item) => item[1] < best[1] ? item : best)[0];\n const portalFor = (point, zone) => {\n if (zone === "left") return { x: leftX, y: point.y, zone };\n if (zone === "right") return { x: rightX, y: point.y, zone };\n if (zone === "bottom") return { x: point.x, y: bottomY, zone };\n return { x: point.x, y: topY, zone: "top" };\n };\n const fromZone = zoneFor(from);\n const toZone = zoneFor(target);\n const start = portalFor(from, fromZone);\n const end = portalFor(target, toZone);\n const nodes = [\n start,\n end,\n { x: leftX, y: topY, zone: "top" },\n { x: rightX, y: topY, zone: "top" },\n { x: rightX, y: bottomY, zone: "bottom" },\n { x: leftX, y: bottomY, zone: "bottom" }\n ];\n const connected = (a, b) => a.x === b.x && (a.x === leftX || a.x === rightX) || a.y === b.y && (a.y === topY || a.y === bottomY);\n const distance = nodes.map(() => Infinity);\n const previous = nodes.map(() => -1);\n const visited = /* @__PURE__ */ new Set();\n distance[0] = 0;\n while (visited.size < nodes.length) {\n let current = -1;\n for (let index = 0; index < nodes.length; index += 1) if (!visited.has(index) && (current < 0 || distance[index] < distance[current])) current = index;\n if (current < 0 || distance[current] === Infinity) break;\n visited.add(current);\n for (let index = 0; index < nodes.length; index += 1) {\n if (visited.has(index) || !connected(nodes[current], nodes[index])) continue;\n const candidate = distance[current] + Math.abs(nodes[current].x - nodes[index].x) + Math.abs(nodes[current].y - nodes[index].y);\n if (candidate < distance[index]) {\n distance[index] = candidate;\n previous[index] = current;\n }\n }\n }\n const route = [];\n for (let index = 1; index >= 0; index = previous[index]) route.unshift(nodes[index]);\n const points = [];\n const push = (point) => {\n const last = points.at(-1);\n if (!last || last.x !== point.x || last.y !== point.y) points.push(point);\n };\n push(start);\n for (const point of route.slice(1)) push(point);\n push({ ...target, zone: toZone });\n return points;\n }\n function path(from, target) {\n if (Math.abs(from.x - target.x) < 0.5 && Math.abs(from.y - target.y) < 0.5) return [];\n if (perimeter) return perimeterPath(from, target);\n const fromLane = from.lane ?? 1;\n const toLane = target.lane ?? 1;\n const points = [];\n const laneY = lanes[fromLane];\n if (Math.abs(from.y - laneY) > 0.5) points.push({ x: from.x, y: laneY });\n if (fromLane !== toLane) {\n const x = connectors.reduce((best, candidate) => Math.abs(candidate - from.x) + Math.abs(candidate - target.x) < Math.abs(best - from.x) + Math.abs(best - target.x) ? candidate : best);\n points.push({ x, y: laneY }, { x, y: lanes[toLane] });\n }\n points.push({ x: target.x, y: lanes[toLane] }, { x: target.x, y: target.y });\n return points;\n }\n function stationKey(action, seatIndex) {\n const capability = layout.legacyActions[action ?? "type"] ?? "create";\n const station = layout.stations[capability];\n return !station || station.kind === "seat" ? `desk${seatIndex ?? 0}` : station.target;\n }\n return Object.freeze({\n W: layout.canvas.width,\n H: layout.canvas.height,\n WALL_H: layout.wallHeight,\n LANES: lanes,\n SEATS: seats,\n TARGETS: targets,\n AMBIENT_EFFECTS: ambientEffects,\n interactions: Object.freeze({ ...layout.interactions ?? {} }),\n currentEnvironment,\n anchorFor,\n seatAnchor,\n standingAnchor,\n npcHomeTarget,\n path,\n stationKey,\n startDayPreview: (durationMs = 24e3) => environment?.startPreview?.(durationMs) ?? false,\n getOfficeTime: currentEnvironment,\n isNpcOnDuty: (role, shift, identity) => environment?.isNpcOnDuty?.(role, shift, identity) ?? true,\n npcShiftLabel: (kind) => environment?.shiftLabel?.(kind) ?? (kind === "arrival" ? "\\u4E0A\\u73ED\\u5566" : "\\u4E0B\\u73ED\\u5566")\n });\n }\n\n // ../web/v2/office-renderer.js\n function createOfficeRenderer(content2, environment = null) {\n const engine = createOfficeEngine(content2, environment);\n const localize = (value) => window.AgentLiveI18n?.text(value) ?? value;\n const layout = content2.layout;\n const propTypes = content2.props.types;\n const C = { ...content2.style.tokens.canvas, ...content2.atmosphere.styleOverrides?.canvas ?? {} };\n const UI = content2.style.tokens.css ?? {};\n const renderMode = content2.preset.render?.detail ?? content2.style.tokens.render?.detail ?? "classic";\n const rich = renderMode === "rich";\n const { W, H, WALL_H, SEATS, TARGETS, AMBIENT_EFFECTS } = engine;\n const AREAS = [...layout.areas ?? []];\n let signs;\n function drawSigns(c) {\n c.save();\n c.textAlign = "center";\n c.textBaseline = "middle";\n signs ??= (layout.textSlots ?? []).map((slot) => {\n const treatment = slot.id === "company" ? { font: \'bold 7px ui-monospace, "PingFang SC", monospace\', ink: UI.accent ?? C.activeAccent, rule: UI.accent ?? C.activeAccent } : slot.id === "slogan" ? { font: \'600 6px ui-monospace, "PingFang SC", monospace\', ink: UI.dim ?? C.boardTextDim, rule: UI.line ?? C.wallTrim } : { font: \'600 6px ui-monospace, "PingFang SC", monospace\', ink: UI.ink ?? C.paper, rule: UI.think ?? C.activeThink };\n let text = String(slot.text ?? slot.defaultText ?? "");\n const original = text;\n c.font = treatment.font;\n while (text && c.measureText(text + (text !== original ? "\\u2026" : "")).width > slot.width - 8) text = [...text].slice(0, -1).join("");\n return { ...slot, ...treatment, text: text + (text !== original && text ? "\\u2026" : "") };\n });\n for (const slot of signs) {\n if (!slot.text) continue;\n px(c, slot.x + 2, slot.y + 2, slot.width, slot.height, C.objectShadow ?? "rgba(0,0,0,.3)");\n px(c, slot.x, slot.y, slot.width, slot.height, UI.line ?? C.outline);\n px(c, slot.x + 1, slot.y + 1, slot.width - 2, slot.height - 2, UI.panel ?? C.wallBase ?? C.woodDark);\n px(c, slot.x + 2, slot.y + 2, slot.width - 4, 1, UI["panel-2"] ?? C.wallHighlight);\n px(c, slot.x + 2, slot.y + slot.height - 2, slot.width - 4, 1, slot.rule);\n c.save();\n c.beginPath();\n c.rect(slot.x + 4, slot.y + 2, slot.width - 8, slot.height - 4);\n c.clip();\n c.font = slot.font;\n c.fillStyle = slot.ink;\n c.fillText(slot.text, slot.x + slot.width / 2, slot.y + slot.height / 2);\n c.restore();\n }\n c.restore();\n }\n const instances = layout.propInstances.map((instance) => {\n const type = propTypes[instance.type];\n return {\n ...instance,\n w: instance.width ?? type.size.width,\n h: instance.height ?? type.size.height,\n capabilities: type.capabilities,\n renderer: type.renderer\n };\n });\n const byRenderer = (renderer) => instances.filter((instance) => instance.renderer === renderer);\n const first = (renderer) => byRenderer(renderer)[0];\n const FURNITURE = {\n archive: first("archive-cabinet"),\n server: first("server-rack"),\n coffee: first("coffee-machine"),\n phone: first("phone-table"),\n whiteboard: first("whiteboard"),\n meeting: first("meeting-table")\n };\n const px = (c, x, y, w, h, color) => {\n c.fillStyle = color;\n c.fillRect(x | 0, y | 0, w | 0, h | 0);\n };\n const poly = (c, points, color) => {\n c.beginPath();\n c.moveTo(points[0][0] | 0, points[0][1] | 0);\n for (let i = 1; i < points.length; i++) c.lineTo(points[i][0] | 0, points[i][1] | 0);\n c.closePath();\n c.fillStyle = color;\n c.fill();\n };\n const noise = (x, y, salt = 0) => {\n let value = Math.imul((x | 0) + 17, 374761393) ^ Math.imul((y | 0) + 31, 668265263) ^ salt;\n value = Math.imul(value ^ value >>> 13, 1274126177);\n return (value ^ value >>> 16) >>> 0;\n };\n const outline = (c, x, y, w, h, color = C.outline ?? C.woodDark) => {\n px(c, x, y, w, 1, color);\n px(c, x, y + h - 1, w, 1, color);\n px(c, x, y, 1, h, color);\n px(c, x + w - 1, y, 1, h, color);\n };\n const pixelLine = (c, x0, y0, x1, y1, color) => {\n x0 |= 0;\n y0 |= 0;\n x1 |= 0;\n y1 |= 0;\n const dx = Math.abs(x1 - x0);\n const sx = x0 < x1 ? 1 : -1;\n const dy = -Math.abs(y1 - y0);\n const sy = y0 < y1 ? 1 : -1;\n let error = dx + dy;\n while (true) {\n px(c, x0, y0, 1, 1, color);\n if (x0 === x1 && y0 === y1) break;\n const twice = 2 * error;\n if (twice >= dy) {\n error += dy;\n x0 += sx;\n }\n if (twice <= dx) {\n error += dx;\n y0 += sy;\n }\n }\n };\n const currentEnvironment = engine.currentEnvironment;\n function drawFloor(c) {\n px(c, 0, WALL_H, W, H - WALL_H, C.floorA);\n if (rich) {\n const plankH = 8;\n const plankW = 32;\n for (let y = WALL_H; y < H; y += plankH) {\n const row = (y - WALL_H) / plankH | 0;\n const offset = row % 2 ? -(plankW >> 1) : 0;\n for (let x = offset; x < W; x += plankW) {\n const tone = noise(x, y, 91) % 3;\n px(c, x + 1, y + 1, plankW - 1, plankH - 1, tone === 0 ? C.floorA : tone === 1 ? C.floorB : C.floorC);\n px(c, x + 5 + noise(x, y, 17) % 15, y + 3, 8 + noise(x, y, 33) % 8, 1, C.floorGrain);\n }\n px(c, 0, y, W, 1, C.floorLine);\n }\n } else {\n for (let y = WALL_H; y < H; y += 16) {\n for (let x = 0; x < W; x += 16) {\n if ((x / 16 | 0) % 2 === (y / 16 | 0) % 2) px(c, x, y, 16, 16, C.floorB);\n }\n }\n for (let y = WALL_H; y < H; y += 16) px(c, 0, y, W, 1, C.floorLine);\n for (let x = 0; x < W; x += 16) px(c, x, WALL_H, 1, H - WALL_H, C.floorLine);\n }\n for (const rug of byRenderer("rug")) {\n if (rich) px(c, rug.x + 2, rug.y + 3, rug.w, rug.h, C.objectShadow);\n px(c, rug.x, rug.y, rug.w, rug.h, C.rug);\n px(c, rug.x, rug.y, rug.w, 1, C.rugEdge);\n px(c, rug.x, rug.y + rug.h - 1, rug.w, 1, C.rugEdge);\n px(c, rug.x, rug.y, 1, rug.h, C.rugEdge);\n px(c, rug.x + rug.w - 1, rug.y, 1, rug.h, C.rugEdge);\n }\n }\n function drawWall(c, t, officeTime) {\n const timedSky = {\n morning: C.skyMorning ?? "#7faec7",\n noon: C.skyNoon ?? "#91cde0",\n evening: C.skyEvening ?? "#d98262",\n night: C.skyNight ?? "#17243c"\n }[officeTime.phase];\n const sky = officeTime.dynamicTime ? timedSky : C.glass;\n px(c, 0, 0, W, WALL_H, C.wall);\n px(c, 0, 0, W, 6, C.wallTop);\n px(c, 0, WALL_H - 3, W, 3, C.wallTrim);\n if (rich) {\n px(c, 0, 6, W, 2, C.wallShade);\n for (let x = 0; x < W; x += 48) {\n px(c, x, 7, 2, WALL_H - 10, C.wallSeam);\n px(c, x + 2, 7, 1, WALL_H - 10, C.wallHighlight);\n }\n px(c, 0, WALL_H - 7, W, 4, C.wallBase);\n px(c, 0, WALL_H - 7, W, 1, C.wallHighlight);\n }\n for (const door of byRenderer("door")) {\n if (rich) px(c, door.x - 3, door.y - 3, door.w + 6, door.h + 4, C.outline);\n px(c, door.x, door.y, door.w, door.h, C.door);\n px(c, door.x, door.y, door.w, 2, C.doorDark);\n px(c, door.x + door.w - 3, door.y, 3, door.h, C.doorDark);\n px(c, door.x + 4, door.y + 4, door.w - 11, door.h - 8, C.doorPanel);\n if (rich) {\n outline(c, door.x + 5, door.y + 5, door.w - 12, Math.max(6, (door.h >> 1) - 5), C.doorDark);\n outline(c, door.x + 5, door.y + (door.h >> 1) + 1, door.w - 12, Math.max(6, (door.h >> 1) - 5), C.doorDark);\n px(c, door.x + 7, door.y + 7, door.w - 16, 1, C.doorLite);\n }\n px(c, door.x + door.w - 9, door.y + 16, 2, 3, C.paper);\n }\n for (const window2 of byRenderer("window")) {\n if (rich) px(c, window2.x - 4, window2.y - 4, window2.w + 8, window2.h + 7, C.outline);\n px(c, window2.x - 2, window2.y - 2, window2.w + 4, window2.h + 4, C.wallTrim);\n px(c, window2.x, window2.y, window2.w, window2.h, sky);\n px(c, window2.x, window2.y, window2.w, 8, officeTime.dynamicTime && officeTime.phase === "night" ? C.skyNightLite ?? C.glass : C.glassLite);\n if (rich) {\n px(c, window2.x + 2, window2.y + 2, window2.w - 4, 2, C.glassShine);\n px(c, window2.x + 3, window2.y + 5, 2, window2.h - 8, C.glassShine);\n px(c, window2.x - 3, window2.y + window2.h + 2, window2.w + 6, 3, C.wallBase);\n }\n px(c, window2.x + (window2.w >> 1), window2.y, 1, window2.h, C.wallTrim);\n px(c, window2.x, window2.y + (window2.h >> 1), window2.w, 1, C.wallTrim);\n if (!officeTime.dynamicTime && Math.sin(t / 900 + window2.x) > 0.7) px(c, window2.x + 6, window2.y + 14, 2, 2, C.windowSpark);\n if (officeTime.dynamicTime && officeTime.phase === "morning") px(c, window2.x + 6, window2.y + 11, 5, 5, C.skySun ?? C.windowSpark);\n if (officeTime.dynamicTime && officeTime.phase === "noon" && Math.sin(t / 900 + window2.x) > 0.7) px(c, window2.x + 6, window2.y + 14, 2, 2, C.windowSpark);\n if (officeTime.dynamicTime && officeTime.phase === "evening") px(c, window2.x + 1, window2.y + window2.h - 6, window2.w - 2, 5, C.skyHorizon ?? C.activeRed);\n if (officeTime.dynamicTime && officeTime.phase === "night") {\n px(c, window2.x + 7, window2.y + 12, 2, 2, C.skyStar ?? C.windowSpark);\n px(c, window2.x + window2.w - 10, window2.y + 6, 1, 1, C.skyStar ?? C.windowSpark);\n for (let bx = window2.x + 4; bx < window2.x + window2.w - 3; bx += 7) {\n const height = 3 + noise(bx, window2.y, 47) % 6;\n px(c, bx, window2.y + window2.h - height, 5, height, C.skyBuilding ?? C.wallTop);\n if (noise(bx, window2.y, 19) % 2) px(c, bx + 2, window2.y + window2.h - height + 2, 1, 1, C.skyWindow ?? C.windowSpark);\n }\n }\n drawWindowWeather(c, window2, t, officeTime);\n }\n for (const window2 of byRenderer("venetian-window")) {\n px(c, window2.x - 2, window2.y - 2, window2.w + 4, window2.h + 4, C.wallTrim);\n px(c, window2.x, window2.y, window2.w, window2.h, sky);\n px(c, window2.x + (window2.w >> 1), window2.y, 1, window2.h, C.wallTrim);\n drawWindowWeather(c, window2, t, officeTime);\n for (let y = window2.y + 3; y < window2.y + window2.h - 1; y += 4) {\n px(c, window2.x + 1, y, window2.w - 2, 1, C.paperLine);\n }\n }\n }\n function drawWindowWeather(c, window2, t, officeTime) {\n const condition = officeTime.dynamicWeather ? officeTime.weather : AMBIENT_EFFECTS.has("rain-window") ? "rain" : "clear";\n if (condition === "cloudy") {\n px(c, window2.x + 5, window2.y + 8, Math.max(8, window2.w - 18), 3, C.cloud ?? C.paper);\n px(c, window2.x + 12, window2.y + 5, Math.max(5, window2.w - 25), 4, C.cloud ?? C.paper);\n }\n if (condition === "rain") {\n for (let i = 0; i < 4; i++) {\n const rx = window2.x + 5 + (i * 11 + (t / 180 | 0) * 3) % Math.max(8, window2.w - 10);\n const ry = window2.y + 3 + (i * 7 + (t / 95 | 0) * 2) % Math.max(6, window2.h - 7);\n px(c, rx, ry, 1, 4, C.rain ?? C.waterActive);\n }\n }\n if (condition === "snow") {\n for (let i = 0; i < 5; i++) {\n const sx = window2.x + 4 + (i * 13 + (t / 260 | 0)) % Math.max(7, window2.w - 8);\n const sy = window2.y + 3 + (i * 7 + (t / 180 | 0)) % Math.max(5, window2.h - 6);\n px(c, sx, sy, 2, 2, C.snow ?? C.paper);\n }\n }\n }\n function drawWhiteboard(c, item, hot, t) {\n px(c, item.x - 2, item.y - 2, item.w + 4, item.h + 4, C.boardFrame);\n px(c, item.x, item.y, item.w, item.h, C.board);\n px(c, item.x + 5, item.y + 4, 34, 1, C.boardText);\n px(c, item.x + 5, item.y + 8, 46, 1, C.boardText);\n px(c, item.x + 5, item.y + 12, 26, 1, C.boardTextDim);\n if (hot.has("whiteboard")) {\n const n = 4 + (t / 140 | 0) % 24;\n px(c, item.x + 5, item.y + 16, n, 1, C.activeRed);\n px(c, item.x + 5, item.y + 20, Math.max(2, n - 9), 1, C.activeBlue);\n } else {\n px(c, item.x + 5, item.y + 16, 18, 1, C.boardIdle);\n }\n }\n function drawDesk(c, seat, hot) {\n const { deskX: x, deskY: y, deskW: w, deskH: h } = seat;\n if (rich) px(c, x + 3, y + 5, w, h + 3, C.objectShadow);\n px(c, x, y + h, w, 3, C.woodDark);\n if (rich) outline(c, x - 1, y - 1, w + 2, h + 3);\n px(c, x, y, w, h, C.deskTop);\n px(c, x, y, w, 2, C.deskLite);\n px(c, x + 2, y + h - 2, w - 4, 2, C.woodDark);\n if (rich) {\n px(c, x + 5, y + 5, w - 10, 1, C.woodGrain);\n px(c, x + 3, y + h, 4, 6, C.woodDark);\n px(c, x + w - 7, y + h, 4, 6, C.woodDark);\n }\n const mx = seat.cx - 9;\n const my = y - 11;\n if (rich) px(c, mx - 2, my - 2, 22, 15, C.outline);\n px(c, mx - 1, my - 1, 20, 13, C.metalDark);\n px(c, mx, my, 18, 11, C.screen);\n if (rich) px(c, mx + 2, my + 2, 14, 1, C.screenGlow);\n if (hot.has(`desk${seat.index}`)) {\n px(c, mx + 2, my + 2, 14, 1, C.activeGreen);\n px(c, mx + 2, my + 4, 10, 1, C.activeThink);\n px(c, mx + 2, my + 6, 12, 1, C.activeThink);\n px(c, mx + 2, my + 8, 6, 1, C.activeAccent);\n } else {\n px(c, mx + 2, my + 3, 8, 1, C.screenIdle);\n px(c, mx + 2, my + 6, 11, 1, C.screenIdle);\n }\n px(c, seat.cx - 2, my + 12, 4, 2, C.metalDark);\n if (rich) {\n px(c, seat.cx - 7, y + 9, 15, 3, C.keyboard);\n for (let key = 0; key < 6; key++) px(c, seat.cx - 6 + key * 2, y + 10, 1, 1, C.keyboardKey);\n const accessory = seat.index % 4;\n if (accessory === 0) {\n px(c, x + 4, y + 4, 3, 4, C.mug);\n px(c, x + 7, y + 5, 1, 2, C.mugLite);\n } else if (accessory === 1) {\n px(c, x + 4, y + 6, 5, 3, C.pot);\n px(c, x + 5, y + 2, 1, 5, C.plantDark);\n px(c, x + 3, y + 2, 3, 3, C.plantLite);\n px(c, x + 6, y + 1, 3, 4, C.plantHighlight);\n } else if (accessory === 2) {\n px(c, x + 3, y + 4, 7, 5, C.activeAccent);\n px(c, x + 4, y + 3, 2, 2, C.activeAccent);\n px(c, x + 7, y + 3, 2, 2, C.activeAccent);\n px(c, x + 4, y + 5, 1, 1, C.faceInk ?? C.outline);\n px(c, x + 8, y + 5, 1, 1, C.faceInk ?? C.outline);\n } else {\n px(c, x + 3, y + 5, 8, 5, C.paper);\n px(c, x + 4, y + 6, 6, 1, C.activeThink);\n px(c, x + 4, y + 8, 4, 1, C.paperLine);\n }\n }\n px(c, seat.cx + 12, y + 4, 7, 5, C.paper);\n px(c, seat.cx + 12, y + 4, 7, 1, C.paperLine);\n }\n function drawChair(c, seat, occupied) {\n const x = seat.cx - 7;\n const y = seat.deskY + 21;\n if (rich) px(c, x + 2, y + 3, 14, 11, C.objectShadow);\n px(c, x, y, 14, 9, occupied ? C.chairDark : C.chair);\n if (rich) {\n outline(c, x - 1, y - 1, 16, 11);\n px(c, x + 2, y + 2, 10, 2, occupied ? C.chair : C.chairLite);\n }\n px(c, x, y + 9, 14, 2, C.chairDark);\n px(c, x + 5, y + 11, 4, 3, C.metalDark);\n }\n function drawCubicleSeat(c, seat, hot) {\n const { deskX: x, deskY: y, deskW: w, deskH: h } = seat;\n px(c, x, y + h, w, 3, C.woodDark);\n px(c, x, y, w, h, C.deskTop);\n px(c, x, y, w, 2, C.deskLite);\n const mx = seat.cx - 8;\n const my = seat.anchor.dir === "down" ? y + h - 1 : y - 13;\n px(c, mx - 1, my - 1, 18, 13, C.metalDark);\n px(c, mx, my, 16, 10, C.screen);\n if (hot.has(`desk${seat.index}`)) {\n px(c, mx + 2, my + 2, 12, 1, C.activeGreen);\n px(c, mx + 2, my + 5, 8, 1, C.activeThink);\n px(c, mx + 2, my + 7, 11, 1, C.activeAccent);\n } else {\n px(c, mx + 2, my + 3, 8, 1, C.screenIdle);\n px(c, mx + 2, my + 6, 11, 1, C.screenIdle);\n }\n px(c, mx + 6, my + 11, 4, 2, C.metalDark);\n px(c, x + w - 10, y + 4, 7, 5, C.phoneDevice);\n px(c, x + 3, y + 5, 8, 5, C.paper);\n }\n function drawBoardroomSeat(c, seat, hot, occupied) {\n const { deskX: x, deskY: y, deskW: w, deskH: h } = seat;\n px(c, x - 1, y - 1, w + 2, h + 2, C.metalDark);\n px(c, x, y, w, h, C.screen);\n px(c, x + 2, y + 2, w - 4, 1, hot.has(`desk${seat.index}`) ? C.activeGreen : C.screenIdle);\n px(c, x + 2, y + 5, Math.max(3, w - 7), 1, hot.has(`desk${seat.index}`) ? C.activeThink : C.screenIdle);\n px(c, x + 2, y + h, w - 4, 2, C.metalDark);\n const a = seat.anchor;\n const chairSeat = { ...seat, deskY: a.y - 21 };\n if (a.dir === "up" || a.dir === "down") {\n drawChair(c, chairSeat, occupied);\n } else {\n const color = occupied ? C.chairDark : C.chair;\n px(c, a.x - 4, a.y - 7, 8, 14, color);\n px(c, a.x + (a.dir === "right" ? -6 : 4), a.y - 5, 2, 10, C.metalDark);\n px(c, a.x - 1, a.y + 7, 2, 3, C.metalDark);\n }\n }\n function drawArchive(c, item, hot, t) {\n if (rich) px(c, item.x + 3, item.y + 4, item.w, item.h, C.objectShadow);\n px(c, item.x, item.y, item.w, item.h, C.metal);\n if (rich) outline(c, item.x - 1, item.y - 1, item.w + 2, item.h + 2);\n px(c, item.x, item.y, item.w, 2, C.metalLite);\n const open = hot.has("archive") ? (t / 260 | 0) % 3 : -1;\n for (let i = 0; i < 3; i++) {\n const dy = item.y + 4 + i * 9;\n px(c, item.x + 3, dy, item.w - 6, 7, C.metalDark);\n px(c, item.x + 3, dy, item.w - 6, 1, C.drawerLite);\n px(c, item.x + (item.w >> 1) - 5, dy + 3, 10, 1, C.drawerHandle);\n if (i === open) {\n px(c, item.x + 3, dy, item.w - 6, 7, C.drawerOpen);\n px(c, item.x + 5, dy + 1, item.w - 10, 5, C.paper);\n }\n }\n }\n function drawServerRack(c, item, hot, t) {\n if (rich) px(c, item.x + 3, item.y + 4, item.w, item.h, C.objectShadow);\n px(c, item.x, item.y, item.w, item.h, C.rack);\n if (rich) outline(c, item.x - 1, item.y - 1, item.w + 2, item.h + 2);\n px(c, item.x, item.y, item.w, 2, C.rackTrim);\n const fast = hot.has("server");\n for (let i = 0; i < 3; i++) {\n const dy = item.y + 5 + i * 9;\n px(c, item.x + 3, dy, item.w - 6, 7, C.rackPanel);\n px(c, item.x + 5, dy + 3, item.w - 24, 1, C.rackLine);\n const on = Math.sin((fast ? t / 90 : t / 520) + i * 1.7) > 0;\n px(c, item.x + item.w - 12, dy + 2, 2, 2, on ? C.signalOn : C.signalOff);\n px(c, item.x + item.w - 8, dy + 2, 2, 2, on ? C.signalOff : C.signalOn);\n }\n }\n function drawCoffee(c, item, hot, t) {\n px(c, item.x, item.y, item.w, item.h, C.metalDark);\n px(c, item.x, item.y, item.w, 2, C.metal);\n px(c, item.x + 4, item.y + 5, item.w - 8, 8, C.coffeePanel);\n px(c, item.x + 6, item.y + 7, 6, 4, hot.has("coffee") ? C.coffeeHot : C.coffeeIdle);\n px(c, item.x + 5, item.y + item.h - 5, item.w - 10, 3, C.metal);\n if (hot.has("coffee") && (t / 240 | 0) % 2) px(c, item.x + 9, item.y - 3, 2, 3, C.steam);\n }\n function drawWaterCooler(c, item, hot, t) {\n const active = hot.has("water");\n px(c, item.x + 2, item.y, item.w - 4, 11, C.waterGlass);\n px(c, item.x + 3, item.y + 2, item.w - 6, 6, active ? C.waterActive : C.waterLevel);\n px(c, item.x + 1, item.y + 10, item.w - 2, item.h - 10, C.waterBody);\n px(c, item.x + 1, item.y + item.h - 3, item.w - 2, 3, C.waterDark);\n px(c, item.x + 4, item.y + 14, 3, 2, C.activeBlue);\n px(c, item.x + item.w - 7, item.y + 14, 3, 2, C.activeRed);\n px(c, item.x + 5, item.y + 18, item.w - 10, 4, C.waterDark);\n if (active && (t / 180 | 0) % 2) px(c, item.x - 2, item.y + 17, 2, 4, C.waterActive);\n }\n function drawPhoneTable(c, item, hot, t) {\n px(c, item.x, item.y, item.w, item.h, C.wood);\n px(c, item.x, item.y, item.w, 2, C.phoneTop);\n px(c, item.x + 8, item.y - 7, 16, 8, C.phoneDevice);\n px(c, item.x + 10, item.y - 5, 12, 3, hot.has("phone") ? C.signalOn : C.phoneIdle);\n if (hot.has("phone") && (t / 200 | 0) % 2) {\n px(c, item.x + 4, item.y - 11, 2, 2, C.activeAccent);\n px(c, item.x + 26, item.y - 11, 2, 2, C.activeAccent);\n }\n }\n function drawMeetingTable(c, item) {\n if (rich) px(c, item.x + 4, item.y + 5, item.w, item.h + 2, C.objectShadow);\n px(c, item.x, item.y + item.h, item.w, 3, C.woodDark);\n if (rich) outline(c, item.x - 1, item.y - 1, item.w + 2, item.h + 3);\n px(c, item.x, item.y, item.w, item.h, C.deskTop);\n px(c, item.x, item.y, item.w, 2, C.deskLite);\n if (rich) {\n px(c, item.x + 7, item.y + 5, item.w - 14, 1, C.woodGrain);\n px(c, item.x + 3, item.y + item.h, 5, 7, C.woodDark);\n px(c, item.x + item.w - 8, item.y + item.h, 5, 7, C.woodDark);\n }\n px(c, item.x + 8, item.y + 8, 12, 8, C.paper);\n px(c, item.x + item.w - 24, item.y + 6, 10, 10, C.meetingPaper);\n px(c, item.x + (item.w >> 1) - 4, item.y + 9, 8, 6, C.meetingDevice);\n }\n function drawLoungeSofa(c, item) {\n px(c, item.x + 4, item.y + 5, item.w, item.h + 2, C.objectShadow);\n px(c, item.x, item.y, item.w, item.h, C.outline);\n px(c, item.x + 3, item.y + 3, item.w - 6, item.h - 6, C.chair);\n px(c, item.x + 3, item.y + 3, item.w - 6, 7, C.chairDark);\n px(c, item.x + 2, item.y + 4, 7, item.h - 8, C.chairDark);\n px(c, item.x + item.w - 9, item.y + 4, 7, item.h - 8, C.chairDark);\n const cushionWidth = Math.floor((item.w - 22) / 3);\n for (let i = 0; i < 3; i++) {\n const x = item.x + 10 + i * cushionWidth;\n px(c, x, item.y + 11, cushionWidth - 2, item.h - 15, C.chairLite);\n px(c, x, item.y + 11, cushionWidth - 2, 1, C.metalLite);\n }\n px(c, item.x + 8, item.y + item.h, 5, 3, C.chairDark);\n px(c, item.x + item.w - 13, item.y + item.h, 5, 3, C.chairDark);\n }\n function drawDumbbell(c, item) {\n const vertical = item.orientation === "vertical";\n if (vertical) {\n px(c, item.x + 5, item.y, 6, 3, C.outline);\n px(c, item.x + 6, item.y + 1, 4, 2, C.metalLite);\n px(c, item.x + 7, item.y + 3, 2, item.h + 3, C.metal);\n px(c, item.x + 5, item.y + item.h + 4, 6, 3, C.outline);\n px(c, item.x + 6, item.y + item.h + 4, 4, 2, C.metalLite);\n return;\n }\n px(c, item.x, item.y + 1, 4, item.h - 2, C.outline);\n px(c, item.x + 1, item.y + 2, 3, item.h - 4, C.metalLite);\n px(c, item.x + 4, item.y + 3, item.w - 8, 2, C.metal);\n px(c, item.x + item.w - 4, item.y + 1, 4, item.h - 2, C.outline);\n px(c, item.x + item.w - 4, item.y + 2, 3, item.h - 4, C.metalLite);\n }\n function drawPlant(c, item) {\n if (rich) px(c, item.x - 5, item.y + 4, 12, 5, C.objectShadow);\n px(c, item.x - 4, item.y, 8, 7, C.pot);\n px(c, item.x - 4, item.y, 8, 2, C.potLite);\n px(c, item.x - 1, item.y - 5, 2, 5, C.plantDark);\n px(c, item.x - 6, item.y - 11, 12, 7, C.plant);\n px(c, item.x - 4, item.y - 14, 8, 4, C.plant);\n px(c, item.x - 6, item.y - 11, 5, 3, C.plantDark);\n if (rich) {\n px(c, item.x - 7, item.y - 9, 4, 3, C.plantLite);\n px(c, item.x + 2, item.y - 13, 5, 4, C.plantLite);\n px(c, item.x - 1, item.y - 16, 3, 5, C.plantHighlight);\n px(c, item.x - 3, item.y + 2, 6, 1, C.potLite);\n }\n }\n let floorLayer = null;\n function drawCachedFloor(c) {\n if (!floorLayer) {\n floorLayer = document.createElement("canvas");\n floorLayer.width = W;\n floorLayer.height = H;\n const floorContext = floorLayer.getContext("2d", { alpha: false });\n floorContext.imageSmoothingEnabled = false;\n drawFloor(floorContext);\n }\n c.drawImage(floorLayer, 0, 0);\n }\n function drawCubicleCell(c, item) {\n px(c, item.x, item.y, item.w, 5, C.metalDark);\n px(c, item.x + 1, item.y, item.w - 2, 3, C.metal);\n px(c, item.x, item.y, 5, item.h, C.metalDark);\n px(c, item.x + 1, item.y + 1, 3, item.h - 2, C.metal);\n px(c, item.x + item.w - 5, item.y, 5, item.h, C.metalDark);\n px(c, item.x + item.w - 4, item.y + 1, 3, item.h - 2, C.metal);\n }\n function drawExecutiveDesk(c, item, hot) {\n px(c, item.x, item.y + item.h, item.w, 5, C.woodDark);\n px(c, item.x, item.y, item.w, item.h, C.deskTop);\n px(c, item.x, item.y, item.w, 2, C.deskLite);\n px(c, item.x + 20, item.y + 4, 26, item.h - 7, C.plantDark);\n px(c, item.x + 4, item.y - 12, 17, 13, C.metalDark);\n px(c, item.x + 5, item.y - 11, 15, 10, C.screen);\n const active = hot.has(`desk${item.seatIndex ?? 0}`);\n px(c, item.x + 7, item.y - 8, active ? 11 : 7, 1, active ? C.activeGreen : C.screenIdle);\n px(c, item.x + item.w - 15, item.y + 4, 9, 5, C.phoneDevice);\n px(c, item.x + item.w - 24, item.y - 9, 2, 11, C.activeAccent);\n px(c, item.x + item.w - 29, item.y - 11, 12, 4, C.plantDark);\n }\n function drawCopyStation(c, item, hot, t) {\n px(c, item.x, item.y + 12, item.w, item.h - 12, C.waterBody);\n px(c, item.x + 2, item.y, item.w - 4, 14, C.metalDark);\n px(c, item.x + 4, item.y + 2, item.w - 8, 8, C.screen);\n px(c, item.x + item.w - 6, item.y + 17, 3, 2, hot.has("archive") && (t / 240 | 0) % 2 ? C.signalOn : C.signalOff);\n px(c, item.x + item.w - 2, item.y + 17, 7, 3, C.paper);\n px(c, item.x + item.w - 2, item.y + 17, 1, 10, C.paperLine);\n }\n function drawReceptionDesk(c, item, hot) {\n px(c, item.x, item.y, item.w, item.h, C.woodDark);\n px(c, item.x + 2, item.y + 2, item.w - 4, item.h - 4, C.wood);\n px(c, item.x, item.y, item.w, 3, C.deskLite);\n px(c, item.x + 8, item.y - 12, 18, 13, C.metalDark);\n px(c, item.x + 10, item.y - 10, 14, 9, C.screen);\n px(c, item.x + 12, item.y - 7, hot.has("phone") ? 10 : 6, 1, hot.has("phone") ? C.signalOn : C.screenIdle);\n px(c, item.x + item.w - 16, item.y - 6, 9, 7, C.phoneDevice);\n }\n function drawPresentationScreen(c, item, hot, t) {\n px(c, item.x, item.y, item.w, item.h, C.metalDark);\n px(c, item.x + 2, item.y + 2, item.w - 4, item.h - 4, C.screen);\n px(c, item.x + 7, item.y + 8, item.w / 2 - 14, 3, C.paper);\n for (let row = 0; row < 3; row++) {\n const y = item.y + 17 + row * 6;\n px(c, item.x + 7, y, 22, 2, [C.activeBlue, C.activeAccent, C.activeGreen][row]);\n px(c, item.x + 33, y, item.w / 2 - 42, 2, C.rackLine);\n const width = hot.has("whiteboard") ? 20 + ((t / 260 | 0) + row * 9) % 24 : 18 + row * 8;\n px(c, item.x + 33, y, width, 2, [C.activeBlue, C.activeAccent, C.activeGreen][row]);\n }\n px(c, item.x + item.w - 47, item.y + 9, 38, 24, C.rackPanel);\n for (let row = 0; row < 4; row++) {\n px(c, item.x + item.w - 42, item.y + 13 + row * 5, 6, 2, row % 2 ? C.activeRed : C.activeGreen);\n px(c, item.x + item.w - 32, item.y + 13 + row * 5, 18, 2, C.rackLine);\n }\n }\n function drawBoardroomTable(c, item) {\n const inset = Math.max(16, Math.round(item.w * 0.25));\n const cx = item.x + (item.w >> 1);\n px(c, cx - 6, item.y - 15, 12, 13, C.chairDark);\n px(c, cx - 4, item.y - 13, 8, 9, C.chair);\n px(c, cx - 8, item.y - 12, 2, 10, C.metalDark);\n px(c, cx + 6, item.y - 12, 2, 10, C.metalDark);\n if (rich) poly(c, [[item.x + inset + 4, item.y + 5], [item.x + item.w - inset + 4, item.y + 5], [item.x + item.w + 5, item.y + item.h + 5], [item.x + 5, item.y + item.h + 5]], C.objectShadow);\n poly(c, [[item.x + inset, item.y], [item.x + item.w - inset, item.y], [item.x + item.w, item.y + item.h], [item.x, item.y + item.h]], C.woodDark);\n poly(c, [[item.x + inset + 3, item.y], [item.x + item.w - inset - 3, item.y], [item.x + item.w - 7, item.y + item.h - 6], [item.x + 7, item.y + item.h - 6]], C.deskTop);\n poly(c, [[item.x + inset + 7, item.y + 4], [item.x + item.w - inset - 7, item.y + 4], [item.x + item.w - 18, item.y + item.h - 13], [item.x + 18, item.y + item.h - 13]], C.deskLite);\n poly(c, [[cx - 4, item.y + 10], [cx + 4, item.y + 10], [cx + 12, item.y + item.h - 20], [cx - 12, item.y + item.h - 20]], C.woodDark);\n if (rich) {\n for (let y = item.y + 18; y < item.y + item.h - 18; y += 20) {\n px(c, item.x + 25, y, item.w - 50, 1, C.woodGrain);\n }\n for (const [x, y, flip] of [\n [item.x + 43, item.y + 31, false],\n [item.x + item.w - 55, item.y + 31, true],\n [item.x + 27, item.y + 77, false],\n [item.x + item.w - 39, item.y + 77, true]\n ]) {\n px(c, x, y, 11, 7, C.meetingPaper);\n px(c, x + 2, y + 2, 7, 1, C.paperLine);\n px(c, x + (flip ? -3 : 12), y + 1, 1, 6, C.activeBlue);\n px(c, x + (flip ? 13 : -5), y + 1, 4, 4, C.waterGlass);\n px(c, x + (flip ? 14 : -4), y + 2, 2, 1, C.waterLevel);\n }\n for (const y of [item.y + 22, item.y + 92]) {\n px(c, cx - 15, y, 30, 3, C.woodDark);\n px(c, cx - 11, y, 22, 1, C.activeAccent);\n }\n }\n const deviceY = item.y + Math.round(item.h * 0.56);\n px(c, cx - 11, deviceY, 22, 12, C.meetingDevice);\n px(c, cx - 5, deviceY + 3, 10, 5, C.phoneDevice);\n px(c, cx - 8, deviceY + 2, 2, 2, C.signalOn);\n px(c, cx + 6, deviceY + 2, 2, 2, C.signalOff);\n }\n function drawAvConsole(c, item, hot, t) {\n px(c, item.x, item.y, item.w, item.h, C.rack);\n px(c, item.x, item.y, item.w, 2, C.rackTrim);\n px(c, item.x + 6, item.y + 6, Math.floor(item.w * 0.42), 15, C.screen);\n px(c, item.x + 9, item.y + 10, Math.floor(item.w * 0.32), 2, hot.has("server") ? C.activeGreen : C.screenIdle);\n px(c, item.x + 9, item.y + 15, Math.floor(item.w * 0.23), 2, C.activeBlue);\n for (let x = item.x + 7; x < item.x + item.w - 7; x += 8) {\n px(c, x, item.y + item.h - 15, 4, 4, Math.sin(t / 190 + x) > 0 ? C.signalOn : C.signalOff);\n }\n px(c, item.x + 6, item.y + item.h - 7, item.w - 12, 4, C.metal);\n }\n function drawSideboard(c, item) {\n px(c, item.x, item.y, item.w, item.h, C.wood);\n px(c, item.x, item.y, item.w, 3, C.deskLite);\n px(c, item.x, item.y + item.h - 3, item.w, 3, C.woodDark);\n for (let x = item.x + 6; x < item.x + item.w - 6; x += 14) px(c, x, item.y - 4, 5, 5, C.paper);\n }\n function drawVendingMachine(c, item) {\n px(c, item.x, item.y, item.w, item.h, C.activeRed);\n px(c, item.x + 3, item.y + 3, item.w - 6, Math.max(8, item.h - 12), C.screen);\n for (let y = item.y + 6; y < item.y + item.h - 8; y += 5) px(c, item.x + 5, y, item.w - 10, 2, y % 2 ? C.activeAccent : C.activeGreen);\n px(c, item.x + 4, item.y + item.h - 6, item.w - 8, 3, C.phoneDevice);\n }\n function drawNoticeBoard(c, item) {\n px(c, item.x - 2, item.y - 2, item.w + 4, item.h + 4, C.woodDark);\n px(c, item.x, item.y, item.w, item.h, C.activeRed);\n px(c, item.x + 3, item.y + 3, item.w - 6, 2, C.activeAccent);\n for (let x = item.x + 5; x < item.x + item.w - 8; x += 14) {\n px(c, x, item.y + 8, 10, 11, C.paper);\n px(c, x + 2, item.y + 10, 6, 1, C.paperLine);\n px(c, x + 2, item.y + 13, 5, 1, C.paperLine);\n px(c, x + 2, item.y + 16, 7, 1, C.activeRed);\n }\n }\n function drawWallCalendar(c, item) {\n px(c, item.x - 1, item.y - 1, item.w + 2, item.h + 2, C.woodDark);\n px(c, item.x, item.y, item.w, 5, C.activeRed);\n px(c, item.x + 2, item.y + 2, item.w - 4, 1, C.activeAccent);\n px(c, item.x, item.y + 5, item.w, item.h - 5, C.paper);\n for (let y = item.y + 8; y < item.y + item.h - 2; y += 4) {\n for (let x = item.x + 2; x < item.x + item.w - 2; x += 4) px(c, x, y, 2, 2, C.paperLine);\n }\n }\n function drawCoatRack(c, item) {\n const cx = item.x + (item.w >> 1);\n px(c, cx - 1, item.y + 3, 3, item.h - 6, C.woodDark);\n px(c, cx - 5, item.y + 5, 11, 2, C.woodDark);\n px(c, item.x + 1, item.y + 4, 2, 6, C.woodDark);\n px(c, item.x + item.w - 3, item.y + 4, 2, 6, C.woodDark);\n px(c, item.x + 1, item.y + 9, 5, 14, C.chairDark);\n px(c, item.x, item.y + 11, 3, 9, C.chair);\n px(c, cx - 5, item.y + item.h - 3, 11, 3, C.woodDark);\n }\n function drawFloorFan(c, item, t) {\n const cx = item.x + (item.w >> 1);\n const cy = item.y + 8;\n px(c, cx - 7, cy - 7, 15, 15, C.metalDark);\n px(c, cx - 5, cy - 5, 11, 11, C.metalLite);\n px(c, cx, cy, 1, 1, C.phoneDevice);\n const flip = (t / 260 | 0) % 2;\n if (flip) {\n px(c, cx - 4, cy - 1, 4, 2, C.chairDark);\n px(c, cx + 1, cy, 4, 2, C.chairDark);\n px(c, cx - 1, cy - 4, 2, 4, C.chairDark);\n } else {\n px(c, cx - 3, cy - 4, 3, 4, C.chairDark);\n px(c, cx, cy + 1, 3, 4, C.chairDark);\n px(c, cx - 4, cy + 1, 4, 2, C.chairDark);\n }\n px(c, cx, cy + 8, 2, item.h - 12, C.metalDark);\n px(c, cx - 5, item.y + item.h - 4, 12, 4, C.metalDark);\n }\n function drawOfficeClock(c, item, officeTime) {\n const { now } = officeTime;\n px(c, item.x, item.y, item.w, item.h, C.outline ?? C.woodDark);\n px(c, item.x + 2, item.y + 1, item.w - 4, item.h - 2, C.paper);\n px(c, item.x + 1, item.y + 2, item.w - 2, item.h - 4, C.paper);\n const cx = item.x + (item.w >> 1);\n const cy = item.y + (item.h >> 1);\n px(c, cx, item.y + 2, 1, 1, C.boardText);\n px(c, cx, item.y + item.h - 3, 1, 1, C.boardText);\n px(c, item.x + 2, cy, 1, 1, C.boardText);\n px(c, item.x + item.w - 3, cy, 1, 1, C.boardText);\n const minuteAngle = now.getMinutes() / 60 * Math.PI * 2 - Math.PI / 2;\n const hourAngle = (now.getHours() % 12 + now.getMinutes() / 60) / 12 * Math.PI * 2 - Math.PI / 2;\n pixelLine(c, cx, cy, cx + Math.round(Math.cos(hourAngle) * 3), cy + Math.round(Math.sin(hourAngle) * 3), C.phoneDevice);\n pixelLine(c, cx, cy, cx + Math.round(Math.cos(minuteAngle) * 5), cy + Math.round(Math.sin(minuteAngle) * 5), C.activeRed);\n px(c, cx, cy, 1, 1, C.outline ?? C.phoneDevice);\n }\n function drawRestroomDoor(c, item) {\n px(c, item.x - 2, item.y - 2, item.w + 4, item.h + 4, C.outline);\n px(c, item.x, item.y, item.w, item.h, C.door);\n px(c, item.x + 3, item.y + 3, item.w - 6, item.h - 6, C.doorPanel);\n px(c, item.x + item.w - 6, item.y + 16, 2, 3, C.paper);\n const cx = item.x + (item.w >> 1);\n px(c, cx - 2, item.y + 7, 5, 5, C.paper);\n px(c, cx - 1, item.y + 12, 3, 7, C.paper);\n px(c, cx - 4, item.y + 13, 3, 2, C.paper);\n px(c, cx + 2, item.y + 13, 3, 2, C.paper);\n px(c, cx - 3, item.y + 19, 2, 5, C.paper);\n px(c, cx + 2, item.y + 19, 2, 5, C.paper);\n }\n function drawBossDesk(c, item) {\n px(c, item.x + 3, item.y + 4, item.w, item.h + 3, C.objectShadow);\n outline(c, item.x - 1, item.y - 1, item.w + 2, item.h + 3);\n px(c, item.x, item.y, item.w, item.h, C.deskTop);\n px(c, item.x, item.y, item.w, 2, C.deskLite);\n px(c, item.x + 3, item.y + item.h, 4, 5, C.woodDark);\n px(c, item.x + item.w - 7, item.y + item.h, 4, 5, C.woodDark);\n px(c, item.x + 4, item.y - 11, 17, 12, C.outline);\n px(c, item.x + 5, item.y - 10, 15, 9, C.screen);\n px(c, item.x + 7, item.y - 7, 10, 1, C.activeGreen);\n px(c, item.x + 7, item.y - 4, 7, 1, C.activeBlue);\n px(c, item.x + 24, item.y + 4, 6, 6, C.mug);\n px(c, item.x + 29, item.y + 5, 2, 3, C.mugLite);\n }\n function drawAutomaticLighting(c, officeTime) {\n if (!officeTime.dynamicTime) return;\n if (officeTime.phase === "night") px(c, 0, WALL_H, W, H - WALL_H, "rgba(8,13,29,0.16)");\n if (!officeTime.lightingOn) return;\n c.save();\n c.globalCompositeOperation = "screen";\n px(c, 0, WALL_H, W, H - WALL_H, officeTime.phase === "night" ? C.roomLightNight ?? "rgba(255,210,112,0.13)" : C.roomLightEvening ?? "rgba(255,220,150,0.07)");\n c.restore();\n }\n function drawAreaLabels(c) {\n if (!AREAS.length) return;\n c.save();\n c.font = \'bold 7px ui-monospace, "PingFang SC", "Microsoft YaHei", sans-serif\';\n c.textAlign = "left";\n c.textBaseline = "top";\n for (const area of AREAS) {\n const name = localize(area.name);\n const width = Math.ceil(c.measureText(name).width) + 6;\n px(c, area.x, area.y, width, 10, C.outline);\n px(c, area.x + 1, area.y + 1, width - 2, 8, C.wallBase ?? C.woodDark);\n px(c, area.x + 2, area.y + 2, 1, 6, C.activeAccent);\n c.fillStyle = C.paper;\n c.fillText(name, area.x + 4, area.y + 1);\n }\n c.restore();\n }\n function drawServiceCart(c, item) {\n px(c, item.x, item.y, item.w, item.h, C.metal);\n px(c, item.x, item.y, item.w, 2, C.metalLite);\n for (let y = item.y + 5; y < item.y + item.h - 4; y += 7) {\n px(c, item.x + 4, y, item.w - 8, 4, C.paper);\n px(c, item.x + 7, y + 1, Math.floor(item.w * 0.4), 1, C.activeBlue);\n }\n px(c, item.x + 5, item.y + item.h, 4, 3, C.phoneDevice);\n px(c, item.x + item.w - 9, item.y + item.h, 4, 3, C.phoneDevice);\n }\n function drawRoom(c, t, hot, occupiedSeats) {\n const officeTime = currentEnvironment();\n drawCachedFloor(c);\n drawWall(c, t, officeTime);\n for (const item of byRenderer("restroom-door")) drawRestroomDoor(c, item);\n for (const item of byRenderer("whiteboard")) drawWhiteboard(c, item, hot, t);\n for (const item of byRenderer("phone-table")) drawPhoneTable(c, item, hot, t);\n for (const item of byRenderer("archive-cabinet")) drawArchive(c, item, hot, t);\n for (const item of byRenderer("server-rack")) drawServerRack(c, item, hot, t);\n for (const item of byRenderer("coffee-machine")) drawCoffee(c, item, hot, t);\n for (const item of byRenderer("water-cooler")) drawWaterCooler(c, item, hot, t);\n for (const item of byRenderer("meeting-table")) drawMeetingTable(c, item);\n for (const item of byRenderer("lounge-sofa")) drawLoungeSofa(c, item);\n for (const item of byRenderer("dumbbell")) drawDumbbell(c, item);\n for (const item of byRenderer("plant")) drawPlant(c, item);\n for (const item of byRenderer("cubicle-cell")) drawCubicleCell(c, item);\n for (const item of byRenderer("executive-desk")) drawExecutiveDesk(c, item, hot);\n for (const item of byRenderer("copy-station")) drawCopyStation(c, item, hot, t);\n for (const item of byRenderer("reception-desk")) drawReceptionDesk(c, item, hot);\n for (const item of byRenderer("presentation-screen")) drawPresentationScreen(c, item, hot, t);\n for (const item of byRenderer("boardroom-table")) drawBoardroomTable(c, item);\n for (const item of byRenderer("av-console")) drawAvConsole(c, item, hot, t);\n for (const item of byRenderer("sideboard")) drawSideboard(c, item);\n for (const item of byRenderer("vending-machine")) drawVendingMachine(c, item);\n for (const item of byRenderer("notice-board")) drawNoticeBoard(c, item);\n for (const item of byRenderer("wall-calendar")) drawWallCalendar(c, item);\n for (const item of byRenderer("coat-rack")) drawCoatRack(c, item);\n for (const item of byRenderer("floor-fan")) drawFloorFan(c, item, t);\n for (const item of byRenderer("office-clock")) drawOfficeClock(c, item, officeTime);\n for (const item of byRenderer("service-cart")) drawServiceCart(c, item);\n for (const item of byRenderer("boss-desk")) drawBossDesk(c, item);\n for (const seat of SEATS) {\n if (seat.renderer === "cubicle-workstation") {\n drawCubicleSeat(c, seat, hot);\n drawChair(c, seat, occupiedSeats.has(seat.index));\n } else if (seat.renderer === "boardroom-seat") {\n drawBoardroomSeat(c, seat, hot, occupiedSeats.has(seat.index));\n } else if (seat.renderer === "executive-seat") {\n drawChair(c, seat, occupiedSeats.has(seat.index));\n } else {\n drawDesk(c, seat, hot);\n drawChair(c, seat, occupiedSeats.has(seat.index));\n }\n }\n drawAreaLabels(c);\n drawSigns(c);\n drawAutomaticLighting(c, officeTime);\n }\n return Object.freeze({\n ...engine,\n FURNITURE,\n colors: C,\n px,\n drawRoom,\n content: content2\n });\n }\n\n // ../web/v2/sprite-renderer.js\n function createSpriteRenderer(content2) {\n const skin = content2.agentSkin;\n const base = content2.style.tokens.character;\n const override = content2.atmosphere.styleOverrides?.character ?? {};\n const V = {\n ...base,\n ...override,\n pips: { ...base.pips, ...override.pips ?? {} },\n particles: { ...base.particles, ...override.particles ?? {} }\n };\n const rich = content2.preset.render?.detail === "rich" || V.detail === "rich";\n const px = (c, x, y, w, h, color) => {\n c.fillStyle = color;\n c.fillRect(x | 0, y | 0, w | 0, h | 0);\n };\n function hash(text) {\n let value = 2166136261;\n for (let i = 0; i < text.length; i++) {\n value ^= text.charCodeAt(i);\n value = value * 16777619 >>> 0;\n }\n return value;\n }\n function roleFor(name) {\n return skin.roles[String(name ?? "").toLowerCase()] ?? null;\n }\n function paletteFor(id, name, isLead) {\n const value = hash(`${id}|${name}`);\n const role = roleFor(name);\n return {\n skin: skin.palettes.skin[value % skin.palettes.skin.length],\n hair: skin.palettes.hair[(value >>> 3) % skin.palettes.hair.length],\n shirt: role ? role.shirt : skin.palettes.fallbackShirts[(value >>> 6) % skin.palettes.fallbackShirts.length],\n trim: role ? role.trim : skin.palettes.defaultTrim,\n badge: isLead ? skin.palettes.leadBadge : skin.palettes.agentBadge\n };\n }\n function drawRichCharacter(c, actor, t) {\n const p = actor.palette;\n const walking = actor.pose === "walk";\n const sitting = actor.pose === "sit" || actor.pose === "type";\n const step = walking ? (actor.walkPhase / 0.14 | 0) % 2 : 0;\n const bob = walking ? step : Math.sin(t / 680 + actor.seed) > 0.72 ? 1 : 0;\n const y = (actor.y | 0) - (sitting ? 3 : 0) + (sitting ? 0 : bob);\n const x = actor.x | 0;\n const dir = actor.dir ?? "down";\n const outline = V.outline ?? V.shoes;\n const skinShade = V.skinShade ?? p.skin;\n px(c, x - 7, y - 1, 14, 2, V.shadow);\n px(c, x - 5, y, 10, 1, V.shadow);\n if (!sitting) {\n const left = step ? 1 : 0;\n const right = step ? 0 : 1;\n px(c, x - 4, y - 7 + left, 4, 7 - left, outline);\n px(c, x + 1, y - 7 + right, 4, 7 - right, outline);\n px(c, x - 3, y - 6 + left, 2, 5 - left, V.pants);\n px(c, x + 2, y - 6 + right, 2, 5 - right, V.pants);\n px(c, x - 4, y - 1, 4, 2, V.shoes);\n px(c, x + 1, y - 1, 4, 2, V.shoes);\n }\n px(c, x - 6, y - 17, 12, 11, outline);\n px(c, x - 5, y - 16, 10, 9, p.shirt);\n px(c, x - 5, y - 16, 10, 2, p.trim);\n px(c, x - 4, y - 9, 8, 2, p.trim);\n px(c, x - 4, y - 13, 2, 4, V.shirtShade ?? p.trim);\n px(c, x + 3, y - 15, 1, 5, V.shirtLite ?? p.shirt);\n const armUp = actor.pose === "type" ? (t / 90 | 0) % 2 : actor.pose === "reach" || actor.pose === "phone" ? 1 : 0;\n if (dir !== "right") {\n px(c, x - 8, y - 16 - armUp, 3, 8, outline);\n px(c, x - 7, y - 15 - armUp, 2, 5, p.shirt);\n px(c, x - 7, y - 10 - armUp, 2, 2, p.skin);\n }\n if (dir !== "left") {\n const other = actor.pose === "type" ? 1 - armUp : armUp;\n px(c, x + 5, y - 16 - other, 3, 8, outline);\n px(c, x + 5, y - 15 - other, 2, 5, p.shirt);\n px(c, x + 5, y - 10 - other, 2, 2, p.skin);\n }\n if (actor.pose === "phone") {\n px(c, x - 2, y - 14, 5, 6, outline);\n px(c, x - 1, y - 13, 3, 4, V.pips.thinking ?? V.faceInk);\n px(c, x, y - 12, 1, 1, V.eyeLite ?? p.skin);\n }\n px(c, x - 5, y - 27, 10, 11, outline);\n px(c, x - 4, y - 26, 8, 9, p.skin);\n px(c, x - 4, y - 26, 8, 4, p.hair);\n px(c, x - 5, y - 25, 2, 6, p.hair);\n px(c, x + 3, y - 25, 2, 6, p.hair);\n px(c, x - 2, y - 25, 3, 1, V.hairLite ?? p.hair);\n px(c, x - 4, y - 19, 2, 2, skinShade);\n px(c, x + 2, y - 19, 2, 2, skinShade);\n if (dir === "down") {\n px(c, x - 2, y - 21, 1, 2, V.faceInk);\n px(c, x + 1, y - 21, 1, 2, V.faceInk);\n px(c, x - 2, y - 22, 1, 1, V.eyeLite ?? p.skin);\n if (actor.pose === "talk") px(c, x - 1, y - 18, 2, 1, V.mouth);\n } else if (dir === "left") {\n px(c, x - 2, y - 21, 1, 2, V.faceInk);\n px(c, x - 5, y - 26, 5, 7, p.hair);\n } else if (dir === "right") {\n px(c, x + 1, y - 21, 1, 2, V.faceInk);\n px(c, x, y - 26, 5, 7, p.hair);\n } else {\n px(c, x - 5, y - 27, 10, 9, p.hair);\n px(c, x - 2, y - 25, 4, 2, V.hairLite ?? p.hair);\n }\n px(c, x - 1, y - 14, 2, 3, p.badge);\n const pip = V.pips[actor.state ?? "idle"];\n if (pip) {\n const hidden = actor.state === "thinking" && Math.sin(t / 240) <= -0.2;\n if (!hidden) {\n px(c, x - 2, y - 33, 5, 5, outline);\n px(c, x - 1, y - 32, 3, 3, pip);\n px(c, x, y - 33, 1, 1, pip);\n }\n }\n }\n function drawCharacter(c, actor, t) {\n if (rich) return drawRichCharacter(c, actor, t);\n const p = actor.palette;\n const walking = actor.pose === "walk";\n const sitting = actor.pose === "sit" || actor.pose === "type";\n const step = walking ? (actor.walkPhase / 0.16 | 0) % 2 : 0;\n const bob = walking ? step ? 1 : 0 : Math.sin(t / 620 + actor.seed) > 0.6 ? 1 : 0;\n const y = (actor.y | 0) - (sitting ? 3 : 0) + (sitting ? 0 : bob);\n const x = actor.x | 0;\n const dir = actor.dir ?? "down";\n px(c, x - 5, y - 1, 10, 2, V.shadow);\n if (!sitting) {\n const leftStep = step ? 1 : 0;\n const rightStep = step ? 0 : 1;\n px(c, x - 3, y - 5 + leftStep, 2, 5 - leftStep, V.pants);\n px(c, x + 1, y - 5 + rightStep, 2, 5 - rightStep, V.pants);\n px(c, x - 3, y - 1, 2, 1, V.shoes);\n px(c, x + 1, y - 1, 2, 1, V.shoes);\n }\n px(c, x - 4, y - 12, 8, 8, p.shirt);\n px(c, x - 4, y - 12, 8, 1, p.trim);\n px(c, x - 4, y - 6, 8, 2, p.trim);\n const armUp = actor.pose === "type" ? (t / 90 | 0) % 2 : actor.pose === "reach" ? 1 : 0;\n if (dir === "left") {\n px(c, x - 6, y - 11 - armUp, 2, 5, p.shirt);\n } else if (dir === "right") {\n px(c, x + 4, y - 11 - armUp, 2, 5, p.shirt);\n } else {\n px(c, x - 6, y - 11 - armUp, 2, 5, p.shirt);\n px(c, x + 4, y - 11 - (actor.pose === "type" ? 1 - armUp : armUp), 2, 5, p.shirt);\n }\n px(c, x - 3, y - 19, 6, 7, p.skin);\n px(c, x - 4, y - 20, 8, 4, p.hair);\n px(c, x - 4, y - 17, 1, 2, p.hair);\n px(c, x + 3, y - 17, 1, 2, p.hair);\n if (dir === "down") {\n px(c, x - 2, y - 16, 1, 2, V.faceInk);\n px(c, x + 1, y - 16, 1, 2, V.faceInk);\n if (actor.pose === "talk") px(c, x - 1, y - 13, 2, 1, V.mouth);\n } else if (dir === "left") {\n px(c, x - 2, y - 16, 1, 2, V.faceInk);\n px(c, x - 4, y - 20, 5, 5, p.hair);\n px(c, x - 3, y - 19, 2, 1, p.skin);\n } else if (dir === "right") {\n px(c, x + 1, y - 16, 1, 2, V.faceInk);\n px(c, x - 1, y - 20, 5, 5, p.hair);\n px(c, x + 1, y - 19, 2, 1, p.skin);\n } else {\n px(c, x - 4, y - 20, 8, 6, p.hair);\n }\n px(c, x - 1, y - 11, 2, 2, p.badge);\n const pip = V.pips[actor.state ?? "idle"];\n if (pip) {\n const hidden = actor.state === "thinking" && Math.sin(t / 240) <= -0.2;\n if (!hidden) {\n px(c, x - 1, y - 25, 3, 3, pip);\n px(c, x, y - 26, 1, 1, pip);\n }\n }\n }\n function drawParticle(c, particle) {\n const alpha = Math.max(0, Math.min(1, particle.life / particle.maxLife));\n c.globalAlpha = alpha;\n const x = particle.x | 0;\n const y = particle.y | 0;\n switch (particle.kind) {\n case "key":\n px(c, x, y, 3, 3, V.particles.key);\n px(c, x + 1, y + 1, 1, 1, V.particles.keyInset);\n break;\n case "paper":\n px(c, x - 3, y - 2, 7, 5, V.particles.paper);\n px(c, x - 2, y - 1, 5, 1, V.particles.paperLine);\n px(c, x - 2, y + 1, 3, 1, V.particles.paperLine);\n break;\n case "check":\n px(c, x - 3, y, 2, 2, V.particles.check);\n px(c, x - 1, y + 2, 2, 2, V.particles.check);\n px(c, x + 1, y, 2, 2, V.particles.check);\n px(c, x + 3, y - 2, 2, 2, V.particles.check);\n break;\n case "bang":\n px(c, x, y - 5, 2, 4, V.particles.bang);\n px(c, x, y, 2, 2, V.particles.bang);\n break;\n case "cross":\n px(c, x - 2, y - 2, 2, 2, V.particles.cross);\n px(c, x + 1, y + 1, 2, 2, V.particles.cross);\n px(c, x + 1, y - 2, 2, 2, V.particles.cross);\n px(c, x - 2, y + 1, 2, 2, V.particles.cross);\n break;\n case "spark":\n px(c, x, y, 2, 2, particle.color ?? V.particles.spark);\n break;\n default:\n px(c, x, y, 2, 2, V.particles.fallback);\n }\n c.globalAlpha = 1;\n }\n return Object.freeze({\n ROLES: skin.roles,\n roleFor,\n paletteFor,\n drawCharacter,\n drawParticle,\n hash,\n content: skin\n });\n }\n\n // ../web/v2/graph-validator.js\n var WORK_CAPABILITIES = ["research", "create", "compute", "plan", "communicate", "collaborate"];\n var DYNAMIC_ACTIVITY_TARGETS = /* @__PURE__ */ new Set(["near-colleague"]);\n function namedInstance(requirement) {\n if (typeof requirement === "string")\n return requirement;\n if (requirement && typeof requirement.prop === "string")\n return requirement.prop;\n return void 0;\n }\n function resolveActivityRequirements(requires, instances, capabilitiesOf, activity) {\n const table = [...instances];\n const bindings = [];\n const issues = [];\n for (const requirement of requires ?? []) {\n const named = namedInstance(requirement);\n if (named !== void 0) {\n const present = table.some(([instanceId]) => instanceId === named);\n bindings.push(present ? named : null);\n if (!present)\n issues.push({ code: "missing-activity-prop", path: "$", message: `${activity} \\u7F3A\\u5C11 Prop\\uFF1A${named}` });\n continue;\n }\n const capability = requirement && typeof requirement.capability === "string" ? requirement.capability : void 0;\n if (!capability) {\n bindings.push(null);\n issues.push({ code: "invalid-activity-requirement", path: "$", message: `${activity} \\u7684\\u4F9D\\u8D56\\u5FC5\\u987B\\u662F\\u5177\\u540D\\u9053\\u5177\\u6216\\u80FD\\u529B\\u9700\\u6C42` });\n continue;\n }\n const match = table.find(([, type]) => capabilitiesOf(type).includes(capability));\n bindings.push(match ? match[0] : null);\n if (!match)\n 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` });\n }\n return { bindings, issues };\n }\n function assertManifest(value, kind) {\n const manifest = value;\n if (!manifest || manifest.schemaVersion !== 1 || manifest.kind !== kind || !manifest.id || !manifest.version) {\n throw new Error(`\\u65E0\\u6548\\u7684 ${kind} \\u5185\\u5BB9\\u6E05\\u5355`);\n }\n }\n function manifestKindFor(key) {\n if (key === "agentSkin")\n return "agent-skin";\n if (key === "lifeActivities")\n return "life-activities";\n return key;\n }\n function validClock(value) {\n if (typeof value !== "string" || !/^\\d{2}:\\d{2}$/.test(value))\n return false;\n const [hour, minute] = value.split(":").map(Number);\n return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59;\n }\n function shiftIssue(value, code, path, label) {\n if (!value || !validClock(value.start) || !validClock(value.end))\n return { code, path, message: `${label} \\u5FC5\\u987B\\u63D0\\u4F9B\\u6709\\u6548\\u7684 HH:MM \\u8D77\\u6B62\\u65F6\\u95F4` };\n if (value.endLatest !== void 0 && !validClock(value.endLatest))\n return { code, path: `${path}.endLatest`, message: `${label} \\u7684\\u6700\\u665A\\u4E0B\\u73ED\\u65F6\\u95F4\\u5FC5\\u987B\\u662F\\u6709\\u6548\\u7684 HH:MM` };\n return null;\n }\n function layoutIssues(layout, propTypeNames) {\n if (!layout || typeof layout !== "object")\n return [{ code: "missing-layout", path: "$.layout", message: "Layout \\u7F3A\\u5931" }];\n const issues = [];\n if (layout.contract !== "single-office-v1")\n issues.push({ code: "invalid-layout-contract", path: "$.layout", message: `\\u4E0D\\u652F\\u6301\\u7684 Layout \\u5408\\u540C\\uFF1A${layout.contract}` });\n if (layout.canvas?.width !== 384 || layout.canvas?.height !== 216)\n issues.push({ code: "invalid-layout-canvas", path: "$.layout", message: "single-office-v1 \\u5FC5\\u987B\\u4F7F\\u7528 384\\xD7216 \\u903B\\u8F91\\u753B\\u5E03" });\n if (!Array.isArray(layout.seats) || layout.seats.length !== 8)\n issues.push({ code: "invalid-layout-seats", path: "$.layout", message: "Demo Layout \\u5FC5\\u987B\\u63D0\\u4F9B 8 \\u4E2A\\u5EA7\\u4F4D" });\n if (!Array.isArray(layout.navigation?.lanes) || !layout.navigation.lanes.length)\n issues.push({ code: "invalid-layout-navigation", path: "$.layout", message: "Layout \\u7F3A\\u5C11\\u5BFC\\u822A\\u901A\\u9053" });\n const propTypes = new Set(propTypeNames);\n const textIds = /* @__PURE__ */ new Set();\n if (layout.textSlots !== void 0 && (!Array.isArray(layout.textSlots) || layout.textSlots.length > 8)) {\n issues.push({ code: "invalid-text-slots", path: "$.layout.textSlots", message: "maximum 8 text areas" });\n } else\n for (const slot of layout.textSlots ?? []) {\n 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);\n if (!valid)\n issues.push({ code: "invalid-text-slot", path: "$.layout.textSlots", message: "invalid, duplicate or out-of-bounds text area" });\n if (slot?.id)\n textIds.add(slot.id);\n }\n const propInstances = /* @__PURE__ */ new Set();\n for (const instance of layout.propInstances ?? []) {\n if (!propTypes.has(instance?.type))\n issues.push({ code: "unknown-layout-prop", path: "$.layout.propInstances", message: `\\u672A\\u77E5 Prop Type\\uFF1A${instance?.type}` });\n if (!instance?.id || propInstances.has(instance.id))\n issues.push({ code: "invalid-layout-prop", path: "$.layout.propInstances", message: `\\u91CD\\u590D\\u6216\\u65E0\\u6548\\u7684 Prop \\u5B9E\\u4F8B\\uFF1A${instance?.id ?? "\\u2014"}` });\n propInstances.add(instance?.id);\n }\n for (const capability of WORK_CAPABILITIES) {\n if (!layout.stations?.[capability])\n issues.push({ code: "missing-layout-station", path: `$.layout.stations.${capability}`, message: `Layout \\u7F3A\\u5C11\\u5DE5\\u4F5C\\u80FD\\u529B\\uFF1A${capability}` });\n }\n return issues;\n }\n function graphIssues(content2) {\n const issues = [];\n for (const [key, value] of Object.entries(content2 ?? {})) {\n if (key === "preset" || key === "agentProfile")\n continue;\n try {\n assertManifest(value, manifestKindFor(key));\n } catch (error) {\n issues.push({ code: "invalid-manifest", path: `$.${key}`, message: error.message });\n }\n }\n const profile = content2?.agentProfile;\n if (!profile || typeof profile !== "object" || typeof profile.template !== "string" || !profile.template || !profile.appearance || typeof profile.appearance !== "object" || Array.isArray(profile.appearance)) {\n issues.push({ code: "invalid-agent-profile", path: "$.agentProfile", message: "\\u65E0\\u6548\\u7684 Agent Profile" });\n }\n const layout = content2?.layout;\n issues.push(...layoutIssues(layout, Object.keys(content2?.props?.types ?? {})));\n const instances = (layout?.propInstances ?? []).map((instance) => [instance?.id, instance?.type]);\n const capabilitiesOf = (type) => content2?.props?.types?.[type]?.capabilities ?? [];\n ;\n (content2?.npcs?.entries ?? []).forEach((npc, index) => {\n const path = `$.npcs.entries[${index}]`;\n if (!npc?.id || !npc.role || !layout?.targets?.[npc.spawn])\n issues.push({ code: "invalid-npc", path, message: `\\u65E0\\u6548\\u7684 NPC\\uFF1A${npc?.id ?? "\\u2014"}` });\n const shift = npc?.shift ? shiftIssue(npc.shift, "invalid-npc-shift", path, `NPC ${npc.id} \\u7684 shift`) : null;\n if (shift)\n issues.push(shift);\n });\n ;\n (content2?.lifeActivities?.entries ?? []).forEach((activity, index) => {\n const path = `$.lifeActivities.entries[${index}]`;\n if (!activity?.id || !["agent", "npc", "person"].includes(activity.participant?.kind) || !activity.steps?.length) {\n issues.push({ code: "invalid-activity", path, message: `\\u65E0\\u6548\\u7684 Life Activity\\uFF1A${activity?.id ?? "\\u2014"}` });\n return;\n }\n if (activity.participant?.minAgents != null && (!Number.isInteger(activity.participant.minAgents) || activity.participant.minAgents < 2)) {\n issues.push({ code: "invalid-activity-participant", path, message: `${activity.id} \\u7684 minAgents \\u5FC5\\u987B\\u662F\\u81F3\\u5C11 2 \\u7684\\u6574\\u6570` });\n }\n const resolved = resolveActivityRequirements(activity.requires, instances, capabilitiesOf, activity.id);\n issues.push(...resolved.issues.map((issue) => ({ ...issue, path })));\n for (const step of activity.steps ?? []) {\n if (!layout?.targets?.[step?.target] && !DYNAMIC_ACTIVITY_TARGETS.has(step?.target))\n issues.push({ code: "missing-activity-target", path, message: `${activity.id} \\u7F3A\\u5C11 Target\\uFF1A${step?.target}` });\n for (const target of step?.targets ?? []) {\n if (!layout?.targets?.[target])\n issues.push({ code: "missing-activity-target", path, message: `${activity.id} \\u7F3A\\u5C11 Group Target\\uFF1A${target}` });\n }\n }\n });\n const environment = content2?.environment;\n if (!environment || !["local", "fixed"].includes(environment.clock?.mode)) {\n issues.push({ code: "invalid-environment", path: "$.environment", message: "Environment \\u7684 clock.mode \\u5FC5\\u987B\\u662F local \\u6216 fixed" });\n } else {\n if (environment.clock.mode === "fixed" && !validClock(environment.clock.fixedTime))\n issues.push({ code: "invalid-environment", path: "$.environment.clock.fixedTime", message: "Environment \\u7684 fixedTime \\u65E0\\u6548" });\n if (!Array.isArray(environment.clock?.phases) || !environment.clock.phases.length)\n issues.push({ code: "invalid-environment", path: "$.environment.clock", message: "Environment \\u7F3A\\u5C11 day phases" });\n for (const phase of environment.clock?.phases ?? []) {\n if (!phase?.id || !validClock(phase.start))\n issues.push({ code: "invalid-environment", path: "$.environment.clock.phases", message: "Environment \\u5305\\u542B\\u65E0\\u6548\\u7684 day phase" });\n }\n if (!Array.isArray(environment.weather?.allowedConditions) || !environment.weather.allowedConditions.length) {\n issues.push({ code: "invalid-environment", path: "$.environment.weather", message: "Environment \\u7F3A\\u5C11\\u5929\\u6C14\\u7C7B\\u578B" });\n }\n const allowedWeather = new Set(environment.weather?.allowedConditions ?? []);\n for (const condition of [environment.weather?.condition, environment.weather?.fallback]) {\n if (condition != null && !allowedWeather.has(condition)) {\n issues.push({ code: "invalid-environment", path: "$.environment.weather", message: `Environment \\u7684\\u5929\\u6C14 ${condition} \\u4E0D\\u5728 allowedConditions \\u4E2D` });\n }\n }\n const shift = environment.npcSchedule?.defaultShift ? shiftIssue(environment.npcSchedule.defaultShift, "invalid-environment", "$.environment.npcSchedule", "Environment \\u7684 NPC \\u9ED8\\u8BA4\\u73ED\\u6B21") : null;\n if (shift)\n issues.push(shift);\n for (const [role, value] of Object.entries(environment.npcSchedule?.roleOverrides ?? {})) {\n const roleIssue = shiftIssue(value, "invalid-environment", `$.environment.npcSchedule.roleOverrides.${role}`, `Environment \\u7684 ${role} \\u73ED\\u6B21`);\n if (roleIssue)\n issues.push(roleIssue);\n }\n }\n return issues;\n }\n function graphIssueMessages(content2) {\n return graphIssues(content2).map((issue) => issue.message);\n }\n\n // ../src/core/limits.ts\n var SCENE_LIMITS = Object.freeze({\n agents: 16,\n seats: 8,\n npcs: 12,\n props: 80,\n animatedProps: 24,\n activities: 16,\n effects: 40,\n visibleBubbles: 4,\n queuedBubbles: 8\n });\n\n // ../web/v2/content/presets/tech-open-office.json\n var tech_open_office_default = {\n schemaVersion: 1,\n kind: "preset",\n id: "builtin/tech-open-office",\n name: "tech",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n officeSpec: "official-offices/tech-open-office.json",\n render: { detail: "rich" },\n content: {\n style: "pixel-classic",\n layout: "tech-open-office",\n agentSkin: "tiny-developers",\n props: "tech-office",\n npcs: "tech-office-staff",\n lifeActivities: "tech-office-routines",\n atmosphere: "default",\n environment: "local-office"\n }\n };\n\n // ../web/v2/content/styles/pixel-classic.json\n var pixel_classic_default = {\n schemaVersion: 1,\n kind: "style",\n id: "builtin/pixel-classic",\n name: "\\u7ECF\\u5178\\u50CF\\u7D20",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "office-2d-style-v1",\n tokens: {\n css: {\n bg: "#0d1017",\n panel: "#151a24",\n "panel-2": "#1c2230",\n line: "#2a3243",\n ink: "#d7dee9",\n dim: "#7c8798",\n accent: "#ffcc4d",\n ok: "#6ee7a8",\n warn: "#ff8f6b",\n think: "#8fb8ff"\n },\n canvas: {\n outline: "#171c25",\n objectShadow: "rgba(0,0,0,0.3)",\n floorA: "#2f3a45",\n floorB: "#334050",\n floorC: "#2b3542",\n floorLine: "#28313b",\n floorGrain: "#405066",\n rug: "#3a3150",\n rugEdge: "#4b3f63",\n wall: "#222b39",\n wallTop: "#1a222e",\n wallTrim: "#39455c",\n wallShade: "#1c2531",\n wallSeam: "#303b4d",\n wallHighlight: "#46546c",\n wallBase: "#151c26",\n wood: "#7d5b3a",\n woodDark: "#5d4229",\n woodGrain: "#b08659",\n deskTop: "#8d6a44",\n deskLite: "#a07d54",\n screen: "#0f1620",\n screenGlow: "#4a86e2",\n chair: "#43506b",\n chairLite: "#596986",\n chairDark: "#333d52",\n metal: "#57616f",\n metalDark: "#3f4854",\n rack: "#232b3a",\n board: "#dde3ec",\n boardFrame: "#8f9aab",\n glass: "#5f87a8",\n glassLite: "#7fa8c9",\n glassShine: "#a7d8f0",\n skyMorning: "#79a9c5",\n skyNoon: "#8bc7dc",\n skyEvening: "#c87863",\n skyNight: "#17243c",\n skyNightLite: "#243754",\n skyHorizon: "#e09a68",\n skySun: "#ffe09a",\n skyStar: "#f5dfa0",\n skyBuilding: "#111a2b",\n skyWindow: "#ffd26f",\n roomLightEvening: "rgba(255,220,150,0.07)",\n roomLightNight: "rgba(255,210,112,0.13)",\n plant: "#3f7d52",\n plantDark: "#2f5e3e",\n plantLite: "#579768",\n plantHighlight: "#78b283",\n pot: "#8a5a3c",\n paper: "#e8e2d0",\n door: "#6b4a2f",\n doorDark: "#4c3421",\n doorPanel: "#7d5738",\n doorLite: "#9d7048",\n windowSpark: "#ffe9a8",\n boardText: "#5f6b7d",\n boardTextDim: "#7f8b9d",\n boardIdle: "#a4aebd",\n activeRed: "#e2564a",\n activeBlue: "#4a86e2",\n activeGreen: "#4fd6a0",\n activeThink: "#8fb8ff",\n activeAccent: "#ffcc4d",\n screenIdle: "#2c3a4c",\n paperLine: "#c9c2ad",\n metalLite: "#697485",\n drawerLite: "#727d8e",\n drawerHandle: "#8b95a5",\n drawerOpen: "#2a323c",\n rackTrim: "#3a4557",\n rackPanel: "#1a212c",\n rackLine: "#2f3a49",\n signalOn: "#6ee7a8",\n signalOff: "#24513c",\n coffeePanel: "#1c2330",\n coffeeHot: "#c98a4b",\n coffeeIdle: "#2a3340",\n steam: "#8fa3b8",\n waterBody: "#d6dde8",\n waterDark: "#596779",\n waterGlass: "#91c9e8",\n waterLevel: "#57a9d6",\n waterActive: "#8fe3ff",\n rain: "#9ac7e8",\n phoneTop: "#96704a",\n phoneDevice: "#1f2735",\n phoneIdle: "#39455c",\n meetingPaper: "#cfd6e2",\n meetingDevice: "#4a5668",\n potLite: "#a06c48",\n keyboard: "#1f2735",\n keyboardKey: "#6d7a8d",\n mug: "#e2564a",\n mugLite: "#ff8f6b"\n },\n character: {\n outline: "#151923",\n shadow: "rgba(0,0,0,0.28)",\n pants: "#2b3242",\n shoes: "#1b202b",\n shirtShade: "#252c3a",\n shirtLite: "rgba(255,255,255,0.2)",\n skinShade: "#b87964",\n hairLite: "rgba(255,255,255,0.18)",\n eyeLite: "#ffe9c9",\n faceInk: "#232a36",\n mouth: "#8d4a44",\n pips: {\n thinking: "#8fb8ff",\n working: "#ffcc4d",\n waiting: "#c79bff",\n talking: "#6ee7a8",\n done: "#6ee7a8",\n error: "#ff8f6b",\n idle: null\n },\n particles: {\n key: "#8fb8ff",\n keyInset: "#0f1620",\n paper: "#e8e2d0",\n paperLine: "#b9b3a1",\n check: "#6ee7a8",\n bang: "#ffcc4d",\n cross: "#ff8f6b",\n spark: "#6ee7a8",\n fallback: "#d7dee9"\n }\n },\n motion: {\n actorSpeed: 44,\n bubbleThinkMs: 4200,\n bubbleSayMs: 6e3,\n bubbleActionMs: 2800\n }\n }\n };\n\n // ../web/v2/content/layouts/tech-open-office.json\n var tech_open_office_default2 = {\n schemaVersion: 1,\n kind: "layout",\n id: "builtin/tech-open-office",\n name: "Tech \\u5F00\\u653E\\u5F0F\\u529E\\u516C\\u5BA4\\u5E03\\u5C40",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n canvas: { width: 384, height: 216 },\n wallHeight: 36,\n camera: { mode: "fixed", fit: "contain", integerScale: true },\n zones: [\n { id: "entry-service", name: "Entry & Service", x: 0, y: 36, width: 104, height: 24 },\n { id: "work-north", name: "North Work Area", x: 12, y: 56, width: 260, height: 46 },\n { id: "work-south", name: "South Work Area", x: 12, y: 112, width: 260, height: 48 },\n { id: "operations", name: "Operations", x: 288, y: 36, width: 84, height: 130 },\n { id: "lounge", name: "Lounge", x: 80, y: 168, width: 208, height: 48 },\n { id: "executive", name: "Executive Corner", x: 332, y: 70, width: 40, height: 72 },\n { id: "decoration", name: "Decoration Edge", x: 0, y: 192, width: 384, height: 24 },\n { id: "circulation", name: "Office Circulation", x: 0, y: 36, width: 384, height: 180 }\n ],\n textSlots: [{ id: "company", name: "Company name / text logo", x: 119, y: 11, width: 64, height: 11, maxLength: 24, defaultText: "" }, { id: "notice", name: "Notice", x: 119, y: 23, width: 64, height: 10, maxLength: 48, defaultText: "" }, { id: "slogan", name: "Area slogan", x: 120, y: 161, width: 112, height: 11, maxLength: 48, defaultText: "" }],\n placementSlots: [\n { id: "extra-1", zone: "circulation", x: 40, y: 177, accepts: ["plant"], maxSize: { width: 14, height: 22 }, required: false },\n { id: "extra-2", zone: "circulation", x: 62, y: 177, accepts: ["plant"], maxSize: { width: 14, height: 22 }, required: false },\n { id: "extra-3", zone: "circulation", x: 330, y: 176, accepts: ["water-cooler", "vending-machine"], maxSize: { width: 18, height: 28 }, required: false },\n { id: "lounge-fitness-1", zone: "lounge", x: 88, y: 179, accepts: ["dumbbell"], maxSize: { width: 16, height: 7 }, required: false, occupiedBy: "dumbbell-1" },\n { id: "lounge-fitness-2", zone: "lounge", x: 92, y: 190, accepts: ["dumbbell"], maxSize: { width: 16, height: 7 }, required: false, occupiedBy: "dumbbell-2" },\n { id: "lounge-service-1", zone: "lounge", x: 252, y: 174, accepts: ["water-cooler", "vending-machine"], maxSize: { width: 18, height: 28 }, required: false, occupiedBy: "water-main" },\n { id: "edge-decoration-left", zone: "decoration", x: 16, y: 200, accepts: ["plant"], maxSize: { width: 14, height: 22 }, required: false, occupiedBy: "plant-1" },\n { id: "edge-decoration-middle", zone: "decoration", x: 78, y: 200, accepts: ["plant"], maxSize: { width: 14, height: 22 }, required: false, occupiedBy: "plant-2" },\n { id: "edge-decoration-right", zone: "decoration", x: 366, y: 200, accepts: ["plant"], maxSize: { width: 14, height: 22 }, required: false, occupiedBy: "plant-3" }\n ],\n navigation: {\n lanes: [46, 100, 162],\n connectors: [76, 140, 204, 276]\n },\n seats: [\n { index: 0, cx: 44, desk: { x: 20, y: 58, width: 48, height: 16 }, anchor: { x: 44, y: 84, dir: "up", lane: 1 } },\n { index: 1, cx: 108, desk: { x: 84, y: 58, width: 48, height: 16 }, anchor: { x: 108, y: 84, dir: "up", lane: 1 } },\n { index: 2, cx: 172, desk: { x: 148, y: 58, width: 48, height: 16 }, anchor: { x: 172, y: 84, dir: "up", lane: 1 } },\n { index: 3, cx: 236, desk: { x: 212, y: 58, width: 48, height: 16 }, anchor: { x: 236, y: 84, dir: "up", lane: 1 } },\n { index: 4, cx: 44, desk: { x: 20, y: 118, width: 48, height: 16 }, anchor: { x: 44, y: 144, dir: "up", lane: 2 } },\n { index: 5, cx: 108, desk: { x: 84, y: 118, width: 48, height: 16 }, anchor: { x: 108, y: 144, dir: "up", lane: 2 } },\n { index: 6, cx: 172, desk: { x: 148, y: 118, width: 48, height: 16 }, anchor: { x: 172, y: 144, dir: "up", lane: 2 } },\n { index: 7, cx: 236, desk: { x: 212, y: 118, width: 48, height: 16 }, anchor: { x: 236, y: 144, dir: "up", lane: 2 } }\n ],\n standingAnchors: [\n { x: 278, y: 92, dir: "left", lane: 1, zone: "circulation" },\n { x: 278, y: 152, dir: "left", lane: 2, zone: "circulation" },\n { x: 92, y: 164, dir: "down", lane: 2, zone: "circulation" },\n { x: 242, y: 164, dir: "down", lane: 2, zone: "circulation" },\n { x: 214, y: 46, dir: "down", lane: 0, zone: "circulation" },\n { x: 246, y: 46, dir: "down", lane: 0, zone: "circulation" },\n { x: 278, y: 46, dir: "down", lane: 0, zone: "circulation" },\n { x: 310, y: 46, dir: "down", lane: 0, zone: "circulation" }\n ],\n targets: {\n "staff-entry": { x: 38, y: 46, dir: "down", lane: 0 },\n archive: { x: 318, y: 100, dir: "up", lane: 1 },\n server: { x: 320, y: 158, dir: "up", lane: 2 },\n whiteboard: { x: 152, y: 46, dir: "up", lane: 0 },\n phone: { x: 364, y: 64, dir: "up", lane: 1 },\n coffee: { x: 290, y: 188, dir: "right", lane: 2 },\n meetA: { x: 108, y: 190, dir: "right", lane: 2 },\n meetB: { x: 270, y: 190, dir: "left", lane: 2 },\n entry: { x: 38, y: 46, dir: "down", lane: 0 },\n "boss-seat": { x: 360, y: 104, dir: "up", lane: 1 },\n restroom: { x: 88, y: 46, dir: "up", lane: 0 },\n water: { x: 264, y: 194, dir: "left", lane: 2 },\n "phone-break": { x: 218, y: 208, dir: "down", lane: 2 },\n "chat-a": { x: 140, y: 208, dir: "right", lane: 2 },\n "chat-b": { x: 172, y: 208, dir: "left", lane: 2 },\n "walk-outside-a": { x: -10, y: 46, dir: "left", lane: 0 },\n "walk-outside-b": { x: -18, y: 46, dir: "left", lane: 0 },\n "cleaner-entry": { x: 376, y: 162, dir: "left", lane: 2 },\n "clean-desks-north": { x: 276, y: 92, dir: "left", lane: 1 },\n "clean-desks-south": { x: 276, y: 152, dir: "left", lane: 2 },\n "clean-lounge": { x: 238, y: 208, dir: "left", lane: 2 },\n "plant-left": { x: 28, y: 202, dir: "left", lane: 2 },\n "plant-middle": { x: 90, y: 202, dir: "left", lane: 2 },\n "plant-right": { x: 354, y: 202, dir: "right", lane: 2 }\n },\n stations: {\n research: { kind: "target", target: "archive" },\n create: { kind: "seat" },\n compute: { kind: "target", target: "server" },\n plan: { kind: "target", target: "whiteboard" },\n communicate: { kind: "target", target: "phone" },\n collaborate: { kind: "seat", meetingTargets: ["meetA", "meetB"] },\n coffee: { kind: "target", target: "coffee" }\n },\n legacyActions: {\n type: "create",\n archive: "research",\n server: "compute",\n whiteboard: "plan",\n phone: "communicate",\n delegate: "collaborate",\n coffee: "coffee"\n },\n furniture: {\n archive: { x: 296, y: 56, w: 44, h: 32 },\n server: { x: 296, y: 116, w: 48, h: 32 },\n coffee: { x: 300, y: 176, w: 26, h: 22 },\n phone: { x: 348, y: 40, w: 32, h: 14 },\n whiteboard: { x: 116, y: 8, w: 72, h: 26 },\n meeting: { x: 120, y: 176, w: 112, h: 26 }\n },\n propInstances: [\n { id: "meeting-rug", type: "rug", x: 104, y: 170 },\n { id: "entry-door", type: "door", x: 20, y: 6 },\n { id: "restroom-door", type: "restroom-door", x: 74, y: 6 },\n { id: "wall-clock", type: "office-clock", x: 356, y: 10 },\n { id: "window-1", type: "window", x: 208, y: 8 },\n { id: "window-2", type: "window", x: 268, y: 8 },\n { id: "whiteboard-main", type: "whiteboard", x: 116, y: 8 },\n { id: "phone-main", type: "phone-table", x: 348, y: 40 },\n { id: "archive-main", type: "archive-cabinet", x: 296, y: 56 },\n { id: "server-main", type: "server-rack", x: 296, y: 116 },\n { id: "coffee-main", type: "coffee-machine", x: 300, y: 176 },\n { id: "meeting-main", type: "lounge-sofa", x: 120, y: 176 },\n { id: "dumbbell-1", type: "dumbbell", x: 88, y: 179 },\n { id: "dumbbell-2", type: "dumbbell", x: 92, y: 190, orientation: "vertical" },\n { id: "water-main", type: "water-cooler", x: 252, y: 174 },\n { id: "boss-desk", type: "boss-desk", x: 343, y: 78 },\n { id: "plant-1", type: "plant", x: 16, y: 200 },\n { id: "plant-2", type: "plant", x: 78, y: 200 },\n { id: "plant-3", type: "plant", x: 366, y: 200 }\n ],\n npcSpawns: ["staff-entry", "boss-seat", "cleaner-entry"]\n };\n\n // ../web/v2/content/agent-skins/tiny-developers.json\n var tiny_developers_default = {\n schemaVersion: 1,\n kind: "agent-skin",\n id: "builtin/tiny-developers",\n name: "\\u539F\\u7248\\u50CF\\u7D20\\u5458\\u5DE5",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "pixel-character-v1",\n animations: ["idle", "walk", "sit", "type", "reach", "talk"],\n palettes: {\n skin: ["#f0cba3", "#e8b98f", "#d69f70", "#b87a4f"],\n hair: ["#2f2418", "#1f2430", "#4a2f22", "#3b3b45", "#5c3b1e"],\n fallbackShirts: ["#4a78c8", "#4f9d69", "#c8913f", "#b8574f", "#7b57b8", "#4fb8b0"],\n defaultTrim: "#2b3242",\n leadBadge: "#ffcc4d",\n agentBadge: "#8fb8ff"\n },\n roles: {\n scout: { title: "\\u4FA6\\u5BDF\\u5458", shirt: "#4f9d69", trim: "#37714b" },\n planner: { title: "\\u89C4\\u5212\\u5E08", shirt: "#4a78c8", trim: "#33569a" },\n reviewer: { title: "\\u8BC4\\u5BA1\\u5458", shirt: "#b8574f", trim: "#8d3f38" },\n worker: { title: "\\u5DE5\\u7A0B\\u5E08", shirt: "#c8913f", trim: "#9a6c28" },\n tester: { title: "\\u6D4B\\u8BD5\\u5458", shirt: "#7b57b8", trim: "#5b3d8d" },\n writer: { title: "\\u6587\\u6848", shirt: "#c85f9b", trim: "#9a4577" },\n ceo: { title: "CEO", shirt: "#d8d2c4", trim: "#a8a294" },\n cto: { title: "CTO", shirt: "#5f7fa8", trim: "#455f80" },\n cpo: { title: "CPO", shirt: "#a87fc8", trim: "#7f5c9a" },\n programmer: { title: "\\u7A0B\\u5E8F\\u5458", shirt: "#c8913f", trim: "#9a6c28" },\n designer: { title: "\\u8BBE\\u8BA1\\u5E08", shirt: "#4fb8b0", trim: "#379088" },\n counselor: { title: "\\u987E\\u95EE", shirt: "#8f8f9f", trim: "#6b6b7a" },\n hr: { title: "HR", shirt: "#c8a04f", trim: "#9a7a33" }\n }\n };\n\n // ../web/v2/content/props/tech-office.json\n var tech_office_default = {\n schemaVersion: 1,\n kind: "props",\n id: "builtin/tech-office",\n name: "Tech \\u5F00\\u653E\\u5F0F\\u529E\\u516C\\u5BA4\\u7269\\u4EF6",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n types: {\n rug: { size: { width: 146, height: 42 }, capabilities: [], renderer: "rug" },\n door: { size: { width: 36, height: 30 }, capabilities: ["entry"], renderer: "door" },\n "restroom-door": { size: { width: 28, height: 30 }, capabilities: ["restroom"], renderer: "restroom-door" },\n window: { size: { width: 44, height: 22 }, capabilities: [], renderer: "window" },\n "office-clock": { size: { width: 14, height: 14 }, capabilities: [], renderer: "office-clock" },\n "boss-desk": { size: { width: 36, height: 16 }, capabilities: [], renderer: "boss-desk" },\n "lounge-sofa": { size: { width: 112, height: 26 }, capabilities: ["collaborate"], renderer: "lounge-sofa" },\n dumbbell: { size: { width: 16, height: 7 }, capabilities: [], renderer: "dumbbell" },\n workstation: { size: { width: 48, height: 16 }, capabilities: ["create", "research", "compute", "communicate"], renderer: "workstation" },\n whiteboard: { size: { width: 72, height: 26 }, capabilities: ["plan"], renderer: "whiteboard" },\n "phone-table": { size: { width: 32, height: 14 }, capabilities: ["communicate"], renderer: "phone-table" },\n "archive-cabinet": { size: { width: 44, height: 32 }, capabilities: ["research"], renderer: "archive-cabinet" },\n "server-rack": { size: { width: 48, height: 32 }, capabilities: ["compute"], renderer: "server-rack" },\n "coffee-machine": { size: { width: 26, height: 22 }, capabilities: ["coffee"], renderer: "coffee-machine" },\n "water-cooler": { size: { width: 16, height: 26 }, capabilities: ["water"], renderer: "water-cooler" },\n "meeting-table": { size: { width: 112, height: 26 }, capabilities: ["collaborate"], renderer: "meeting-table" },\n plant: { size: { width: 12, height: 21 }, capabilities: [], renderer: "plant" }\n }\n };\n\n // ../web/v2/content/npcs/tech-office-staff.json\n var tech_office_staff_default = {\n schemaVersion: 1,\n kind: "npcs",\n id: "builtin/tech-office-staff",\n name: "Tech \\u529E\\u516C\\u5BA4 NPC",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n entries: [\n {\n id: "boss",\n name: "\\u8001\\u677F",\n role: "boss",\n title: "\\u8001\\u677F",\n spawn: "boss-seat",\n pose: "sit",\n appearance: {\n skin: "#d5a47c",\n hair: "#31333a",\n shirt: "#516687",\n trim: "#34455f",\n badge: "#ffcc4d"\n }\n },\n {\n id: "cleaner-lin",\n name: "\\u6797\\u59E8",\n role: "cleaner",\n title: "\\u4FDD\\u6D01",\n spawn: "cleaner-entry",\n appearance: {\n skin: "#d69f70",\n hair: "#3f424a",\n shirt: "#4f9d88",\n trim: "#347264",\n badge: "#b7eadb"\n }\n }\n ]\n };\n\n // ../web/v2/content/life-activities/tech-office-routines.json\n var tech_office_routines_default = {\n schemaVersion: 1,\n kind: "life-activities",\n id: "builtin/tech-office-routines",\n name: "Tech \\u529E\\u516C\\u5BA4\\u65E5\\u5E38",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n entries: [\n {\n id: "boss-plan-round",\n name: "\\u8001\\u677F\\u5DE1\\u89C6\\u89C4\\u5212",\n participant: { kind: "npc", roles: ["boss"] },\n requires: [{ capability: "plan" }],\n interruptible: false,\n initialDelayMs: [3500, 7e3],\n cooldownMs: [15e3, 26e3],\n steps: [\n { target: "whiteboard", durationMs: 3600, pose: "reach", bubble: "\\u770B\\u770B\\u4ECA\\u5929\\u7684\\u8BA1\\u5212" },\n { target: "boss-seat", durationMs: 1800, pose: "sit" }\n ]\n },\n {\n id: "boss-water-break",\n name: "\\u8001\\u677F\\u63A5\\u6C34",\n participant: { kind: "npc", roles: ["boss"] },\n requires: [{ capability: "water" }],\n interruptible: false,\n initialDelayMs: [6e3, 1e4],\n cooldownMs: [18e3, 3e4],\n steps: [\n { target: "water", durationMs: 2400, pose: "stand", bubble: "\\u63A5\\u676F\\u6C34" },\n { target: "boss-seat", durationMs: 1600, pose: "sit" }\n ]\n },\n {\n id: "boss-cheer-round",\n name: "\\u8001\\u677F\\u7ED9\\u5458\\u5DE5\\u6253\\u6C14",\n participant: { kind: "npc", roles: ["boss"] },\n requires: [],\n interruptible: false,\n initialDelayMs: [9e3, 14e3],\n cooldownMs: [36e3, 56e3],\n steps: [\n { target: "near-colleague", personIndex: 0, durationMs: 3e3, pose: "talk", bubble: "\\u5144\\u5F1F\\u4EEC\\u52A0\\u6CB9\\u52AA\\u529B\\u5E72\\uFF01", particle: "bang" },\n { target: "near-colleague", personIndex: 1, durationMs: 2600, pose: "talk", bubble: "\\u5927\\u5BB6\\u52A0\\u6CB9\\uFF0C\\u51B2\\u4E00\\u51B2\\uFF01", particle: "bang" },\n { target: "boss-seat", durationMs: 1600, pose: "sit" }\n ]\n },\n {\n id: "colleague-chat",\n name: "\\u548C\\u540C\\u4E8B\\u804A\\u804A\\u5929",\n participant: { kind: "person", states: ["idle", "waiting", "done"], minAgents: 2 },\n requires: [{ capability: "collaborate" }],\n onlyWhenSessionIdle: true,\n interruptible: true,\n startBubble: "\\u804A\\u4E24\\u53E5\\uFF1F",\n initialDelayMs: [6500, 1e4],\n cooldownMs: [3e4, 48e3],\n steps: [\n {\n target: "chat-a",\n targets: ["chat-a", "chat-b"],\n durationMs: 5200,\n pose: "talk",\n bubbles: ["\\u6700\\u8FD1\\u8FDB\\u5C55\\u600E\\u4E48\\u6837\\uFF1F", "\\u521A\\u5904\\u7406\\u5B8C\\u4E00\\u5757\\uFF0C\\u6B47\\u4E00\\u4E0B"]\n }\n ]\n },\n {\n id: "outside-walk",\n name: "\\u548C\\u540C\\u4E8B\\u51FA\\u53BB\\u6E9C\\u8FBE",\n participant: { kind: "person", roles: ["colleague"], states: ["idle", "waiting", "done"], minAgents: 2 },\n requires: [{ capability: "entry" }],\n onlyWhenSessionIdle: true,\n interruptible: true,\n startBubble: "\\u51FA\\u53BB\\u8D70\\u8D70\\uFF1F",\n initialDelayMs: [1e4, 16e3],\n cooldownMs: [46e3, 72e3],\n steps: [\n {\n target: "walk-outside-a",\n targets: ["walk-outside-a", "walk-outside-b"],\n durationMs: 6500,\n pose: "stand",\n bubbles: ["\\u900F\\u53E3\\u6C14", "\\u8D70\\u4E00\\u5708\\u518D\\u56DE\\u6765"]\n }\n ]\n },\n {\n id: "phone-break",\n name: "\\u6478\\u9C7C\\u73A9\\u624B\\u673A",\n participant: { kind: "person", roles: ["colleague"], states: ["idle", "waiting", "done"] },\n requires: [],\n onlyWhenSessionIdle: true,\n interruptible: true,\n startBubble: "\\u6478\\u4F1A\\u513F\\u9C7C",\n initialDelayMs: [5500, 9e3],\n cooldownMs: [26e3, 44e3],\n steps: [\n { target: "phone-break", durationMs: 4800, pose: "phone", bubble: "\\u5237\\u4E00\\u4E0B\\u624B\\u673A" }\n ]\n },\n {\n id: "restroom-break",\n name: "\\u53BB\\u6D17\\u624B\\u95F4",\n participant: { kind: "person", roles: ["colleague"], states: ["idle", "waiting", "done"] },\n requires: [{ capability: "restroom" }],\n onlyWhenSessionIdle: true,\n interruptible: true,\n startBubble: "\\u53BB\\u8D9F\\u6D17\\u624B\\u95F4",\n initialDelayMs: [7e3, 12e3],\n cooldownMs: [42e3, 7e4],\n steps: [\n { target: "restroom", durationMs: 4200, pose: "stand", bubble: "\\u7A0D\\u7B49\\u4E00\\u4E0B" }\n ]\n },\n {\n id: "cleaning-round",\n name: "\\u4FDD\\u6D01\\u5DE1\\u56DE",\n participant: { kind: "npc", roles: ["cleaner"] },\n requires: [],\n interruptible: false,\n initialDelayMs: [1e3, 1800],\n cooldownMs: [5200, 8200],\n steps: [\n { target: "clean-desks-north", durationMs: 2200, pose: "reach", bubble: "\\u6574\\u7406\\u4E00\\u4E0B\\u684C\\u9762", particle: "spark" },\n { target: "clean-desks-south", durationMs: 2300, pose: "reach", bubble: "\\u8FD9\\u8FB9\\u4E5F\\u6536\\u62FE\\u597D", particle: "spark" },\n { target: "clean-lounge", durationMs: 2400, pose: "reach", bubble: "\\u4F11\\u606F\\u533A\\u6574\\u7406\\u597D\\u4E86", particle: "spark" }\n ]\n },\n {\n id: "plant-watering",\n name: "\\u6797\\u59E8\\u6D47\\u82B1",\n participant: { kind: "npc", roles: ["cleaner"] },\n requires: [],\n interruptible: false,\n initialDelayMs: [5e3, 9e3],\n cooldownMs: [3e4, 48e3],\n steps: [\n { target: "plant-left", durationMs: 2200, pose: "reach", bubble: "\\u7ED9\\u82B1\\u6D47\\u70B9\\u6C34", particle: "water" },\n { target: "plant-middle", durationMs: 2100, pose: "reach", bubble: "\\u559D\\u9971\\u6C34\\uFF0C\\u957F\\u5F97\\u7CBE\\u795E\\u70B9", particle: "water" },\n { target: "plant-right", durationMs: 2200, pose: "reach", bubble: "\\u8FD9\\u76C6\\u4E5F\\u4E0D\\u80FD\\u843D\\u4E0B", particle: "water" },\n { target: "cleaner-entry", durationMs: 1500, pose: "stand" }\n ]\n }\n ]\n };\n\n // ../web/v2/content/atmospheres/default.json\n var default_default = {\n schemaVersion: 1,\n kind: "atmosphere",\n id: "builtin/default-atmosphere",\n name: "\\u539F\\u7248\\u73AF\\u5883",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n contract: "single-office-v1",\n styleOverrides: { css: {} },\n windowScene: "classic",\n ambientEffects: [],\n audio: null\n };\n\n // ../web/v2/content/environments/local-office.json\n var local_office_default = {\n schemaVersion: 1,\n kind: "environment",\n id: "builtin/local-office-environment",\n name: "\\u672C\\u5730\\u771F\\u5B9E\\u73AF\\u5883",\n version: "1.0.0",\n engine: { min: "0.2.0" },\n render: { dynamicTime: true, dynamicWeather: true },\n clock: {\n mode: "local",\n phases: [\n { id: "morning", start: "06:00" },\n { id: "noon", start: "11:00" },\n { id: "evening", start: "17:00" },\n { id: "night", start: "20:00" }\n ],\n preview: { enabled: true, startTime: "06:00", durationMs: 24e3 }\n },\n weather: {\n source: "runtime",\n fallback: "clear",\n allowedConditions: ["clear", "cloudy", "rain", "snow"]\n },\n lighting: { enabled: true, activePhases: ["evening", "night"] },\n npcSchedule: {\n enabled: true,\n defaultShift: { start: "06:00", end: "18:30", endLatest: "21:30" },\n roleOverrides: {},\n labels: { arrival: "\\u4E0A\\u73ED\\u5566", departure: "\\u4E0B\\u73ED\\u5566" }\n }\n };\n\n // ../web/v2/locales/en.json\n var en_default = {\n locale: "en",\n strings: {\n "connection.connecting": "Connecting\\u2026",\n "connection.connected": "Connected",\n "connection.reconnecting": "Reconnecting\\u2026",\n "nav.preset": "Office preset",\n "nav.soundOff": "Sound: off",\n "nav.soundOn": "Sound: on",\n "nav.demo": "Demo",\n "nav.language": "Switch language",\n "task.label": "Current task",\n "task.empty": "Waiting for a task\\u2026",\n "panel.crew": "Team",\n "panel.activity": "Activity",\n "replay.day": "{name}\'s Day",\n "replay.playing": "Replaying at 3\\xD7",\n "replay.empty": "No history yet",\n "replay.noActivity": "No activity to replay",\n "replay.progress": "Replay {current}/{total} \\xB7 3\\xD7",\n "replay.complete": "Replay complete \\xB7 Idle",\n "approval.deny": "Deny",\n "approval.allow": "Allow",\n "client.model": "Codex model",\n "client.loadingModels": "Loading models\\u2026",\n "client.prompt": "Give Codex a task\\u2026",\n "client.stop": "Stop",\n "client.stopping": "Stopping\\u2026",\n "client.send": "Send",\n "status.running": "running",\n "state.idle": "Idle",\n "state.thinking": "Thinking",\n "state.working": "Working",\n "state.waiting": "Waiting",\n "state.talking": "Reporting",\n "state.done": "Done",\n "state.error": "Error",\n "npc.arrival": "Starting my shift",\n "npc.departure": "Heading home",\n "agent.joined": "I\'m here",\n "agent.teammate": "{name}\'s Teammate{index}",\n "delegate.owner": "{name} will take this",\n "delegate.assign": "Assign: {task}",\n "config.error": "Configuration error"\n },\n text: {\n "\\u539F\\u7248\\u57FA\\u51C6\\uFF08Legacy\\uFF09": "Original Baseline (Legacy)",\n \\u6709\\u751F\\u6D3B\\u7684\\u50CF\\u7D20\\u529E\\u516C\\u5BA4: "Lively Pixel Office",\n \\u96E8\\u591C\\u52A0\\u73ED\\u529E\\u516C\\u5BA4: "Rainy Night Office",\n \\u6696\\u8C03\\u521B\\u610F\\u5DE5\\u4F5C\\u5BA4: "Warm Creative Studio",\n \\u554A\\u6D3E: "A Pi",\n \\u963F\\u6D3E: "A Pi",\n \\u79D1\\u8FEA: "Cody",\n \\u8001\\u677F: "Boss",\n \\u6797\\u59E8: "Lin",\n \\u9648\\u59D0: "Chen",\n \\u8001\\u5468: "Zhou",\n \\u4E3B\\u7BA1: "Lead",\n \\u5916\\u63F4: "Teammate",\n \\u4FDD\\u6D01: "Cleaner",\n \\u4F1A\\u8BAE\\u4E3B\\u6301: "Meeting host",\n \\u884C\\u653F\\u79D8\\u4E66: "Executive assistant",\n \\u4F1A\\u8BAE\\u670D\\u52A1: "Meeting support",\n \\u4FA6\\u5BDF\\u5458: "Scout",\n \\u89C4\\u5212\\u5E08: "Planner",\n \\u8BC4\\u5BA1\\u5458: "Reviewer",\n \\u5DE5\\u7A0B\\u5E08: "Engineer",\n \\u6587\\u6848: "Writer",\n \\u7A0B\\u5E8F\\u5458: "Developer",\n \\u8BBE\\u8BA1\\u5E08: "Designer",\n \\u987E\\u95EE: "Advisor",\n \\u524D\\u53F0\\u79D8\\u4E66: "Receptionist",\n \\u540E\\u52E4: "Facilities",\n \\u7EFC\\u5408\\u529E\\u516C\\u533A: "General Office",\n \\u4E3B\\u4EFB\\u5BA4: "Manager\'s Office",\n \\u5C0F\\u4F1A\\u8BAE\\u5BA4: "Meeting Room",\n \\u6863\\u6848\\u5BA4: "Records",\n \\u673A\\u623F: "Server Room",\n \\u884C\\u653F\\u524D\\u53F0: "Reception",\n \\u8336\\u6C34\\u95F4: "Break Room",\n \\u4E0A\\u73ED\\u5566: "Starting my shift",\n \\u4E0B\\u73ED\\u5566: "Heading home",\n "\\u804A\\u4E24\\u53E5\\uFF1F": "Quick chat?",\n "\\u6700\\u8FD1\\u8FDB\\u5C55\\u600E\\u4E48\\u6837\\uFF1F": "How is it going?",\n "\\u521A\\u5904\\u7406\\u5B8C\\u4E00\\u5757\\uFF0C\\u6B47\\u4E00\\u4E0B": "Wrapped up a piece\\u2014taking a breather",\n "\\u51FA\\u53BB\\u8D70\\u8D70\\uFF1F": "Want to take a walk?",\n \\u900F\\u53E3\\u6C14: "Getting some fresh air",\n \\u8D70\\u4E00\\u5708\\u518D\\u56DE\\u6765: "One lap, then back to work",\n \\u6478\\u4F1A\\u513F\\u9C7C: "Taking a quick break",\n \\u5237\\u4E00\\u4E0B\\u624B\\u673A: "Checking my phone",\n \\u53BB\\u8D9F\\u6D17\\u624B\\u95F4: "Heading to the restroom",\n \\u7A0D\\u7B49\\u4E00\\u4E0B: "Back in a moment",\n \\u6574\\u7406\\u4E00\\u4E0B\\u684C\\u9762: "Tidying the desks",\n \\u8FD9\\u8FB9\\u4E5F\\u6536\\u62FE\\u597D: "This area is tidy too",\n \\u4F11\\u606F\\u533A\\u6574\\u7406\\u597D\\u4E86: "The lounge is all set",\n \\u63A5\\u5230\\u65B0\\u9700\\u6C42: "New task received",\n \\u6784\\u601D\\u4E2D: "Planning",\n \\u601D\\u8003\\u4E2D: "Thinking",\n \\u6C47\\u62A5\\u4E2D: "Preparing an update",\n \\u7EE7\\u7EED\\u63A8\\u8FDB: "Continuing",\n \\u5904\\u7406\\u62A5\\u9519: "Handling an error",\n \\u4EFB\\u52A1\\u5931\\u8D25: "Task failed",\n \\u5F85\\u547D: "Standing by",\n \\u4FEE\\u6539\\u4EE3\\u7801: "Editing code",\n \\u8054\\u7F51\\u68C0\\u7D22: "Searching the web",\n \\u67E5\\u770B\\u56FE\\u7247: "Viewing an image",\n \\u534F\\u8C03\\u540C\\u4E8B: "Coordinating teammates",\n \\u66F4\\u65B0\\u4EFB\\u52A1\\u677F: "Updating the task board",\n \\u4EFB\\u52A1\\u542F\\u52A8\\u5931\\u8D25: "Task failed to start",\n \\u8C03\\u6574\\u65B9\\u6848: "Adjusting the plan",\n \\u7B49\\u5F85\\u4F60\\u7684\\u786E\\u8BA4: "Waiting for your approval",\n \\u6267\\u884C\\u547D\\u4EE4: "Run command",\n \\u4FEE\\u6539\\u6587\\u4EF6: "Edit files",\n "\\u5141\\u8BB8\\u6267\\u884C\\u547D\\u4EE4\\uFF1F": "Allow this command?",\n "\\u5141\\u8BB8\\u4FEE\\u6539\\u6587\\u4EF6\\uFF1F": "Allow these file changes?",\n \\u5148\\u770B\\u4ECA\\u5929\\u7684\\u91CD\\u70B9: "Let\'s review today\'s priorities",\n \\u8FD9\\u91CC\\u9700\\u8981\\u91CD\\u70B9\\u63A8\\u8FDB: "This needs focused progress",\n \\u8D1F\\u8D23\\u4EBA\\u6309\\u8282\\u70B9\\u540C\\u6B65: "Owners, report at each milestone",\n \\u53BB\\u63A5\\u676F\\u6C34: "Getting some water",\n \\u559D\\u53E3\\u6C34\\u7EE7\\u7EED: "A sip, then back to it",\n "\\u63A5\\u676F\\u6C34\\uFF0C\\u6B47\\u4E00\\u4E0B": "Getting water and taking a breather",\n \\u66F4\\u65B0\\u8BAE\\u7A0B: "Updating the agenda",\n \\u8D44\\u6599\\u653E\\u684C\\u4E0A\\u4E86: "The materials are on the table",\n \\u8865\\u5145\\u5496\\u5561: "Refilling the coffee",\n \\u8BF7\\u7528\\u8336: "Tea is ready",\n "\\u51FA\\u53BB\\u804A\\u4E24\\u53E5\\uFF1F": "Quick chat in the hall?",\n "\\u8FD9\\u4E2A\\u6587\\u4EF6\\u4F60\\u770B\\u4E86\\u5417\\uFF1F": "Have you reviewed this file?",\n "\\u521A\\u9001\\u8FC7\\u6765\\uFF0C\\u6B63\\u51C6\\u5907\\u770B": "It just arrived\\u2014I\'m about to review it",\n \\u6CE1\\u676F\\u8336: "Making some tea",\n \\u8336\\u7F38\\u7EED\\u4E0A\\u6C34: "Refilling the tea mug",\n "\\u5582\\uFF0C\\u8FD9\\u91CC\\u662F\\u529E\\u516C\\u5BA4": "Hello, this is the office",\n \\u590D\\u5370\\u5F52\\u6863: "Copying and filing",\n \\u76D6\\u7AE0\\u767B\\u8BB0\\u597D\\u4E86: "Stamped and registered",\n \\u770B\\u770B\\u683C\\u5B50\\u95F4: "Checking the cubicles",\n \\u6574\\u7406\\u4E00\\u4E0B: "Tidying up",\n \\u98CE\\u6247\\u522B\\u5FD8\\u4E86\\u5173: "Remember to turn off the fan",\n \\u4F1A\\u8BAE\\u5BA4\\u6536\\u597D\\u4E86: "The meeting room is ready",\n \\u64E6\\u64E6\\u684C\\u9762: "Wiping the desks",\n \\u8FD9\\u8FB9\\u4E5F\\u6536\\u62FE\\u4E00\\u4E0B: "Tidying this side too",\n \\u4F1A\\u8BAE\\u533A\\u6574\\u7406\\u597D\\u5566: "The meeting area is ready",\n \\u6696\\u8C03\\u5DE5\\u4F5C\\u5BA4\\u56E2\\u961F: "Warm Studio Team",\n \\u6D4B\\u8BD5\\u5458: "Tester",\n \\u539F\\u7248\\u50CF\\u7D20\\u5458\\u5DE5: "Original Pixel Team",\n \\u539F\\u7248\\u73AF\\u5883: "Original Environment",\n \\u96E8\\u591C\\u52A0\\u73ED: "Rainy Night Shift",\n \\u8001\\u677F\\u4E3B\\u6301\\u4F1A\\u8BAE: "Boss-led Meeting",\n \\u4F1A\\u524D\\u8D44\\u6599: "Meeting Materials",\n \\u63A5\\u6C34: "Get Water",\n \\u8336\\u6C34\\u670D\\u52A1: "Refreshments",\n \\u6E05\\u6D01\\u5DE1\\u68C0: "Cleaning Round",\n \\u8D70\\u5ECA\\u804A\\u4E24\\u53E5: "Hallway Chat",\n \\u540E\\u52E4\\u5DE1\\u68C0: "Facilities Round",\n \\u6CE1\\u8336: "Make Tea",\n \\u6587\\u4EF6\\u6D41\\u8F6C: "Document Run",\n \\u8001\\u677F\\u7ED9\\u5458\\u5DE5\\u6253\\u6C14: "Team Pep Talk",\n "\\u5144\\u5F1F\\u4EEC\\u52A0\\u6CB9\\u52AA\\u529B\\u5E72\\uFF01": "Team, let\'s keep pushing!",\n "\\u5927\\u5BB6\\u52A0\\u6CB9\\uFF0C\\u51B2\\u4E00\\u51B2\\uFF01": "Come on, team\\u2014let\'s go!",\n \\u8001\\u677F\\u5DE1\\u89C6\\u89C4\\u5212: "Boss Planning Round",\n \\u770B\\u770B\\u4ECA\\u5929\\u7684\\u8BA1\\u5212: "Let\'s review today\'s plan",\n \\u8001\\u677F\\u63A5\\u6C34: "Boss Gets Water",\n \\u63A5\\u676F\\u6C34: "Getting a glass of water",\n \\u4FDD\\u6D01\\u5DE1\\u56DE: "Cleaner Round",\n \\u548C\\u540C\\u4E8B\\u804A\\u804A\\u5929: "Chat with a colleague",\n \\u548C\\u540C\\u4E8B\\u51FA\\u53BB\\u6E9C\\u8FBE: "Take a walk with a colleague",\n \\u6478\\u9C7C\\u73A9\\u624B\\u673A: "Take a phone break",\n \\u6797\\u59E8\\u6D47\\u82B1: "Lin Waters the Plants",\n \\u7ED9\\u82B1\\u6D47\\u70B9\\u6C34: "Giving the plants some water",\n "\\u559D\\u9971\\u6C34\\uFF0C\\u957F\\u5F97\\u7CBE\\u795E\\u70B9": "Drink up and grow strong",\n \\u8FD9\\u76C6\\u4E5F\\u4E0D\\u80FD\\u843D\\u4E0B: "Can\'t forget this one",\n \\u53BB\\u6D17\\u624B\\u95F4: "Visit the Restroom",\n \\u672C\\u5730\\u771F\\u5B9E\\u73AF\\u5883: "Local Live Environment",\n \\u56FA\\u5B9A\\u96E8\\u591C\\u73AF\\u5883: "Fixed Rainy Night Environment",\n \\u9759\\u6001\\u517C\\u5BB9\\u73AF\\u5883: "Static Compatibility Environment",\n \\u5927\\u4F1A\\u8BAE\\u5BA4\\u957F\\u684C\\u529E\\u516C\\u5E03\\u5C40: "Boardroom Layout",\n \\u539F\\u7248\\u529E\\u516C\\u5BA4\\u5E03\\u5C40: "Original Office Layout",\n \\u6709\\u751F\\u6D3B\\u7684\\u539F\\u7248\\u529E\\u516C\\u5BA4\\u5E03\\u5C40: "Lively Original Office Layout",\n \\u8001\\u6D3E\\u4F01\\u4E1A\\u529E\\u516C\\u5BA4\\u5E03\\u5C40: "Old-school Office Layout",\n "Tech \\u5F00\\u653E\\u5F0F\\u529E\\u516C\\u5BA4\\u5E03\\u5C40": "Tech Open Office Layout",\n \\u5927\\u4F1A\\u8BAE\\u5BA4\\u65E5\\u5E38: "Boardroom Routines",\n \\u4E0D\\u542F\\u7528\\u529E\\u516C\\u5BA4\\u751F\\u6D3B: "Office Life Disabled",\n \\u57FA\\u7840\\u529E\\u516C\\u5BA4\\u751F\\u6D3B: "Basic Office Routines",\n \\u8001\\u6D3E\\u529E\\u516C\\u5BA4\\u65E5\\u5E38: "Old-school Office Routines",\n "Tech \\u529E\\u516C\\u5BA4\\u65E5\\u5E38": "Tech Office Routines",\n \\u4F1A\\u8BAE\\u5BA4\\u652F\\u6301\\u4EBA\\u5458: "Boardroom Support Staff",\n \\u79D8\\u4E66: "Secretary",\n \\u4F1A\\u52A1: "Meeting Attendant",\n "\\u4E0D\\u542F\\u7528 NPC": "NPCs Disabled",\n \\u529E\\u516C\\u5BA4\\u57FA\\u7840\\u5458\\u5DE5: "Basic Office Staff",\n \\u8001\\u6D3E\\u529E\\u516C\\u5BA4\\u804C\\u5458: "Old-school Office Staff",\n "Tech \\u529E\\u516C\\u5BA4 NPC": "Tech Office NPCs",\n "Agent Live \\u539F\\u7248": "Agent Live Original",\n \\u539F\\u7248\\u529E\\u516C\\u5BA4\\u7269\\u4EF6: "Original Office Props",\n \\u6269\\u5C55\\u529E\\u516C\\u5BA4\\u7269\\u4EF6: "Extended Office Props",\n "Tech \\u5F00\\u653E\\u5F0F\\u529E\\u516C\\u5BA4\\u7269\\u4EF6": "Tech Open Office Props",\n \\u7ECF\\u5178\\u50CF\\u7D20: "Classic Pixel"\n },\n patterns: [\n { source: "^(.+) \\u4E0A\\u73ED\\u4E86$", replace: "$1 started work" },\n { source: "^(.+) \\u4EA4\\u4ED8\\u5B8C\\u6210\\uFF0C\\u4E0B\\u73ED$", replace: "$1 finished and signed off" },\n { source: "^(.+) \\u5F02\\u5E38\\u9000\\u51FA$", replace: "$1 exited unexpectedly" },\n { source: "^\\u67E5\\u9605 (.+)$", replace: "Read $1" },\n { source: "^\\u7FFB\\u627E (.+)$", replace: "Browse $1" },\n { source: "^\\u68C0\\u7D22 (.+)$", replace: "Search $1" },\n { source: "^\\u65B0\\u5EFA (.+)$", replace: "Create $1" },\n { source: "^\\u4FEE\\u6539 (.+)$", replace: "Edit $1" },\n { source: "^\\u6267\\u884C (.+)$", replace: "Run $1" },\n { source: "^\\u7B49\\u5F85 (.+) \\u4F4D\\u540C\\u4E8B$", replace: "Waiting for $1 teammates" }\n ]\n };\n\n // ../web/v2/locales/zh-CN.json\n var zh_CN_default = {\n locale: "zh-CN",\n strings: {\n "connection.connecting": "\\u8FDE\\u63A5\\u4E2D\\u2026",\n "connection.connected": "\\u5DF2\\u8FDE\\u63A5",\n "connection.reconnecting": "\\u91CD\\u8FDE\\u4E2D\\u2026",\n "nav.preset": "\\u529E\\u516C\\u5BA4\\u9884\\u8BBE",\n "nav.soundOff": "\\u97F3\\u6548: \\u5173",\n "nav.soundOn": "\\u97F3\\u6548: \\u5F00",\n "nav.demo": "\\u6F14\\u793A",\n "nav.language": "\\u5207\\u6362\\u8BED\\u8A00",\n "task.label": "\\u5F53\\u524D\\u9700\\u6C42",\n "task.empty": "\\u7B49\\u5F85\\u6307\\u4EE4\\u2026",\n "panel.crew": "\\u5458\\u5DE5",\n "panel.activity": "\\u52A8\\u6001",\n "replay.day": "{name} \\u7684\\u4E00\\u5929",\n "replay.playing": "\\u6B63\\u5728\\u4EE5 3\\xD7 \\u56DE\\u653E",\n "replay.empty": "\\u6682\\u65E0\\u5386\\u53F2",\n "replay.noActivity": "\\u6682\\u65E0\\u53EF\\u56DE\\u653E\\u4EFB\\u52A1",\n "replay.progress": "\\u56DE\\u653E {current}/{total} \\xB7 3\\xD7",\n "replay.complete": "\\u56DE\\u653E\\u5B8C\\u6210 \\xB7 \\u7A7A\\u95F2",\n "approval.deny": "\\u62D2\\u7EDD",\n "approval.allow": "\\u5141\\u8BB8",\n "client.model": "Codex \\u6A21\\u578B",\n "client.loadingModels": "\\u52A0\\u8F7D\\u6A21\\u578B\\u2026",\n "client.prompt": "\\u7ED9 Codex \\u4E00\\u4E2A\\u4EFB\\u52A1\\u2026",\n "client.stop": "\\u505C\\u6B62",\n "client.stopping": "\\u6B63\\u5728\\u505C\\u6B62\\u2026",\n "client.send": "\\u53D1\\u9001",\n "status.running": "\\u8FD0\\u884C\\u4E2D",\n "state.idle": "\\u5F85\\u547D",\n "state.thinking": "\\u601D\\u8003",\n "state.working": "\\u5DE5\\u4F5C",\n "state.waiting": "\\u7B49\\u5F85",\n "state.talking": "\\u6C47\\u62A5",\n "state.done": "\\u5B8C\\u6210",\n "state.error": "\\u51FA\\u9519",\n "npc.arrival": "\\u4E0A\\u73ED\\u5566",\n "npc.departure": "\\u4E0B\\u73ED\\u5566",\n "agent.joined": "\\u6211\\u6765\\u4E86",\n "agent.teammate": "{name} \\u7684\\u540C\\u4E8B{index}",\n "delegate.owner": "{name}\\u6765\\u8D1F\\u8D23\\u8FD9\\u5757",\n "delegate.assign": "\\u6D3E\\u6D3B\\uFF1A{task}",\n "config.error": "\\u914D\\u7F6E\\u9519\\u8BEF"\n },\n text: {\n tech: "\\u79D1\\u6280",\n meetingroom: "\\u4F1A\\u8BAE\\u5BA4",\n oldschool: "\\u683C\\u5B50\\u95F4"\n },\n patterns: []\n };\n\n // src/frame-runtime.ts\n var fallbackContent = {\n preset: tech_open_office_default,\n style: pixel_classic_default,\n layout: tech_open_office_default2,\n agentSkin: tiny_developers_default,\n props: tech_office_default,\n npcs: tech_office_staff_default,\n lifeActivities: tech_office_routines_default,\n atmosphere: default_default,\n environment: local_office_default,\n agentProfile: { template: "builtin/host-agent", appearance: {} }\n };\n var injected = window.AgentLiveInitialContent;\n var content = injected ?? fallbackContent;\n var contentProblems = graphIssueMessages(content);\n if (contentProblems.length) {\n reportUnrenderableContent(contentProblems, !injected);\n } else {\n const listeners = /* @__PURE__ */ new Set();\n let latest = null;\n const interpolate = (source, vars = {}) => {\n let value = source;\n for (const [key, replacement] of Object.entries(vars)) value = value.replaceAll(`{${key}}`, String(replacement));\n return value;\n };\n const bundles = { en: en_default, "zh-CN": zh_CN_default };\n const initialLocale = window.AgentLiveInitialLocale === "zh-CN" ? "zh-CN" : "en";\n let activeLocale = initialLocale;\n let locale = bundles[activeLocale];\n const i18n = {\n locale: activeLocale,\n t(key, vars) {\n return interpolate(locale.strings?.[key] ?? key, vars);\n },\n text(value) {\n const source = String(value ?? "");\n const exact = locale.text?.[source];\n if (exact) return exact;\n for (const pattern of locale.patterns ?? []) {\n const expression = new RegExp(pattern.source);\n if (expression.test(source)) return source.replace(expression, pattern.replace);\n }\n return source;\n }\n };\n window.AgentLiveI18n = i18n;\n const applyLocale = (next) => {\n activeLocale = next;\n locale = bundles[next];\n i18n.locale = next;\n document.documentElement.lang = next;\n for (const element of document.querySelectorAll("[data-i18n]")) {\n element.textContent = i18n.t(element.dataset.i18n ?? "");\n }\n const language = document.getElementById("language");\n if (language) {\n language.textContent = next === "en" ? "\\u4E2D\\u6587" : "EN";\n language.setAttribute("aria-label", i18n.t("nav.language"));\n }\n window.dispatchEvent(new CustomEvent("agent-live:locale", { detail: { locale: next } }));\n };\n document.getElementById("language")?.addEventListener("click", () => {\n const next = activeLocale === "en" ? "zh-CN" : "en";\n applyLocale(next);\n parent.postMessage({ source: "agent-live-dsh-frame", type: "locale", locale: next }, "*");\n });\n applyLocale(initialLocale);\n window.AgentLiveClientKind = "dsh";\n window.OfficeContent = content;\n window.SceneLimits = { ...SCENE_LIMITS };\n const environmentRuntime = createEnvironmentRuntime(content.environment);\n window.OfficeEnvironment = environmentRuntime;\n window.Office = createOfficeRenderer(content, environmentRuntime);\n window.Sprites = createSpriteRenderer(content);\n window.AgentLiveSubscribe = (listener) => {\n listeners.add(listener);\n if (latest) listener(latest);\n return () => listeners.delete(listener);\n };\n window.AgentLiveGetSnapshot = async () => latest;\n window.addEventListener("message", (message) => {\n if (message.source !== parent || message.data?.source !== "agent-live-dsh" || !message.data.event) return;\n const event = message.data.event;\n if (event.type === "snapshot") latest = event;\n else if (latest && typeof latest === "object") {\n const snapshot = latest;\n snapshot.history = [...snapshot.history ?? [], { at: Date.now(), event }].slice(-800);\n }\n for (const listener of listeners) listener(event);\n });\n void Promise.resolve().then(() => (init_app(), app_exports)).then(() => parent.postMessage({ source: "agent-live-dsh-frame", type: "ready" }, "*"));\n }\n function reportUnrenderableContent(problems, fatal) {\n const message = `Agent Live content is not renderable \\u2014 ${problems.join("; ")}`;\n console.error(message);\n renderFailureState(message);\n parent.postMessage({ source: "agent-live-dsh-frame", type: "invalid-content", problems }, "*");\n if (fatal) throw new Error(message);\n }\n function renderFailureState(message) {\n const host = document.getElementById("stageWrap") ?? document.body;\n const panel = document.createElement("div");\n panel.setAttribute("role", "alert");\n panel.dataset.agentLiveError = "content";\n panel.style.cssText = [\n "position:absolute",\n "inset:12px",\n "z-index:5",\n "display:flex",\n "flex-direction:column",\n "justify-content:center",\n "gap:8px",\n "padding:16px",\n "overflow:auto",\n "background:rgba(13,16,23,0.94)",\n "border:1px solid #5c3a2c",\n "color:#ff8f6b",\n "font:12px/1.6 ui-monospace,SFMono-Regular,Menlo,monospace",\n "white-space:pre-wrap"\n ].join(";");\n const title = document.createElement("strong");\n title.textContent = "Agent Live \\u65E0\\u6CD5\\u6E32\\u67D3\\u8FD9\\u4EFD\\u5185\\u5BB9";\n const detail = document.createElement("span");\n detail.textContent = message;\n panel.append(title, detail);\n host.appendChild(panel);\n }\n})();\n<\/script>\n</body>\n</html>\n';
|
|
42
|
+
function frameDocument(content, locale) {
|
|
43
|
+
return document.replace("__AGENT_LIVE_CONTENT_URI__", encodeURIComponent(JSON.stringify(content ?? null))).replace("__AGENT_LIVE_LOCALE__", locale === "zh-CN" ? "zh-CN" : "en");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ../src/core/mapping.ts
|
|
47
|
+
var DELEGATION_TOOLS = /^(subagent|task|dispatch_agent|spawn_agent|agent)$/i;
|
|
48
|
+
var ACTION_BY_TOOL = [
|
|
49
|
+
[/^(read|ls|glob|grep|find|rg|search_files|list)/i, "archive"],
|
|
50
|
+
[/^(write|edit|multi_edit|apply_patch|create|notebook)/i, "type"],
|
|
51
|
+
[/^(bash|shell|exec|run|terminal)/i, "server"],
|
|
52
|
+
[/^(todo|plan|task_list|note)/i, "whiteboard"],
|
|
53
|
+
[/(browser|web|fetch|http|curl|search)/i, "phone"],
|
|
54
|
+
[/^(ask_user|question|confirm)/i, "phone"]
|
|
55
|
+
];
|
|
56
|
+
function isDelegationTool(toolName) {
|
|
57
|
+
return DELEGATION_TOOLS.test(toolName);
|
|
58
|
+
}
|
|
59
|
+
function actionForTool(toolName) {
|
|
60
|
+
if (isDelegationTool(toolName)) return "delegate";
|
|
61
|
+
for (const [pattern, action] of ACTION_BY_TOOL) {
|
|
62
|
+
if (pattern.test(toolName)) return action;
|
|
63
|
+
}
|
|
64
|
+
return "type";
|
|
65
|
+
}
|
|
66
|
+
function short(value, max = 64) {
|
|
67
|
+
if (value === void 0 || value === null) return "";
|
|
68
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
69
|
+
const clean = text.replace(/\s+/g, " ").trim();
|
|
70
|
+
return clean.length > max ? `${clean.slice(0, max)}\u2026` : clean;
|
|
71
|
+
}
|
|
72
|
+
function baseName(p) {
|
|
73
|
+
const text = short(p, 120);
|
|
74
|
+
if (!text) return "";
|
|
75
|
+
const parts = text.split("/");
|
|
76
|
+
return parts.length > 2 ? `\u2026/${parts.slice(-2).join("/")}` : text;
|
|
77
|
+
}
|
|
78
|
+
function labelForTool(toolName, args = {}) {
|
|
79
|
+
const a = args ?? {};
|
|
80
|
+
switch (true) {
|
|
81
|
+
case /^read/i.test(toolName):
|
|
82
|
+
return `\u67E5\u9605 ${baseName(a.path ?? a.file ?? a.filePath)}`;
|
|
83
|
+
case /^(ls|list|glob|find)/i.test(toolName):
|
|
84
|
+
return `\u7FFB\u627E ${baseName(a.path ?? a.pattern ?? a.glob_pattern ?? ".")}`;
|
|
85
|
+
case /^(grep|rg|search)/i.test(toolName):
|
|
86
|
+
return `\u68C0\u7D22 /${short(a.pattern ?? a.query, 32)}/`;
|
|
87
|
+
case /^(write|create)/i.test(toolName):
|
|
88
|
+
return `\u65B0\u5EFA ${baseName(a.path)}`;
|
|
89
|
+
case /^(edit|multi_edit|apply_patch)/i.test(toolName):
|
|
90
|
+
return `\u4FEE\u6539 ${baseName(a.path ?? a.file)}`;
|
|
91
|
+
case /^(bash|shell|exec|run|terminal)/i.test(toolName):
|
|
92
|
+
return `\u6267\u884C ${short(a.command ?? a.cmd, 56)}`;
|
|
93
|
+
case /^(todo|plan|task_list)/i.test(toolName):
|
|
94
|
+
return "\u66F4\u65B0\u4EFB\u52A1\u677F";
|
|
95
|
+
case /browser/i.test(toolName):
|
|
96
|
+
return `\u8054\u7F51 ${short(a.url ?? a.args ?? a.action ?? "browser", 40)}`;
|
|
97
|
+
case /^(web|fetch|http|curl)/i.test(toolName):
|
|
98
|
+
return `\u8054\u7F51 ${short(a.url ?? a.search_term ?? a.query, 40)}`;
|
|
99
|
+
case /^(ask_user|question|confirm)/i.test(toolName):
|
|
100
|
+
return `\u8BF7\u793A ${short(a.question, 40)}`;
|
|
101
|
+
case isDelegationTool(toolName):
|
|
102
|
+
return `\u6D3E\u6D3B ${short(describeDelegation(a).map((t) => t.agent).join(", "), 40)}`;
|
|
103
|
+
default: {
|
|
104
|
+
const hint = short(a.path ?? a.command ?? a.query ?? a.name, 40);
|
|
105
|
+
return hint ? `${toolName} ${hint}` : toolName;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function describeDelegation(args = {}) {
|
|
110
|
+
const a = args ?? {};
|
|
111
|
+
const out = [];
|
|
112
|
+
const push = (agent, task, slot) => {
|
|
113
|
+
out.push({
|
|
114
|
+
agent: String(agent ?? "worker"),
|
|
115
|
+
task: short(task ?? "", 200) || "(\u672A\u63CF\u8FF0\u7684\u4EFB\u52A1)",
|
|
116
|
+
slot
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
if (Array.isArray(a.tasks)) {
|
|
120
|
+
a.tasks.forEach((t, i) => push(t?.agent, t?.task, i));
|
|
121
|
+
} else if (Array.isArray(a.chain)) {
|
|
122
|
+
a.chain.forEach((t, i) => push(t?.agent, t?.task, i));
|
|
123
|
+
} else if (a.agent || a.task || a.prompt || a.description) {
|
|
124
|
+
push(a.agent ?? a.subagent_type, a.task ?? a.prompt ?? a.description, 0);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ../src/core/limits.ts
|
|
130
|
+
var SCENE_LIMITS = Object.freeze({
|
|
131
|
+
agents: 16,
|
|
132
|
+
seats: 8,
|
|
133
|
+
npcs: 12,
|
|
134
|
+
props: 80,
|
|
135
|
+
animatedProps: 24,
|
|
136
|
+
activities: 16,
|
|
137
|
+
effects: 40,
|
|
138
|
+
visibleBubbles: 4,
|
|
139
|
+
queuedBubbles: 8
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// src/adapter.ts
|
|
143
|
+
var short2 = (value, max) => {
|
|
144
|
+
const clean = value.replace(/\s+/g, " ").trim();
|
|
145
|
+
return clean.length > max ? `${clean.slice(0, max)}\u2026` : clean;
|
|
146
|
+
};
|
|
147
|
+
var OBSERVATION_WINDOW = 4e3;
|
|
148
|
+
function sessionOf(input, startedAt) {
|
|
149
|
+
return {
|
|
150
|
+
cwd: "",
|
|
151
|
+
...input.model ? { model: input.model } : {},
|
|
152
|
+
...input.thinkingLevel ? { thinkingLevel: input.thinkingLevel } : {},
|
|
153
|
+
busy: input.running,
|
|
154
|
+
turns: Math.max(0, Math.trunc(input.turns)),
|
|
155
|
+
startedAt
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function mainAgent(input, startedAt) {
|
|
159
|
+
const lastTask = [...input.messages].reverse().find((item) => item.kind === "user")?.text;
|
|
160
|
+
const activeTool = input.tools.find((tool) => tool.running);
|
|
161
|
+
const hasError = input.messages.at(-1)?.kind === "turn-error";
|
|
162
|
+
const state = hasError ? "error" : activeTool ? "working" : input.running ? "thinking" : "idle";
|
|
163
|
+
return {
|
|
164
|
+
id: `dsh:${input.sessionId}`,
|
|
165
|
+
name: "DeepSeek",
|
|
166
|
+
role: "Main Agent",
|
|
167
|
+
state,
|
|
168
|
+
...lastTask ? { task: short2(lastTask, 300) } : {},
|
|
169
|
+
...activeTool ? {
|
|
170
|
+
action: actionForTool(activeTool.name),
|
|
171
|
+
detail: labelForTool(activeTool.name, activeTool.args)
|
|
172
|
+
} : {},
|
|
173
|
+
...input.model ? { model: input.model } : {},
|
|
174
|
+
tokens: Math.max(0, input.tokens ?? 0, ...input.messages.map((item) => item.tokens ?? 0)),
|
|
175
|
+
cost: 0,
|
|
176
|
+
toolCalls: input.tools.length,
|
|
177
|
+
joinedAt: startedAt,
|
|
178
|
+
seat: 0
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function recoveredHistory(input, mainId) {
|
|
182
|
+
const recovered = [];
|
|
183
|
+
let order = 0;
|
|
184
|
+
const record = (at, event) => recovered.push({ at, event, order: order++ });
|
|
185
|
+
for (const message of input.messages.slice(-OBSERVATION_WINDOW)) {
|
|
186
|
+
const text = short2(message.text ?? "", message.kind === "user" ? 300 : 400);
|
|
187
|
+
if (message.kind === "user" && text) record(message.at, { type: "task", id: mainId, task: text });
|
|
188
|
+
if (message.kind === "assistant" && text) record(message.at, { type: "say", id: mainId, text });
|
|
189
|
+
}
|
|
190
|
+
for (const tool of input.tools.slice(-OBSERVATION_WINDOW)) {
|
|
191
|
+
record(tool.at, {
|
|
192
|
+
type: "action",
|
|
193
|
+
id: mainId,
|
|
194
|
+
action: actionForTool(tool.name),
|
|
195
|
+
label: labelForTool(tool.name, tool.args),
|
|
196
|
+
toolCallId: tool.callId
|
|
197
|
+
});
|
|
198
|
+
if (!tool.running) record(tool.at + 1, { type: "action_end", id: mainId, toolCallId: tool.callId, ok: tool.ok !== false });
|
|
199
|
+
}
|
|
200
|
+
return recovered.sort((left, right) => left.at - right.at || left.order - right.order).slice(-OBSERVATION_WINDOW).map(({ at, event }) => ({ at, event }));
|
|
201
|
+
}
|
|
202
|
+
var DshSnapshotAdapter = class _DshSnapshotAdapter {
|
|
203
|
+
static SEEN_LIMIT = OBSERVATION_WINDOW;
|
|
204
|
+
startedAt;
|
|
205
|
+
initialized = false;
|
|
206
|
+
previousSession = "";
|
|
207
|
+
previousSessionView = "";
|
|
208
|
+
previousState = "idle";
|
|
209
|
+
previousTokens = 0;
|
|
210
|
+
seenMessages = /* @__PURE__ */ new Set();
|
|
211
|
+
seenTools = /* @__PURE__ */ new Set();
|
|
212
|
+
activeTools = /* @__PURE__ */ new Set();
|
|
213
|
+
visibleChildren = /* @__PURE__ */ new Set();
|
|
214
|
+
constructor(now = Date.now()) {
|
|
215
|
+
this.startedAt = now;
|
|
216
|
+
}
|
|
217
|
+
update(input) {
|
|
218
|
+
if (!this.initialized || this.previousSession !== input.sessionId) return [this.bootstrap(input)];
|
|
219
|
+
const output = [];
|
|
220
|
+
const mainId = `dsh:${input.sessionId}`;
|
|
221
|
+
const session = sessionOf(input, this.startedAt);
|
|
222
|
+
const sessionView = JSON.stringify(session);
|
|
223
|
+
if (sessionView !== this.previousSessionView) {
|
|
224
|
+
output.push({ type: "session", session });
|
|
225
|
+
this.previousSessionView = sessionView;
|
|
226
|
+
}
|
|
227
|
+
this.syncChildren(input, mainId, output);
|
|
228
|
+
for (const tool of input.tools.slice(-_DshSnapshotAdapter.SEEN_LIMIT)) {
|
|
229
|
+
if (tool.running && !this.activeTools.has(tool.callId)) {
|
|
230
|
+
this.seenTools.add(tool.callId);
|
|
231
|
+
this.trimSeen(this.seenTools);
|
|
232
|
+
this.activeTools.add(tool.callId);
|
|
233
|
+
output.push({
|
|
234
|
+
type: "action",
|
|
235
|
+
id: mainId,
|
|
236
|
+
action: actionForTool(tool.name),
|
|
237
|
+
label: labelForTool(tool.name, tool.args),
|
|
238
|
+
toolCallId: tool.callId
|
|
239
|
+
});
|
|
240
|
+
} else if (!tool.running) {
|
|
241
|
+
let emittedStart = false;
|
|
242
|
+
if (!this.seenTools.has(tool.callId)) {
|
|
243
|
+
this.seenTools.add(tool.callId);
|
|
244
|
+
this.trimSeen(this.seenTools);
|
|
245
|
+
output.push({ type: "action", id: mainId, action: actionForTool(tool.name), label: labelForTool(tool.name, tool.args), toolCallId: tool.callId });
|
|
246
|
+
emittedStart = true;
|
|
247
|
+
}
|
|
248
|
+
if (this.activeTools.delete(tool.callId) || emittedStart) {
|
|
249
|
+
output.push({ type: "action_end", id: mainId, toolCallId: tool.callId, ok: tool.ok !== false });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
for (const message of input.messages.slice(-_DshSnapshotAdapter.SEEN_LIMIT)) {
|
|
254
|
+
if (this.seenMessages.has(message.key)) continue;
|
|
255
|
+
this.seenMessages.add(message.key);
|
|
256
|
+
this.trimSeen(this.seenMessages);
|
|
257
|
+
const text = short2(message.text ?? "", message.kind === "user" ? 300 : 400);
|
|
258
|
+
if (message.kind === "user" && text) output.push({ type: "task", id: mainId, task: text });
|
|
259
|
+
if (message.kind === "assistant" && text) output.push({ type: "say", id: mainId, text });
|
|
260
|
+
}
|
|
261
|
+
const tokens = Math.max(0, input.tokens ?? 0, ...input.messages.map((item) => item.tokens ?? 0));
|
|
262
|
+
if (tokens !== this.previousTokens) {
|
|
263
|
+
output.push({ type: "usage", id: mainId, tokens, cost: 0 });
|
|
264
|
+
this.previousTokens = tokens;
|
|
265
|
+
}
|
|
266
|
+
const state = mainAgent(input, this.startedAt).state;
|
|
267
|
+
if (state !== this.previousState) {
|
|
268
|
+
output.push({
|
|
269
|
+
type: "agent_state",
|
|
270
|
+
id: mainId,
|
|
271
|
+
state,
|
|
272
|
+
detail: state === "idle" ? "Ready" : state === "working" ? "Using a tool" : state === "error" ? "Task failed" : "Thinking"
|
|
273
|
+
});
|
|
274
|
+
this.previousState = state;
|
|
275
|
+
}
|
|
276
|
+
return output;
|
|
277
|
+
}
|
|
278
|
+
trimSeen(values) {
|
|
279
|
+
while (values.size > _DshSnapshotAdapter.SEEN_LIMIT) values.delete(values.values().next().value);
|
|
280
|
+
}
|
|
281
|
+
bootstrap(input) {
|
|
282
|
+
this.initialized = true;
|
|
283
|
+
this.previousSession = input.sessionId;
|
|
284
|
+
this.previousSessionView = JSON.stringify(sessionOf(input, this.startedAt));
|
|
285
|
+
this.seenMessages.clear();
|
|
286
|
+
this.seenTools.clear();
|
|
287
|
+
this.activeTools.clear();
|
|
288
|
+
this.visibleChildren.clear();
|
|
289
|
+
for (const item of input.messages.slice(-_DshSnapshotAdapter.SEEN_LIMIT)) this.seenMessages.add(item.key);
|
|
290
|
+
for (const tool of input.tools.slice(-_DshSnapshotAdapter.SEEN_LIMIT)) {
|
|
291
|
+
this.seenTools.add(tool.callId);
|
|
292
|
+
if (tool.running) this.activeTools.add(tool.callId);
|
|
293
|
+
}
|
|
294
|
+
const main = mainAgent(input, this.startedAt);
|
|
295
|
+
this.previousTokens = main.tokens;
|
|
296
|
+
this.previousState = main.state;
|
|
297
|
+
const agents = [main];
|
|
298
|
+
for (const [index, child] of input.children.filter((child2) => child2.running).slice(0, SCENE_LIMITS.agents - 1).entries()) {
|
|
299
|
+
this.visibleChildren.add(child.id);
|
|
300
|
+
agents.push(this.childView(child, main.id, index + 1));
|
|
301
|
+
}
|
|
302
|
+
const log = input.messages.slice(-30).flatMap((item) => {
|
|
303
|
+
const text = short2(item.text ?? "", 400);
|
|
304
|
+
if (!text) return [];
|
|
305
|
+
return [{
|
|
306
|
+
at: item.at,
|
|
307
|
+
agentId: main.id,
|
|
308
|
+
kind: item.kind === "user" ? "system" : item.kind === "assistant" ? "say" : "system",
|
|
309
|
+
text
|
|
310
|
+
}];
|
|
311
|
+
});
|
|
312
|
+
return { type: "snapshot", agents, log, session: sessionOf(input, this.startedAt), history: recoveredHistory(input, main.id) };
|
|
313
|
+
}
|
|
314
|
+
syncChildren(input, mainId, output) {
|
|
315
|
+
const current = /* @__PURE__ */ new Set();
|
|
316
|
+
for (const [index, child] of input.children.filter((child2) => child2.running).slice(0, SCENE_LIMITS.agents - 1).entries()) {
|
|
317
|
+
current.add(child.id);
|
|
318
|
+
if (this.visibleChildren.has(child.id)) continue;
|
|
319
|
+
this.visibleChildren.add(child.id);
|
|
320
|
+
output.push({ type: "agent_join", agent: this.childView(child, mainId, index + 1) });
|
|
321
|
+
output.push({ type: "delegate", from: mainId, to: child.id, task: child.task ?? child.name });
|
|
322
|
+
}
|
|
323
|
+
for (const id of [...this.visibleChildren]) {
|
|
324
|
+
if (current.has(id)) continue;
|
|
325
|
+
this.visibleChildren.delete(id);
|
|
326
|
+
output.push({ type: "agent_leave", id });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
childView(child, parent, seat) {
|
|
330
|
+
return {
|
|
331
|
+
id: child.id,
|
|
332
|
+
name: child.name || "Teammate",
|
|
333
|
+
role: "Subagent",
|
|
334
|
+
parent,
|
|
335
|
+
state: "working",
|
|
336
|
+
...child.task ? { task: child.task } : {},
|
|
337
|
+
tokens: 0,
|
|
338
|
+
cost: 0,
|
|
339
|
+
toolCalls: 0,
|
|
340
|
+
joinedAt: this.startedAt,
|
|
341
|
+
...seat < 8 ? { seat } : {}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// src/client.tsx
|
|
347
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
348
|
+
var inject = ["slots"];
|
|
349
|
+
var viewSessions = /* @__PURE__ */ new Map();
|
|
350
|
+
var MAX_VIEW_SESSIONS = 24;
|
|
351
|
+
var LOCALE_STORAGE_KEY = "agent-live:locale";
|
|
352
|
+
function readLocale(value) {
|
|
353
|
+
return String(value ?? "").toLowerCase().startsWith("zh") ? "zh-CN" : "en";
|
|
354
|
+
}
|
|
355
|
+
function savedLocale() {
|
|
356
|
+
try {
|
|
357
|
+
return readLocale(localStorage.getItem(LOCALE_STORAGE_KEY));
|
|
358
|
+
} catch {
|
|
359
|
+
return "en";
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
var activeLocale = savedLocale();
|
|
363
|
+
var localeWatchers = /* @__PURE__ */ new Set();
|
|
364
|
+
function applyLocale(next) {
|
|
365
|
+
if (next === activeLocale) return;
|
|
366
|
+
activeLocale = next;
|
|
367
|
+
try {
|
|
368
|
+
localStorage.setItem(LOCALE_STORAGE_KEY, next);
|
|
369
|
+
} catch {
|
|
370
|
+
}
|
|
371
|
+
for (const watcher of localeWatchers) watcher(next);
|
|
372
|
+
}
|
|
373
|
+
function viewSession(id) {
|
|
374
|
+
const existing = viewSessions.get(id);
|
|
375
|
+
if (existing) {
|
|
376
|
+
existing.touchedAt = Date.now();
|
|
377
|
+
return existing;
|
|
378
|
+
}
|
|
379
|
+
const created = { adapter: new DshSnapshotAdapter(), journal: [], touchedAt: Date.now() };
|
|
380
|
+
viewSessions.set(id, created);
|
|
381
|
+
if (viewSessions.size > MAX_VIEW_SESSIONS) {
|
|
382
|
+
const oldest = [...viewSessions.entries()].sort((left, right) => left[1].touchedAt - right[1].touchedAt)[0]?.[0];
|
|
383
|
+
if (oldest && oldest !== id) viewSessions.delete(oldest);
|
|
384
|
+
}
|
|
385
|
+
return created;
|
|
386
|
+
}
|
|
387
|
+
function textContent(blocks) {
|
|
388
|
+
return blocks.flatMap((block) => {
|
|
389
|
+
if (!block || typeof block !== "object") return [];
|
|
390
|
+
const value = block;
|
|
391
|
+
return (value.type === "text" || value.kind === "text") && typeof value.text === "string" ? [value.text] : [];
|
|
392
|
+
}).join("\n").trim();
|
|
393
|
+
}
|
|
394
|
+
function tokenCount(value) {
|
|
395
|
+
if (!value || typeof value !== "object") return void 0;
|
|
396
|
+
const usage = value;
|
|
397
|
+
for (const key of ["totalTokens", "total_tokens", "total"]) {
|
|
398
|
+
const count = Number(usage[key]);
|
|
399
|
+
if (Number.isFinite(count) && count > 0) return count;
|
|
400
|
+
}
|
|
401
|
+
const input = Number(usage.inputTokens ?? usage.input_tokens ?? 0);
|
|
402
|
+
const output = Number(usage.outputTokens ?? usage.output_tokens ?? 0);
|
|
403
|
+
return input + output > 0 ? input + output : void 0;
|
|
404
|
+
}
|
|
405
|
+
function projectedTokenCount(value) {
|
|
406
|
+
if (!value || typeof value !== "object") return void 0;
|
|
407
|
+
const usage = value;
|
|
408
|
+
const total = Number(usage.uncachedInputTokens ?? 0) + Number(usage.outputTokens ?? 0) + Number(usage.cacheReadTokens ?? 0) + Number(usage.cacheWriteTokens ?? 0);
|
|
409
|
+
return Number.isFinite(total) && total > 0 ? total : void 0;
|
|
410
|
+
}
|
|
411
|
+
function messageTokenCount(messages) {
|
|
412
|
+
const total = messages.reduce((sum, message) => sum + Math.max(0, message.tokens ?? 0), 0);
|
|
413
|
+
return total > 0 ? total : void 0;
|
|
414
|
+
}
|
|
415
|
+
function messageRecord(node) {
|
|
416
|
+
if (node.kind === "user") return { key: `user:${node.seq}`, kind: "user", text: textContent(node.content), at: node.time };
|
|
417
|
+
if (node.kind === "assistant") {
|
|
418
|
+
const tokens = tokenCount(node.usage);
|
|
419
|
+
return { key: `assistant:${node.seq}`, kind: "assistant", text: textContent(node.blocks), at: node.time, ...tokens ? { tokens } : {} };
|
|
420
|
+
}
|
|
421
|
+
if (node.kind === "turn-error") return { key: `turn-error:${node.seq}`, kind: "turn-error", text: node.message, at: node.time };
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
function parseArgs(raw) {
|
|
425
|
+
try {
|
|
426
|
+
const value = JSON.parse(raw);
|
|
427
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
428
|
+
} catch {
|
|
429
|
+
return raw ? { args: raw.slice(0, 200) } : {};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
function runningTool(call) {
|
|
433
|
+
return { callId: call.callId, name: call.name, args: parseArgs(call.argsRaw), at: call.time, running: true };
|
|
434
|
+
}
|
|
435
|
+
function completedTool(node) {
|
|
436
|
+
return { callId: node.callId, name: node.call?.name ?? "tool", args: parseArgs(node.call?.argsRaw ?? ""), at: node.time, running: false, ok: !node.isError };
|
|
437
|
+
}
|
|
438
|
+
function buildObservation(input) {
|
|
439
|
+
const nodes = input.chat?.legacy.nodes ?? [];
|
|
440
|
+
const messages = nodes.map(messageRecord).filter((item) => item !== null);
|
|
441
|
+
const completed = nodes.filter((node) => node.kind === "tool-result").map(completedTool);
|
|
442
|
+
const running = (input.chat?.legacy.runningCalls ?? []).map(runningTool);
|
|
443
|
+
const runningIds = new Set(running.map((tool) => tool.callId));
|
|
444
|
+
const children = input.childCatalog.map((child, index) => {
|
|
445
|
+
const id = String(child.id);
|
|
446
|
+
const summary = input.summaries[child.id];
|
|
447
|
+
const task = child.label || summary?.displayTitle || void 0;
|
|
448
|
+
return {
|
|
449
|
+
id: `dsh:${id}`,
|
|
450
|
+
name: `Teammate ${index + 1}`,
|
|
451
|
+
...task ? { task } : {},
|
|
452
|
+
// A stopped parent turn cannot retain a live child in the office even if
|
|
453
|
+
// a lazily refreshed catalog still carries its previous activity bit.
|
|
454
|
+
running: input.running && (input.childActivity.get(id) ?? summary?.running === true)
|
|
455
|
+
};
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
sessionId: input.sessionId,
|
|
459
|
+
running: input.running,
|
|
460
|
+
turns: input.chat?.timeline.turnOrder.length ?? 0,
|
|
461
|
+
...input.model?.model ? { model: input.model.model } : {},
|
|
462
|
+
...input.model?.reasoningEffort ? { thinkingLevel: input.model.reasoningEffort } : {},
|
|
463
|
+
...input.tokens ?? messageTokenCount(messages) ? { tokens: input.tokens ?? messageTokenCount(messages) } : {},
|
|
464
|
+
messages,
|
|
465
|
+
tools: [...completed.filter((tool) => !runningIds.has(tool.callId)), ...running],
|
|
466
|
+
children
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function AgentLiveView({ sessionId, useSession, useConversation, useProjection, useSessions }) {
|
|
470
|
+
const iframe = import_react.default.useRef(null);
|
|
471
|
+
const ready = import_react.default.useRef(false);
|
|
472
|
+
const [locale, setLocale] = import_react.default.useState(activeLocale);
|
|
473
|
+
const id = String(sessionId);
|
|
474
|
+
const store = import_react.default.useMemo(() => viewSession(id), [id]);
|
|
475
|
+
const running = useSession((snapshot) => snapshot.running);
|
|
476
|
+
const chat = useConversation((snapshot) => snapshot.views.get("chat"));
|
|
477
|
+
const modelSelection = useProjection("modelSelection");
|
|
478
|
+
const tokenUsage = useProjection("tokenUsage");
|
|
479
|
+
const office = useProjection("agentLiveOffice");
|
|
480
|
+
const childCatalog = useProjection("subagentCatalog") ?? [];
|
|
481
|
+
const summaries = useSessions((snapshot) => snapshot.byId);
|
|
482
|
+
const catalog = useSessions((snapshot) => snapshot.subagentsByParent[sessionId]);
|
|
483
|
+
const childActivity = /* @__PURE__ */ new Map();
|
|
484
|
+
for (const entry of catalog?.entries ?? []) {
|
|
485
|
+
if (entry.kind === "child") childActivity.set(String(entry.id), entry.activity === "running");
|
|
486
|
+
}
|
|
487
|
+
const model = modelSelection?.next ?? modelSelection?.lastUsed ?? void 0;
|
|
488
|
+
const observation = import_react.default.useMemo(() => buildObservation({
|
|
489
|
+
sessionId: id,
|
|
490
|
+
running,
|
|
491
|
+
chat,
|
|
492
|
+
model,
|
|
493
|
+
tokens: projectedTokenCount(tokenUsage),
|
|
494
|
+
childCatalog,
|
|
495
|
+
childActivity,
|
|
496
|
+
summaries
|
|
497
|
+
}), [id, running, chat, model, tokenUsage, childCatalog, catalog, summaries]);
|
|
498
|
+
import_react.default.useEffect(() => {
|
|
499
|
+
store.touchedAt = Date.now();
|
|
500
|
+
const events = store.adapter.update(observation);
|
|
501
|
+
if (!events.length) return;
|
|
502
|
+
store.journal.push(...events);
|
|
503
|
+
if (store.journal.length > 800) store.journal.splice(1, store.journal.length - 800);
|
|
504
|
+
if (!ready.current) return;
|
|
505
|
+
for (const event of events) iframe.current?.contentWindow?.postMessage({ source: "agent-live-dsh", event }, "*");
|
|
506
|
+
}, [observation, store]);
|
|
507
|
+
import_react.default.useEffect(() => {
|
|
508
|
+
localeWatchers.add(setLocale);
|
|
509
|
+
setLocale(activeLocale);
|
|
510
|
+
return () => {
|
|
511
|
+
localeWatchers.delete(setLocale);
|
|
512
|
+
};
|
|
513
|
+
}, []);
|
|
514
|
+
import_react.default.useEffect(() => {
|
|
515
|
+
ready.current = false;
|
|
516
|
+
}, [locale]);
|
|
517
|
+
import_react.default.useEffect(() => {
|
|
518
|
+
const receive = (message) => {
|
|
519
|
+
if (message.source !== iframe.current?.contentWindow || message.data?.source !== "agent-live-dsh-frame") return;
|
|
520
|
+
if (message.data.type === "locale") {
|
|
521
|
+
applyLocale(readLocale(message.data.locale));
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
ready.current = true;
|
|
525
|
+
for (const event of store.journal) iframe.current?.contentWindow?.postMessage({ source: "agent-live-dsh", event }, "*");
|
|
526
|
+
};
|
|
527
|
+
window.addEventListener("message", receive);
|
|
528
|
+
return () => {
|
|
529
|
+
ready.current = false;
|
|
530
|
+
window.removeEventListener("message", receive);
|
|
531
|
+
};
|
|
532
|
+
}, [id, store]);
|
|
533
|
+
const document2 = import_react.default.useMemo(() => frameDocument(office?.content, locale), [office?.revision, locale]);
|
|
534
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("iframe", { ref: iframe, title: "Agent Live", srcDoc: document2, sandbox: "allow-scripts", style: { border: 0, display: "block", height: "100%", width: "100%" } }, `${office?.revision ?? "default"}:${locale}`);
|
|
535
|
+
}
|
|
536
|
+
function apply(ctx) {
|
|
537
|
+
ctx.slots.inject("conversation.view", () => ctx.slots.register({ name: "conversation.view", id: "agent-live", order: 20, label: "Agent Live" }, AgentLiveView));
|
|
538
|
+
}
|
|
539
|
+
return module.exports; } });
|