@4fu/dsh-arbor-ui 0.0.0-stage → 2026.1110.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/client.js ADDED
@@ -0,0 +1,2700 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@4fu/dsh-arbor-ui",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+
8
+ "use strict";
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
+ var __getOwnPropNames = Object.getOwnPropertyNames;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __export = (target, all) => {
14
+ for (var name in all)
15
+ __defProp(target, name, { get: all[name], enumerable: true });
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
26
+
27
+ // src/client.ts
28
+ var client_exports = {};
29
+ __export(client_exports, {
30
+ apply: () => apply,
31
+ inject: () => inject
32
+ });
33
+ module.exports = __toCommonJS(client_exports);
34
+ var import_react2 = require("react");
35
+
36
+ // src/channel.ts
37
+ var TEXT_LIMIT = 6e3;
38
+ var SEND_LIMIT = 1500;
39
+ var ACTIVITY_MS = 1600;
40
+ var RETRY_BASE_MS = 800;
41
+ var RETRY_MAX_MS = 5e3;
42
+ function sendContentOf(raw) {
43
+ const match = /"content"\s*:\s*"((?:[^"\\]|\\.)*)/.exec(raw);
44
+ if (match === null) return void 0;
45
+ const body = match[1] ?? "";
46
+ return body.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
47
+ }
48
+ function tail(text, limit) {
49
+ return text.length <= limit ? text : text.slice(text.length - limit);
50
+ }
51
+ var FactsChannel = class {
52
+ url;
53
+ listeners = /* @__PURE__ */ new Set();
54
+ snapshot = { status: "connecting", streams: {}, streaming: false };
55
+ controller;
56
+ retry;
57
+ retryDelay = RETRY_BASE_MS;
58
+ activity;
59
+ holds = 0;
60
+ stopped = true;
61
+ constructor(url) {
62
+ this.url = url;
63
+ }
64
+ /** The current snapshot; the same reference until something changed. */
65
+ getSnapshot = () => this.snapshot;
66
+ /** Subscribe one React store listener. */
67
+ subscribe = (listener) => {
68
+ this.listeners.add(listener);
69
+ return () => {
70
+ this.listeners.delete(listener);
71
+ };
72
+ };
73
+ /** Take one hold on the stream; the first starts it. */
74
+ retain() {
75
+ this.holds += 1;
76
+ if (this.holds !== 1) return;
77
+ this.stopped = false;
78
+ void this.connect();
79
+ }
80
+ /** Release one hold; the last stops the stream. */
81
+ release() {
82
+ this.holds -= 1;
83
+ if (this.holds > 0) return;
84
+ this.close();
85
+ }
86
+ /** Stop this channel for good, whoever still holds it. */
87
+ close() {
88
+ this.stopped = true;
89
+ this.holds = 0;
90
+ this.controller?.abort();
91
+ this.controller = void 0;
92
+ if (this.retry !== void 0) clearTimeout(this.retry);
93
+ this.retry = void 0;
94
+ if (this.activity !== void 0) clearTimeout(this.activity);
95
+ this.activity = void 0;
96
+ }
97
+ /** Open the stream and read it until the page's carrier drops it. */
98
+ async connect() {
99
+ if (this.stopped) return;
100
+ this.set({ ...this.snapshot, status: this.snapshot.facts === void 0 ? "connecting" : "live" });
101
+ const controller = new AbortController();
102
+ this.controller = controller;
103
+ try {
104
+ const response = await fetch(this.url, {
105
+ signal: controller.signal,
106
+ headers: { accept: "application/x-ndjson" },
107
+ cache: "no-store"
108
+ });
109
+ if (!response.ok) throw new Error(`facts stream answered ${response.status}`);
110
+ if (response.body === null) throw new Error("facts stream has no body");
111
+ this.retryDelay = RETRY_BASE_MS;
112
+ this.set({ ...this.snapshot, status: "live", error: void 0 });
113
+ await this.read(response.body, controller.signal);
114
+ if (controller.signal.aborted) return;
115
+ throw new Error("facts stream ended");
116
+ } catch (error) {
117
+ if (controller.signal.aborted || this.stopped) return;
118
+ this.set({
119
+ ...this.snapshot,
120
+ status: this.snapshot.facts === void 0 ? "error" : "live",
121
+ error: error instanceof Error ? error.message : String(error)
122
+ });
123
+ this.retry = setTimeout(() => {
124
+ this.retry = void 0;
125
+ void this.connect();
126
+ }, this.retryDelay);
127
+ this.retryDelay = Math.min(this.retryDelay * 2, RETRY_MAX_MS);
128
+ }
129
+ }
130
+ /** Read one response's NDJSON body, one frame at a time. */
131
+ async read(body, signal) {
132
+ const reader = body.getReader();
133
+ const decoder = new TextDecoder();
134
+ let buffer = "";
135
+ for (; ; ) {
136
+ const { value, done } = await reader.read();
137
+ if (signal.aborted) {
138
+ await reader.cancel().catch(() => void 0);
139
+ return;
140
+ }
141
+ if (done) return;
142
+ buffer += decoder.decode(value, { stream: true });
143
+ for (; ; ) {
144
+ const newline = buffer.indexOf("\n");
145
+ if (newline < 0) break;
146
+ const line = buffer.slice(0, newline).trim();
147
+ buffer = buffer.slice(newline + 1);
148
+ if (line !== "") this.handle(JSON.parse(line));
149
+ }
150
+ }
151
+ }
152
+ /** Fold one frame into the snapshot. */
153
+ handle(frame) {
154
+ if (frame.type === "facts") {
155
+ this.set({ ...this.snapshot, status: "live", facts: frame.facts, error: void 0 });
156
+ return;
157
+ }
158
+ if (frame.type === "error") {
159
+ this.set({ ...this.snapshot, error: frame.message });
160
+ return;
161
+ }
162
+ this.stream(frame.window, frame.event);
163
+ }
164
+ /** Fold one live window delta into its own buffer. */
165
+ stream(window2, event) {
166
+ const before = this.snapshot.streams[window2] ?? { text: "", send: "", streaming: false };
167
+ let next = before;
168
+ if (event.kind === "text_delta") {
169
+ next = { ...before, text: tail(before.text + event.delta, TEXT_LIMIT) };
170
+ } else {
171
+ const content = sendContentOf(event.args);
172
+ if (event.name === "send" && content !== void 0) {
173
+ next = { ...before, send: tail(content, SEND_LIMIT) };
174
+ }
175
+ }
176
+ const streams = { ...this.snapshot.streams, [window2]: { ...next, streaming: true } };
177
+ this.set({ ...this.snapshot, streams, streaming: true });
178
+ if (this.activity !== void 0) clearTimeout(this.activity);
179
+ this.activity = setTimeout(() => {
180
+ this.activity = void 0;
181
+ const cleared = {};
182
+ for (const [ref, view] of Object.entries(this.snapshot.streams)) cleared[ref] = { ...view, streaming: false };
183
+ this.set({ ...this.snapshot, streams: cleared, streaming: false });
184
+ }, ACTIVITY_MS);
185
+ }
186
+ /** Publish one snapshot and wake React. */
187
+ set(snapshot) {
188
+ this.snapshot = snapshot;
189
+ for (const listener of this.listeners) listener();
190
+ }
191
+ };
192
+ var channels = /* @__PURE__ */ new Map();
193
+ function channelFor(sessionId) {
194
+ const key = `session:${sessionId}`;
195
+ let channel = channels.get(key);
196
+ if (channel === void 0) {
197
+ channel = new FactsChannel(`/api/arbor/facts?sessionId=${encodeURIComponent(sessionId)}`);
198
+ channels.set(key, channel);
199
+ }
200
+ return channel;
201
+ }
202
+ function hostChannel() {
203
+ const key = "host";
204
+ let channel = channels.get(key);
205
+ if (channel === void 0) {
206
+ channel = new FactsChannel("/api/arbor/host");
207
+ channels.set(key, channel);
208
+ }
209
+ return channel;
210
+ }
211
+ function closeChannels() {
212
+ for (const channel of channels.values()) channel.close();
213
+ channels.clear();
214
+ }
215
+
216
+ // src/i18n.ts
217
+ var NAMESPACE = "arborUi";
218
+ var en = {
219
+ expandStream: "Show more",
220
+ collapseStream: "Show less",
221
+ heroIdleOne: "1 session is quiet; {ended} windows have finished.",
222
+ chainSummaryOne: "{count} window \xB7 {window} {state}",
223
+ dateLocale: "en-US",
224
+ filterAll: "All",
225
+ filterWindows: "Windows",
226
+ filterBoard: "Board",
227
+ filterNotes: "Notes",
228
+ filterEnv: "Environment",
229
+ filterSchedules: "Schedules",
230
+ heroLive: "{window} is working. The board holds {board} things.",
231
+ heroLiveBoardOne: "{window} is working. The board holds {board} thing.",
232
+ heroFirst: "{window} is working. The board holds {board} things.",
233
+ heroIdle: "{sessions} sessions are quiet; {ended} windows have finished.",
234
+ heroIdleEndedOne: "{sessions} sessions are quiet; {ended} window has finished.",
235
+ heroIdleBothOne: "1 session is quiet; {ended} window has finished.",
236
+ heroIdleWait: "{waiting} sessions are waiting for you; {ended} windows have finished.",
237
+ heroIdleWaitEndedOne: "{waiting} sessions are waiting for you; {ended} window has finished.",
238
+ heroIdleWaitOne: "1 session is waiting for you; {ended} windows have finished.",
239
+ heroIdleWaitBothOne: "1 session is waiting for you; {ended} window has finished.",
240
+ heroLiveWait: "{window} is working. The board holds {board} things, and {waiting} other sessions are waiting for you.",
241
+ heroLiveWaitBoardOne: "{window} is working. The board holds {board} thing, and {waiting} other sessions are waiting for you.",
242
+ heroLiveWaitWaitingOne: "{window} is working. The board holds {board} things, and {waiting} other session is waiting for you.",
243
+ heroLiveWaitBothOne: "{window} is working. The board holds {board} thing, and {waiting} other session is waiting for you.",
244
+ heroLiveNoBoardWait: "{window} is working; the board is empty, and {waiting} other sessions are waiting for you.",
245
+ heroLiveNoBoardWaitOne: "{window} is working; the board is empty, and {waiting} other session is waiting for you.",
246
+ heroEmpty: "No arbor window yet.",
247
+ heroEmptyHint: "Pick Arbor in any session and send a message; this page will light up.",
248
+ sessionHeroLive: "{window} is carrying on from {previous}, with {board} things still on the board.",
249
+ sessionHeroLiveBoardOne: "{window} is carrying on from {previous}, with {board} thing still on the board.",
250
+ sessionHeroFirst: "{window} is working, with {board} things on the board.",
251
+ sessionHeroFirstBoardOne: "{window} is working, with {board} thing on the board.",
252
+ sessionHeroIdle: "{window} is waiting for your next message.",
253
+ sessionHeroEnded: "All {count} windows are finished; the chain ends at {window}.",
254
+ sessionHeroEndedOne: "1 window is finished; the chain ends at {window}.",
255
+ sessionHeroEmpty: "This session has no window yet.",
256
+ sessionHeroEmptyHint: "Send the first message and arbor will open its first window.",
257
+ cardChain: "Window chain",
258
+ cardWorkspace: "Workspace",
259
+ cardEnvironment: "Environment",
260
+ cardSchedules: "Schedules",
261
+ cardSession: "Session",
262
+ streamEmpty: "When the model starts writing, the lines appear here.",
263
+ emptyWindows: "No window yet.",
264
+ emptyWindowsHint: "The first message opens the first window.",
265
+ emptyBoard: "The board is empty.",
266
+ emptyBoardHint: "A running window writes what it is doing here.",
267
+ emptyNotes: "No active note yet.",
268
+ emptyNotesHint: "Notes are the memory a window writes for the next one.",
269
+ emptyEnv: "The environment is empty.",
270
+ emptyEnvHint: "Skills, MCP servers and services appear here.",
271
+ emptySchedules: "No schedule yet.",
272
+ emptySchedulesHint: "When arbor needs to work at a time, its schedule appears here.",
273
+ openSearch: "Search",
274
+ openSettings: "Settings",
275
+ closeSheet: "Close",
276
+ settingsHostData: "Host data",
277
+ settingsBackend: "Workspace backend",
278
+ settingsShared: "Shared folder",
279
+ chainRestart: "restart",
280
+ chainHandoff: "handoff",
281
+ chainStart: "start",
282
+ chainEnd: "end",
283
+ tabTitle: "Arbor",
284
+ panelLabel: "Arbor",
285
+ pageTitle: "Arbor",
286
+ pageIntro: "The whole arbor host: every session, window, board line, note, environment item, schedule and setting.",
287
+ tabOverview: "Overview",
288
+ sessions: "Sessions",
289
+ liveWindows: "Live windows",
290
+ noSessions: "No arbor window yet.",
291
+ noSessionsHint: "Pick Arbor in any session and send a message; its windows appear here.",
292
+ openSessionTab: "Expand session",
293
+ closeTab: "Close tab",
294
+ openInDsh: "Open session",
295
+ now: "Now",
296
+ noLiveWindow: "No live window.",
297
+ chain: "Window chain",
298
+ chainSummary: "{count} windows \xB7 {window} {state}",
299
+ summarySessions: "Sessions",
300
+ summaryWindows: "Live windows",
301
+ summaryNotes: "Active notes",
302
+ summaryEnv: "Environment",
303
+ summarySchedules: "Schedules",
304
+ host: "host",
305
+ dataRoot: "Data root",
306
+ sessionTabHint: "This tab has only this session's state.",
307
+ chipTitle: "Arbor",
308
+ chipAria: "Open the arbor panel",
309
+ chipNoWindow: "No window",
310
+ chipWindows: "{count} windows",
311
+ chipWindowOne: "1 window",
312
+ chipIdle: "Idle \xB7 {count} windows",
313
+ chipIdleOne: "Idle \xB7 1 window",
314
+ chipLive: "Live",
315
+ panelIntro: "What arbor is actually doing in this session, read from arbor itself.",
316
+ notArbor: "This session does not run on arbor.",
317
+ loading: "Reading arbor\u2026",
318
+ error: "The arbor channel is unavailable: {message}",
319
+ retry: "Retrying\u2026",
320
+ statusRunning: "Running",
321
+ statusIdle: "Idle",
322
+ statusEnded: "Ended",
323
+ statusPaused: "Paused",
324
+ sectionWindows: "Windows",
325
+ sectionWorkspace: "Workspace",
326
+ sectionBoard: "Board",
327
+ sectionNotes: "Notes",
328
+ sectionEnvironment: "Environment",
329
+ sectionSchedules: "Schedules",
330
+ sectionSettings: "Settings",
331
+ sectionSearch: "Search",
332
+ noWindows: "No window yet.",
333
+ noWindowsHint: "The first message opens this session's first arbor window.",
334
+ live: "live",
335
+ ended: "ended",
336
+ windowCount: "{count} in the chain",
337
+ endModel: "model ended",
338
+ endIdle: "idle timeout",
339
+ endHardLimit: "context limit",
340
+ endRestart: "host restart",
341
+ endFailed: "model failed",
342
+ endPaused: "paused",
343
+ handoff: "handoff",
344
+ handoffTo: "continued by {window}",
345
+ continuesFrom: "continues {window}",
346
+ handoffRefs: "handoff refs",
347
+ handoffTopics: "Topics",
348
+ handoffProgress: "Progress",
349
+ handoffPending: "Pending",
350
+ handoffAgreements: "Agreements",
351
+ refused: "refused: {rule}",
352
+ endRefusedTitle: "The last end was refused",
353
+ endRefusedHint: "The window is still open, so nothing ended. Send another message in DSH to keep working; the model can call end again with the handoff fields arbor named.",
354
+ usage: "{used} / {limit} tokens",
355
+ streamText: "Model output",
356
+ streamSend: "send draft",
357
+ streamWaiting: "No output yet.",
358
+ workspaceBackend: "Workspace",
359
+ workspaceShared: "Shared folder",
360
+ workspaceProject: "Project",
361
+ workspaceCwd: "Session directory",
362
+ workspacePointers: "External pointers",
363
+ workspaceCheckouts: "Checkouts",
364
+ workspaceKeeps: "Persistent directories",
365
+ workspaceOpenWork: "Open work",
366
+ workspaceDirty: "unapplied",
367
+ workspaceClean: "clean",
368
+ none: "None.",
369
+ boardEmpty: "No live window is on the board.",
370
+ boardTargets: "Targets",
371
+ notesActive: "Active set",
372
+ notesCount: "{count} notes \xB7 {tokens} / {budget} tokens",
373
+ notesCountOne: "1 note \xB7 {tokens} / {budget} tokens",
374
+ notesEpoch: "Epoch {epoch}",
375
+ notesEmpty: "The active set is empty.",
376
+ envCounts: "{skills} skills \xB7 {servers} MCP servers \xB7 {services} services",
377
+ envBuiltin: "built-in",
378
+ envFunctions: "functions",
379
+ envSkills: "Skills",
380
+ envSkillOne: "Skill",
381
+ envMcp: "MCP servers",
382
+ envMcpOne: "MCP server",
383
+ envServices: "Services",
384
+ envServiceOne: "Service",
385
+ envState: "state",
386
+ envPath: "path",
387
+ envEmpty: "The environment holds no item.",
388
+ schedulesEmpty: "Arbor holds no schedule.",
389
+ scheduleNext: "next {time}",
390
+ scheduleEvery: "every {value}",
391
+ scheduleDaily: "daily at {value}",
392
+ scheduleWeekly: "weekdays {value}",
393
+ schedulePaused: "paused",
394
+ scheduleMine: "this session",
395
+ scheduleFired: "{count} fired",
396
+ settingsSourceEnvironment: "from /env/settings.json",
397
+ settingsSourceDeployment: "deployment seed",
398
+ settingsIdentity: "Identity",
399
+ settingsRules: "General rules",
400
+ settingsJudge: "Judge",
401
+ settingsTiers: "Model tiers",
402
+ settingsDefaultTier: "Default tier",
403
+ settingsWindow: "Window",
404
+ settingsIdle: "Idle",
405
+ settingsContext: "Context",
406
+ settingsReserve: "Reserve",
407
+ settingsProtection: "Protection",
408
+ settingsNotes: "Notes",
409
+ settingsNotesCount: "Active count",
410
+ settingsNotesTokens: "Active tokens",
411
+ settingsSecrets: "Secret names",
412
+ settingsJudgeOn: "on",
413
+ settingsJudgeOff: "off",
414
+ settingsJudgeShadow: "shadow",
415
+ settingsDsh: "DSH's Agent loop page configures ordinary sessions; an arbor window runs under the tier and window limits above.",
416
+ settingsEmpty: "Arbor runs on its own defaults.",
417
+ searchPlaceholder: "Search the journal and notes",
418
+ searchButton: "Search",
419
+ searchEmpty: "Nothing matched.",
420
+ searchTotal: "{count} hits",
421
+ searchTotalOne: "1 hit",
422
+ searchJournal: "Journal",
423
+ searchNotes: "Notes",
424
+ searchBoard: "Board",
425
+ searchHint: "Arbor's own search over its journal, notes and board.",
426
+ searchError: "Search failed: {message}",
427
+ seconds: "{value}s",
428
+ minutes: "{value}m",
429
+ hours: "{value}h",
430
+ tokens: "tokens",
431
+ window: "window",
432
+ heroLiveNoBoard: "{window} is working; the board is empty."
433
+ };
434
+ var zh = {
435
+ expandStream: "\u5C55\u5F00\u5168\u6587",
436
+ collapseStream: "\u6536\u8D77",
437
+ heroIdleOne: "1 \u4E2A\u4F1A\u8BDD\u5B89\u9759\u7740\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
438
+ chainSummaryOne: "{count} \u4E2A\u7A97\u53E3 \xB7 {window} {state}",
439
+ dateLocale: "zh-CN",
440
+ filterAll: "\u5168\u90E8",
441
+ filterWindows: "\u7A97\u53E3",
442
+ filterBoard: "\u770B\u677F",
443
+ filterNotes: "\u7B14\u8BB0",
444
+ filterEnv: "\u73AF\u5883",
445
+ filterSchedules: "\u65E5\u7A0B",
446
+ heroLive: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
447
+ heroLiveBoardOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
448
+ heroFirst: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
449
+ heroIdle: "{sessions} \u4E2A\u4F1A\u8BDD\u5B89\u9759\u7740\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
450
+ heroIdleEndedOne: "{sessions} \u4E2A\u4F1A\u8BDD\u5B89\u9759\u7740\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
451
+ heroIdleBothOne: "1 \u4E2A\u4F1A\u8BDD\u5B89\u9759\u7740\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
452
+ heroIdleWait: "{waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
453
+ heroIdleWaitEndedOne: "{waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
454
+ heroIdleWaitOne: "1 \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
455
+ heroIdleWaitBothOne: "1 \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\uFF0C{ended} \u4E2A\u7A97\u53E3\u5DF2\u7ECF\u6536\u5DE5\u3002",
456
+ heroLiveWait: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
457
+ heroLiveWaitBoardOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
458
+ heroLiveWaitWaitingOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
459
+ heroLiveWaitBothOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
460
+ heroLiveNoBoardWait: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u7A7A\u7740\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
461
+ heroLiveNoBoardWaitOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u7A7A\u7740\uFF1B\u53E6\u5916 {waiting} \u4E2A\u4F1A\u8BDD\u5728\u7B49\u4F60\u3002",
462
+ heroEmpty: "\u8FD8\u6CA1\u6709 Arbor \u7A97\u53E3\u3002",
463
+ heroEmptyHint: "\u5728\u4EFB\u610F\u4F1A\u8BDD\u91CC\u9009\u62E9 Arbor\uFF0C\u53D1\u4E00\u6761\u6D88\u606F\uFF0C\u8FD9\u91CC\u5C31\u4F1A\u4EAE\u8D77\u6765\u3002",
464
+ sessionHeroLive: "{window} \u6B63\u5728\u63A5\u7740 {previous} \u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
465
+ sessionHeroLiveBoardOne: "{window} \u6B63\u5728\u63A5\u7740 {previous} \u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
466
+ sessionHeroFirst: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
467
+ sessionHeroFirstBoardOne: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u4E0A\u8FD8\u6709 {board} \u4EF6\u4E8B\u3002",
468
+ sessionHeroIdle: "{window} \u7A7A\u7740\uFF0C\u7B49\u4F60\u53D1\u4E0B\u4E00\u53E5\u8BDD\u3002",
469
+ sessionHeroEnded: "\u8FD9\u4E2A\u4F1A\u8BDD\u7684 {count} \u4E2A\u7A97\u53E3\u90FD\u6536\u5DE5\u4E86\uFF0C\u6700\u540E\u505C\u5728 {window}\u3002",
470
+ sessionHeroEndedOne: "\u8FD9\u4E2A\u4F1A\u8BDD\u7684 1 \u4E2A\u7A97\u53E3\u6536\u5DE5\u4E86\uFF0C\u6700\u540E\u505C\u5728 {window}\u3002",
471
+ sessionHeroEmpty: "\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u7A97\u53E3\u3002",
472
+ sessionHeroEmptyHint: "\u53D1\u7B2C\u4E00\u6761\u6D88\u606F\uFF0Carbor \u4F1A\u4E3A\u5B83\u5F00\u7B2C\u4E00\u4E2A\u7A97\u53E3\u3002",
473
+ cardChain: "\u7A97\u53E3\u94FE",
474
+ cardWorkspace: "\u5DE5\u4F5C\u533A",
475
+ cardEnvironment: "\u73AF\u5883",
476
+ cardSchedules: "\u65E5\u7A0B",
477
+ cardSession: "\u4F1A\u8BDD",
478
+ streamEmpty: "\u6A21\u578B\u5F00\u59CB\u5199\u65F6\uFF0C\u8FD9\u91CC\u4F1A\u4E00\u884C\u884C\u51FA\u73B0\u3002",
479
+ emptyWindows: "\u8FD8\u6CA1\u6709\u7A97\u53E3\u3002",
480
+ emptyWindowsHint: "\u53D1\u7B2C\u4E00\u6761\u6D88\u606F\uFF0C\u7B2C\u4E00\u4E2A\u7A97\u53E3\u5C31\u4F1A\u5F00\u3002",
481
+ emptyBoard: "\u770B\u677F\u662F\u7A7A\u7684\u3002",
482
+ emptyBoardHint: "\u7A97\u53E3\u8DD1\u8D77\u6765\u540E\uFF0C\u5B83\u4F1A\u5728\u8FD9\u91CC\u5199\u4E0B\u6B63\u5728\u505A\u4EC0\u4E48\u3002",
483
+ emptyNotes: "\u8FD8\u6CA1\u6709\u6D3B\u8DC3\u7B14\u8BB0\u3002",
484
+ emptyNotesHint: "\u7B14\u8BB0\u662F\u7A97\u53E3\u5199\u7ED9\u4E0B\u4E00\u4E2A\u7A97\u53E3\u7684\u8BB0\u5FC6\u3002",
485
+ emptyEnv: "\u73AF\u5883\u91CC\u8FD8\u6CA1\u6709\u4E1C\u897F\u3002",
486
+ emptyEnvHint: "\u6280\u80FD\u3001MCP \u670D\u52A1\u548C\u670D\u52A1\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
487
+ emptySchedules: "\u8FD8\u6CA1\u6709\u65E5\u7A0B\u3002",
488
+ emptySchedulesHint: "\u9700\u8981\u5230\u70B9\u5E72\u6D3B\u65F6\uFF0Carbor \u7684\u65E5\u7A0B\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
489
+ openSearch: "\u641C\u7D22",
490
+ openSettings: "\u8BBE\u7F6E",
491
+ closeSheet: "\u5173\u95ED",
492
+ settingsHostData: "\u5BBF\u4E3B\u6570\u636E",
493
+ settingsBackend: "\u5DE5\u4F5C\u533A\u540E\u7AEF",
494
+ settingsShared: "\u5171\u4EAB\u76EE\u5F55",
495
+ chainRestart: "\u91CD\u542F",
496
+ chainHandoff: "\u4EA4\u63A5",
497
+ chainStart: "\u5F00\u59CB",
498
+ chainEnd: "\u7ED3\u675F",
499
+ tabTitle: "Arbor",
500
+ panelLabel: "Arbor",
501
+ pageTitle: "Arbor",
502
+ pageIntro: "\u6574\u4E2A arbor \u5BBF\u4E3B\uFF1A\u6240\u6709\u4F1A\u8BDD\u3001\u7A97\u53E3\u3001\u770B\u677F\u3001\u7B14\u8BB0\u3001\u73AF\u5883\u3001\u5B9A\u65F6\u4E0E\u8BBE\u7F6E\u3002",
503
+ tabOverview: "\u603B\u89C8",
504
+ sessions: "\u4F1A\u8BDD",
505
+ liveWindows: "\u6D3B\u52A8\u7A97\u53E3",
506
+ noSessions: "\u8FD9\u4E2A\u5BBF\u4E3B\u8FD8\u6CA1\u6709 arbor \u7A97\u53E3\u3002",
507
+ noSessionsHint: "\u5728\u4EFB\u610F\u4F1A\u8BDD\u91CC\u9009\u62E9 Arbor \u5E76\u53D1\u4E00\u6761\u6D88\u606F\uFF0C\u7A97\u53E3\u5C31\u4F1A\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
508
+ openSessionTab: "\u5C55\u5F00\u4F1A\u8BDD",
509
+ closeTab: "\u5173\u95ED\u6807\u7B7E",
510
+ openInDsh: "\u6253\u5F00\u4F1A\u8BDD",
511
+ now: "\u6B63\u5728\u8FD0\u884C",
512
+ noLiveWindow: "\u5F53\u524D\u6CA1\u6709\u6D3B\u52A8\u7A97\u53E3\u3002",
513
+ chain: "\u7A97\u53E3\u94FE",
514
+ chainSummary: "{count} \u4E2A\u7A97\u53E3 \xB7 {window} {state}",
515
+ summarySessions: "\u4F1A\u8BDD",
516
+ summaryWindows: "\u6D3B\u52A8\u7A97\u53E3",
517
+ summaryNotes: "\u6D3B\u8DC3\u7B14\u8BB0",
518
+ summaryEnv: "\u73AF\u5883",
519
+ summarySchedules: "\u5B9A\u65F6",
520
+ host: "\u5BBF\u4E3B",
521
+ dataRoot: "\u6570\u636E\u76EE\u5F55",
522
+ sessionTabHint: "\u8FD9\u4E2A\u6807\u7B7E\u53EA\u663E\u793A\u8FD9\u4E2A\u4F1A\u8BDD\u81EA\u5DF1\u7684\u72B6\u6001\u3002",
523
+ chipTitle: "Arbor",
524
+ chipAria: "\u6253\u5F00 Arbor \u9762\u677F",
525
+ chipNoWindow: "\u65E0\u7A97\u53E3",
526
+ chipWindows: "{count} \u4E2A\u7A97\u53E3",
527
+ chipWindowOne: "1 \u4E2A\u7A97\u53E3",
528
+ chipIdle: "\u7A7A\u95F2 \xB7 {count} \u4E2A\u7A97\u53E3",
529
+ chipIdleOne: "\u7A7A\u95F2 \xB7 1 \u4E2A\u7A97\u53E3",
530
+ chipLive: "\u8FD0\u884C\u4E2D",
531
+ panelIntro: "\u8FD9\u4E2A\u4F1A\u8BDD\u91CC arbor \u771F\u5B9E\u5728\u505A\u4EC0\u4E48\uFF0C\u5168\u90E8\u8BFB\u81EA arbor \u672C\u8EAB\u3002",
532
+ notArbor: "\u8FD9\u4E2A\u4F1A\u8BDD\u4E0D\u662F arbor \u4F1A\u8BDD\u3002",
533
+ loading: "\u6B63\u5728\u8BFB\u53D6 arbor\u2026",
534
+ error: "arbor \u901A\u9053\u4E0D\u53EF\u7528\uFF1A{message}",
535
+ retry: "\u6B63\u5728\u91CD\u8BD5\u2026",
536
+ statusRunning: "\u8FD0\u884C\u4E2D",
537
+ statusIdle: "\u7A7A\u95F2",
538
+ statusEnded: "\u5DF2\u7ED3\u675F",
539
+ statusPaused: "\u6682\u505C",
540
+ sectionWindows: "\u7A97\u53E3",
541
+ sectionWorkspace: "\u5DE5\u4F5C\u533A",
542
+ sectionBoard: "\u770B\u677F",
543
+ sectionNotes: "\u7B14\u8BB0",
544
+ sectionEnvironment: "\u73AF\u5883",
545
+ sectionSchedules: "\u5B9A\u65F6",
546
+ sectionSettings: "\u8BBE\u7F6E",
547
+ sectionSearch: "\u641C\u7D22",
548
+ noWindows: "\u8FD8\u6CA1\u6709\u7A97\u53E3\u3002",
549
+ noWindowsHint: "\u53D1\u7B2C\u4E00\u6761\u6D88\u606F\u540E\uFF0Carbor \u4F1A\u4E3A\u8FD9\u4E2A\u4F1A\u8BDD\u5F00\u7B2C\u4E00\u4E2A\u7A97\u53E3\u3002",
550
+ live: "\u6D3B\u52A8",
551
+ ended: "\u7ED3\u675F",
552
+ windowCount: "\u94FE\u4E0A {count} \u4E2A",
553
+ endModel: "\u6A21\u578B\u7ED3\u675F",
554
+ endIdle: "\u7A7A\u95F2\u8D85\u65F6",
555
+ endHardLimit: "\u4E0A\u4E0B\u6587\u4E0A\u9650",
556
+ endRestart: "\u5BBF\u4E3B\u91CD\u542F",
557
+ endFailed: "\u6A21\u578B\u5931\u8D25",
558
+ endPaused: "\u6682\u505C",
559
+ handoff: "\u4EA4\u63A5",
560
+ handoffTo: "\u7531 {window} \u7EE7\u7EED",
561
+ continuesFrom: "\u7EE7\u7EED {window}",
562
+ handoffRefs: "\u4EA4\u63A5\u5F15\u7528",
563
+ handoffTopics: "\u4E3B\u9898",
564
+ handoffProgress: "\u8FDB\u5C55",
565
+ handoffPending: "\u5F85\u529E",
566
+ handoffAgreements: "\u7EA6\u5B9A",
567
+ refused: "\u88AB\u62D2\u7EDD\uFF1A{rule}",
568
+ endRefusedTitle: "\u4E0A\u4E00\u6B21 end \u88AB\u62D2\u7EDD",
569
+ endRefusedHint: "\u7A97\u53E3\u4ECD\u7136\u5F00\u7740\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u6709\u7ED3\u675F\u3002\u5728 DSH \u91CC\u7EE7\u7EED\u53D1\u6D88\u606F\u5C31\u80FD\u63A5\u7740\u505A\uFF1B\u6A21\u578B\u4E5F\u53EF\u4EE5\u6309 arbor \u6307\u51FA\u7684\u4EA4\u63A5\u5B57\u6BB5\u91CD\u65B0\u8C03\u7528 end\u3002",
570
+ usage: "{used} / {limit} \u4EE4\u724C",
571
+ streamText: "\u6A21\u578B\u8F93\u51FA",
572
+ streamSend: "send \u8349\u7A3F",
573
+ streamWaiting: "\u8FD8\u6CA1\u6709\u8F93\u51FA\u3002",
574
+ workspaceBackend: "\u5DE5\u4F5C\u533A",
575
+ workspaceShared: "\u5171\u4EAB\u6587\u4EF6\u5939",
576
+ workspaceProject: "\u9879\u76EE",
577
+ workspaceCwd: "\u4F1A\u8BDD\u76EE\u5F55",
578
+ workspacePointers: "\u5916\u90E8\u6307\u5411",
579
+ workspaceCheckouts: "\u68C0\u51FA",
580
+ workspaceKeeps: "\u4FDD\u6301\u76EE\u5F55",
581
+ workspaceOpenWork: "\u672A\u5B8C\u6210\u7684\u5DE5\u4F5C",
582
+ workspaceDirty: "\u672A\u5408\u5E76",
583
+ workspaceClean: "\u5E72\u51C0",
584
+ none: "\u65E0\u3002",
585
+ boardEmpty: "\u770B\u677F\u4E0A\u6CA1\u6709\u6D3B\u52A8\u7A97\u53E3\u3002",
586
+ boardTargets: "\u76EE\u6807",
587
+ notesActive: "\u6D3B\u8DC3\u96C6",
588
+ notesCount: "{count} \u6761 \xB7 {tokens} / {budget} \u4EE4\u724C",
589
+ notesCountOne: "1 \u6761 \xB7 {tokens} / {budget} \u4EE4\u724C",
590
+ notesEpoch: "\u7B2C {epoch} \u4EE3",
591
+ notesEmpty: "\u6D3B\u8DC3\u96C6\u4E3A\u7A7A\u3002",
592
+ envCounts: "{skills} \u4E2A\u6280\u80FD \xB7 {servers} \u4E2A MCP \u670D\u52A1 \xB7 {services} \u4E2A\u670D\u52A1",
593
+ envBuiltin: "\u5185\u7F6E",
594
+ envFunctions: "\u51FD\u6570",
595
+ envSkills: "\u6280\u80FD",
596
+ envSkillOne: "\u6280\u80FD",
597
+ envMcp: "MCP \u670D\u52A1",
598
+ envMcpOne: "MCP \u670D\u52A1",
599
+ envServices: "\u670D\u52A1",
600
+ envServiceOne: "\u670D\u52A1",
601
+ envState: "\u72B6\u6001",
602
+ envPath: "\u8DEF\u5F84",
603
+ envEmpty: "\u73AF\u5883\u91CC\u6CA1\u6709\u6761\u76EE\u3002",
604
+ schedulesEmpty: "arbor \u6CA1\u6709\u5B9A\u65F6\u4EFB\u52A1\u3002",
605
+ scheduleNext: "\u4E0B\u6B21 {time}",
606
+ scheduleEvery: "\u6BCF {value}",
607
+ scheduleDaily: "\u6BCF\u5929 {value}",
608
+ scheduleWeekly: "\u6BCF\u5468 {value}",
609
+ schedulePaused: "\u5DF2\u6682\u505C",
610
+ scheduleMine: "\u672C\u4F1A\u8BDD",
611
+ scheduleFired: "\u5DF2\u89E6\u53D1 {count} \u6B21",
612
+ settingsSourceEnvironment: "\u6765\u81EA /env/settings.json",
613
+ settingsSourceDeployment: "\u90E8\u7F72\u79CD\u5B50",
614
+ settingsIdentity: "\u8EAB\u4EFD",
615
+ settingsRules: "\u901A\u7528\u89C4\u5219",
616
+ settingsJudge: "\u5224\u65AD",
617
+ settingsTiers: "\u6A21\u578B\u5C42\u7EA7",
618
+ settingsDefaultTier: "\u9ED8\u8BA4\u5C42\u7EA7",
619
+ settingsWindow: "\u7A97\u53E3",
620
+ settingsIdle: "\u7A7A\u95F2",
621
+ settingsContext: "\u4E0A\u4E0B\u6587",
622
+ settingsReserve: "\u9884\u7559",
623
+ settingsProtection: "\u4FDD\u62A4\u671F",
624
+ settingsNotes: "\u7B14\u8BB0",
625
+ settingsNotesCount: "\u6D3B\u8DC3\u6761\u6570",
626
+ settingsNotesTokens: "\u6D3B\u8DC3\u4EE4\u724C",
627
+ settingsSecrets: "\u5BC6\u94A5\u540D",
628
+ settingsJudgeOn: "\u5F00\u542F",
629
+ settingsJudgeOff: "\u5173\u95ED",
630
+ settingsJudgeShadow: "\u5F71\u5B50",
631
+ settingsDsh: "DSH \u7684 Agent loop \u63D2\u4EF6\u9875\u9762\u7BA1\u7684\u662F\u666E\u901A\u4F1A\u8BDD\uFF1Barbor \u7A97\u53E3\u6309\u4E0A\u9762\u7684\u5C42\u7EA7\u548C\u7A97\u53E3\u9650\u5236\u8FD0\u884C\u3002",
632
+ settingsEmpty: "arbor \u4F7F\u7528\u81EA\u5DF1\u7684\u9ED8\u8BA4\u503C\u3002",
633
+ searchPlaceholder: "\u641C\u7D22\u65E5\u5FD7\u4E0E\u7B14\u8BB0",
634
+ searchButton: "\u641C\u7D22",
635
+ searchEmpty: "\u6CA1\u6709\u547D\u4E2D\u3002",
636
+ searchTotal: "{count} \u6761\u547D\u4E2D",
637
+ searchTotalOne: "1 \u6761\u547D\u4E2D",
638
+ searchJournal: "\u65E5\u5FD7",
639
+ searchNotes: "\u7B14\u8BB0",
640
+ searchBoard: "\u770B\u677F",
641
+ searchHint: "arbor \u81EA\u5DF1\u7684\u641C\u7D22\uFF0C\u67E5\u65E5\u5FD7\u3001\u7B14\u8BB0\u548C\u770B\u677F\u3002",
642
+ searchError: "\u641C\u7D22\u5931\u8D25\uFF1A{message}",
643
+ seconds: "{value} \u79D2",
644
+ minutes: "{value} \u5206\u949F",
645
+ hours: "{value} \u5C0F\u65F6",
646
+ tokens: "\u4EE4\u724C",
647
+ window: "\u7A97\u53E3",
648
+ heroLiveNoBoard: "{window} \u6B63\u5728\u5E72\u6D3B\uFF0C\u770B\u677F\u7A7A\u7740\u3002"
649
+ };
650
+ function format(template, args) {
651
+ if (args === void 0) return template;
652
+ return template.replace(/\{(\w+)\}/g, (whole, name) => Object.hasOwn(args, name) ? String(args[name]) : whole);
653
+ }
654
+ function fallbackTranslate(key, args) {
655
+ const value = zh[key];
656
+ return value === void 0 ? key : format(value, args);
657
+ }
658
+
659
+ // src/styles.ts
660
+ var STYLE_ID = "arbor-ui-panel-style";
661
+ var CSS = `.arbor-page{
662
+ --arbor-font-body:var(--dsw-font-family);
663
+ --arbor-font-display:var(--dsw-font-family);
664
+ --arbor-font-numeric:var(--dsw-font-family);
665
+ --arbor-font-code:var(--ds-font-family-code);
666
+ box-sizing:border-box;min-height:100%;overflow:auto;
667
+ padding:24px 28px 32px;
668
+ padding-bottom:var(--arbor-dock-reserve,32px);
669
+ background:var(--dsw-alias-bg-base,#fff);
670
+ color:var(--dsw-alias-label-primary,#0f1115);
671
+ font-family:var(--arbor-font-body);
672
+ font-size:13px;line-height:1.55;
673
+ }
674
+ .arbor-page *{box-sizing:border-box}
675
+ .arbor-page [data-arbor-text="body"]{font-family:var(--arbor-font-body)}
676
+ .arbor-page [data-arbor-text="display"]{font-family:var(--arbor-font-display)}
677
+ .arbor-page [data-arbor-text="numeric"]{font-family:var(--arbor-font-numeric);font-variant-numeric:tabular-nums}
678
+ .arbor-page [data-arbor-text="code"]{font-family:var(--arbor-font-code)}
679
+ .arbor-page-inner{width:100%;max-width:1160px;min-height:calc(100vh - 56px);margin:0 auto;display:flex;flex-direction:column;gap:14px}
680
+ .arbor-page .arbor-tabs{display:flex;flex:none;gap:4px;align-items:center;overflow-x:auto;padding:0 1px}
681
+ .arbor-page .arbor-tab-group{display:flex;align-items:center;flex:none}
682
+ .arbor-page .arbor-tab{height:27px;padding:0 10px;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#61666b);font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
683
+ .arbor-page .arbor-tab:hover{background:var(--dsw-alias-interactive-bg-hover,#2631480f);color:var(--dsw-alias-label-primary,#0f1115)}
684
+ .arbor-page .arbor-tab.is-active, .arbor-page .arbor-tab-group.is-active .arbor-tab{background:var(--dsw-alias-bg-layer-1,#fff);border-color:var(--dsw-alias-border-l1,#0000000a);color:var(--dsw-alias-label-primary,#0f1115)}
685
+ .arbor-page .arbor-tab-close{width:20px;height:20px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-label-tertiary,#81858c);cursor:pointer;font-size:13px;line-height:1}
686
+ .arbor-page .arbor-tab-close:hover{background:var(--dsw-alias-interactive-bg-hover,#2631480f);color:var(--dsw-alias-label-primary,#0f1115)}
687
+ .arbor-page .arbor-hero{display:flex;flex-direction:column;gap:2px;padding:8px 0 10px}
688
+ .arbor-page .arbor-hero-top{display:flex;align-items:center;gap:12px;min-height:32px}
689
+ .arbor-page .arbor-date{flex:1;min-width:0;font-size:11px;color:var(--dsw-alias-label-tertiary,#81858c);letter-spacing:.06em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
690
+ .arbor-page .arbor-top-actions{display:flex;gap:6px;align-items:center;flex:none}
691
+ .arbor-page .arbor-icon-button{width:32px;height:32px;padding:0;border:1px solid var(--dsw-alias-border-l1,#0000000a);border-radius:50%;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-secondary,#61666b);display:inline-flex;align-items:center;justify-content:center;cursor:pointer;flex:none;box-shadow:var(--dsw-elevation-soft,0 4px 16px #00000008)}
692
+ .arbor-page .arbor-icon-button:hover{background:var(--dsw-alias-interactive-bg-hover,#2631480f);color:var(--dsw-alias-label-primary,#0f1115)}
693
+ .arbor-page .arbor-hero-title{max-width:960px;margin:10px 0 2px;font-size:clamp(20px,2vw,27px);line-height:1.5;font-weight:400;letter-spacing:.005em;color:var(--dsw-alias-label-tertiary,#81858c);text-wrap:balance}
694
+ .arbor-page .arbor-hero-num{font-family:var(--arbor-font-numeric);font-weight:600;font-size:1.18em;line-height:.95;letter-spacing:-.01em;color:var(--dsw-alias-label-primary,#0f1115);font-variant-numeric:tabular-nums}
695
+ .arbor-page .arbor-hero-name{font-weight:400;color:inherit}.arbor-page .arbor-hero-title .arbor-hero-name{color:inherit}
696
+ .arbor-page .arbor-hero-hint{display:block;margin-top:10px;font-size:12.5px;line-height:1.6;color:var(--dsw-alias-label-tertiary,#81858c)}
697
+ .arbor-page .arbor-hero-links{display:flex;gap:10px;margin-top:6px}.arbor-page .arbor-hero-links .arbor-card-link{margin-left:0;align-self:flex-start}
698
+ .arbor-page .arbor-overview{display:flex;flex:1;flex-direction:column;gap:4px;min-height:0}
699
+ .arbor-page .arbor-sky{position:relative;width:100%;flex:none;min-height:0}
700
+ .arbor-page .arbor-sky-cell{position:absolute;min-width:0;z-index:1;transform-origin:50% 50%;transition:transform 150ms ease}
701
+ .arbor-page .arbor-sky-cell>.arbor-card{width:100%}
702
+ .arbor-page .arbor-sky-cell[data-paper="true"]{transform:rotate(var(--arbor-tilt,0deg))}
703
+ .arbor-page .arbor-sky-cell[data-paper="true"]:hover{transform:rotate(0deg) translateY(-5px) scale(1.01);z-index:4}
704
+ .arbor-page .arbor-sky-cell[data-paper="true"]>.arbor-card{transition:box-shadow 150ms ease}
705
+ .arbor-page .arbor-sky-cell[data-paper="true"]:hover>.arbor-card{box-shadow:0 18px 36px #0000002b}
706
+ .arbor-page .arbor-index-card{background:var(--dsw-alias-bg-layer-1,#fff);border-top:3px solid color-mix(in srgb,var(--dsw-alias-state-business-primary,#4176e6) 55%,transparent);border-radius:7px;box-shadow:0 7px 18px #00000012}
707
+ .arbor-page .arbor-note-card{border-radius:8px;box-shadow:0 8px 20px #00000014}
708
+ .arbor-page .arbor-card{min-width:0;display:flex;flex-direction:column;gap:7px;padding:15px 17px 12px;border:1px solid var(--dsw-alias-border-l1,#0000000a);border-radius:17px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:var(--dsw-elevation-soft,0 4px 16px #00000008);color:var(--dsw-alias-label-primary,#0f1115)}
709
+ .arbor-page .arbor-card-head{display:flex;align-items:center;gap:8px;min-width:0}
710
+ .arbor-page .arbor-card-kicker{flex:1;min-width:0;font-size:10px;line-height:14px;letter-spacing:.06em;color:var(--dsw-alias-label-tertiary,#81858c);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
711
+ .arbor-page .arbor-card-aside{flex:none;display:inline-flex;align-items:center;gap:6px}
712
+ .arbor-page .arbor-card-session{font-size:10px;color:var(--dsw-alias-label-tertiary,#81858c);max-width:170px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
713
+ .arbor-page .arbor-card-title{font-size:16px;line-height:1.5;font-weight:500;color:var(--dsw-alias-label-primary,#0f1115);overflow-wrap:anywhere}
714
+ .arbor-page .arbor-card-project{font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary,#61666b);overflow-wrap:anywhere}
715
+ .arbor-page .arbor-card-meta{font-size:10.5px;color:var(--dsw-alias-label-tertiary,#81858c)}
716
+ .arbor-page .arbor-card-foot{margin-top:auto;display:flex;align-items:baseline;gap:3px 7px;flex-wrap:wrap;padding-top:9px;border-top:1px solid var(--dsw-alias-border-l1,#0000000a);font-size:10px;line-height:1.5;color:var(--dsw-alias-label-tertiary,#81858c)}
717
+ .arbor-page .arbor-card-foot-line{flex-basis:100%;margin-top:1px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
718
+ .arbor-page .arbor-card-link{margin-left:auto;padding:0;border:none;background:transparent;color:var(--dsw-alias-label-secondary,#61666b);font:inherit;font-size:10.5px;cursor:pointer;white-space:nowrap}
719
+ .arbor-page .arbor-card-link:hover{color:var(--dsw-alias-label-primary,#0f1115)}
720
+ .arbor-page .arbor-board-card{background:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4176e6) 7%,var(--dsw-alias-bg-layer-1,#fff));border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary,#4176e6) 16%,var(--dsw-alias-border-l1,#0000000a))}
721
+ .arbor-page .arbor-note-card{background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 9%,var(--dsw-alias-bg-layer-1,#fff));border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 20%,var(--dsw-alias-border-l1,#0000000a))}
722
+ .arbor-page .arbor-empty-card{min-height:220px;align-items:center;justify-content:center;text-align:center;gap:8px;border-style:dashed;background:color-mix(in srgb,var(--dsw-alias-bg-layer-1,#fff) 55%,transparent)}
723
+ .arbor-page .arbor-empty-mark{width:40px;height:40px;border:1px dashed var(--dsw-alias-border-l3,#0000001f);border-radius:50%;position:relative;margin-bottom:4px}
724
+ .arbor-page .arbor-empty-mark:after{content:'';position:absolute;inset:13px;border-radius:50%;background:var(--dsw-alias-brand-primary,#0f1115);opacity:.16}
725
+ .arbor-page .arbor-empty-title{font-size:19px;font-weight:500;color:var(--dsw-alias-label-primary,#0f1115)}
726
+ .arbor-page .arbor-empty-hint{max-width:44ch;font-size:12.5px;line-height:1.65;color:var(--dsw-alias-label-secondary,#61666b)}
727
+ .arbor-page .arbor-state{display:inline-flex;align-items:center;gap:4px;padding:1px 7px;border-radius:999px;font-size:9.5px;line-height:18px;white-space:nowrap;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#61666b)}
728
+ .arbor-page .arbor-state-running{color:var(--dsw-alias-state-success-primary,#22c55e);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 12%,transparent)}
729
+ .arbor-page .arbor-state-idle, .arbor-page .arbor-state-paused{color:var(--dsw-alias-state-warn-primary,#f59e0b);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 12%,transparent)}
730
+ .arbor-page .arbor-state-ended{color:var(--dsw-alias-label-tertiary,#81858c)}
731
+ .arbor-page .arbor-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--dsw-alias-label-tertiary,#81858c)}
732
+ .arbor-page .arbor-dot-running{background:var(--dsw-alias-state-success-primary,#22c55e)}
733
+ .arbor-page .arbor-dot-idle{background:var(--dsw-alias-state-warn-primary,#f59e0b)}
734
+ .arbor-page .arbor-dot-paused{background:var(--dsw-alias-state-warn-primary,#f59e0b)}
735
+ .arbor-page .arbor-dot-ended{background:var(--dsw-alias-label-tertiary,#81858c)}
736
+ .arbor-page .arbor-stream-block{margin-top:2px;padding:10px 11px;border-radius:12px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l1,#0000000a);display:flex;flex-direction:column;gap:5px;min-width:0}
737
+ .arbor-page .arbor-stream-head{display:flex;align-items:center;gap:6px}
738
+ .arbor-page .arbor-pulse{width:5px;height:5px;border-radius:50%;background:var(--dsw-alias-label-tertiary,#81858c);flex:none}
739
+ .arbor-page .arbor-pulse.is-live{background:var(--dsw-alias-state-success-primary,#22c55e);animation:arbor-pulse 1.1s ease-in-out infinite}
740
+ .arbor-page .arbor-stream-label{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c)}
741
+ .arbor-page .arbor-md-wrap{max-height:260px;overflow:hidden;position:relative}
742
+ .arbor-page .arbor-md-wrap.is-clamped{mask-image:linear-gradient(180deg,#000 72%,transparent 100%)}
743
+ .arbor-page .arbor-window-card .arbor-md-wrap{max-height:140px}
744
+ .arbor-page .arbor-md-wrap.is-expanded{max-height:none;overflow:visible}
745
+ .arbor-page .arbor-md{font-size:12.5px;line-height:1.7;color:var(--dsw-alias-label-secondary,#61666b);overflow-wrap:anywhere}
746
+ .arbor-page .arbor-md-p{margin:0 0 8px}
747
+ .arbor-page .arbor-md-p:last-child{margin-bottom:0}
748
+ .arbor-page .arbor-md-heading{margin:4px 0 7px;font-size:14px;font-weight:600;line-height:1.5;color:var(--dsw-alias-label-primary,#0f1115)}
749
+ .arbor-page .arbor-md-list{margin:0 0 8px;padding-left:18px}
750
+ .arbor-page .arbor-md-list li{margin:1px 0}
751
+ .arbor-page .arbor-md-pre{margin:0 0 8px;padding:8px 10px;border-radius:8px;background:var(--dsw-alias-markdown-code-block,var(--dsw-alias-bg-layer-2,#f5f5f5));overflow:auto;font-family:var(--arbor-font-code);font-size:11px;line-height:1.55}
752
+ .arbor-page .arbor-md-code{padding:1px 4px;border-radius:4px;background:var(--dsw-alias-markdown-inline-code,var(--dsw-alias-bg-layer-2,#f5f5f5));font-family:var(--arbor-font-code);font-size:11px}
753
+ .arbor-page .arbor-md-link{color:var(--dsw-alias-link,var(--dsw-alias-state-business-primary,#4176e6));text-decoration:underline;text-underline-offset:2px}
754
+ .arbor-page .arbor-stream-toggle{align-self:flex-start;padding:0;border:none;background:transparent;color:var(--dsw-alias-label-secondary,#61666b);font:inherit;font-size:10.5px;cursor:pointer}
755
+ .arbor-page .arbor-stream-toggle:hover{color:var(--dsw-alias-label-primary,#0f1115)}
756
+ .arbor-page .arbor-stream-empty{font-size:12px;line-height:1.6;color:var(--dsw-alias-label-tertiary,#81858c)}
757
+ .arbor-page .arbor-stream-send{margin-top:4px;padding-top:6px;border-top:1px dashed var(--dsw-alias-border-l1,#0000000a);display:flex;flex-direction:column;gap:3px}
758
+ .arbor-page .arbor-chain-scroll{overflow-x:auto;padding:3px 0 7px;scrollbar-width:thin}
759
+ .arbor-page .arbor-chain-card{container-type:inline-size}
760
+ .arbor-page .arbor-chain{display:flex;align-items:center;min-width:max-content}
761
+ .arbor-page .arbor-node{min-width:112px;max-width:166px;padding:9px 10px;border:1px solid var(--dsw-alias-border-l1,#0000000a);border-radius:12px;background:var(--dsw-alias-interactive-bg-hover,#2631480f);display:flex;flex-direction:column;gap:1px;min-height:72px}
762
+ .arbor-page .arbor-node-running{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 35%,var(--dsw-alias-border-l1,#0000000a));background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 7%,var(--dsw-alias-bg-layer-1,#fff))}
763
+ .arbor-page .arbor-node-idle{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 28%,var(--dsw-alias-border-l1,#0000000a))}
764
+ .arbor-page .arbor-node-ref{font-family:var(--arbor-font-numeric);font-size:15px;line-height:19px;font-weight:500;color:var(--dsw-alias-label-primary,#0f1115);font-variant-numeric:tabular-nums}
765
+ .arbor-page .arbor-node-state{font-size:9px;line-height:13px;letter-spacing:.1em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c)}
766
+ .arbor-page .arbor-node-running .arbor-node-state{color:var(--dsw-alias-state-success-primary,#22c55e)}
767
+ .arbor-page .arbor-node-doing{font-size:10.5px;line-height:1.45;color:var(--dsw-alias-label-secondary,#61666b);overflow-wrap:anywhere}
768
+ .arbor-page .arbor-connector{width:52px;flex:none;height:72px;display:flex;align-items:center;justify-content:center;position:relative}
769
+ .arbor-page .arbor-connector-line{position:absolute;left:0;right:0;top:50%;height:1px;background:var(--dsw-alias-border-l2,#0000001a)}
770
+ .arbor-page .arbor-connector-label{position:relative;z-index:1;padding:0 4px;background:var(--dsw-alias-bg-layer-1,#fff);font-size:8px;letter-spacing:.06em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c);white-space:nowrap}
771
+ .arbor-page .arbor-connector-arrow{position:relative;z-index:1;padding-left:2px;background:var(--dsw-alias-bg-layer-1,#fff);font-size:12px;color:var(--dsw-alias-label-tertiary,#81858c);line-height:1}
772
+ .arbor-page .arbor-handoffs{display:flex;flex-direction:column;gap:3px;padding-top:7px;border-top:1px solid var(--dsw-alias-border-l1,#0000000a)}
773
+ .arbor-page .arbor-handoff-row{display:flex;gap:8px;align-items:baseline;min-width:0;font-size:11px;line-height:1.5;color:var(--dsw-alias-label-secondary,#61666b)}
774
+ .arbor-page .arbor-handoff-ref{flex:none;font-size:9.5px;color:var(--dsw-alias-label-tertiary,#81858c);letter-spacing:.04em}
775
+ .arbor-page .arbor-handoff-text{min-width:0;overflow-wrap:anywhere}
776
+ .arbor-page .arbor-note-sum{font-size:12.5px;line-height:1.6;color:var(--dsw-alias-label-secondary,#61666b);overflow-wrap:anywhere}
777
+ .arbor-page .arbor-tags{display:flex;flex-wrap:wrap;gap:4px}
778
+ .arbor-page .arbor-tag{display:inline-flex;align-items:center;height:19px;padding:0 6px;border-radius:6px;font-size:9.5px;line-height:19px;white-space:nowrap;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-secondary,#61666b)}
779
+ .arbor-page .arbor-tag-ok{color:var(--dsw-alias-state-success-primary,#22c55e);background:color-mix(in srgb,var(--dsw-alias-state-success-primary,#22c55e) 12%,transparent)}
780
+ .arbor-page .arbor-tag-warn{color:var(--dsw-alias-state-warn-primary,#f59e0b);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 12%,transparent)}
781
+ .arbor-page .arbor-tag-err{color:var(--dsw-alias-state-error-primary,#ef4444);background:color-mix(in srgb,var(--dsw-alias-state-error-primary,#ef4444) 12%,transparent)}
782
+ .arbor-page .arbor-tag-plain{opacity:.7}
783
+ .arbor-page .arbor-refusal{display:flex;flex-direction:column;gap:4px;padding:9px 10px;border-radius:10px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 28%,var(--dsw-alias-border-l1,#0000000a));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 8%,var(--dsw-alias-bg-layer-1,#fff))}
784
+ .arbor-page .arbor-refusal-head{display:flex;align-items:center;gap:7px;min-width:0;flex-wrap:wrap}
785
+ .arbor-page .arbor-refusal-title{flex:1;min-width:0;font-size:10px;letter-spacing:.04em;color:var(--dsw-alias-state-warn-primary,#f59e0b)}
786
+ .arbor-page .arbor-refusal-message{font-size:11px;line-height:1.55;color:var(--dsw-alias-label-secondary,#61666b);overflow-wrap:anywhere}
787
+ .arbor-page .arbor-refusal-hint{font-size:10.5px;line-height:1.55;color:var(--dsw-alias-label-tertiary,#81858c)}
788
+ .arbor-page .arbor-env-counts{display:flex;gap:18px;align-items:baseline;flex-wrap:wrap;padding:1px 0 4px}
789
+ .arbor-page .arbor-env-count{display:flex;align-items:baseline;gap:3px;color:var(--dsw-alias-label-secondary,#61666b);font-size:10.5px}
790
+ .arbor-page .arbor-env-big{font-family:var(--arbor-font-numeric);font-size:26px;line-height:30px;font-weight:500;color:var(--dsw-alias-label-primary,#0f1115);font-variant-numeric:tabular-nums}
791
+ .arbor-page .arbor-env-group{padding-top:6px;border-top:1px solid var(--dsw-alias-border-l1,#0000000a);display:flex;flex-direction:column;gap:1px}
792
+ .arbor-page .arbor-env-group-title{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c);margin-bottom:2px}
793
+ .arbor-page .arbor-env-row{display:flex;align-items:baseline;gap:7px;min-width:0;font-size:11.5px;line-height:1.6}
794
+ .arbor-page .arbor-env-name{flex:none;max-width:55%;overflow-wrap:anywhere;color:var(--dsw-alias-label-primary,#0f1115)}
795
+ .arbor-page .arbor-env-path{flex:1;min-width:0;overflow-wrap:anywhere;font-size:10px;color:var(--dsw-alias-label-tertiary,#81858c)}
796
+ .arbor-page .arbor-env-functions{flex:1;min-width:0;overflow-wrap:anywhere;color:var(--dsw-alias-label-secondary,#61666b);font-size:10.5px}
797
+ .arbor-page .arbor-env-revision{font-size:10px;color:var(--dsw-alias-label-tertiary,#81858c)}
798
+ .arbor-page .arbor-schedule-next{font-size:12.5px;line-height:1.5;color:var(--dsw-alias-label-secondary,#61666b)}
799
+ .arbor-page .arbor-workspace-rows{display:flex;flex-direction:column;gap:7px;min-width:0}
800
+ .arbor-page .arbor-workspace-row{display:flex;flex-direction:column;gap:2px;min-width:0;font-size:11.5px;line-height:1.5;color:var(--dsw-alias-label-primary,#0f1115)}
801
+ .arbor-page .arbor-workspace-row>.arbor-label{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c)}
802
+ .arbor-page .arbor-workspace-list{display:flex;flex-direction:column;gap:2px;min-width:0}
803
+ .arbor-page .arbor-workspace-item{display:flex;align-items:baseline;gap:6px;flex-wrap:wrap;min-width:0;font-size:11px}
804
+ .arbor-page .arbor-muted{color:var(--dsw-alias-label-tertiary,#81858c);font-size:11px}
805
+ .arbor-page .arbor-error{color:var(--dsw-alias-state-error-primary,#ef4444);font-size:11px;line-height:1.5}
806
+ .arbor-page .arbor-mono{font-family:var(--arbor-font-code);font-size:10.5px;overflow-wrap:anywhere}
807
+ .arbor-page .arbor-clamp{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
808
+ .arbor-page .arbor-label{letter-spacing:.02em}
809
+ .arbor-page .arbor-num{font-variant-numeric:tabular-nums}
810
+ .arbor-page .arbor-dock{position:sticky;bottom:18px;z-index:8;margin:10px auto 0;width:fit-content;max-width:calc(100% - 24px);padding:5px;border:1px solid var(--dsw-alias-border-l1,#0000000a);border-radius:999px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:var(--dsw-elevation-prominent,0 3px 8px #0000000a);isolation:isolate}
811
+ .arbor-page .arbor-dock-pills{display:flex;gap:2px;align-items:center;overflow-x:auto;scrollbar-width:none}
812
+ .arbor-page .arbor-dock-pills::-webkit-scrollbar{display:none}
813
+ .arbor-page .arbor-pill{height:30px;padding:0 13px;border:none;border-radius:999px;background:transparent;color:var(--dsw-alias-label-secondary,#61666b);font:inherit;font-size:11.5px;cursor:pointer;white-space:nowrap}
814
+ .arbor-page .arbor-pill:hover{background:var(--dsw-alias-interactive-bg-hover,#2631480f);color:var(--dsw-alias-label-primary,#0f1115)}
815
+ .arbor-page .arbor-pill.is-active{background:var(--dsw-alias-label-primary,#0f1115);color:var(--dsw-alias-label-primary-foreground,#fff)}
816
+ .arbor-page .arbor-sheet-layer{position:fixed;inset:0;z-index:60;display:flex;justify-content:flex-end}
817
+ .arbor-page .arbor-sheet-backdrop{position:absolute;inset:0;background:var(--dsw-alias-bg-mask-2,#0000001f);backdrop-filter:blur(1px)}
818
+ .arbor-page .arbor-sheet{position:relative;width:min(560px,100%);height:100%;overflow:auto;background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-1,#fff));border-left:1px solid var(--dsw-alias-border-l1,#0000000a);box-shadow:var(--dsw-elevation-prominent,0 3px 8px #0000000a);padding:18px 20px 28px;display:flex;flex-direction:column;gap:14px}
819
+ .arbor-page .arbor-sheet-head{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:10px;padding-bottom:10px;background:inherit;border-bottom:1px solid var(--dsw-alias-border-l1,#0000000a)}
820
+ .arbor-page .arbor-sheet-title{flex:1;margin:0;font-size:16px;font-weight:500;color:var(--dsw-alias-label-primary,#0f1115)}
821
+ .arbor-page .arbor-sheet-body{display:flex;flex-direction:column;gap:12px}
822
+ .arbor-page .arbor-settings{display:flex;flex-direction:column;gap:8px;font-size:12px}
823
+ .arbor-page .arbor-settings-source{font-size:10.5px;color:var(--dsw-alias-label-tertiary,#81858c)}
824
+ .arbor-page .arbor-settings-group{display:flex;flex-direction:column;gap:6px;padding:9px 11px;border-radius:11px;background:var(--dsw-alias-bg-layer-2,#fff);border:1px solid var(--dsw-alias-border-l1,#0000000a)}
825
+ .arbor-page .arbor-settings-group-title{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c)}
826
+ .arbor-page .arbor-setting-row{display:flex;gap:10px;align-items:baseline;min-width:0;padding:5px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#0000000a)}
827
+ .arbor-page .arbor-setting-row:last-child{border-bottom:none}
828
+ .arbor-page .arbor-setting-key{flex:none;width:98px;font-size:9.5px;line-height:1.45;letter-spacing:.02em;color:var(--dsw-alias-label-tertiary,#81858c)}
829
+ .arbor-page .arbor-setting-value{flex:1;min-width:0;font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-primary,#0f1115)}
830
+ .arbor-page .arbor-settings-wrap{overflow-wrap:anywhere}
831
+ .arbor-page .arbor-settings-note{padding:10px 12px;border-radius:11px;border:1px solid color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 22%,var(--dsw-alias-border-l1,#0000000a));background:color-mix(in srgb,var(--dsw-alias-state-warn-primary,#f59e0b) 7%,var(--dsw-alias-bg-layer-1,#fff));font-size:11.5px;line-height:1.65;color:var(--dsw-alias-label-secondary,#61666b)}
832
+ .arbor-page .arbor-search{display:flex;flex-direction:column;gap:8px}
833
+ .arbor-page .arbor-search-row{display:flex;gap:7px;align-items:center}
834
+ .arbor-page .arbor-search-input{flex:1;min-width:0;height:34px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2,#0000001a);border-radius:9px;background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#0f1115);font:inherit;font-size:12px;outline:none}
835
+ .arbor-page .arbor-search-input:focus{border-color:var(--dsw-alias-brand-primary,#0f1115)}
836
+ .arbor-page .arbor-search-go{height:34px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2,#0000001a);border-radius:9px;background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#0f1115);font-size:10px;letter-spacing:.05em;cursor:pointer}
837
+ .arbor-page .arbor-search-go:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#2631480f)}
838
+ .arbor-page .arbor-search-go:disabled{opacity:.45;cursor:default}
839
+ .arbor-page .arbor-search-hint{font-size:10.5px;line-height:1.55;color:var(--dsw-alias-label-tertiary,#81858c)}
840
+ .arbor-page .arbor-search-empty{padding:18px 0;text-align:center;font-size:12.5px;color:var(--dsw-alias-label-secondary,#61666b)}
841
+ .arbor-page .arbor-search-results{display:flex;flex-direction:column;gap:12px}
842
+ .arbor-page .arbor-search-total{font-size:10.5px;color:var(--dsw-alias-label-secondary,#61666b)}
843
+ .arbor-page .arbor-search-group{display:flex;flex-direction:column;gap:5px}
844
+ .arbor-page .arbor-search-group-title{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--dsw-alias-label-tertiary,#81858c)}
845
+ .arbor-page .arbor-hit{display:flex;flex-direction:column;gap:2px;padding:8px 10px;border-radius:10px;border:1px solid var(--dsw-alias-border-l1,#0000000a);background:var(--dsw-alias-bg-layer-2,#fff)}
846
+ .arbor-page .arbor-hit-head{display:flex;gap:7px;align-items:baseline;flex-wrap:wrap}
847
+ .arbor-page .arbor-hit-ref{font-size:10px;color:var(--dsw-alias-label-tertiary,#81858c)}
848
+ .arbor-page .arbor-hit-kind{font-size:9.5px;color:var(--dsw-alias-label-secondary,#61666b)}
849
+ .arbor-page .arbor-hit-window{font-size:9.5px;color:var(--dsw-alias-label-tertiary,#81858c)}
850
+ .arbor-page .arbor-hit-sum{font-size:11.5px;line-height:1.55;color:var(--dsw-alias-label-secondary,#61666b);overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
851
+ @container (max-width:460px){
852
+ .arbor-page .arbor-node{min-width:90px;max-width:130px;padding:8px 9px}
853
+ .arbor-page .arbor-connector{width:28px}
854
+ }
855
+ @container (max-width:360px){
856
+ .arbor-page .arbor-node{min-width:76px;max-width:120px;padding:7px 8px}
857
+ .arbor-page .arbor-connector{width:20px}
858
+ .arbor-page .arbor-node-ref{font-size:13px;line-height:17px}
859
+ .arbor-page .arbor-node-handoff{font-size:8px;padding:0 4px}
860
+ }
861
+ @container (max-width:260px){
862
+ .arbor-page .arbor-node{min-width:70px;max-width:100px;padding:6px 7px}
863
+ .arbor-page .arbor-connector{width:16px}
864
+ .arbor-page .arbor-node-handoff{display:none}
865
+ }
866
+ @keyframes arbor-pulse{0%,100%{opacity:.45;transform:scale(.86)}50%{opacity:1;transform:scale(1)}}
867
+ @media (prefers-reduced-motion: reduce){
868
+ .arbor-page .arbor-sky-cell{transform:none!important;transition:none!important}
869
+ .arbor-page .arbor-sky-cell>.arbor-card{transition:none!important}
870
+ }
871
+ @media (max-width:620px){
872
+ .arbor-page{padding:18px 14px 24px;padding-bottom:var(--arbor-dock-reserve,24px)}
873
+ .arbor-page .arbor-hero-title{font-size:23px}
874
+ .arbor-page .arbor-hero-hint{font-size:12px}
875
+ .arbor-page .arbor-dock{max-width:100%}
876
+ .arbor-page .arbor-pill{padding:0 10px}
877
+ }
878
+ @media (prefers-reduced-motion:reduce){.arbor-page .arbor-pulse.is-live{animation:none}}`;
879
+ function installStyles() {
880
+ if (typeof document === "undefined") return () => {
881
+ };
882
+ const existing = document.getElementById(STYLE_ID);
883
+ if (existing !== null) existing.remove();
884
+ const style = document.createElement("style");
885
+ style.id = STYLE_ID;
886
+ style.textContent = CSS;
887
+ document.head.append(style);
888
+ return () => {
889
+ style.remove();
890
+ };
891
+ }
892
+
893
+ // src/ui.ts
894
+ var import_react = require("react");
895
+ var PANEL_ID = "arbor";
896
+ var FILTERS = ["all", "windows", "board", "notes", "env", "schedules"];
897
+ function useChannel(channel) {
898
+ (0, import_react.useEffect)(() => {
899
+ channel.retain();
900
+ return () => {
901
+ channel.release();
902
+ };
903
+ }, [channel]);
904
+ return (0, import_react.useSyncExternalStore)(channel.subscribe, channel.getSnapshot);
905
+ }
906
+ function shownState(window2, stream) {
907
+ return window2.live && stream?.streaming === true ? "running" : window2.state;
908
+ }
909
+ function shownWindows(windows, streams) {
910
+ return windows.map((window2) => {
911
+ const state = shownState(window2, streams[window2.ref]);
912
+ return state === window2.state ? window2 : { ...window2, state };
913
+ });
914
+ }
915
+ function stateLabel(t, window2) {
916
+ if (window2.state === "running") return t("statusRunning");
917
+ if (window2.state === "idle") return t("statusIdle");
918
+ if (window2.state === "paused") return t("statusPaused");
919
+ return t("statusEnded");
920
+ }
921
+ function boardStateLabel(t, state) {
922
+ if (state === "running") return t("statusRunning");
923
+ if (state === "idle") return t("statusIdle");
924
+ if (state === "paused") return t("statusPaused");
925
+ if (state === "ended") return t("statusEnded");
926
+ return state;
927
+ }
928
+ function endLabel(t, reason) {
929
+ if (reason === void 0) return void 0;
930
+ if (reason === "model_ended") return t("endModel");
931
+ if (reason === "idle_timeout") return t("endIdle");
932
+ if (reason === "hard_limit") return t("endHardLimit");
933
+ if (reason === "host_restart") return t("endRestart");
934
+ if (reason === "failed") return t("endFailed");
935
+ if (reason === "paused") return t("endPaused");
936
+ return reason;
937
+ }
938
+ function compact(value) {
939
+ if (value === void 0) return "\u2014";
940
+ if (value < 1e3) return String(value);
941
+ if (value < 1e6) return `${(value / 1e3).toFixed(value < 1e4 ? 1 : 0)}k`;
942
+ return `${(value / 1e6).toFixed(1)}M`;
943
+ }
944
+ function duration(t, ms) {
945
+ if (ms === void 0 || !Number.isFinite(ms) || ms < 0) return void 0;
946
+ if (ms < 6e4) return t("seconds", { value: Math.round(ms / 1e3) });
947
+ if (ms < 36e5) return t("minutes", { value: Math.round(ms / 6e4) });
948
+ return t("hours", { value: Math.round(ms / 36e5) });
949
+ }
950
+ function clock(value, t) {
951
+ if (value === void 0) return "\u2014";
952
+ return new Date(value).toLocaleString(t("dateLocale"), {
953
+ month: "2-digit",
954
+ day: "2-digit",
955
+ hour: "2-digit",
956
+ minute: "2-digit",
957
+ hour12: false
958
+ });
959
+ }
960
+ function dateLine(t, at) {
961
+ const date = new Intl.DateTimeFormat(t("dateLocale"), {
962
+ year: "numeric",
963
+ month: "long",
964
+ day: "numeric",
965
+ weekday: "long"
966
+ }).format(new Date(at)).replace(/(星期[一二三四五六日天])/, " $1");
967
+ return date;
968
+ }
969
+ function chainSummary(t, windows) {
970
+ const last = windows.at(-1);
971
+ return t(windows.length === 1 ? "chainSummaryOne" : "chainSummary", {
972
+ count: windows.length,
973
+ window: last?.ref ?? "\u2014",
974
+ state: last === void 0 ? t("statusIdle") : stateLabel(t, last)
975
+ });
976
+ }
977
+ function sessionTitle(session) {
978
+ if (session.title !== void 0 && session.title !== "") return session.title;
979
+ return session.project?.name ?? (session.sessionId.length > 22 ? `${session.sessionId.slice(0, 22)}\u2026` : session.sessionId);
980
+ }
981
+ function projectLabel(session) {
982
+ return session.project === void 0 ? session.cwd ?? "" : `${session.project.name} \xB7 ${session.project.path}`;
983
+ }
984
+ function liveWindowOf(windows) {
985
+ return [...windows].reverse().find((window2) => window2.live);
986
+ }
987
+ function hero(template, values) {
988
+ return template.split(/(\{\w+\})/g).map((piece, index) => {
989
+ const match = /^\{(\w+)\}$/.exec(piece);
990
+ if (match === null) return piece;
991
+ const value = values[match[1]];
992
+ if (value === void 0) return piece;
993
+ const text = typeof value === "string" ? value : value.text;
994
+ const num = typeof value === "string" ? true : value.num !== false;
995
+ return (0, import_react.createElement)("span", {
996
+ key: `hero-${index}`,
997
+ className: num ? "arbor-hero-num" : "arbor-hero-name",
998
+ "data-arbor-text": num ? "numeric" : "display"
999
+ }, text);
1000
+ });
1001
+ }
1002
+ var CJK = /[\u2018\u2019\u201c\u201d\u3000-\u303f\u3040-\u30ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff\ufe30-\ufe4f\uff00-\uffef\uac00-\ud7af]/;
1003
+ function softJoin(lines) {
1004
+ let joined = "";
1005
+ for (const line of lines) {
1006
+ const next = line.trim();
1007
+ if (next === "") continue;
1008
+ if (joined === "") {
1009
+ joined = next;
1010
+ continue;
1011
+ }
1012
+ const left = joined.at(-1) ?? "";
1013
+ const right = next[0] ?? "";
1014
+ joined += (CJK.test(left) && CJK.test(right) ? "" : " ") + next;
1015
+ }
1016
+ return joined;
1017
+ }
1018
+ function inlineMarkdown(text, key) {
1019
+ return text.split(/(\[[^\]]+\]\([^)]+\)|\*\*[^*]+\*\*|`[^`]+`|\n)/g).map((piece, index) => {
1020
+ const id = `${key}-${index}`;
1021
+ const link = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(piece);
1022
+ if (link !== null) {
1023
+ const label = link[1];
1024
+ const href = link[2];
1025
+ if (/^(https?:|mailto:|#)/.test(href)) {
1026
+ return (0, import_react.createElement)("a", { key: id, className: "arbor-md-link", href, target: "_blank", rel: "noreferrer", "data-arbor-text": "body" }, label);
1027
+ }
1028
+ return (0, import_react.createElement)("span", { key: id, className: "arbor-md-link", title: href, "data-arbor-text": "body" }, label);
1029
+ }
1030
+ if (piece.startsWith("**") && piece.endsWith("**")) {
1031
+ return (0, import_react.createElement)("strong", { key: id, "data-arbor-text": "body" }, piece.slice(2, -2));
1032
+ }
1033
+ if (piece.startsWith("`") && piece.endsWith("`")) {
1034
+ return (0, import_react.createElement)("code", { key: id, className: "arbor-md-code", "data-arbor-text": "code" }, piece.slice(1, -1));
1035
+ }
1036
+ if (piece === "\n") return " ";
1037
+ return piece;
1038
+ });
1039
+ }
1040
+ function MarkdownText(props) {
1041
+ const lines = props.text.split(/\r?\n/);
1042
+ const blocks = [];
1043
+ let index = 0;
1044
+ let block = 0;
1045
+ while (index < lines.length) {
1046
+ const line = lines[index];
1047
+ if (line.trim() === "") {
1048
+ index += 1;
1049
+ continue;
1050
+ }
1051
+ if (line.trimStart().startsWith("```")) {
1052
+ const code = [];
1053
+ index += 1;
1054
+ while (index < lines.length && !lines[index].trimStart().startsWith("```")) {
1055
+ code.push(lines[index]);
1056
+ index += 1;
1057
+ }
1058
+ index += 1;
1059
+ blocks.push((0, import_react.createElement)(
1060
+ "pre",
1061
+ { className: "arbor-md-pre", "data-arbor-text": "code", key: `b${block++}` },
1062
+ (0, import_react.createElement)("code", { "data-arbor-text": "code" }, code.join("\n"))
1063
+ ));
1064
+ continue;
1065
+ }
1066
+ if (/^\s*[-*]\s+/.test(line)) {
1067
+ const items = [];
1068
+ while (index < lines.length && /^\s*[-*]\s+/.test(lines[index])) {
1069
+ const item = lines[index].replace(/^\s*[-*]\s+/, "");
1070
+ items.push((0, import_react.createElement)("li", { key: `li-${index}`, "data-arbor-text": "body" }, ...inlineMarkdown(item, `li${index}`)));
1071
+ index += 1;
1072
+ }
1073
+ blocks.push((0, import_react.createElement)("ul", { className: "arbor-md-list", key: `b${block++}` }, ...items));
1074
+ continue;
1075
+ }
1076
+ const heading = /^(#{1,4})\s+(.*)$/.exec(line);
1077
+ if (heading !== null) {
1078
+ blocks.push((0, import_react.createElement)(
1079
+ "div",
1080
+ { className: "arbor-md-heading", "data-arbor-text": "display", key: `b${block++}` },
1081
+ ...inlineMarkdown(heading[2], `h${index}`)
1082
+ ));
1083
+ index += 1;
1084
+ continue;
1085
+ }
1086
+ const paragraph = [];
1087
+ while (index < lines.length) {
1088
+ const next = lines[index];
1089
+ if (next.trim() === "" || /^\s*[-*]\s+/.test(next) || /^#{1,4}\s+/.test(next) || next.trimStart().startsWith("```")) break;
1090
+ paragraph.push(next);
1091
+ index += 1;
1092
+ }
1093
+ blocks.push((0, import_react.createElement)(
1094
+ "p",
1095
+ { className: "arbor-md-p", "data-arbor-text": "body", key: `b${block++}` },
1096
+ ...inlineMarkdown(softJoin(paragraph), `p${block}`)
1097
+ ));
1098
+ }
1099
+ return (0, import_react.createElement)("div", { className: "arbor-md" }, ...blocks);
1100
+ }
1101
+ var SKY_GUTTER = 20;
1102
+ var SKY_NARROW = 640;
1103
+ var CHAIN_MIN_WIDTH = 300;
1104
+ function hashOf(value) {
1105
+ let hash = 2166136261;
1106
+ for (let at = 0; at < value.length; at += 1) {
1107
+ hash ^= value.charCodeAt(at);
1108
+ hash = Math.imul(hash, 16777619);
1109
+ }
1110
+ return hash >>> 0;
1111
+ }
1112
+ function paperOf(item) {
1113
+ if (item.kind !== "note" && item.kind !== "board") return void 0;
1114
+ const hash = hashOf(item.key);
1115
+ return {
1116
+ tilt: Math.round((-1.6 + hash % 3201 / 1e3) * 100) / 100,
1117
+ offset: (hash >>> 12) % 13
1118
+ };
1119
+ }
1120
+ function cardScale(surface) {
1121
+ return Math.max(0.55, Math.min(1.2, surface / 1100));
1122
+ }
1123
+ function cardWidth(item, surface) {
1124
+ if (item.kind === "empty" || item.kind === "loading") return surface;
1125
+ if (surface < SKY_NARROW) return surface;
1126
+ const scale = cardScale(surface);
1127
+ const base = (() => {
1128
+ switch (item.kind) {
1129
+ case "window":
1130
+ return 560;
1131
+ case "chain":
1132
+ return Math.max(CHAIN_MIN_WIDTH, (item.nodes ?? 1) * 150 + 50);
1133
+ case "session":
1134
+ case "workspace":
1135
+ return 360;
1136
+ case "env":
1137
+ return 320;
1138
+ case "board":
1139
+ return 280;
1140
+ case "note": {
1141
+ const length = item.text?.length ?? 0;
1142
+ return 240 + Math.min(40, length);
1143
+ }
1144
+ case "schedule":
1145
+ return 230;
1146
+ default:
1147
+ return 240;
1148
+ }
1149
+ })();
1150
+ const minimum = item.kind === "chain" ? CHAIN_MIN_WIDTH : 140;
1151
+ return Math.min(surface, Math.max(minimum, Math.round(base * scale)));
1152
+ }
1153
+ function clearOf(box, placed) {
1154
+ return placed.every((other) => box.x >= other.x + other.width + SKY_GUTTER || box.x + box.width + SKY_GUTTER <= other.x || box.y >= other.y + other.height + SKY_GUTTER || box.y + box.height + SKY_GUTTER <= other.y);
1155
+ }
1156
+ function measureCards(surface, items) {
1157
+ const cells = /* @__PURE__ */ new Map();
1158
+ for (const element of Array.from(surface.querySelectorAll("[data-sky-key]"))) {
1159
+ const key = element.dataset["skyKey"];
1160
+ if (key !== void 0) cells.set(key, element);
1161
+ }
1162
+ return items.map((item) => {
1163
+ const cell = cells.get(item.key);
1164
+ if (cell === void 0) return 0;
1165
+ const card = cell.querySelector(".arbor-card") ?? cell;
1166
+ return card.offsetHeight;
1167
+ });
1168
+ }
1169
+ function largestHoleArea(placements, surface) {
1170
+ const boxes = placements.map((one) => ({ x: one.x, y: one.y, width: one.width, height: one.height }));
1171
+ if (boxes.length === 0) return 0;
1172
+ const top = Math.min(...boxes.map((box) => box.y));
1173
+ const bottom = Math.max(...boxes.map((box) => box.y + box.height));
1174
+ const xs = [.../* @__PURE__ */ new Set([0, surface, ...boxes.flatMap((box) => [box.x, box.x + box.width])])].filter((value) => value >= 0 && value <= surface).sort((left, right) => left - right);
1175
+ const ys = [.../* @__PURE__ */ new Set([top, bottom, ...boxes.flatMap((box) => [box.y, box.y + box.height])])].sort((left, right) => left - right);
1176
+ const nx = xs.length - 1;
1177
+ const ny = ys.length - 1;
1178
+ if (nx <= 0 || ny <= 0) return 0;
1179
+ const heights = Array(nx).fill(0);
1180
+ let largest = 0;
1181
+ for (let yi = 0; yi < ny; yi += 1) {
1182
+ for (let xi = 0; xi < nx; xi += 1) {
1183
+ const x = (xs[xi] + xs[xi + 1]) / 2;
1184
+ const y = (ys[yi] + ys[yi + 1]) / 2;
1185
+ const inside = boxes.some((box) => x > box.x + 0.25 && x < box.x + box.width - 0.25 && y > box.y + 0.25 && y < box.y + box.height - 0.25);
1186
+ heights[xi] = inside ? 0 : heights[xi] + (ys[yi + 1] - ys[yi]);
1187
+ }
1188
+ const stack = [];
1189
+ for (let xi = 0; xi <= nx; xi += 1) {
1190
+ const height = xi < nx ? heights[xi] : 0;
1191
+ let start = xi;
1192
+ while (stack.length > 0 && stack[stack.length - 1].height >= height) {
1193
+ const topBar = stack.pop();
1194
+ const width = xs[xi] - xs[topBar.start];
1195
+ largest = Math.max(largest, width * topBar.height);
1196
+ start = topBar.start;
1197
+ }
1198
+ if (stack.length === 0 || stack[stack.length - 1].height < height) stack.push({ height, start });
1199
+ }
1200
+ }
1201
+ return largest;
1202
+ }
1203
+ function packSkySearch(items, widths, heights, surface) {
1204
+ let states = [{ placements: [], boxes: [], lefts: 0, hole: 0 }];
1205
+ const beam = 1e4;
1206
+ for (let index = 0; index < items.length; index += 1) {
1207
+ const item = items[index];
1208
+ const width = widths[index];
1209
+ const height = heights[index];
1210
+ const paper = paperOf(item);
1211
+ const offset = paper?.offset ?? 0;
1212
+ const reserved = height + offset;
1213
+ const next = [];
1214
+ for (const state of states) {
1215
+ const ys = [0, ...state.boxes.map((box) => box.y + box.height + SKY_GUTTER)].sort((left, right) => left - right);
1216
+ let placedAtSomeY = false;
1217
+ for (const at of ys) {
1218
+ const xs = /* @__PURE__ */ new Set([0, surface - width]);
1219
+ for (const box of state.boxes) {
1220
+ xs.add(box.x);
1221
+ xs.add(box.x + box.width + SKY_GUTTER);
1222
+ xs.add(box.x - width - SKY_GUTTER);
1223
+ xs.add(box.x + box.width - width);
1224
+ }
1225
+ const fitting = [...xs].filter((left) => left >= 0 && left + width <= surface).filter((left) => clearOf({ x: left, y: at, width, height: reserved }, state.boxes)).filter((value, atIndex, all) => all.indexOf(value) === atIndex).sort((left, right) => left - right);
1226
+ if (fitting.length === 0) continue;
1227
+ placedAtSomeY = true;
1228
+ const leftmost = fitting[0];
1229
+ for (const x of fitting) {
1230
+ const placements2 = [...state.placements, {
1231
+ key: item.key,
1232
+ x,
1233
+ y: at + offset,
1234
+ width,
1235
+ height,
1236
+ tilt: paper?.tilt ?? 0,
1237
+ offset
1238
+ }];
1239
+ const boxes = [...state.boxes, { x, y: at, width, height: reserved }];
1240
+ next.push({
1241
+ placements: placements2,
1242
+ boxes,
1243
+ lefts: state.lefts + (x === leftmost ? 1 : 0),
1244
+ hole: largestHoleArea(placements2, surface)
1245
+ });
1246
+ }
1247
+ break;
1248
+ }
1249
+ if (placedAtSomeY) continue;
1250
+ const y = state.boxes.length === 0 ? 0 : Math.max(...state.boxes.map((box) => box.y + box.height + SKY_GUTTER));
1251
+ const placements = [...state.placements, { key: item.key, x: 0, y: y + offset, width, height, tilt: paper?.tilt ?? 0, offset }];
1252
+ next.push({
1253
+ placements,
1254
+ boxes: [...state.boxes, { x: 0, y, width, height: reserved }],
1255
+ lefts: state.lefts + 1,
1256
+ hole: largestHoleArea(placements, surface)
1257
+ });
1258
+ }
1259
+ const filtered = index === 0 ? next.filter((state) => state.placements[0]?.x === 0) : next;
1260
+ states = (filtered.length === 0 ? next : filtered).sort((left, right) => left.hole - right.hole || right.lefts - left.lefts).slice(0, beam);
1261
+ }
1262
+ states.sort((left, right) => left.hole - right.hole || right.lefts - left.lefts);
1263
+ return states[0] === void 0 ? [] : [...states[0].placements];
1264
+ }
1265
+ function samePlacements(left, right) {
1266
+ if (left.length !== right.length) return false;
1267
+ return left.every((one, at) => {
1268
+ const other = right[at];
1269
+ return other !== void 0 && one.key === other.key && Math.abs(one.x - other.x) < 0.5 && Math.abs(one.y - other.y) < 0.5 && Math.abs(one.width - other.width) < 0.5 && Math.abs(one.height - other.height) < 0.5;
1270
+ });
1271
+ }
1272
+ function Skyline(props) {
1273
+ const ref = (0, import_react.useRef)(null);
1274
+ const [width, setWidth] = (0, import_react.useState)(0);
1275
+ const [layout, setLayout] = (0, import_react.useState)(void 0);
1276
+ const signature = props.items.map((item) => `${item.key}:${item.kind}:${item.nodes ?? ""}:${item.text?.length ?? 0}`).join("|");
1277
+ const key = `${width}:${signature}`;
1278
+ const active = layout !== void 0 && layout.key === key;
1279
+ const widths = props.items.map((item) => cardWidth(item, width === 0 ? 1100 : width));
1280
+ (0, import_react.useLayoutEffect)(() => {
1281
+ const surface = ref.current;
1282
+ if (surface === null) return;
1283
+ const measure = () => {
1284
+ setWidth(surface.clientWidth);
1285
+ };
1286
+ measure();
1287
+ if (typeof ResizeObserver === "undefined") return;
1288
+ const observer = new ResizeObserver(measure);
1289
+ observer.observe(surface);
1290
+ return () => {
1291
+ observer.disconnect();
1292
+ };
1293
+ }, []);
1294
+ (0, import_react.useLayoutEffect)(() => {
1295
+ const surface = ref.current;
1296
+ if (surface === null || width === 0) return;
1297
+ const heights = measureCards(surface, props.items);
1298
+ if (heights.every((height) => height === 0)) return;
1299
+ const placements = packSkySearch(props.items, widths, heights, width);
1300
+ const bottom = placements.length === 0 ? 0 : Math.max(...placements.map((one) => one.y + one.height));
1301
+ const next = { key, placements, height: bottom + SKY_GUTTER };
1302
+ setLayout((current) => current !== void 0 && current.key === key && samePlacements(current.placements, placements) ? current : next);
1303
+ });
1304
+ return (0, import_react.createElement)("div", {
1305
+ className: "arbor-sky",
1306
+ ref,
1307
+ style: { height: active ? layout.height : 0 }
1308
+ }, ...props.items.map((item, index) => {
1309
+ const placement = active ? layout.placements.find((one) => one.key === item.key) : void 0;
1310
+ const paper = paperOf(item);
1311
+ const style = placement === void 0 ? { left: 0, top: 0, width: widths[index], visibility: "hidden" } : {
1312
+ left: placement.x,
1313
+ top: placement.y,
1314
+ width: placement.width,
1315
+ "--arbor-tilt": `${String(placement.tilt)}deg`
1316
+ };
1317
+ return (0, import_react.createElement)("div", {
1318
+ className: `arbor-sky-cell${paper === void 0 ? "" : " is-paper"}${item.kind === "board" ? " is-board" : ""}`,
1319
+ key: item.key,
1320
+ style,
1321
+ "data-sky-key": item.key,
1322
+ "data-paper": paper === void 0 ? void 0 : "true"
1323
+ }, item.node);
1324
+ }));
1325
+ }
1326
+ function IconButton(props) {
1327
+ return (0, import_react.createElement)("button", {
1328
+ type: "button",
1329
+ className: "arbor-icon-button",
1330
+ title: props.label,
1331
+ "aria-label": props.label,
1332
+ onClick: props.onClick
1333
+ }, props.children);
1334
+ }
1335
+ function SearchIcon() {
1336
+ return (0, import_react.createElement)(
1337
+ "svg",
1338
+ {
1339
+ viewBox: "0 0 20 20",
1340
+ width: 18,
1341
+ height: 18,
1342
+ fill: "none",
1343
+ stroke: "currentColor",
1344
+ strokeWidth: 1.6,
1345
+ strokeLinecap: "round",
1346
+ "aria-hidden": true
1347
+ },
1348
+ (0, import_react.createElement)("circle", { cx: 8.6, cy: 8.6, r: 5.4 }),
1349
+ (0, import_react.createElement)("path", { d: "m12.7 12.7 3.6 3.6" })
1350
+ );
1351
+ }
1352
+ function GearIcon() {
1353
+ return (0, import_react.createElement)(
1354
+ "svg",
1355
+ {
1356
+ viewBox: "0 0 20 20",
1357
+ width: 18,
1358
+ height: 18,
1359
+ fill: "none",
1360
+ stroke: "currentColor",
1361
+ strokeWidth: 1.5,
1362
+ strokeLinecap: "round",
1363
+ "aria-hidden": true
1364
+ },
1365
+ (0, import_react.createElement)("path", { d: "M3 5.5h14M3 10h14M3 14.5h14" }),
1366
+ (0, import_react.createElement)("circle", { cx: 7.5, cy: 5.5, r: 1.8, fill: "var(--dsw-alias-bg-layer-1,#fff)" }),
1367
+ (0, import_react.createElement)("circle", { cx: 12.5, cy: 10, r: 1.8, fill: "var(--dsw-alias-bg-layer-1,#fff)" }),
1368
+ (0, import_react.createElement)("circle", { cx: 6.5, cy: 14.5, r: 1.8, fill: "var(--dsw-alias-bg-layer-1,#fff)" })
1369
+ );
1370
+ }
1371
+ function CloseIcon() {
1372
+ return (0, import_react.createElement)(
1373
+ "svg",
1374
+ {
1375
+ viewBox: "0 0 20 20",
1376
+ width: 16,
1377
+ height: 16,
1378
+ fill: "none",
1379
+ stroke: "currentColor",
1380
+ strokeWidth: 1.6,
1381
+ strokeLinecap: "round",
1382
+ "aria-hidden": true
1383
+ },
1384
+ (0, import_react.createElement)("path", { d: "m5 5 10 10M15 5 5 15" })
1385
+ );
1386
+ }
1387
+ function TopActions(props) {
1388
+ return (0, import_react.createElement)(
1389
+ "div",
1390
+ { className: "arbor-top-actions" },
1391
+ (0, import_react.createElement)(IconButton, { label: props.t("openSearch"), onClick: props.onSearch, children: (0, import_react.createElement)(SearchIcon) }),
1392
+ (0, import_react.createElement)(IconButton, { label: props.t("openSettings"), onClick: props.onSettings, children: (0, import_react.createElement)(GearIcon) })
1393
+ );
1394
+ }
1395
+ function Card(props) {
1396
+ return (0, import_react.createElement)("section", {
1397
+ className: `arbor-card${props.className === void 0 ? "" : ` ${props.className}`}`,
1398
+ ...props.attrs ?? {}
1399
+ }, props.children);
1400
+ }
1401
+ function CardHead(props) {
1402
+ return (0, import_react.createElement)(
1403
+ "div",
1404
+ { className: "arbor-card-head" },
1405
+ (0, import_react.createElement)("span", { className: "arbor-card-kicker arbor-label", "data-arbor-text": "display" }, props.kicker),
1406
+ props.aside === void 0 ? null : (0, import_react.createElement)("span", { className: "arbor-card-aside" }, props.aside)
1407
+ );
1408
+ }
1409
+ function CardFoot(props) {
1410
+ return (0, import_react.createElement)("footer", { className: "arbor-card-foot arbor-label", "data-arbor-text": "body" }, props.children);
1411
+ }
1412
+ function StateChip(props) {
1413
+ return (0, import_react.createElement)("span", {
1414
+ className: `arbor-state arbor-state-${props.state} arbor-label`,
1415
+ "data-arbor-text": "display"
1416
+ }, props.t(props.state === "running" ? "statusRunning" : props.state === "idle" ? "statusIdle" : props.state === "paused" ? "statusPaused" : "statusEnded"));
1417
+ }
1418
+ function refusalWording(refusal) {
1419
+ const prefix = `${refusal.rule} `;
1420
+ return refusal.message.startsWith(prefix) ? refusal.message.slice(prefix.length) : refusal.message;
1421
+ }
1422
+ function EndRefusal(props) {
1423
+ const { refusal, t } = props;
1424
+ return (0, import_react.createElement)(
1425
+ "div",
1426
+ {
1427
+ className: "arbor-refusal",
1428
+ role: "status",
1429
+ "data-arbor-end-refused": refusal.rule
1430
+ },
1431
+ (0, import_react.createElement)(
1432
+ "div",
1433
+ { className: "arbor-refusal-head" },
1434
+ (0, import_react.createElement)("span", { className: "arbor-refusal-title arbor-label", "data-arbor-text": "display" }, t("endRefusedTitle")),
1435
+ (0, import_react.createElement)("span", { className: "arbor-tag arbor-tag-warn arbor-num" }, refusal.rule)
1436
+ ),
1437
+ (0, import_react.createElement)("div", { className: "arbor-refusal-message", "data-arbor-text": "body" }, refusalWording(refusal)),
1438
+ (0, import_react.createElement)("div", { className: "arbor-refusal-hint", "data-arbor-text": "body" }, t("endRefusedHint"))
1439
+ );
1440
+ }
1441
+ function EmptyCard(props) {
1442
+ return (0, import_react.createElement)(
1443
+ Card,
1444
+ { className: "arbor-empty-card" },
1445
+ (0, import_react.createElement)("div", { className: "arbor-empty-mark", "aria-hidden": true }),
1446
+ (0, import_react.createElement)("div", { className: "arbor-empty-title", "data-arbor-text": "display" }, props.title),
1447
+ (0, import_react.createElement)("div", { className: "arbor-empty-hint", "data-arbor-text": "body" }, props.hint)
1448
+ );
1449
+ }
1450
+ function Stream(props) {
1451
+ const text = props.stream?.text ?? "";
1452
+ const send = props.stream?.send ?? "";
1453
+ const [expanded, setExpanded] = (0, import_react.useState)(false);
1454
+ const long = text.length > 160 || send.length > 160;
1455
+ return (0, import_react.createElement)(
1456
+ "div",
1457
+ { className: "arbor-stream-block", "data-arbor-stream": true },
1458
+ (0, import_react.createElement)(
1459
+ "div",
1460
+ { className: "arbor-stream-head" },
1461
+ (0, import_react.createElement)("span", { className: `arbor-pulse${props.stream?.streaming === true ? " is-live" : ""}` }),
1462
+ (0, import_react.createElement)("span", { className: "arbor-stream-label arbor-label", "data-arbor-text": "display" }, props.t("streamText"))
1463
+ ),
1464
+ text === "" ? (0, import_react.createElement)("div", { className: "arbor-stream-empty", "data-arbor-text": "body" }, props.t("streamEmpty")) : (0, import_react.createElement)(
1465
+ "div",
1466
+ { className: `arbor-md-wrap${long && !expanded ? " is-clamped" : ""}${expanded ? " is-expanded" : ""}`, "data-arbor-text": "body" },
1467
+ (0, import_react.createElement)(MarkdownText, { text })
1468
+ ),
1469
+ send === "" ? null : (0, import_react.createElement)(
1470
+ "div",
1471
+ { className: "arbor-stream-send" },
1472
+ (0, import_react.createElement)("div", { className: "arbor-stream-label arbor-label" }, props.t("streamSend")),
1473
+ (0, import_react.createElement)(
1474
+ "div",
1475
+ { className: `arbor-md-wrap${long && !expanded ? " is-clamped" : ""}${expanded ? " is-expanded" : ""}`, "data-arbor-text": "body" },
1476
+ (0, import_react.createElement)(MarkdownText, { text: send })
1477
+ )
1478
+ ),
1479
+ long ? (0, import_react.createElement)("button", {
1480
+ type: "button",
1481
+ className: "arbor-stream-toggle arbor-label",
1482
+ "data-arbor-text": "display",
1483
+ onClick: () => {
1484
+ setExpanded((value) => !value);
1485
+ }
1486
+ }, props.t(expanded ? "collapseStream" : "expandStream")) : null
1487
+ );
1488
+ }
1489
+ function LiveWindowCard(props) {
1490
+ const { window: window2, session, stream, t } = props;
1491
+ const state = shownState(window2, stream);
1492
+ return (0, import_react.createElement)(
1493
+ Card,
1494
+ { className: `arbor-window-card arbor-window-${state}`, attrs: { "data-arbor-window": window2.ref } },
1495
+ (0, import_react.createElement)(CardHead, {
1496
+ kicker: `${t("filterWindows")} \xB7 ${window2.ref}`,
1497
+ aside: (0, import_react.createElement)(StateChip, { state, t })
1498
+ }),
1499
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, window2.doing ?? (session === void 0 ? t("live") : sessionTitle(session))),
1500
+ window2.endRefused === void 0 ? null : (0, import_react.createElement)(EndRefusal, { refusal: window2.endRefused, t }),
1501
+ window2.live ? (0, import_react.createElement)(Stream, { stream, t }) : null,
1502
+ (0, import_react.createElement)(
1503
+ CardFoot,
1504
+ null,
1505
+ session === void 0 ? null : (0, import_react.createElement)("span", { className: "arbor-card-session" }, sessionTitle(session)),
1506
+ (0, import_react.createElement)("span", { className: "arbor-num" }, window2.used === void 0 ? "\u2014" : `${compact(window2.used)} / ${compact(window2.limit)}`),
1507
+ window2.startedAt === void 0 ? null : (0, import_react.createElement)("span", null, " \xB7 ", clock(window2.startedAt, t))
1508
+ )
1509
+ );
1510
+ }
1511
+ function WindowCard(props) {
1512
+ const { window: window2, session, stream, t } = props;
1513
+ const state = shownState(window2, stream);
1514
+ return (0, import_react.createElement)(
1515
+ Card,
1516
+ { className: `arbor-window-card arbor-window-${state}`, attrs: { "data-arbor-window": window2.ref } },
1517
+ (0, import_react.createElement)(CardHead, {
1518
+ kicker: `${t("filterWindows")} \xB7 ${window2.ref}`,
1519
+ aside: (0, import_react.createElement)(StateChip, { state, t })
1520
+ }),
1521
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, window2.doing ?? (session === void 0 ? t("live") : sessionTitle(session))),
1522
+ window2.endRefused === void 0 ? null : (0, import_react.createElement)(EndRefusal, { refusal: window2.endRefused, t }),
1523
+ window2.live ? (0, import_react.createElement)(Stream, { stream, t }) : null,
1524
+ (0, import_react.createElement)(
1525
+ CardFoot,
1526
+ null,
1527
+ (0, import_react.createElement)("span", { className: "arbor-num" }, window2.used === void 0 ? "\u2014" : `${compact(window2.used)} / ${compact(window2.limit)}`),
1528
+ window2.endReason === void 0 ? null : (0, import_react.createElement)("span", null, " \xB7 ", endLabel(t, window2.endReason)),
1529
+ window2.startedAt === void 0 ? null : (0, import_react.createElement)("span", null, " \xB7 ", clock(window2.startedAt, t))
1530
+ )
1531
+ );
1532
+ }
1533
+ function ChainCard(props) {
1534
+ const { session, windows, t } = props;
1535
+ if (windows.length === 0) return null;
1536
+ const shown = shownWindows(windows, props.streams);
1537
+ const handoffs = windows.filter((window2) => window2.handoff !== void 0 && (window2.handoff.topics !== void 0 || window2.handoff.progress !== void 0 || window2.handoff.pending !== void 0 || window2.handoff.agreements !== void 0));
1538
+ return (0, import_react.createElement)(
1539
+ Card,
1540
+ { className: "arbor-chain-card" },
1541
+ (0, import_react.createElement)(CardHead, {
1542
+ kicker: t("cardChain"),
1543
+ aside: session === void 0 ? void 0 : (0, import_react.createElement)("span", { className: "arbor-card-session arbor-label" }, sessionTitle(session))
1544
+ }),
1545
+ (0, import_react.createElement)(
1546
+ "div",
1547
+ { className: "arbor-chain-scroll" },
1548
+ (0, import_react.createElement)(
1549
+ "div",
1550
+ { className: "arbor-chain" },
1551
+ shown.flatMap((window2, index) => {
1552
+ const next = shown[index + 1];
1553
+ const link = next?.continuationOf === window2.ref && window2.endReason === "host_restart" ? t("chainRestart") : void 0;
1554
+ const node = (0, import_react.createElement)(
1555
+ "div",
1556
+ {
1557
+ className: `arbor-node arbor-node-${window2.state}`,
1558
+ key: `node-${window2.ref}`,
1559
+ "data-arbor-window": window2.ref
1560
+ },
1561
+ (0, import_react.createElement)("span", { className: "arbor-node-ref arbor-num" }, window2.ref),
1562
+ (0, import_react.createElement)("span", { className: "arbor-node-state arbor-label" }, stateLabel(t, window2)),
1563
+ window2.handoff === void 0 ? null : (0, import_react.createElement)("span", { className: "arbor-node-handoff arbor-tag arbor-tag-warn" }, t("handoff"))
1564
+ );
1565
+ if (next === void 0) return [node];
1566
+ return [node, (0, import_react.createElement)(
1567
+ "div",
1568
+ { className: "arbor-connector", key: `edge-${window2.ref}` },
1569
+ (0, import_react.createElement)("span", { className: "arbor-connector-line", "aria-hidden": true }),
1570
+ link === void 0 ? null : (0, import_react.createElement)("span", { className: "arbor-connector-label arbor-label" }, link),
1571
+ (0, import_react.createElement)("span", { className: "arbor-connector-arrow", "aria-hidden": true }, "\u2192")
1572
+ )];
1573
+ })
1574
+ )
1575
+ ),
1576
+ handoffs.length === 0 ? null : (0, import_react.createElement)(
1577
+ "div",
1578
+ { className: "arbor-handoffs" },
1579
+ handoffs.map((window2) => (0, import_react.createElement)(
1580
+ "div",
1581
+ { className: "arbor-handoff-row", key: `h-${window2.ref}` },
1582
+ (0, import_react.createElement)("span", { className: "arbor-handoff-ref arbor-num" }, `${window2.ref} ${t("handoff")}`),
1583
+ (0, import_react.createElement)(
1584
+ "span",
1585
+ { className: "arbor-handoff-text", "data-arbor-text": "body" },
1586
+ [...window2.handoff?.topics ?? [], ...window2.handoff?.progress ?? [], ...window2.handoff?.pending ?? []].join(" \xB7 ")
1587
+ )
1588
+ ))
1589
+ ),
1590
+ (0, import_react.createElement)(
1591
+ CardFoot,
1592
+ null,
1593
+ (0, import_react.createElement)("span", { className: "arbor-num" }, chainSummary(t, shown))
1594
+ )
1595
+ );
1596
+ }
1597
+ function SessionCard(props) {
1598
+ const { session, t, onOpen } = props;
1599
+ const shown = shownWindows(session.windows, props.streams);
1600
+ const last = liveWindowOf(shown) ?? shown.at(-1);
1601
+ return (0, import_react.createElement)(
1602
+ Card,
1603
+ { className: "arbor-session-card", attrs: { "data-arbor-session": session.sessionId } },
1604
+ (0, import_react.createElement)(CardHead, {
1605
+ kicker: t("cardSession"),
1606
+ aside: (0, import_react.createElement)("span", { className: `arbor-dot arbor-dot-${last?.state ?? "ended"}` })
1607
+ }),
1608
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, sessionTitle(session)),
1609
+ (0, import_react.createElement)("div", { className: "arbor-card-project", "data-arbor-text": "code" }, projectLabel(session)),
1610
+ (0, import_react.createElement)(
1611
+ CardFoot,
1612
+ null,
1613
+ (0, import_react.createElement)("span", { className: "arbor-num" }, chainSummary(t, shown)),
1614
+ (0, import_react.createElement)("button", {
1615
+ type: "button",
1616
+ className: "arbor-card-link arbor-label",
1617
+ onClick: () => {
1618
+ onOpen(session.sessionId);
1619
+ }
1620
+ }, t("openSessionTab"), " \u2192")
1621
+ )
1622
+ );
1623
+ }
1624
+ function BoardCard(props) {
1625
+ const { line, streaming, t } = props;
1626
+ const state = streaming ? "running" : line.state;
1627
+ return (0, import_react.createElement)(
1628
+ Card,
1629
+ { className: "arbor-board-card arbor-index-card" },
1630
+ (0, import_react.createElement)(CardHead, { kicker: t("filterBoard"), aside: (0, import_react.createElement)("span", { className: "arbor-card-session arbor-label" }, boardStateLabel(t, state)) }),
1631
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "body" }, line.doing),
1632
+ line.openWork === void 0 || line.openWork.length === 0 ? null : (0, import_react.createElement)(
1633
+ "div",
1634
+ { className: "arbor-tags" },
1635
+ line.openWork.map((item) => (0, import_react.createElement)("span", { className: "arbor-tag arbor-tag-warn", key: item }, item))
1636
+ ),
1637
+ (0, import_react.createElement)(
1638
+ CardFoot,
1639
+ null,
1640
+ (0, import_react.createElement)("span", { className: "arbor-num" }, line.window),
1641
+ (0, import_react.createElement)("span", null, " \xB7 ", boardStateLabel(t, state)),
1642
+ (0, import_react.createElement)("span", { className: "arbor-num" }, ` \xB7 ${compact(line.usage.used)} / ${compact(line.usage.limit)}`)
1643
+ )
1644
+ );
1645
+ }
1646
+ function NoteCard(props) {
1647
+ const { note, t } = props;
1648
+ return (0, import_react.createElement)(
1649
+ Card,
1650
+ { className: "arbor-note-card" },
1651
+ (0, import_react.createElement)(CardHead, { kicker: t("filterNotes"), aside: (0, import_react.createElement)("span", { className: "arbor-card-session arbor-num" }, note.ref) }),
1652
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, note.title),
1653
+ note.summary === "" ? null : (0, import_react.createElement)("div", { className: "arbor-note-sum", "data-arbor-text": "body" }, note.summary),
1654
+ (0, import_react.createElement)(
1655
+ CardFoot,
1656
+ null,
1657
+ (0, import_react.createElement)("span", null, t("notesActive")),
1658
+ (0, import_react.createElement)("span", { className: "arbor-num" }, ` \xB7 ${clock(note.time, t)}`)
1659
+ )
1660
+ );
1661
+ }
1662
+ function EnvCard(props) {
1663
+ const { env, t } = props;
1664
+ const skills = env.items.filter((item) => item.kind === "skill");
1665
+ const servers = env.items.filter((item) => item.kind === "mcp");
1666
+ const services = env.items.filter((item) => item.kind === "service");
1667
+ return (0, import_react.createElement)(
1668
+ Card,
1669
+ { className: "arbor-env-card" },
1670
+ (0, import_react.createElement)(CardHead, { kicker: t("cardEnvironment") }),
1671
+ (0, import_react.createElement)(
1672
+ "div",
1673
+ { className: "arbor-env-counts" },
1674
+ (0, import_react.createElement)("span", { className: "arbor-env-count" }, (0, import_react.createElement)("span", { className: "arbor-env-big arbor-num" }, String(skills.length)), (0, import_react.createElement)("span", { className: "arbor-label" }, ` ${t(skills.length === 1 ? "envSkillOne" : "envSkills")}`)),
1675
+ (0, import_react.createElement)("span", { className: "arbor-env-count" }, (0, import_react.createElement)("span", { className: "arbor-env-big arbor-num" }, String(servers.length)), (0, import_react.createElement)("span", { className: "arbor-label" }, ` ${t(servers.length === 1 ? "envMcpOne" : "envMcp")}`)),
1676
+ (0, import_react.createElement)("span", { className: "arbor-env-count" }, (0, import_react.createElement)("span", { className: "arbor-env-big arbor-num" }, String(services.length)), (0, import_react.createElement)("span", { className: "arbor-label" }, ` ${t(services.length === 1 ? "envServiceOne" : "envServices")}`))
1677
+ ),
1678
+ skills.length === 0 ? null : (0, import_react.createElement)(
1679
+ "div",
1680
+ { className: "arbor-env-group" },
1681
+ (0, import_react.createElement)("div", { className: "arbor-env-group-title arbor-label" }, t("envSkills")),
1682
+ skills.map((item) => (0, import_react.createElement)(
1683
+ "div",
1684
+ { className: "arbor-env-row", key: `sk:${item.name}` },
1685
+ (0, import_react.createElement)("span", { className: "arbor-env-name", "data-arbor-text": "display" }, item.name),
1686
+ item.builtin === true ? (0, import_react.createElement)("span", { className: "arbor-tag arbor-tag-ok" }, t("envBuiltin")) : null
1687
+ ))
1688
+ ),
1689
+ servers.length === 0 ? null : (0, import_react.createElement)(
1690
+ "div",
1691
+ { className: "arbor-env-group" },
1692
+ (0, import_react.createElement)("div", { className: "arbor-env-group-title arbor-label" }, t("envMcp")),
1693
+ servers.map((item) => (0, import_react.createElement)(
1694
+ "div",
1695
+ { className: "arbor-env-row", key: `mcp:${item.name}` },
1696
+ (0, import_react.createElement)("span", { className: "arbor-env-name", "data-arbor-text": "display" }, item.name),
1697
+ (0, import_react.createElement)("span", { className: "arbor-env-functions" }, item.functions === void 0 || item.functions.length === 0 ? "\u2014" : item.functions.join(" \xB7 "))
1698
+ ))
1699
+ ),
1700
+ services.length === 0 ? null : (0, import_react.createElement)(
1701
+ "div",
1702
+ { className: "arbor-env-group" },
1703
+ (0, import_react.createElement)("div", { className: "arbor-env-group-title arbor-label" }, t("envServices")),
1704
+ services.map((item) => (0, import_react.createElement)(
1705
+ "div",
1706
+ { className: "arbor-env-row", key: `svc:${item.name}` },
1707
+ (0, import_react.createElement)("span", { className: "arbor-env-name", "data-arbor-text": "display" }, item.name),
1708
+ (0, import_react.createElement)("span", { className: `arbor-tag arbor-tag-${item.state === "running" ? "ok" : item.state === "failed" ? "err" : "warn"}` }, item.state ?? "\u2014")
1709
+ ))
1710
+ )
1711
+ );
1712
+ }
1713
+ function ScheduleCard(props) {
1714
+ const { schedule, t } = props;
1715
+ const repeat = schedule.everyMs !== void 0 ? t("scheduleEvery", { value: duration(t, schedule.everyMs) ?? String(schedule.everyMs) }) : schedule.dailyAt !== void 0 ? t("scheduleDaily", { value: schedule.dailyAt }) : schedule.weekdays !== void 0 ? t("scheduleWeekly", { value: schedule.weekdays.join(" ") }) : "";
1716
+ return (0, import_react.createElement)(
1717
+ Card,
1718
+ { className: "arbor-schedule-card" },
1719
+ (0, import_react.createElement)(CardHead, {
1720
+ kicker: t("cardSchedules"),
1721
+ aside: schedule.paused ? (0, import_react.createElement)("span", { className: "arbor-tag arbor-tag-warn" }, t("schedulePaused")) : void 0
1722
+ }),
1723
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, schedule.name ?? schedule.ref),
1724
+ (0, import_react.createElement)("div", { className: "arbor-schedule-next arbor-num", "data-arbor-text": "numeric" }, t("scheduleNext", { time: clock(schedule.nextAt, t) })),
1725
+ (0, import_react.createElement)(
1726
+ CardFoot,
1727
+ null,
1728
+ (0, import_react.createElement)("span", { className: "arbor-num" }, schedule.ref),
1729
+ repeat === "" ? null : (0, import_react.createElement)("span", null, ` \xB7 ${repeat}`),
1730
+ schedule.fired === void 0 ? null : (0, import_react.createElement)("span", { className: "arbor-num" }, ` \xB7 ${t("scheduleFired", { count: schedule.fired })}`)
1731
+ )
1732
+ );
1733
+ }
1734
+ function WorkspaceCard(props) {
1735
+ const { workspace, t } = props;
1736
+ const projectPath = workspace.project?.path;
1737
+ const showCwd = workspace.project !== void 0 && workspace.cwd !== void 0 && workspace.cwd !== projectPath;
1738
+ const hasFooter = workspace.pointers.length > 0 || workspace.checkouts.length > 0 || workspace.keeps.length > 0 || workspace.openWork.length > 0;
1739
+ return (0, import_react.createElement)(
1740
+ Card,
1741
+ { className: "arbor-workspace-card" },
1742
+ (0, import_react.createElement)(CardHead, { kicker: t("cardWorkspace") }),
1743
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, workspace.project?.name ?? workspace.cwd ?? t("none")),
1744
+ projectPath === void 0 ? null : (0, import_react.createElement)("div", { className: "arbor-card-project arbor-mono", "data-arbor-text": "code" }, projectPath),
1745
+ (0, import_react.createElement)(
1746
+ "div",
1747
+ { className: "arbor-workspace-rows" },
1748
+ showCwd ? (0, import_react.createElement)(
1749
+ "div",
1750
+ { className: "arbor-workspace-row" },
1751
+ (0, import_react.createElement)("span", { className: "arbor-label" }, t("workspaceCwd")),
1752
+ (0, import_react.createElement)("span", { className: "arbor-mono" }, workspace.cwd)
1753
+ ) : null,
1754
+ workspace.pointers.length === 0 ? null : (0, import_react.createElement)(
1755
+ "div",
1756
+ { className: "arbor-workspace-row" },
1757
+ (0, import_react.createElement)("span", { className: "arbor-label" }, t("workspacePointers")),
1758
+ (0, import_react.createElement)("span", { className: "arbor-workspace-list" }, workspace.pointers.map((pointer) => (0, import_react.createElement)(
1759
+ "span",
1760
+ { key: pointer.path, className: "arbor-workspace-item" },
1761
+ (0, import_react.createElement)("span", null, pointer.name),
1762
+ (0, import_react.createElement)("span", { className: "arbor-mono" }, pointer.path)
1763
+ )))
1764
+ ),
1765
+ workspace.checkouts.length === 0 ? null : (0, import_react.createElement)(
1766
+ "div",
1767
+ { className: "arbor-workspace-row" },
1768
+ (0, import_react.createElement)("span", { className: "arbor-label" }, t("workspaceCheckouts")),
1769
+ (0, import_react.createElement)("span", { className: "arbor-workspace-list" }, workspace.checkouts.map((checkout) => (0, import_react.createElement)(
1770
+ "span",
1771
+ { className: "arbor-workspace-item", key: checkout.path },
1772
+ (0, import_react.createElement)("span", { className: "arbor-mono" }, checkout.name),
1773
+ (0, import_react.createElement)("span", { className: `arbor-tag arbor-tag-${checkout.dirty ? "warn" : "ok"}` }, checkout.dirty ? t("workspaceDirty") : t("workspaceClean"))
1774
+ )))
1775
+ ),
1776
+ workspace.keeps.length === 0 ? null : (0, import_react.createElement)(
1777
+ "div",
1778
+ { className: "arbor-workspace-row" },
1779
+ (0, import_react.createElement)("span", { className: "arbor-label" }, t("workspaceKeeps")),
1780
+ (0, import_react.createElement)("span", { className: "arbor-workspace-list" }, workspace.keeps.map((keep) => (0, import_react.createElement)(
1781
+ "span",
1782
+ { className: "arbor-workspace-item", key: keep.path },
1783
+ (0, import_react.createElement)("span", null, keep.name),
1784
+ (0, import_react.createElement)("span", { className: "arbor-mono" }, keep.path)
1785
+ )))
1786
+ ),
1787
+ workspace.openWork.length === 0 ? null : (0, import_react.createElement)(
1788
+ "div",
1789
+ { className: "arbor-workspace-row" },
1790
+ (0, import_react.createElement)("span", { className: "arbor-label" }, t("workspaceOpenWork")),
1791
+ (0, import_react.createElement)("span", { className: "arbor-workspace-list" }, workspace.openWork.map((item) => (0, import_react.createElement)(
1792
+ "span",
1793
+ { className: "arbor-workspace-item", key: `${item.window}:${item.name}` },
1794
+ (0, import_react.createElement)("span", { className: "arbor-num" }, item.window),
1795
+ (0, import_react.createElement)("span", null, item.name)
1796
+ )))
1797
+ )
1798
+ ),
1799
+ hasFooter ? (0, import_react.createElement)(
1800
+ CardFoot,
1801
+ null,
1802
+ workspace.pointers.length === 0 ? null : (0, import_react.createElement)("span", { className: "arbor-num" }, `${workspace.pointers.length} ${t("workspacePointers")}`),
1803
+ workspace.checkouts.length === 0 ? null : (0, import_react.createElement)("span", { className: "arbor-num" }, `${workspace.checkouts.length} ${t("workspaceCheckouts")}`)
1804
+ ) : null
1805
+ );
1806
+ }
1807
+ function SettingRow(props) {
1808
+ return (0, import_react.createElement)(
1809
+ "div",
1810
+ { className: "arbor-setting-row" },
1811
+ (0, import_react.createElement)("span", { className: "arbor-setting-key arbor-label" }, props.label),
1812
+ (0, import_react.createElement)("span", { className: "arbor-setting-value" }, props.children)
1813
+ );
1814
+ }
1815
+ function SettingsBlock(props) {
1816
+ const { settings, dataRoot, backend, shared, t } = props;
1817
+ const tiers = Object.entries(settings.modelTiers ?? {});
1818
+ const judge = Object.entries(settings.judge?.modes ?? {});
1819
+ const judgeLabel = (mode) => mode === "off" ? t("settingsJudgeOff") : mode === "shadow" ? t("settingsJudgeShadow") : t("settingsJudgeOn");
1820
+ const window2 = settings.window ?? {};
1821
+ const notes = settings.notes ?? {};
1822
+ const windowLine = [
1823
+ window2.idleSeconds === void 0 ? "" : `${t("settingsIdle")} ${t("seconds", { value: window2.idleSeconds })}`,
1824
+ window2.contextTokens === void 0 ? "" : `${t("settingsContext")} ${compact(window2.contextTokens)}`,
1825
+ window2.reserveTokens === void 0 ? "" : `${t("settingsReserve")} ${compact(window2.reserveTokens)}`,
1826
+ window2.protectionSeconds === void 0 ? "" : `${t("settingsProtection")} ${t("seconds", { value: window2.protectionSeconds })}`
1827
+ ].filter(Boolean).join(" \xB7 ");
1828
+ const notesLine = [
1829
+ notes.activeCount === void 0 ? "" : `${t("settingsNotesCount")} ${notes.activeCount}`,
1830
+ notes.activeTokenBudget === void 0 ? "" : `${t("settingsNotesTokens")} ${compact(notes.activeTokenBudget)}`
1831
+ ].filter(Boolean).join(" \xB7 ");
1832
+ return (0, import_react.createElement)(
1833
+ "div",
1834
+ { className: "arbor-settings" },
1835
+ (0, import_react.createElement)("div", { className: "arbor-settings-source arbor-muted" }, t(settings.source === "environment" ? "settingsSourceEnvironment" : "settingsSourceDeployment")),
1836
+ (0, import_react.createElement)(
1837
+ "div",
1838
+ { className: "arbor-settings-group" },
1839
+ (0, import_react.createElement)("div", { className: "arbor-settings-group-title arbor-label", "data-arbor-text": "display" }, t("settingsHostData")),
1840
+ (0, import_react.createElement)(SettingRow, { label: t("dataRoot") }, (0, import_react.createElement)("span", { className: "arbor-mono" }, dataRoot)),
1841
+ (0, import_react.createElement)(SettingRow, { label: t("settingsBackend") }, (0, import_react.createElement)("span", { className: "arbor-label" }, backend)),
1842
+ (0, import_react.createElement)(SettingRow, { label: t("settingsShared") }, (0, import_react.createElement)("span", { className: "arbor-mono" }, shared))
1843
+ ),
1844
+ settings.identity === void 0 ? null : (0, import_react.createElement)(
1845
+ SettingRow,
1846
+ { label: t("settingsIdentity") },
1847
+ (0, import_react.createElement)("span", { className: "arbor-clamp" }, settings.identity)
1848
+ ),
1849
+ settings.generalRules === void 0 || settings.generalRules.length === 0 ? null : (0, import_react.createElement)(
1850
+ SettingRow,
1851
+ { label: t("settingsRules") },
1852
+ (0, import_react.createElement)("span", { className: "arbor-num" }, String(settings.generalRules.length))
1853
+ ),
1854
+ tiers.length === 0 ? null : (0, import_react.createElement)(
1855
+ SettingRow,
1856
+ { label: t("settingsTiers") },
1857
+ (0, import_react.createElement)("span", { className: "arbor-mono arbor-settings-wrap" }, tiers.map(([tier, model]) => `${tier} \u2192 ${model}`).join(" \xB7 "))
1858
+ ),
1859
+ settings.defaultTier === void 0 ? null : (0, import_react.createElement)(
1860
+ SettingRow,
1861
+ { label: t("settingsDefaultTier") },
1862
+ (0, import_react.createElement)("span", { className: "arbor-label" }, settings.defaultTier)
1863
+ ),
1864
+ judge.length === 0 ? null : (0, import_react.createElement)(
1865
+ SettingRow,
1866
+ { label: t("settingsJudge") },
1867
+ (0, import_react.createElement)("span", { className: "arbor-tags" }, judge.map(([kind, mode]) => (0, import_react.createElement)("span", {
1868
+ key: kind,
1869
+ className: `arbor-tag arbor-tag-${mode === "off" ? "plain" : mode === "shadow" ? "warn" : "ok"}`
1870
+ }, `${kind} ${judgeLabel(mode)}`)))
1871
+ ),
1872
+ windowLine === "" ? null : (0, import_react.createElement)(SettingRow, { label: t("settingsWindow") }, (0, import_react.createElement)("span", { className: "arbor-num" }, windowLine)),
1873
+ notesLine === "" ? null : (0, import_react.createElement)(SettingRow, { label: t("settingsNotes") }, (0, import_react.createElement)("span", { className: "arbor-num" }, notesLine)),
1874
+ settings.secrets === void 0 || settings.secrets.length === 0 ? null : (0, import_react.createElement)(
1875
+ SettingRow,
1876
+ { label: t("settingsSecrets") },
1877
+ (0, import_react.createElement)("span", { className: "arbor-mono arbor-settings-wrap" }, settings.secrets.join(" \xB7 "))
1878
+ ),
1879
+ (0, import_react.createElement)("div", { className: "arbor-settings-note" }, t("settingsDsh"))
1880
+ );
1881
+ }
1882
+ function SearchGroup(props) {
1883
+ if (props.rows.length === 0) return null;
1884
+ return (0, import_react.createElement)(
1885
+ "div",
1886
+ { className: "arbor-search-group" },
1887
+ (0, import_react.createElement)("div", { className: "arbor-search-group-title arbor-label", "data-arbor-text": "display" }, props.label),
1888
+ props.rows
1889
+ );
1890
+ }
1891
+ function SearchBlock(props) {
1892
+ const { t } = props;
1893
+ const [query, setQuery] = (0, import_react.useState)("");
1894
+ const [result, setResult] = (0, import_react.useState)(void 0);
1895
+ const [error, setError] = (0, import_react.useState)(void 0);
1896
+ const [busy, setBusy] = (0, import_react.useState)(false);
1897
+ const run = async () => {
1898
+ const text = query.trim();
1899
+ if (text === "") return;
1900
+ setBusy(true);
1901
+ setError(void 0);
1902
+ try {
1903
+ const response = await fetch(`/api/arbor/search?q=${encodeURIComponent(text)}`, { cache: "no-store" });
1904
+ if (!response.ok) throw new Error(`search answered ${response.status}`);
1905
+ const body = await response.json();
1906
+ setResult(body);
1907
+ } catch (cause) {
1908
+ setResult(void 0);
1909
+ setError(cause instanceof Error ? cause.message : String(cause));
1910
+ } finally {
1911
+ setBusy(false);
1912
+ }
1913
+ };
1914
+ return (0, import_react.createElement)(
1915
+ "div",
1916
+ { className: "arbor-search" },
1917
+ (0, import_react.createElement)(
1918
+ "div",
1919
+ { className: "arbor-search-row" },
1920
+ (0, import_react.createElement)("input", {
1921
+ className: "arbor-search-input",
1922
+ value: query,
1923
+ placeholder: t("searchPlaceholder"),
1924
+ autoFocus: true,
1925
+ onChange: (event) => {
1926
+ setQuery(event.target.value);
1927
+ },
1928
+ onKeyDown: (event) => {
1929
+ if (event.key === "Enter") {
1930
+ event.preventDefault();
1931
+ void run();
1932
+ }
1933
+ }
1934
+ }),
1935
+ (0, import_react.createElement)("button", {
1936
+ className: "arbor-search-go arbor-label",
1937
+ "data-arbor-text": "display",
1938
+ type: "button",
1939
+ disabled: busy || query.trim() === "",
1940
+ onClick: () => {
1941
+ void run();
1942
+ }
1943
+ }, t("searchButton"))
1944
+ ),
1945
+ (0, import_react.createElement)("div", { className: "arbor-search-hint" }, t("searchHint")),
1946
+ error === void 0 ? null : (0, import_react.createElement)("div", { className: "arbor-error" }, t("searchError", { message: error })),
1947
+ result === void 0 ? null : result.total === 0 ? (0, import_react.createElement)("div", { className: "arbor-search-empty" }, t("searchEmpty")) : (0, import_react.createElement)(
1948
+ "div",
1949
+ { className: "arbor-search-results" },
1950
+ (0, import_react.createElement)("div", { className: "arbor-search-total arbor-num" }, t(result.total === 1 ? "searchTotalOne" : "searchTotal", { count: result.total })),
1951
+ (0, import_react.createElement)(SearchGroup, {
1952
+ label: t("searchJournal"),
1953
+ rows: result.journal.map((hit) => (0, import_react.createElement)(
1954
+ "div",
1955
+ { className: "arbor-hit", key: hit.ref },
1956
+ (0, import_react.createElement)(
1957
+ "div",
1958
+ { className: "arbor-hit-head" },
1959
+ (0, import_react.createElement)("span", { className: "arbor-hit-ref arbor-num" }, hit.ref),
1960
+ (0, import_react.createElement)("span", { className: "arbor-hit-kind arbor-label" }, hit.kind),
1961
+ hit.window === null ? null : (0, import_react.createElement)("span", { className: "arbor-hit-window arbor-num" }, hit.window)
1962
+ ),
1963
+ (0, import_react.createElement)("div", { className: "arbor-hit-sum" }, hit.summary)
1964
+ ))
1965
+ }),
1966
+ (0, import_react.createElement)(SearchGroup, {
1967
+ label: t("searchNotes"),
1968
+ rows: result.notes.map((hit) => (0, import_react.createElement)(
1969
+ "div",
1970
+ { className: "arbor-hit", key: hit.ref },
1971
+ (0, import_react.createElement)(
1972
+ "div",
1973
+ { className: "arbor-hit-head" },
1974
+ (0, import_react.createElement)("span", { className: "arbor-hit-ref arbor-num" }, hit.ref),
1975
+ (0, import_react.createElement)("span", { className: "arbor-hit-kind arbor-label" }, hit.title)
1976
+ ),
1977
+ (0, import_react.createElement)("div", { className: "arbor-hit-sum" }, hit.summary)
1978
+ ))
1979
+ }),
1980
+ (0, import_react.createElement)(SearchGroup, {
1981
+ label: t("searchBoard"),
1982
+ rows: result.board.map((hit) => (0, import_react.createElement)(
1983
+ "div",
1984
+ { className: "arbor-hit", key: hit.window },
1985
+ (0, import_react.createElement)(
1986
+ "div",
1987
+ { className: "arbor-hit-head" },
1988
+ (0, import_react.createElement)("span", { className: "arbor-hit-ref arbor-num" }, hit.window)
1989
+ ),
1990
+ (0, import_react.createElement)("div", { className: "arbor-hit-sum" }, hit.doing)
1991
+ ))
1992
+ })
1993
+ )
1994
+ );
1995
+ }
1996
+ function Sheet(props) {
1997
+ return (0, import_react.createElement)(
1998
+ "div",
1999
+ { className: "arbor-sheet-layer" },
2000
+ (0, import_react.createElement)("div", { className: "arbor-sheet-backdrop", onClick: props.onClose }),
2001
+ (0, import_react.createElement)(
2002
+ "section",
2003
+ { className: "arbor-sheet", role: "dialog", "aria-modal": true, "aria-label": props.title },
2004
+ (0, import_react.createElement)(
2005
+ "header",
2006
+ { className: "arbor-sheet-head" },
2007
+ (0, import_react.createElement)("h2", { className: "arbor-sheet-title", "data-arbor-text": "display" }, props.title),
2008
+ (0, import_react.createElement)("button", { type: "button", className: "arbor-icon-button", title: props.close, "aria-label": props.close, onClick: props.onClose }, (0, import_react.createElement)(CloseIcon))
2009
+ ),
2010
+ (0, import_react.createElement)("div", { className: "arbor-sheet-body" }, props.children)
2011
+ )
2012
+ );
2013
+ }
2014
+ function Dock(props) {
2015
+ const label = (filter) => {
2016
+ if (filter === "all") return props.t("filterAll");
2017
+ if (filter === "windows") return props.t("filterWindows");
2018
+ if (filter === "board") return props.t("filterBoard");
2019
+ if (filter === "notes") return props.t("filterNotes");
2020
+ if (filter === "env") return props.t("filterEnv");
2021
+ return props.t("filterSchedules");
2022
+ };
2023
+ return (0, import_react.createElement)(
2024
+ "nav",
2025
+ { className: "arbor-dock", "aria-label": props.t("pageTitle") },
2026
+ (0, import_react.createElement)(
2027
+ "div",
2028
+ { className: "arbor-dock-pills", role: "tablist" },
2029
+ FILTERS.map((filter) => (0, import_react.createElement)("button", {
2030
+ key: filter,
2031
+ type: "button",
2032
+ role: "tab",
2033
+ "aria-selected": props.filter === filter,
2034
+ className: `arbor-pill${props.filter === filter ? " is-active" : ""} arbor-label`,
2035
+ "data-arbor-text": "display",
2036
+ onClick: () => {
2037
+ props.setFilter(filter);
2038
+ }
2039
+ }, label(filter)))
2040
+ )
2041
+ );
2042
+ }
2043
+ function Overview(props) {
2044
+ const { facts, snapshot, t, filter, setFilter, onSearch, onSettings, onOpenSession } = props;
2045
+ const live = facts.windows.filter((window2) => window2.live);
2046
+ const active = facts.windows.filter((window2) => shownState(window2, snapshot.streams[window2.ref]) === "running");
2047
+ const sessions = facts.sessions;
2048
+ const latest = active.at(-1);
2049
+ const ended = facts.windows.filter((window2) => window2.state === "ended").length;
2050
+ const waiting = sessions.filter((session) => session.windows.length > 0 && !session.windows.some((window2) => shownState(window2, snapshot.streams[window2.ref]) === "running") && session.windows.at(-1)?.state === "idle").length;
2051
+ const show = (section) => filter === "all" || filter === section;
2052
+ const boardCount = facts.board.length;
2053
+ const boardOne = boardCount === 1;
2054
+ const waitingOne = waiting === 1;
2055
+ const endedOne = ended === 1;
2056
+ const sessionsOne = sessions.length === 1;
2057
+ const heroKey = latest === void 0 ? waiting > 0 ? waitingOne ? endedOne ? "heroIdleWaitBothOne" : "heroIdleWaitOne" : endedOne ? "heroIdleWaitEndedOne" : "heroIdleWait" : sessionsOne ? endedOne ? "heroIdleBothOne" : "heroIdleOne" : endedOne ? "heroIdleEndedOne" : "heroIdle" : boardCount === 0 ? waiting > 0 ? waitingOne ? "heroLiveNoBoardWaitOne" : "heroLiveNoBoardWait" : "heroLiveNoBoard" : waiting > 0 ? boardOne ? waitingOne ? "heroLiveWaitBothOne" : "heroLiveWaitBoardOne" : waitingOne ? "heroLiveWaitWaitingOne" : "heroLiveWait" : boardOne ? "heroLiveBoardOne" : "heroLive";
2058
+ const heroContent = sessions.length === 0 ? [(0, import_react.createElement)("span", { key: "empty" }, t("heroEmpty")), (0, import_react.createElement)("span", { className: "arbor-hero-hint", key: "empty-hint", "data-arbor-text": "body" }, t("heroEmptyHint"))] : hero(t(heroKey), {
2059
+ window: latest?.ref ?? "",
2060
+ board: String(boardCount),
2061
+ waiting: String(waiting),
2062
+ ended: String(ended),
2063
+ sessions: String(sessions.length)
2064
+ });
2065
+ const cards = [];
2066
+ if (sessions.length > 0) {
2067
+ if (show("windows")) {
2068
+ for (const window2 of live) {
2069
+ cards.push({
2070
+ key: `live-${window2.ref}`,
2071
+ kind: "window",
2072
+ node: (0, import_react.createElement)(LiveWindowCard, {
2073
+ window: window2,
2074
+ session: sessions.find((session) => session.windows.some((one) => one.ref === window2.ref)),
2075
+ stream: snapshot.streams[window2.ref],
2076
+ t
2077
+ })
2078
+ });
2079
+ }
2080
+ }
2081
+ if (show("windows")) {
2082
+ for (const session of sessions) {
2083
+ cards.push({
2084
+ key: `chain-${session.sessionId}`,
2085
+ kind: "chain",
2086
+ nodes: session.windows.length,
2087
+ node: (0, import_react.createElement)(ChainCard, { session, windows: session.windows, streams: snapshot.streams, t })
2088
+ });
2089
+ }
2090
+ if (live.length === 0 && filter === "windows") {
2091
+ cards.push({ key: "empty-windows", kind: "empty", node: (0, import_react.createElement)(EmptyCard, { title: t("emptyWindows"), hint: t("emptyWindowsHint") }) });
2092
+ }
2093
+ }
2094
+ if (show("all") || filter === "windows") {
2095
+ for (const session of sessions) {
2096
+ cards.push({
2097
+ key: `session-${session.sessionId}`,
2098
+ kind: "session",
2099
+ node: (0, import_react.createElement)(SessionCard, { session, streams: snapshot.streams, t, onOpen: onOpenSession })
2100
+ });
2101
+ }
2102
+ }
2103
+ if (show("board")) {
2104
+ for (const line of facts.board) {
2105
+ cards.push({
2106
+ key: `board-${line.window}`,
2107
+ kind: "board",
2108
+ node: (0, import_react.createElement)(BoardCard, { line, streaming: snapshot.streams[line.window]?.streaming === true, t })
2109
+ });
2110
+ }
2111
+ if (facts.board.length === 0 && filter === "board") {
2112
+ cards.push({ key: "empty-board", kind: "empty", node: (0, import_react.createElement)(EmptyCard, { title: t("emptyBoard"), hint: t("emptyBoardHint") }) });
2113
+ }
2114
+ }
2115
+ if (show("notes")) {
2116
+ for (const note of facts.notes.items) {
2117
+ cards.push({
2118
+ key: `note-${note.ref}`,
2119
+ kind: "note",
2120
+ text: `${note.title} ${note.summary}`,
2121
+ node: (0, import_react.createElement)(NoteCard, { note, t })
2122
+ });
2123
+ }
2124
+ if (facts.notes.items.length === 0 && filter === "notes") {
2125
+ cards.push({ key: "empty-notes", kind: "empty", node: (0, import_react.createElement)(EmptyCard, { title: t("emptyNotes"), hint: t("emptyNotesHint") }) });
2126
+ }
2127
+ }
2128
+ if (show("env")) {
2129
+ cards.push({ key: "env", kind: "env", node: (0, import_react.createElement)(EnvCard, { env: facts.env, t }) });
2130
+ }
2131
+ if (show("schedules")) {
2132
+ for (const schedule of facts.schedules) {
2133
+ cards.push({
2134
+ key: `schedule-${schedule.ref}`,
2135
+ kind: "schedule",
2136
+ node: (0, import_react.createElement)(ScheduleCard, { schedule, t })
2137
+ });
2138
+ }
2139
+ if (facts.schedules.length === 0 && filter === "schedules") {
2140
+ cards.push({ key: "empty-schedules", kind: "empty", node: (0, import_react.createElement)(EmptyCard, { title: t("emptySchedules"), hint: t("emptySchedulesHint") }) });
2141
+ }
2142
+ }
2143
+ }
2144
+ return (0, import_react.createElement)(
2145
+ "div",
2146
+ { className: "arbor-overview" },
2147
+ (0, import_react.createElement)(
2148
+ "header",
2149
+ { className: "arbor-hero" },
2150
+ (0, import_react.createElement)(
2151
+ "div",
2152
+ { className: "arbor-hero-top" },
2153
+ (0, import_react.createElement)("span", { className: "arbor-date arbor-label", "data-arbor-text": "numeric" }, dateLine(t, facts.at)),
2154
+ (0, import_react.createElement)(TopActions, { t, onSearch, onSettings })
2155
+ ),
2156
+ (0, import_react.createElement)("h1", { className: "arbor-hero-title", "data-arbor-text": "display" }, ...heroContent)
2157
+ ),
2158
+ (0, import_react.createElement)(Skyline, { items: cards }),
2159
+ sessions.length === 0 ? null : (0, import_react.createElement)(Dock, { filter, setFilter, t })
2160
+ );
2161
+ }
2162
+ function SessionView(props) {
2163
+ const { sessionId, session, t, onSearch, onSettings, openDshSession } = props;
2164
+ const snapshot = useChannel(channelFor(sessionId));
2165
+ const facts = snapshot.facts;
2166
+ if (facts === void 0) {
2167
+ return (0, import_react.createElement)(
2168
+ "div",
2169
+ { className: "arbor-overview", "data-arbor-page": true, "data-arbor-session-view": sessionId },
2170
+ (0, import_react.createElement)(
2171
+ "header",
2172
+ { className: "arbor-hero" },
2173
+ (0, import_react.createElement)(
2174
+ "div",
2175
+ { className: "arbor-hero-top" },
2176
+ (0, import_react.createElement)("span", { className: "arbor-date arbor-label", "data-arbor-text": "numeric" }, dateLine(t, Date.now())),
2177
+ (0, import_react.createElement)(TopActions, { t, onSearch, onSettings })
2178
+ )
2179
+ ),
2180
+ (0, import_react.createElement)(Skyline, {
2181
+ items: [{
2182
+ key: "loading",
2183
+ kind: "loading",
2184
+ node: (0, import_react.createElement)(
2185
+ Card,
2186
+ { className: "arbor-loading-card" },
2187
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, t("loading")),
2188
+ snapshot.status === "error" && snapshot.error !== void 0 ? (0, import_react.createElement)("div", { className: "arbor-error" }, t("error", { message: snapshot.error })) : null
2189
+ )
2190
+ }]
2191
+ })
2192
+ );
2193
+ }
2194
+ const windows = facts.windows;
2195
+ const now = liveWindowOf(windows) ?? windows.at(-1);
2196
+ const nowState = now === void 0 ? void 0 : shownState(now, snapshot.streams[now.ref]);
2197
+ const boardOne = facts.board.length === 1;
2198
+ const heroContent = windows.length === 0 ? [(0, import_react.createElement)("span", { key: "none" }, t("sessionHeroEmpty")), (0, import_react.createElement)("span", { className: "arbor-hero-hint", key: "hint", "data-arbor-text": "body" }, t("sessionHeroEmptyHint"))] : now === void 0 ? [(0, import_react.createElement)("span", { key: "none" }, t("sessionHeroEmpty"))] : nowState === "running" ? now.continuationOf === void 0 ? hero(t(boardOne ? "sessionHeroFirstBoardOne" : "sessionHeroFirst"), { window: now.ref, board: String(facts.board.length) }) : hero(t(boardOne ? "sessionHeroLiveBoardOne" : "sessionHeroLive"), { window: now.ref, previous: now.continuationOf, board: String(facts.board.length) }) : nowState === "idle" ? hero(t("sessionHeroIdle"), { window: now.ref }) : hero(t(windows.length === 1 ? "sessionHeroEndedOne" : "sessionHeroEnded"), { count: String(windows.length), window: now.ref });
2199
+ const cards = [];
2200
+ if (windows.length === 0) {
2201
+ cards.push({ key: "empty", kind: "empty", node: (0, import_react.createElement)(EmptyCard, { title: t("emptyWindows"), hint: t("sessionHeroEmptyHint") }) });
2202
+ } else {
2203
+ if (now !== void 0) {
2204
+ cards.push({
2205
+ key: `window-${now.ref}`,
2206
+ kind: "window",
2207
+ node: (0, import_react.createElement)(WindowCard, { window: now, session, stream: snapshot.streams[now.ref], t })
2208
+ });
2209
+ }
2210
+ cards.push({
2211
+ key: "chain",
2212
+ kind: "chain",
2213
+ nodes: windows.length,
2214
+ node: (0, import_react.createElement)(ChainCard, { session, windows, streams: snapshot.streams, t })
2215
+ });
2216
+ cards.push({ key: "workspace", kind: "workspace", node: (0, import_react.createElement)(WorkspaceCard, { workspace: facts.workspace, t }) });
2217
+ for (const line of facts.board) {
2218
+ cards.push({
2219
+ key: `board-${line.window}`,
2220
+ kind: "board",
2221
+ node: (0, import_react.createElement)(BoardCard, { line, streaming: snapshot.streams[line.window]?.streaming === true, t })
2222
+ });
2223
+ }
2224
+ for (const note of facts.notes.items) {
2225
+ cards.push({
2226
+ key: `note-${note.ref}`,
2227
+ kind: "note",
2228
+ text: `${note.title} ${note.summary}`,
2229
+ node: (0, import_react.createElement)(NoteCard, { note, t })
2230
+ });
2231
+ }
2232
+ }
2233
+ return (0, import_react.createElement)(
2234
+ "div",
2235
+ { className: "arbor-overview", "data-arbor-page": true, "data-arbor-session-view": sessionId },
2236
+ (0, import_react.createElement)(
2237
+ "header",
2238
+ { className: "arbor-hero" },
2239
+ (0, import_react.createElement)(
2240
+ "div",
2241
+ { className: "arbor-hero-top" },
2242
+ (0, import_react.createElement)("span", { className: "arbor-date arbor-label", "data-arbor-text": "numeric" }, dateLine(t, facts.at)),
2243
+ (0, import_react.createElement)(TopActions, { t, onSearch, onSettings })
2244
+ ),
2245
+ (0, import_react.createElement)("h1", { className: "arbor-hero-title", "data-arbor-text": "display" }, ...heroContent),
2246
+ openDshSession === void 0 ? null : (0, import_react.createElement)(
2247
+ "div",
2248
+ { className: "arbor-hero-links" },
2249
+ (0, import_react.createElement)("button", { type: "button", className: "arbor-card-link arbor-label", onClick: () => {
2250
+ openDshSession(sessionId);
2251
+ } }, t("openInDsh"), " \u2192")
2252
+ )
2253
+ ),
2254
+ (0, import_react.createElement)(Skyline, { items: cards })
2255
+ );
2256
+ }
2257
+ function ArborPage(props) {
2258
+ const t = props.t ?? ((key) => key);
2259
+ const host = useChannel(hostChannel());
2260
+ const facts = host.facts;
2261
+ const [opened, setOpened] = (0, import_react.useState)([]);
2262
+ const [active, setActive] = (0, import_react.useState)(null);
2263
+ const [filter, setFilter] = (0, import_react.useState)("all");
2264
+ const [searchOpen, setSearchOpen] = (0, import_react.useState)(false);
2265
+ const [settingsOpen, setSettingsOpen] = (0, import_react.useState)(false);
2266
+ const sessions = facts?.sessions ?? [];
2267
+ const activeSession = active !== null && sessions.some((session) => session.sessionId === active) ? active : null;
2268
+ (0, import_react.useEffect)(() => {
2269
+ if (!searchOpen && !settingsOpen) return;
2270
+ const close = (event) => {
2271
+ if (event.key !== "Escape") return;
2272
+ setSearchOpen(false);
2273
+ setSettingsOpen(false);
2274
+ };
2275
+ window.addEventListener("keydown", close);
2276
+ return () => {
2277
+ window.removeEventListener("keydown", close);
2278
+ };
2279
+ }, [searchOpen, settingsOpen]);
2280
+ const openSession = (sessionId) => {
2281
+ setOpened((current) => current.includes(sessionId) ? current : [...current, sessionId]);
2282
+ setActive(sessionId);
2283
+ };
2284
+ const closeSession = (sessionId) => {
2285
+ setOpened((current) => current.filter((one) => one !== sessionId));
2286
+ if (active === sessionId) setActive(null);
2287
+ };
2288
+ const tabLabel = (sessionId) => {
2289
+ const session = sessions.find((one) => one.sessionId === sessionId);
2290
+ return session === void 0 ? sessionId.slice(0, 14) : sessionTitle(session);
2291
+ };
2292
+ (0, import_react.useLayoutEffect)(() => {
2293
+ const root = document.querySelector("[data-arbor-page]");
2294
+ if (!(root instanceof HTMLElement)) return;
2295
+ const mark = (selector, role) => {
2296
+ for (const element of Array.from(root.querySelectorAll(selector))) {
2297
+ if (!element.hasAttribute("data-arbor-text")) element.setAttribute("data-arbor-text", role);
2298
+ }
2299
+ };
2300
+ mark(".arbor-num,.arbor-hero-num,.arbor-date,.arbor-node-ref,.arbor-handoff-ref,.arbor-env-big,.arbor-search-total,.arbor-hit-ref,.arbor-hit-window", "numeric");
2301
+ mark(".arbor-mono,.arbor-card-project,.arbor-md-code,.arbor-md-pre,.arbor-md-pre code", "code");
2302
+ mark(".arbor-card-kicker,.arbor-card-title,.arbor-tab,.arbor-pill,.arbor-state,.arbor-tag,.arbor-empty-title,.arbor-card-link,.arbor-stream-label,.arbor-sheet-title,.arbor-search-group-title,.arbor-search-go,.arbor-settings-group-title,.arbor-node-state,.arbor-hero-name,.arbor-env-group-title,.arbor-workspace-row > .arbor-label,.arbor-hit-kind,.arbor-env-count > .arbor-label,.arbor-tab-close,.arbor-connector-label,.arbor-connector-arrow", "display");
2303
+ for (const element of Array.from(root.querySelectorAll("*"))) {
2304
+ if (element.namespaceURI === "http://www.w3.org/2000/svg") continue;
2305
+ if (element.tagName === "SCRIPT" || element.tagName === "STYLE") continue;
2306
+ if (element.hasAttribute("data-arbor-text")) continue;
2307
+ const direct = Array.from(element.childNodes).some((node) => node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim() !== "");
2308
+ if (direct) element.setAttribute("data-arbor-text", "body");
2309
+ }
2310
+ const dock = root.querySelector(".arbor-dock");
2311
+ if (dock === null) {
2312
+ root.style.removeProperty("--arbor-dock-reserve");
2313
+ } else {
2314
+ const bottom = Number.parseFloat(getComputedStyle(dock).bottom) || 0;
2315
+ root.style.setProperty("--arbor-dock-reserve", `${dock.getBoundingClientRect().height + bottom}px`);
2316
+ }
2317
+ });
2318
+ return (0, import_react.createElement)(
2319
+ "div",
2320
+ { className: "arbor-page", "data-arbor-page": true, "data-arbor-text": "body" },
2321
+ (0, import_react.createElement)(
2322
+ "div",
2323
+ { className: "arbor-page-inner" },
2324
+ (0, import_react.createElement)(
2325
+ "nav",
2326
+ { className: "arbor-tabs", "aria-label": t("pageTitle") },
2327
+ (0, import_react.createElement)("button", {
2328
+ type: "button",
2329
+ className: `arbor-tab arbor-label${activeSession === null ? " is-active" : ""}`,
2330
+ "data-arbor-text": "display",
2331
+ onClick: () => {
2332
+ setActive(null);
2333
+ }
2334
+ }, t("tabOverview")),
2335
+ opened.map((sessionId) => (0, import_react.createElement)(
2336
+ "div",
2337
+ { className: `arbor-tab-group${activeSession === sessionId ? " is-active" : ""}`, key: sessionId },
2338
+ (0, import_react.createElement)("button", {
2339
+ type: "button",
2340
+ className: "arbor-tab arbor-label",
2341
+ "data-arbor-text": "display",
2342
+ onClick: () => {
2343
+ setActive(sessionId);
2344
+ }
2345
+ }, tabLabel(sessionId)),
2346
+ (0, import_react.createElement)("button", {
2347
+ type: "button",
2348
+ className: "arbor-tab-close",
2349
+ title: t("closeTab"),
2350
+ "aria-label": t("closeTab"),
2351
+ onClick: () => {
2352
+ closeSession(sessionId);
2353
+ }
2354
+ }, "\xD7")
2355
+ ))
2356
+ ),
2357
+ facts === void 0 ? (0, import_react.createElement)(Skyline, {
2358
+ items: [{
2359
+ key: "loading",
2360
+ kind: "loading",
2361
+ node: (0, import_react.createElement)(
2362
+ Card,
2363
+ { className: "arbor-loading-card" },
2364
+ (0, import_react.createElement)("div", { className: "arbor-card-title", "data-arbor-text": "display" }, t("loading")),
2365
+ host.status === "error" && host.error !== void 0 ? (0, import_react.createElement)("div", { className: "arbor-error" }, t("error", { message: host.error })) : null
2366
+ )
2367
+ }]
2368
+ }) : activeSession === null ? (0, import_react.createElement)(Overview, {
2369
+ facts,
2370
+ snapshot: host,
2371
+ t,
2372
+ filter,
2373
+ setFilter,
2374
+ onSearch: () => {
2375
+ setSearchOpen(true);
2376
+ },
2377
+ onSettings: () => {
2378
+ setSettingsOpen(true);
2379
+ },
2380
+ onOpenSession: openSession
2381
+ }) : (0, import_react.createElement)(SessionView, {
2382
+ sessionId: activeSession,
2383
+ session: sessions.find((session) => session.sessionId === activeSession),
2384
+ t,
2385
+ onSearch: () => {
2386
+ setSearchOpen(true);
2387
+ },
2388
+ onSettings: () => {
2389
+ setSettingsOpen(true);
2390
+ },
2391
+ ...props.openDshSession === void 0 ? {} : { openDshSession: props.openDshSession }
2392
+ })
2393
+ ),
2394
+ searchOpen ? (0, import_react.createElement)(
2395
+ Sheet,
2396
+ { title: t("openSearch"), close: t("closeSheet"), onClose: () => {
2397
+ setSearchOpen(false);
2398
+ } },
2399
+ (0, import_react.createElement)(SearchBlock, { t })
2400
+ ) : null,
2401
+ settingsOpen && facts !== void 0 ? (0, import_react.createElement)(
2402
+ Sheet,
2403
+ { title: t("openSettings"), close: t("closeSheet"), onClose: () => {
2404
+ setSettingsOpen(false);
2405
+ } },
2406
+ (0, import_react.createElement)(SettingsBlock, {
2407
+ settings: facts.settings,
2408
+ dataRoot: facts.dataRoot,
2409
+ backend: facts.backend,
2410
+ shared: facts.shared,
2411
+ t
2412
+ })
2413
+ ) : null
2414
+ );
2415
+ }
2416
+ function ArborPanelIcon(props) {
2417
+ const size = props.size ?? 16;
2418
+ return (0, import_react.createElement)(
2419
+ "svg",
2420
+ {
2421
+ width: size,
2422
+ height: size,
2423
+ viewBox: "0 0 16 16",
2424
+ fill: "none",
2425
+ stroke: "currentColor",
2426
+ strokeWidth: 1.4,
2427
+ strokeLinecap: "round",
2428
+ strokeLinejoin: "round",
2429
+ "aria-hidden": true,
2430
+ "data-arbor-icon": true
2431
+ },
2432
+ (0, import_react.createElement)("circle", { cx: 4, cy: 3.5, r: 1.6 }),
2433
+ (0, import_react.createElement)("circle", { cx: 12, cy: 6, r: 1.6 }),
2434
+ (0, import_react.createElement)("circle", { cx: 4, cy: 12.5, r: 1.6 }),
2435
+ (0, import_react.createElement)("path", { d: "M4 5.1v5.8" }),
2436
+ (0, import_react.createElement)("path", { d: "M5.4 3.9h2.1c2.2 0 3 1 3 2.1" })
2437
+ );
2438
+ }
2439
+
2440
+ // src/client.ts
2441
+ var SEAT_SLOT = "conversation.input.permission";
2442
+ var HERO_SEAT_SLOT = "conversation.hero.agentPreset";
2443
+ var ARBOR_PRESET = "arbor";
2444
+ var PRESET_PROJECTION = "agentPreset";
2445
+ var POLL_MS = 40;
2446
+ var MAX_POLLS = 500;
2447
+ var CARRY_MS = 2e3;
2448
+ var inject = ["slots"];
2449
+ function apply(ctx) {
2450
+ const slots = ctx.slots;
2451
+ const service = (name) => ctx.get(name);
2452
+ let dispose;
2453
+ let poll;
2454
+ let attempts = 0;
2455
+ let heroDispose;
2456
+ let heroAttempts = 0;
2457
+ const stop = () => {
2458
+ if (poll !== void 0) {
2459
+ clearInterval(poll);
2460
+ poll = void 0;
2461
+ }
2462
+ };
2463
+ let bound;
2464
+ ctx.inject(["locale"], () => {
2465
+ const locale = service("locale");
2466
+ if (locale === void 0) return;
2467
+ try {
2468
+ locale.register(NAMESPACE, { zh, en });
2469
+ bound = locale.bind(NAMESPACE);
2470
+ } catch (error) {
2471
+ ctx.logger.warn(`arbor-ui: the arbor locale did not register: ${error instanceof Error ? error.message : String(error)}`);
2472
+ }
2473
+ });
2474
+ const t = (key, args) => (bound ?? fallbackTranslate)(key, args);
2475
+ installStyles();
2476
+ ctx.effect(() => () => {
2477
+ closeChannels();
2478
+ }, "arbor-ui: channels");
2479
+ const openDshSession = (sessionId) => {
2480
+ service("uiWorkspace")?.openSession(sessionId);
2481
+ };
2482
+ const registerSeat = (options, component) => {
2483
+ let stop2;
2484
+ let timer;
2485
+ let attempts2 = 0;
2486
+ const attempt = () => {
2487
+ if (stop2 !== void 0) return;
2488
+ if (++attempts2 > MAX_POLLS) {
2489
+ if (timer !== void 0) clearInterval(timer);
2490
+ timer = void 0;
2491
+ return;
2492
+ }
2493
+ try {
2494
+ stop2 = slots.register(options, component);
2495
+ document.body.dataset["arborSeat"] = `${document.body.dataset["arborSeat"] ?? ""}|ok:${options.name}:${options.id ?? options.key ?? ""}`;
2496
+ } catch (error) {
2497
+ document.body.dataset["arborSeat"] = `${document.body.dataset["arborSeat"] ?? ""}|fail:${options.name}:${error instanceof Error ? error.message : String(error)}`;
2498
+ return;
2499
+ }
2500
+ if (timer !== void 0) clearInterval(timer);
2501
+ timer = void 0;
2502
+ };
2503
+ attempt();
2504
+ if (stop2 === void 0) timer = setInterval(attempt, POLL_MS);
2505
+ return () => {
2506
+ if (timer !== void 0) clearInterval(timer);
2507
+ timer = void 0;
2508
+ stop2?.();
2509
+ };
2510
+ };
2511
+ registerSeat({
2512
+ name: "main",
2513
+ key: PANEL_ID,
2514
+ locale: NAMESPACE,
2515
+ inject: () => ({ openDshSession })
2516
+ }, ArborPage);
2517
+ registerSeat({
2518
+ name: "sidebar.panellist",
2519
+ id: PANEL_ID,
2520
+ order: 10,
2521
+ locale: NAMESPACE,
2522
+ label: () => t("panelLabel")
2523
+ }, ArborPanelIcon);
2524
+ let heroInject;
2525
+ let unboundSeat;
2526
+ const heroSeatFor = (sessionId) => {
2527
+ if (heroInject === void 0) return void 0;
2528
+ if (sessionId === void 0) {
2529
+ unboundSeat ??= heroInject()?.hooks?.agentPresetSeat;
2530
+ return unboundSeat;
2531
+ }
2532
+ return heroInject(sessionId)?.hooks?.agentPresetSeat;
2533
+ };
2534
+ const gate = (seat) => (props) => {
2535
+ const runtime = props;
2536
+ const main = useMainSession();
2537
+ const staged = usePresetPick(runtime.sessionId);
2538
+ const projected = runtime.useProjection?.(PRESET_PROJECTION);
2539
+ const stagedCounts = main === void 0 || main.blank === true;
2540
+ if (projected === ARBOR_PRESET) return null;
2541
+ if (stagedCounts && staged === ARBOR_PRESET) return null;
2542
+ return (0, import_react2.createElement)(seat.component, props);
2543
+ };
2544
+ function usePresetPick(sessionId) {
2545
+ const bound2 = heroSeatFor(sessionId);
2546
+ const unbound = heroSeatFor(void 0);
2547
+ return (0, import_react2.useSyncExternalStore)(
2548
+ (onChange) => {
2549
+ const stops = [bound2, unbound].map((one) => one?.subscribe(onChange));
2550
+ return () => {
2551
+ for (const halt of stops) halt?.();
2552
+ };
2553
+ },
2554
+ () => {
2555
+ const picks = [bound2, unbound].map((one) => one?.getSnapshot().current);
2556
+ return picks[0] !== void 0 && picks[0] !== "" ? picks[0] : picks.find((pick) => pick !== void 0 && pick !== "");
2557
+ }
2558
+ );
2559
+ }
2560
+ const mainSession = () => {
2561
+ const byId = service("sessions")?.list.getSnapshot().byId ?? {};
2562
+ for (const id of Object.keys(byId)) {
2563
+ if ((byId[id]?.retainedBy?.mainView ?? 0) > 0) return byId[id];
2564
+ }
2565
+ return void 0;
2566
+ };
2567
+ function useMainSession() {
2568
+ const sessions = service("sessions");
2569
+ return (0, import_react2.useSyncExternalStore)(
2570
+ (onChange) => sessions?.list.subscribe(onChange) ?? (() => void 0),
2571
+ () => mainSession()
2572
+ );
2573
+ }
2574
+ const presetOf = (session) => typeof session?.projectionValues?.agentPreset === "string" ? session.projectionValues.agentPreset : void 0;
2575
+ const workspaceOf = (sessionId) => {
2576
+ const items = service("workspaces")?.list.getSnapshot().items ?? [];
2577
+ return items.find((item) => item.sessionIds.includes(sessionId));
2578
+ };
2579
+ const crossesArbor = (id) => {
2580
+ const main = mainSession();
2581
+ if (main === void 0 || main.blank !== true) return false;
2582
+ return presetOf(main) === ARBOR_PRESET !== (id === ARBOR_PRESET);
2583
+ };
2584
+ const carryDraft = async (from, to) => {
2585
+ const input = service("conversation")?.input;
2586
+ if (input === void 0) return;
2587
+ let draft;
2588
+ let attachmentIds = [];
2589
+ try {
2590
+ const shell = input.shell(from);
2591
+ draft = shell.draftSnapshot;
2592
+ attachmentIds = shell.snapshot?.attachmentIds ?? [];
2593
+ } catch {
2594
+ return;
2595
+ }
2596
+ if ((draft?.text ?? "") === "" && attachmentIds.length === 0) return;
2597
+ const sessions = service("sessions");
2598
+ const deadline = Date.now() + CARRY_MS;
2599
+ for (; ; ) {
2600
+ if (sessions?.binding(to) !== void 0) {
2601
+ try {
2602
+ const next = input.shell(to);
2603
+ if (attachmentIds.length === 0 || next.addAttachments(attachmentIds)) {
2604
+ next.setDraft(draft ?? { text: "" });
2605
+ }
2606
+ } catch {
2607
+ }
2608
+ return;
2609
+ }
2610
+ if (Date.now() >= deadline) return;
2611
+ await new Promise((resolve) => setTimeout(resolve, POLL_MS));
2612
+ }
2613
+ };
2614
+ const mintUnderPreset = async (id) => {
2615
+ const main = mainSession();
2616
+ const sessions = service("sessions");
2617
+ const navigation = service("uiWorkspace");
2618
+ if (main === void 0 || sessions === void 0 || navigation === void 0) return false;
2619
+ const workspace = workspaceOf(main.id);
2620
+ if (workspace === void 0) return false;
2621
+ try {
2622
+ const minted = await sessions.create({ workspaceId: workspace.workspaceId, agentPreset: id });
2623
+ navigation.openSession(minted);
2624
+ await carryDraft(main.id, minted);
2625
+ await navigation.archiveSession(main.id).catch(() => void 0);
2626
+ return true;
2627
+ } catch (error) {
2628
+ ctx.logger.warn(
2629
+ `arbor-ui: minting a session under preset ${id} failed: ${error instanceof Error ? error.message : String(error)}`
2630
+ );
2631
+ return false;
2632
+ }
2633
+ };
2634
+ const tryShadowHeroSeat = () => {
2635
+ if (heroDispose !== void 0) return;
2636
+ if (++heroAttempts > MAX_POLLS) return;
2637
+ const entry = slots.entriesOfSlot(HERO_SEAT_SLOT)[0];
2638
+ if (entry === void 0) return;
2639
+ const shipped = entry.inject;
2640
+ if (typeof shipped !== "function") return;
2641
+ const wrapped = (sessionId) => {
2642
+ const face = shipped(sessionId);
2643
+ const select = face.select;
2644
+ if (typeof select !== "function") return face;
2645
+ return {
2646
+ ...face,
2647
+ select: (id) => {
2648
+ if (!crossesArbor(id)) return select(id);
2649
+ void mintUnderPreset(id).then((taken) => {
2650
+ if (!taken) void select(id);
2651
+ });
2652
+ return void 0;
2653
+ }
2654
+ };
2655
+ };
2656
+ heroDispose = slots.register({
2657
+ name: HERO_SEAT_SLOT,
2658
+ // Lower than the shipped occupant's default 0, so this gate renders.
2659
+ priority: -1,
2660
+ locale: entry.locale,
2661
+ inject: wrapped
2662
+ }, entry.component);
2663
+ };
2664
+ const tryRegister = () => {
2665
+ if (dispose !== void 0) {
2666
+ stop();
2667
+ return;
2668
+ }
2669
+ if (++attempts > MAX_POLLS) {
2670
+ stop();
2671
+ return;
2672
+ }
2673
+ if (heroInject === void 0) {
2674
+ const face = slots.entriesOfSlot(HERO_SEAT_SLOT)[0]?.inject;
2675
+ if (typeof face === "function") heroInject = face;
2676
+ if (heroInject === void 0) return;
2677
+ }
2678
+ const seat = slots.entriesOfSlot(SEAT_SLOT)[0];
2679
+ if (seat === void 0) return;
2680
+ dispose = slots.register({
2681
+ name: SEAT_SLOT,
2682
+ // Lower than the shipped occupant's default 0, so this gate renders.
2683
+ priority: -1,
2684
+ locale: seat.locale,
2685
+ inject: seat.inject
2686
+ }, gate(seat));
2687
+ tryShadowHeroSeat();
2688
+ stop();
2689
+ };
2690
+ ctx.effect(() => () => {
2691
+ stop();
2692
+ });
2693
+ poll = setInterval(tryRegister, POLL_MS);
2694
+ tryRegister();
2695
+ }
2696
+
2697
+ return module.exports;
2698
+ }
2699
+ });
2700
+