antechamber 0.2.0 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -7,6 +7,37 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-10-15
11
+
12
+ ### Fixed
13
+
14
+ - Sub-agent sessions spawned without a `task` tool call (for example `agent()`
15
+ inside an `eval` cell) now open from their OmO task record instead of
16
+ returning `session not found`, and appear in the session list as soon as
17
+ they exist or complete, so their OmO Graph cards are clickable.
18
+
19
+ ### Changed
20
+
21
+ - ci(release): every push to `main` now ships a patch release automatically; CI bumps the package and OmO Graph extension versions (now kept equal), rolls the CHANGELOG, tags, publishes to npm and attaches the extension zip to the GitHub release. The release guide moved to `RELEASE.md`
22
+
23
+ ## [0.2.1] - 2026-10-15
24
+
25
+ ### Fixed
26
+
27
+ - fix(extension): save OmO Graph data paths without form submission, which OpenChamber's iframe sandbox blocks
28
+ - fix(session): accept OmO messages stored as plain-string content when titling, binding and restoring sessions instead of crashing
29
+ - fix(api): answer OpenChamber's location eviction (`DELETE /api/debug/location`) with OpenCode's empty 204
30
+ - fix(api): answer the v2 routes OpenChamber 2.3.2 calls instead of a generic 404/501. Plugin list and check return an empty inventory, `/api/experimental/generate` returns OpenCode's typed 503, and plugin update, credential update/activate, integration connections and session generate/background/move return `UnsupportedError`
31
+ - fix(api): stop faking mutations. `POST /api/credential` and `PATCH /api/project/{projectID}` return `UnsupportedError` instead of keeping an in-memory credential or echoing an unsaved project
32
+ - fix(api): reject missing or empty prompt and synthetic-context input with a typed 400 before anything reaches OmO
33
+ - fix(vcs): back `/api/vcs/status`, `/api/vcs/diff`, `/api/vcs/branch` and `/api/vcs/base` with read-only git instead of fixed empty data
34
+ - fix(cli): refuse `opencode-antechamber upgrade` with the antechamber upgrade command, and document OpenChamber 2.3.2's OpenCode update notice
35
+
36
+ ### Changed
37
+
38
+ - refactor: use `findLast` for last-match scans
39
+ - docs(api): unknown routes return 404 on both APIs; only unknown subpaths of an existing v2 session return 501
40
+
10
41
  ## [0.2.0] - 2026-10-14
11
42
 
12
43
  ### Added
package/README.md CHANGED
@@ -147,6 +147,13 @@ Restart OpenChamber, or `systemctl --user restart openchamber.service` for a
147
147
  managed service. Run `antechamber doctor` again. If the executable moved,
148
148
  rerun `antechamber setup`.
149
149
 
150
+ OpenChamber 2.3.2 may announce a newer OpenCode release. antechamber reports
151
+ OpenCode API 2.0.20, the minimum OpenChamber accepts, and OpenChamber offers
152
+ no signal that keeps that check while hiding the notice. Dismiss it or turn off
153
+ OpenCode update notifications in OpenChamber. Its Update action runs
154
+ `opencode-antechamber upgrade`, which refuses; upgrade antechamber with the
155
+ commands above.
156
+
150
157
  To roll back wiring, copy the backup printed by setup over the settings file:
151
158
 
152
159
  ```bash
@@ -286,9 +293,10 @@ The smoke scripts (`npm run smoke`, `npm run omo-smoke`, `npm run probe`,
286
293
  process end to end and cost model tokens; they are not part of `npm test`
287
294
  and are not run in CI. See [docs/development.md](docs/development.md).
288
295
 
289
- Releases are cut by pushing a `vX.Y.Z` tag; CI then runs the checks,
290
- publishes to npm with provenance and creates the GitHub release from the
291
- CHANGELOG. See [docs/releasing.md](docs/releasing.md).
296
+ Every commit on `main` is released as a new patch version: CI runs the checks,
297
+ bumps the package and extension versions, tags, publishes to npm with
298
+ provenance and creates the GitHub release from the CHANGELOG. See
299
+ [RELEASE.md](RELEASE.md).
292
300
 
293
301
  ## Docs
294
302
 
@@ -297,7 +305,7 @@ CHANGELOG. See [docs/releasing.md](docs/releasing.md).
297
305
  - [docs/configuration.md](docs/configuration.md) - full environment variable reference
298
306
  - [docs/development.md](docs/development.md) - source layout, testing, smoke scripts
299
307
  - [docs/backends/omo.md](docs/backends/omo.md) - OmO backend details (agent name, task hosts, transcripts)
300
- - [docs/releasing.md](docs/releasing.md) - versioning, tag-driven npm and GitHub releases
308
+ - [RELEASE.md](RELEASE.md) - automatic patch releases to npm and GitHub on every push to `main`
301
309
 
302
310
  - [extensions/omo-dag/README.md](extensions/omo-dag/README.md) - the OmO Graph OpenChamber extension ([design](extensions/omo-dag/docs/DESIGN.md))
303
311
 
@@ -22,6 +22,10 @@ fi
22
22
  case "${1:-}" in
23
23
  serve) shift; exec node "$CLI" serve "$@" ;;
24
24
  --version|-v) exec node "$CLI" --version ;;
25
+ upgrade)
26
+ echo "opencode-antechamber: OpenChamber cannot upgrade antechamber; run 'npm update -g antechamber' (or 'bun add -g antechamber') instead" >&2
27
+ exit 1
28
+ ;;
25
29
  esac
26
30
  echo "opencode-antechamber: unsupported command '${1:-}' (only 'serve' and '--version' are implemented)" >&2
27
31
  exit 1
@@ -21,7 +21,7 @@ export declare function id(prefix: string): string;
21
21
  */
22
22
  export declare function piModelId(responseModel: unknown, model: unknown): unknown;
23
23
  export declare function piStopReason(stopReason: string | undefined, errorMessage?: string): Pick<OCAssistantMessage, "finish" | "error">;
24
- export declare function textOf(content: Array<Record<string, unknown>> | undefined): string;
24
+ export declare function textOf(content: string | Array<Record<string, unknown>> | undefined): string;
25
25
  /**
26
26
  * Recompute the session's own spend from its loaded assistant messages. Subagent
27
27
  * spend stays on the child sessions: OpenChamber sums the parentID subtree, so
@@ -38,6 +38,9 @@ export function piStopReason(stopReason, errorMessage) {
38
38
  }
39
39
  }
40
40
  export function textOf(content) {
41
+ // pi allows plain-string message content (extensions, append_user_message).
42
+ if (typeof content === "string")
43
+ return content;
41
44
  if (!Array.isArray(content))
42
45
  return "";
43
46
  return content.map((b) => (b?.type === "text" && typeof b.text === "string" ? b.text : "")).join("");
@@ -177,7 +180,7 @@ export class PiEventTranslator {
177
180
  });
178
181
  if (status.type === "idle") {
179
182
  s.info.time.idle = Date.now();
180
- const last = [...(s.messages ?? [])].reverse().find((message) => message.info.role === "assistant");
183
+ const last = s.messages?.findLast((message) => message.info.role === "assistant");
181
184
  s.info.outcome =
182
185
  s.interruptRequested || (last?.info.role === "assistant" && last.info.error?.name === "MessageAbortedError")
183
186
  ? "interrupted"
@@ -418,7 +421,7 @@ export class PiEventTranslator {
418
421
  // Trust that over our snapshot, which goes stale whenever pi switches on
419
422
  // its own side (subagent routing, an in-session /model, a resumed file).
420
423
  this.services.applyPiModel(s, msg.provider, piModelId(msg.responseModel, msg.model));
421
- const parent = [...(s.messages ?? [])].reverse().find((m) => m.info.role === "user");
424
+ const parent = s.messages?.findLast((m) => m.info.role === "user");
422
425
  const assistant = {
423
426
  id: id("msg"),
424
427
  sessionID: s.info.id,
package/dist/routes-v1.js CHANGED
@@ -31,6 +31,8 @@ function handleV1GlobalRoute(res, path, method) {
31
31
  return false;
32
32
  }
33
33
  function parseV1Prompt(body, defaultModel) {
34
+ if (!Array.isArray(body.parts))
35
+ throw new RequestBodyError(400, "parts must be an array");
34
36
  const parts = parsePromptParts(body.parts);
35
37
  if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue")
36
38
  throw new RequestBodyError(400, "delivery must be steer or queue");
@@ -39,8 +41,9 @@ function parseV1Prompt(body, defaultModel) {
39
41
  .map((part) => part.text ?? "")
40
42
  .join("\n");
41
43
  const model = modelRefFromBody(body.model ?? (body.variant !== undefined ? defaultModel : undefined), body.variant);
42
- if (!body.noReply)
43
- nativePromptInput(text, parts);
44
+ const native = nativePromptInput(text, parts);
45
+ if (native.message.trim() === "" && !native.images?.length)
46
+ throw new RequestBodyError(400, "prompt requires non-whitespace text, file content, or an agent mention");
44
47
  return { text, model, parts, delivery: body.delivery ?? "steer" };
45
48
  }
46
49
  export async function handleV1(services, req, res, url, path, method, directory) {
package/dist/routes-v2.js CHANGED
@@ -10,6 +10,7 @@ import { OPENCODE_API_VERSION } from "./version.js";
10
10
  import { toV2Permission } from "./approvals.js";
11
11
  import { formQuestionAnswer, questionFormID, toQuestionForm } from "./question-forms.js";
12
12
  import { getAgentName } from "./agent-name.js";
13
+ import { vcsRead, VcsError } from "./vcs.js";
13
14
  import { announceSessionError, compactSession, getCommands, unsupportedShare } from "./session-commands.js";
14
15
  import { defaultV2Model, flattenModels, toV2Message, toV2Session } from "./v2.js";
15
16
  import { agentDescriptors, modelRefFromBody, numberParam, OPENCODE_COMPAT_VERSION, promptAsync, runSessionShell, runSessionSlash, sessionDirectory, sessionMetadata, } from "./route-services.js";
@@ -31,15 +32,6 @@ function v2Session(services, info) {
31
32
  return toV2Session(info, { model: managed?.model, agent: info.agent ?? getAgentName() });
32
33
  }
33
34
  const execFileAsync = promisify(execFile);
34
- const credentialsByDirectory = new Map();
35
- function credentials(directory) {
36
- let stored = credentialsByDirectory.get(directory);
37
- if (stored === undefined) {
38
- stored = new Map();
39
- credentialsByDirectory.set(directory, stored);
40
- }
41
- return stored;
42
- }
43
35
  function encodeSessionCursor(session) {
44
36
  return Buffer.from(JSON.stringify({ updated: session.time.updated, id: session.id })).toString("base64url");
45
37
  }
@@ -179,45 +171,24 @@ export async function handleV2(services, req, res, url, path, method, directory)
179
171
  if (await handleV2Bootstrap(services, res, path, method, directory))
180
172
  return;
181
173
  if (path === "/api/debug/location" && method === "DELETE") {
182
- json(res, 400, errorBody("UnsupportedError", "Location eviction is not available with OmO"));
174
+ // OpenChamber evicts a location after git worktree changes so OpenCode re-reads it. antechamber
175
+ // keeps no per-location instance (project and VCS reads are fresh per request): nothing to evict.
176
+ res.writeHead(204).end();
183
177
  return;
184
178
  }
185
179
  // --- project / vcs / permission / form (2.0.4 bootstrap) ---
186
180
  if (path === "/api/credential" && method === "GET") {
187
- json(res, 200, withLocation([...credentials(directory).values()]));
181
+ json(res, 200, withLocation([]));
188
182
  return;
189
183
  }
190
- if (path === "/api/credential" && method === "POST") {
191
- const body = (await services.body(req));
192
- if (typeof body.id !== "string" ||
193
- body.id === "" ||
194
- typeof body.integrationID !== "string" ||
195
- body.integrationID === "" ||
196
- typeof body.label !== "string") {
197
- json(res, 400, errorBody("BadRequestError", "credential id, integrationID, and label are required"));
198
- return;
199
- }
200
- const stored = credentials(directory);
201
- if (body.activate === true) {
202
- for (const credential of stored.values()) {
203
- if (credential.integrationID === body.integrationID)
204
- credential.active = false;
205
- }
206
- }
207
- const credential = {
208
- id: body.id,
209
- integrationID: body.integrationID,
210
- label: body.label,
211
- value: body.value,
212
- active: body.activate === true,
213
- };
214
- stored.set(credential.id, credential);
215
- json(res, 200, withLocation(credential));
184
+ const credentialMatch = path.match(/^\/api\/credential\/([^/]+)(\/activate)?$/);
185
+ if ((path === "/api/credential" && method === "POST") ||
186
+ (credentialMatch && method === (credentialMatch[2] ? "POST" : "PATCH"))) {
187
+ await services.body(req);
188
+ json(res, 400, errorBody("UnsupportedError", "Credential management is not available; configure providers in OmO"));
216
189
  return;
217
190
  }
218
- const credentialMatch = path.match(/^\/api\/credential\/([^/]+)$/);
219
- if (credentialMatch && method === "DELETE") {
220
- credentials(directory).delete(decodeURIComponent(credentialMatch[1]));
191
+ if (credentialMatch && !credentialMatch[2] && method === "DELETE") {
221
192
  res.writeHead(204).end();
222
193
  return;
223
194
  }
@@ -225,6 +196,36 @@ export async function handleV2(services, req, res, url, path, method, directory)
225
196
  json(res, 200, withLocation([]));
226
197
  return;
227
198
  }
199
+ const integrationMatch = path.match(/^\/api\/integration\/([^/]+)$/);
200
+ if (integrationMatch && method === "GET") {
201
+ json(res, 404, errorBody("NotFoundError", `integration not found: ${decodeURIComponent(integrationMatch[1])}`));
202
+ return;
203
+ }
204
+ if ((method === "POST" && /^\/api\/integration\/[^/]+\/connect\/(key|external|oauth|command)$/.test(path)) ||
205
+ ((method === "GET" || method === "DELETE") &&
206
+ /^\/api\/integration\/[^/]+\/connect\/(oauth|command)\/[^/]+$/.test(path)) ||
207
+ (method === "POST" && /^\/api\/integration\/[^/]+\/connect\/oauth\/[^/]+\/complete$/.test(path))) {
208
+ json(res, 400, errorBody("UnsupportedError", "Integration connections are not available; configure providers in OmO"));
209
+ return;
210
+ }
211
+ if ((path === "/api/plugin" && method === "GET") || (path === "/api/plugin/check" && method === "POST")) {
212
+ if (method === "POST")
213
+ await services.body(req);
214
+ json(res, 200, withLocation([]));
215
+ return;
216
+ }
217
+ if (path === "/api/plugin/update" && method === "POST") {
218
+ json(res, 400, errorBody("UnsupportedError", "Plugin updates are not available with OmO"));
219
+ return;
220
+ }
221
+ if (path === "/api/experimental/generate" && method === "POST") {
222
+ json(res, 503, {
223
+ _tag: "ServiceUnavailableError",
224
+ message: "No generation model is available through antechamber/OmO",
225
+ service: "opencode",
226
+ });
227
+ return;
228
+ }
228
229
  // Read-only inventories OpenChamber loads at bootstrap: OmO has no background shells,
229
230
  // web-search providers or MCP servers, so each is an honest empty list (mutations stay 400).
230
231
  if ((path === "/api/shell" || path === "/api/websearch/provider" || path === "/api/mcp") && method === "GET") {
@@ -263,14 +264,8 @@ export async function handleV2(services, req, res, url, path, method, directory)
263
264
  }
264
265
  const projectMatch = path.match(/^\/api\/project\/([^/]+)$/);
265
266
  if (projectMatch && method === "PATCH") {
266
- const projectId = decodeURIComponent(projectMatch[1]);
267
- const body = (await services.body(req));
268
- json(res, 200, {
269
- id: projectId,
270
- canonical: body.canonical ?? projectId,
271
- time: { created: 0, updated: Date.now() },
272
- sandboxes: [],
273
- });
267
+ await services.body(req);
268
+ json(res, 400, errorBody("UnsupportedError", "Project updates are not available with OmO"));
274
269
  return;
275
270
  }
276
271
  if (path === "/api/permission/request" && method === "GET") {
@@ -309,20 +304,15 @@ export async function handleV2(services, req, res, url, path, method, directory)
309
304
  json(res, 200, withLocation({ provider: "git", branch: current === undefined ? {} : { current } }));
310
305
  return;
311
306
  }
312
- if (path === "/api/vcs/base" && method === "GET") {
313
- json(res, 200, withLocation(null));
314
- return;
315
- }
316
- if (path === "/api/vcs/status" && method === "GET") {
317
- json(res, 200, withLocation([]));
318
- return;
319
- }
320
- if (path === "/api/vcs/branch" && method === "GET") {
321
- json(res, 200, withLocation([]));
322
- return;
323
- }
324
- if (path === "/api/vcs/diff" && method === "GET") {
325
- json(res, 200, withLocation(""));
307
+ if (method === "GET" && ["/api/vcs/base", "/api/vcs/status", "/api/vcs/branch", "/api/vcs/diff"].includes(path)) {
308
+ try {
309
+ json(res, 200, withLocation(await vcsRead(directory, path.slice("/api/vcs/".length), url)));
310
+ }
311
+ catch (error) {
312
+ if (!(error instanceof VcsError))
313
+ throw error;
314
+ json(res, 400, errorBody(error.name, error.message));
315
+ }
326
316
  return;
327
317
  }
328
318
  // --- sessions ---
@@ -494,6 +484,11 @@ export async function handleV2(services, req, res, url, path, method, directory)
494
484
  }
495
485
  }
496
486
  switch (`${method} ${sub}`) {
487
+ case "POST /generate":
488
+ case "POST /background":
489
+ case "POST /move":
490
+ json(res, 400, errorBody("UnsupportedError", `Session ${sub.slice(1)} is not available with OmO`));
491
+ return;
497
492
  case "GET /todo":
498
493
  json(res, 200, { data: services.sessions.todos(sessionId) });
499
494
  return;
@@ -586,12 +581,22 @@ export async function handleV2(services, req, res, url, path, method, directory)
586
581
  return;
587
582
  case "POST /prompt": {
588
583
  const body = (await services.body(req));
584
+ if (typeof body.text !== "string") {
585
+ json(res, 400, { _tag: "InvalidRequestError", message: "text must be a string" });
586
+ return;
587
+ }
589
588
  const explicit = parsePromptParts(body.parts);
590
- const firstText = explicit.find((part) => part.type === "text");
591
- const text = typeof body.text === "string" ? body.text : firstText?.type === "text" ? firstText.text : "";
589
+ const text = body.text;
592
590
  const parts = explicit.length > 0
593
591
  ? [...explicit, ...v2PromptParts("", body.files, body.agents)]
594
592
  : v2PromptParts(text, body.files, body.agents);
593
+ if (text.trim() === "" && !parts.some((part) => part.type === "file" || part.type === "agent")) {
594
+ json(res, 400, {
595
+ _tag: "InvalidRequestError",
596
+ message: "prompt requires non-whitespace text, files, or agents",
597
+ });
598
+ return;
599
+ }
595
600
  const metadata = sessionMetadata(body.metadata);
596
601
  if (body.skills !== undefined) {
597
602
  if (!Array.isArray(body.skills))
@@ -633,13 +638,17 @@ export async function handleV2(services, req, res, url, path, method, directory)
633
638
  case "POST /synthetic":
634
639
  case "POST /context": {
635
640
  const body = (await services.body(req));
641
+ if (typeof body.text !== "string" || body.text.trim() === "") {
642
+ json(res, 400, { _tag: "InvalidRequestError", message: "text must be a non-whitespace string" });
643
+ return;
644
+ }
636
645
  if (body.delivery !== undefined && body.delivery !== "steer" && body.delivery !== "queue") {
637
646
  json(res, 400, errorBody("BadRequestError", 'delivery must be "steer" or "queue"'));
638
647
  return;
639
648
  }
640
649
  const metadata = sessionMetadata(body.metadata);
641
650
  const messageID = typeof body.id === "string" && body.id !== "" ? body.id : `msg_${randomUUID().replaceAll("-", "")}`;
642
- const text = typeof body.text === "string" ? body.text : "";
651
+ const text = body.text;
643
652
  promptAsync(services, managed, text, undefined, true, messageID, body.delivery ?? "steer", undefined, metadata);
644
653
  // OpenChamber 2.3.2 treats anything but 200 + the admitted record as a failed send (captured OpenCode shape).
645
654
  json(res, 200, {
@@ -24,7 +24,7 @@ function assistantMessage(session) {
24
24
  const messages = session.messages ?? [];
25
25
  const boundary = messages.findIndex((message) => message.info.id === session.tree.revert?.messageID);
26
26
  const visible = boundary < 0 ? messages : messages.slice(0, boundary);
27
- const parentID = [...visible].reverse().find((message) => message.info.role === "user")?.info.id ?? "";
27
+ const parentID = visible.findLast((message) => message.info.role === "user")?.info.id ?? "";
28
28
  return {
29
29
  id: id("msg"),
30
30
  sessionID: session.info.id,
@@ -141,7 +141,7 @@ export async function runSlashCommand(sessions, sessionId, input) {
141
141
  await sessions.prompt(sessionId, text, undefined, false, input.messageID, "steer", input.parts ? [{ type: "text", text }, ...input.parts] : undefined);
142
142
  await sessions.reloadMessages(sessionId);
143
143
  const messages = sessions.messages(sessionId) ?? [];
144
- return [...messages].reverse().find((message) => message.info.role === "assistant") ?? messages[messages.length - 1];
144
+ return messages.findLast((message) => message.info.role === "assistant") ?? messages[messages.length - 1];
145
145
  }
146
146
  export function parseShellCommand(value) {
147
147
  if (!isRecord(value) || typeof value.command !== "string" || value.command.trim() === "")
@@ -5,7 +5,7 @@ import { type PiUsage } from "./pi-event-translation.js";
5
5
  export interface PiFileMessage {
6
6
  clientMessageId?: string;
7
7
  role?: string;
8
- content?: Array<Record<string, unknown>>;
8
+ content?: string | Array<Record<string, unknown>>;
9
9
  usage?: PiUsage;
10
10
  provider?: string;
11
11
  model?: string;
@@ -90,7 +90,7 @@ function activeChain(s, entries) {
90
90
  for (const entry of entries)
91
91
  if (typeof entry.id === "string")
92
92
  byId.set(entry.id, entry);
93
- let leaf = [...entries].reverse().find((entry) => entry.type !== "session" && typeof entry.id === "string");
93
+ let leaf = entries.findLast((entry) => entry.type !== "session" && typeof entry.id === "string");
94
94
  let selectedLeaf = s.tree.revert ? s.tree.revert.previousLeafId : s.tree.leafId;
95
95
  if (!s.tree.revert && s.tree.localShellTurns?.length) {
96
96
  // Follow a newly recorded descendant, never a sibling of the selected native branch.
@@ -411,7 +411,7 @@ export function parsePiSessionText(s, agentName, raw) {
411
411
  }
412
412
  // system / custom entries are not rendered
413
413
  }
414
- const lastAssistant = [...chain].reverse().find((entry) => entry.message?.role === "assistant");
414
+ const lastAssistant = chain.findLast((entry) => entry.message?.role === "assistant");
415
415
  const stopReason = lastAssistant?.message?.stopReason;
416
416
  if (lastAssistant !== undefined && ["stop", "length", "error", "aborted"].includes(stopReason ?? "")) {
417
417
  finishProviderToolParts([...toolParts.values()].flat(), entryTime(lastAssistant), stopReason === "aborted" ? "Cancelled" : "No result");
@@ -71,11 +71,14 @@ function titleFromHead(head, piSessionId) {
71
71
  }
72
72
  if (entry.type !== "message" || entry.message?.role !== "user")
73
73
  continue;
74
- const text = (entry.message.content ?? [])
75
- .filter((b) => b?.type === "text" && typeof b.text === "string")
76
- .map((b) => b.text)
77
- .join("\n")
78
- .trim();
74
+ const content = entry.message.content ?? [];
75
+ // pi allows plain-string message content (extensions, append_user_message).
76
+ const text = (typeof content === "string"
77
+ ? content
78
+ : content
79
+ .filter((b) => b?.type === "text" && typeof b.text === "string")
80
+ .map((b) => b.text)
81
+ .join("\n")).trim();
79
82
  if (text !== "")
80
83
  return text.slice(0, TITLE_MAX_CHARS);
81
84
  }
@@ -33,7 +33,7 @@ export interface TreeEntry {
33
33
  message?: {
34
34
  role: string;
35
35
  clientMessageId?: string;
36
- content?: Array<{
36
+ content?: string | Array<{
37
37
  type: string;
38
38
  text?: string;
39
39
  }>;
@@ -86,10 +86,14 @@ function pruneStaleShellTurns(s, positions) {
86
86
  });
87
87
  }
88
88
  function bindOrdinaryEntry(s, entry, messages, cursor) {
89
- const text = entry.message?.content
90
- ?.filter((part) => part.type === "text")
91
- .map((part) => part.text ?? "")
92
- .join("");
89
+ const content = entry.message?.content;
90
+ // pi allows plain-string message content (extensions, append_user_message).
91
+ const text = typeof content === "string"
92
+ ? content
93
+ : content
94
+ ?.filter((part) => part.type === "text")
95
+ .map((part) => part.text ?? "")
96
+ .join("");
93
97
  const index = messages.findIndex((message, position) => position >= cursor &&
94
98
  !s.localMessageIds?.has(message.info.id) &&
95
99
  s.tree.messageEntries[message.info.id] === undefined &&
package/dist/sessions.js CHANGED
@@ -711,7 +711,7 @@ export class SessionManager {
711
711
  }
712
712
  approvalsFor(s) {
713
713
  s.hasPendingInteraction = () => s.approvals?.hasPending(s.info.id) ?? false;
714
- return new ApprovalBridge(s.info.id, (event) => this.emit(s.info.directory, event), (response) => s.pi?.respondExtensionUI(response), () => s.streaming?.message.id ?? [...(s.messages ?? [])].reverse().find((m) => m.info.role === "assistant")?.info.id, async (response) => {
714
+ return new ApprovalBridge(s.info.id, (event) => this.emit(s.info.directory, event), (response) => s.pi?.respondExtensionUI(response), () => s.streaming?.message.id ?? s.messages?.findLast((m) => m.info.role === "assistant")?.info.id, async (response) => {
715
715
  if (!s.pi)
716
716
  return false;
717
717
  const reply = await s.pi.command({ ...response, type: "extension_ui_response" });
@@ -9,6 +9,11 @@ export declare function taskParts(template: OCToolPart, args: Record<string, unk
9
9
  export declare function updateTaskParts(parts: OCToolPart[], result: ToolResult, ended: boolean, now: number): void;
10
10
  /** Wake messages carry arrays of completion envelopes, not a flat details object. */
11
11
  export declare function taskCompletions(value: unknown): Record<string, unknown>[];
12
+ /** Reverse lookup of a child session id, for tasks whose spawn never surfaced as a `task` tool call. */
13
+ export declare function findTaskBySession(sessionId: string): {
14
+ path: string;
15
+ data: Record<string, unknown>;
16
+ } | undefined;
12
17
  export declare function findTask(id: string, parentPiId: string): {
13
18
  path: string;
14
19
  data: Record<string, unknown>;
@@ -36,6 +41,9 @@ export declare class SubagentBridge {
36
41
  private readonly parse;
37
42
  private readonly children;
38
43
  private readonly calls;
44
+ private readonly removed;
45
+ /** Parent pi session of each task record already read; a record's parent never changes. */
46
+ private readonly taskParents;
39
47
  constructor(store: SessionStore, sessions: Map<string, ManagedSession>, emit: (directory: string, event: OCEvent) => void, parse: ParseHistory);
40
48
  private archive;
41
49
  wire(parent: ManagedSession): void;
@@ -46,6 +54,18 @@ export declare class SubagentBridge {
46
54
  private discoverParts;
47
55
  private create;
48
56
  restore(id: string): ManagedSession | undefined;
57
+ /**
58
+ * Tasks spawned outside a `task` tool call (e.g. `agent()` inside an `eval`
59
+ * cell) never produce a card, so their session is built from the task record.
60
+ */
61
+ private adopt;
62
+ /**
63
+ * Root sessions are keyed by the md5 of their pi session id, but a nested child
64
+ * is keyed by its task id, so its pi session id is matched through the registry.
65
+ */
66
+ private parentFor;
67
+ private childIdsByPi;
68
+ private detachedParent;
49
69
  private managedSession;
50
70
  restoreAll(): void;
51
71
  owns(id: string): boolean;
package/dist/subagents.js CHANGED
@@ -147,16 +147,53 @@ function directories(path) {
147
147
  throw error;
148
148
  }
149
149
  }
150
- export function findTask(id, parentPiId) {
151
- if (!taskId(id))
152
- return undefined;
150
+ function taskStores() {
153
151
  const agent = piAgentDir();
154
152
  const registry = jsonObject(join(agent, "rpc", "task-stores.json"));
155
- const stores = new Set([
153
+ return new Set([
156
154
  ...Object.keys(record(registry?.stores) ? registry.stores : {}),
157
155
  ...directories(join(agent, "projects")).map((project) => join(project, "senpi-task")),
158
156
  ]);
159
- for (const store of stores) {
157
+ }
158
+ function taskFiles() {
159
+ const files = [];
160
+ for (const store of taskStores()) {
161
+ let names;
162
+ try {
163
+ names = readdirSync(join(store, "tasks"));
164
+ }
165
+ catch (error) {
166
+ if (error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR", "EACCES"].includes(String(error.code)))
167
+ continue;
168
+ throw error;
169
+ }
170
+ for (const name of names) {
171
+ const id = name.endsWith(".json") ? name.slice(0, -5) : "";
172
+ if (taskId(id))
173
+ files.push({ id, path: join(store, "tasks", name) });
174
+ }
175
+ }
176
+ return files;
177
+ }
178
+ function childRecord(id, path) {
179
+ const data = jsonObject(path);
180
+ return data?.task_id === id && typeof data.parent_session_id === "string" ? data : undefined;
181
+ }
182
+ /** Reverse lookup of a child session id, for tasks whose spawn never surfaced as a `task` tool call. */
183
+ export function findTaskBySession(sessionId) {
184
+ for (const { id, path } of taskFiles()) {
185
+ if (ocIdFor(id) !== sessionId)
186
+ continue;
187
+ const data = childRecord(id, path);
188
+ if (data)
189
+ return { path, data };
190
+ }
191
+ return undefined;
192
+ }
193
+ export function findTask(id, parentPiId) {
194
+ if (!taskId(id))
195
+ return undefined;
196
+ for (const store of taskStores()) {
160
197
  const path = join(store, "tasks", `${id}.json`);
161
198
  const data = jsonObject(path);
162
199
  if (data?.task_id === id && data.parent_session_id === parentPiId)
@@ -211,6 +248,9 @@ export class SubagentBridge {
211
248
  parse;
212
249
  children = new Map();
213
250
  calls = new Map();
251
+ removed = new Set();
252
+ /** Parent pi session of each task record already read; a record's parent never changes. */
253
+ taskParents = new Map();
214
254
  constructor(store, sessions, emit, parse) {
215
255
  this.store = store;
216
256
  this.sessions = sessions;
@@ -251,7 +291,12 @@ export class SubagentBridge {
251
291
  if (message.customType !== "omo-senpi:wake")
252
292
  return;
253
293
  for (const data of taskCompletions(message.details)) {
254
- const child = this.children.get(ocIdFor(String(data.task_id)));
294
+ const id = ocIdFor(String(data.task_id));
295
+ // A registry-only child (after a restart) or a task with no card (spawned from
296
+ // `eval`) is not loaded yet; restore() covers both.
297
+ if (!this.children.has(id))
298
+ this.restore(id);
299
+ const child = this.children.get(id);
255
300
  if (child?.session.info.parentID !== parent.info.id)
256
301
  continue;
257
302
  child.task.details = { ...child.task.details, ...data };
@@ -326,19 +371,74 @@ export class SubagentBridge {
326
371
  if (known)
327
372
  return known.session;
328
373
  const entry = this.store.get(id);
329
- if (!entry?.task || !entry.parentID)
374
+ if (!entry)
375
+ return this.removed.has(id) ? undefined : this.adopt(id);
376
+ if (!entry.task || !entry.parentID)
330
377
  return undefined;
331
- const parent = this.managedSession({
332
- id: entry.parentID,
333
- projectID: entry.directory,
334
- directory: entry.directory,
378
+ const child = this.create(this.detachedParent(entry.parentID, entry.directory, entry), entry.task);
379
+ child.session.info.title = entry.title ?? child.session.info.title;
380
+ return child.session;
381
+ }
382
+ /**
383
+ * Tasks spawned outside a `task` tool call (e.g. `agent()` inside an `eval`
384
+ * cell) never produce a card, so their session is built from the task record.
385
+ */
386
+ adopt(id, found = findTaskBySession(id)) {
387
+ if (!found)
388
+ return undefined;
389
+ const parentPi = String(found.data.parent_session_id);
390
+ const parent = this.parentFor(parentPi);
391
+ if (!parent)
392
+ return undefined;
393
+ this.wire(parent);
394
+ const spec = record(found.data.spawn_spec) ? found.data.spawn_spec : {};
395
+ const name = found.data.name ?? found.data.description;
396
+ const child = this.create(parent, {
397
+ id: String(found.data.task_id),
398
+ parentPiId: parentPi,
399
+ input: {
400
+ ...(typeof name === "string" ? { description: name } : {}),
401
+ ...(typeof spec.prompt === "string" ? { prompt: spec.prompt } : {}),
402
+ ...(typeof found.data.agent_type === "string" ? { subagent_type: found.data.agent_type } : {}),
403
+ },
404
+ details: found.data,
405
+ });
406
+ this.refresh(child, false);
407
+ return child.session;
408
+ }
409
+ /**
410
+ * Root sessions are keyed by the md5 of their pi session id, but a nested child
411
+ * is keyed by its task id, so its pi session id is matched through the registry.
412
+ */
413
+ parentFor(parentPi, childByPi = this.childIdsByPi()) {
414
+ const rootID = ocIdFor(parentPi);
415
+ const loaded = [...this.sessions.values()].find((s) => s.info.id === rootID || s.piSessionId === parentPi || s.restored?.piSessionId === parentPi);
416
+ if (loaded)
417
+ return loaded;
418
+ const id = this.store.get(rootID) ? rootID : childByPi.get(parentPi);
419
+ if (id === undefined)
420
+ return undefined;
421
+ const entry = this.store.get(id);
422
+ return this.sessions.get(id) ?? (entry ? this.detachedParent(id, entry.directory, entry) : undefined);
423
+ }
424
+ childIdsByPi() {
425
+ const byPi = new Map();
426
+ for (const id of this.store.childIds()) {
427
+ const piSessionId = this.store.get(id)?.piSessionId;
428
+ if (piSessionId)
429
+ byPi.set(piSessionId, id);
430
+ }
431
+ return byPi;
432
+ }
433
+ detachedParent(id, directory, times) {
434
+ return this.managedSession({
435
+ id,
436
+ projectID: directory,
437
+ directory,
335
438
  title: "",
336
439
  version: ADAPTER_VERSION,
337
- time: { created: entry.created, updated: entry.updated },
440
+ time: { created: times.created, updated: times.updated },
338
441
  });
339
- const child = this.create(parent, entry.task);
340
- child.session.info.title = entry.title ?? child.session.info.title;
341
- return child.session;
342
442
  }
343
443
  managedSession(info) {
344
444
  return {
@@ -357,6 +457,24 @@ export class SubagentBridge {
357
457
  restoreAll() {
358
458
  for (const id of this.store.childIds())
359
459
  this.restore(id);
460
+ const childByPi = this.childIdsByPi();
461
+ for (const { id, path } of taskFiles()) {
462
+ const sessionId = ocIdFor(id);
463
+ if (this.children.has(sessionId) || this.removed.has(sessionId) || this.store.get(sessionId))
464
+ continue;
465
+ let parentPi = this.taskParents.get(id);
466
+ if (parentPi === undefined) {
467
+ const parent = childRecord(id, path)?.parent_session_id;
468
+ if (typeof parent !== "string")
469
+ continue;
470
+ this.taskParents.set(id, (parentPi = parent));
471
+ }
472
+ if (!this.parentFor(parentPi, childByPi))
473
+ continue;
474
+ const data = childRecord(id, path);
475
+ if (data)
476
+ this.adopt(sessionId, { path, data });
477
+ }
360
478
  }
361
479
  owns(id) {
362
480
  return this.children.has(id);
@@ -628,6 +746,7 @@ export class SubagentBridge {
628
746
  this.closeWatchers(child);
629
747
  child.parts.clear();
630
748
  this.children.delete(id);
749
+ this.removed.add(id);
631
750
  }
632
751
  dispose() {
633
752
  for (const child of this.children.values()) {
package/dist/vcs.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare class VcsError extends Error {
2
+ name: string;
3
+ }
4
+ /** Read-only VCS operations; the router supplies the same directory as vcs.get. */
5
+ export declare function vcsRead(directory: string, operation: string, url: URL): Promise<unknown>;
package/dist/vcs.js ADDED
@@ -0,0 +1,199 @@
1
+ import { execFile } from "node:child_process";
2
+ import { devNull } from "node:os";
3
+ import { promisify } from "node:util";
4
+ const exec = promisify(execFile);
5
+ const GIT_EXEC_TIMEOUT_MS = 5_000;
6
+ const GIT_EXEC_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
7
+ export class VcsError extends Error {
8
+ name = "BadRequestError";
9
+ }
10
+ async function git(directory, args, difference = false) {
11
+ try {
12
+ const { stdout } = await exec("git", args, {
13
+ cwd: directory,
14
+ timeout: GIT_EXEC_TIMEOUT_MS,
15
+ maxBuffer: GIT_EXEC_MAX_BUFFER_BYTES,
16
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" },
17
+ });
18
+ return stdout;
19
+ }
20
+ catch (error) {
21
+ // git diff --no-index exits 1 when it successfully finds a difference.
22
+ if (difference &&
23
+ error instanceof Error &&
24
+ "code" in error &&
25
+ error.code === 1 &&
26
+ "stdout" in error &&
27
+ typeof error.stdout === "string") {
28
+ return error.stdout;
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ async function optionalRef(directory, args) {
34
+ try {
35
+ return (await git(directory, args)).trim() || undefined;
36
+ }
37
+ catch (error) {
38
+ if (error instanceof Error && "code" in error && error.code === 1)
39
+ return undefined;
40
+ throw error;
41
+ }
42
+ }
43
+ async function base(directory) {
44
+ if ((await optionalRef(directory, ["rev-parse", "--verify", "--quiet", "HEAD"])) === undefined)
45
+ return null;
46
+ const current = await optionalRef(directory, ["symbolic-ref", "--quiet", "--short", "HEAD"]);
47
+ if (current !== undefined) {
48
+ const reflog = await git(directory, ["reflog", "show", "--format=%gs", "--max-count=100", "HEAD"]);
49
+ for (const line of reflog.split("\n")) {
50
+ const checkout = line.match(/^checkout: moving from (.+) to (.+)$/);
51
+ if (!checkout || checkout[2] !== current || checkout[1] === current)
52
+ continue;
53
+ const name = checkout[1];
54
+ const ref = await optionalRef(directory, ["show-ref", "--verify", "--hash", `refs/heads/${name}`]);
55
+ if (ref !== undefined)
56
+ return { name, ref, source: "reflog" };
57
+ }
58
+ }
59
+ const remote = await optionalRef(directory, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
60
+ for (const name of [...(remote === undefined ? [] : [remote]), "main", "master"]) {
61
+ const ref = await optionalRef(directory, ["rev-parse", "--verify", "--quiet", `${name}^{commit}`]);
62
+ if (ref !== undefined)
63
+ return { name, ref, source: "default" };
64
+ }
65
+ return null;
66
+ }
67
+ function nonNegativeInteger(url, key, fallback) {
68
+ const value = url.searchParams.get(key);
69
+ if (value === null)
70
+ return fallback;
71
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value))) {
72
+ throw new VcsError(`${key} must be a non-negative integer`);
73
+ }
74
+ return Number(value);
75
+ }
76
+ async function diffs(directory, revision, committed, context, patches) {
77
+ const args = ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--no-renames", revision];
78
+ if (committed)
79
+ args.push("HEAD");
80
+ const names = (await git(directory, [...args, "--name-status", "-z", "--"])).split("\0");
81
+ const statuses = new Map();
82
+ for (let i = 0; i + 1 < names.length; i += 2) {
83
+ const file = names[i + 1];
84
+ statuses.set(file, names[i] === "A" ? "added" : names[i] === "D" ? "deleted" : "modified");
85
+ }
86
+ const counts = (await git(directory, [...args, "--numstat", "-z", "--"])).split("\0").filter(Boolean);
87
+ const result = [];
88
+ let bytes = 0;
89
+ const append = (entry) => {
90
+ bytes += Buffer.byteLength(entry.patch);
91
+ if (bytes > GIT_EXEC_MAX_BUFFER_BYTES)
92
+ throw new VcsError("Git diff exceeds the output limit");
93
+ result.push(entry);
94
+ };
95
+ for (const entry of counts) {
96
+ const first = entry.indexOf("\t");
97
+ const second = entry.indexOf("\t", first + 1);
98
+ const file = entry.slice(second + 1);
99
+ append({
100
+ file,
101
+ additions: Number(entry.slice(0, first)) || 0,
102
+ deletions: Number(entry.slice(first + 1, second)) || 0,
103
+ status: statuses.get(file) ?? "modified",
104
+ patch: patches ? await git(directory, [...args, `--unified=${context}`, "--", file]) : "",
105
+ });
106
+ }
107
+ if (!committed) {
108
+ const untracked = (await git(directory, ["ls-files", "--others", "--exclude-standard", "-z"])).split("\0");
109
+ for (const file of untracked.filter(Boolean)) {
110
+ const args = ["diff", "--no-index", "--no-ext-diff", "--no-textconv", "--no-color", `--unified=${context}`];
111
+ const counts = await git(directory, [...args, "--numstat", "--", devNull, file], true);
112
+ const [additions, deletions] = counts.split("\t");
113
+ append({
114
+ file,
115
+ additions: Number(additions) || 0,
116
+ deletions: Number(deletions) || 0,
117
+ status: "added",
118
+ patch: patches ? await git(directory, [...args, "--", devNull, file], true) : "",
119
+ });
120
+ }
121
+ }
122
+ return result.sort((left, right) => left.file.localeCompare(right.file));
123
+ }
124
+ /** Read-only VCS operations; the router supplies the same directory as vcs.get. */
125
+ export async function vcsRead(directory, operation, url) {
126
+ const mode = url.searchParams.get("mode");
127
+ if (operation === "diff" && !["working", "branch", "committed"].includes(mode ?? "")) {
128
+ throw new VcsError("mode must be working, branch, or committed");
129
+ }
130
+ const context = operation === "diff" ? nonNegativeInteger(url, "context", 3) : 0;
131
+ const limit = operation === "branch" ? nonNegativeInteger(url, "limit", 100) : 0;
132
+ try {
133
+ let root;
134
+ try {
135
+ root = (await git(directory, ["rev-parse", "--show-toplevel"])).trim();
136
+ }
137
+ catch (error) {
138
+ if (error instanceof Error &&
139
+ "stderr" in error &&
140
+ typeof error.stderr === "string" &&
141
+ error.stderr.startsWith("fatal: not a git repository")) {
142
+ return operation === "base" ? null : [];
143
+ }
144
+ throw error;
145
+ }
146
+ if (operation === "base")
147
+ return await base(root);
148
+ if (operation === "branch") {
149
+ const search = url.searchParams.get("search") ?? "";
150
+ return (await git(root, ["for-each-ref", "--format=%(refname:short)", "refs/heads/"]))
151
+ .split("\n")
152
+ .filter((name) => name !== "" && name.includes(search))
153
+ .sort()
154
+ .slice(0, limit);
155
+ }
156
+ let revision = await optionalRef(root, ["rev-parse", "--verify", "--quiet", "HEAD"]);
157
+ if (operation === "diff" && mode !== "working") {
158
+ const requestedBase = url.searchParams.get("base") ?? (await base(root))?.name;
159
+ if (requestedBase === undefined)
160
+ throw new VcsError("No base branch is available");
161
+ const ref = await optionalRef(root, [
162
+ "rev-parse",
163
+ "--verify",
164
+ "--quiet",
165
+ "--end-of-options",
166
+ `${requestedBase}^{commit}`,
167
+ ]);
168
+ if (ref === undefined)
169
+ throw new VcsError("Base must identify an existing commit");
170
+ revision = (await git(root, ["merge-base", ref, "HEAD"])).trim();
171
+ }
172
+ // Without HEAD, git's empty tree is the baseline for staged and untracked files.
173
+ revision ??= (await git(root, ["hash-object", "-t", "tree", devNull])).trim();
174
+ const files = await diffs(root, revision, operation === "diff" && mode === "committed", context, operation === "diff");
175
+ if (operation !== "status")
176
+ return files;
177
+ const statuses = new Map(files.map(({ patch: _patch, ...status }) => [status.file, status]));
178
+ // Index and worktree changes can cancel in the HEAD diff while still dirty.
179
+ const porcelain = await git(root, ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames"]);
180
+ for (const entry of porcelain.split("\0").filter(Boolean)) {
181
+ const file = entry.slice(3);
182
+ if (statuses.has(file))
183
+ continue;
184
+ const code = entry.slice(0, 2);
185
+ statuses.set(file, {
186
+ file,
187
+ additions: 0,
188
+ deletions: 0,
189
+ status: code.includes("D") ? "deleted" : code.includes("A") || code === "??" ? "added" : "modified",
190
+ });
191
+ }
192
+ return [...statuses.values()].sort((left, right) => left.file.localeCompare(right.file));
193
+ }
194
+ catch (error) {
195
+ if (error instanceof VcsError)
196
+ throw error;
197
+ throw new VcsError(`Git VCS operation failed: ${error instanceof Error ? error.message : String(error)}`);
198
+ }
199
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-graph",
3
- "version": "0.1.0",
3
+ "version": "0.2.2",
4
4
  "description": "OpenChamber extension: live, interactive graph of the OmO sub-agents behind the current session",
5
5
  "private": true,
6
6
  "type": "module",
@@ -50,7 +50,7 @@
50
50
  #settings input { padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); font: 12px var(--mono); }
51
51
  #settings .row { display: flex; justify-content: flex-end; gap: 6px; }
52
52
  #settings button { padding: 5px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-2); cursor: pointer; }
53
- #settings button[type="submit"] { background: var(--accent); color: var(--oc-primary-fg, #001); border-color: transparent; }
53
+ #settings #btn-save { background: var(--accent); color: var(--oc-primary-fg, #001); border-color: transparent; }
54
54
 
55
55
  #viewport { position: relative; flex: 1; overflow: hidden; cursor: grab; touch-action: none;
56
56
  background-image: radial-gradient(color-mix(in srgb, var(--muted) 22%, transparent) 1px, transparent 1px);
@@ -158,7 +158,7 @@
158
158
  <form id="settings" hidden>
159
159
  <label>OmO agent directory <input name="agentDir" placeholder="~/.omo/agent (default, then ~/.pi/agent)" autocomplete="off" spellcheck="false" /></label>
160
160
  <label>Connector state directory <input name="stateDir" placeholder="~/.config/antechamber (default)" autocomplete="off" spellcheck="false" /></label>
161
- <div class="row"><button type="submit">Save</button></div>
161
+ <div class="row"><button id="btn-save" type="button">Save</button></div>
162
162
  </form>
163
163
  <div id="viewport">
164
164
  <div id="world" hidden>
@@ -28,7 +28,7 @@
28
28
  * { scrollbar-color: auto; }
29
29
  ::-webkit-scrollbar-thumb, ::-webkit-scrollbar-thumb:hover { background: CanvasText; }
30
30
  }
31
- `;var Tt=["file","directory","other","missing"],ze=e=>!!(e&&"sessionId"in e),Ke=e=>!!(e&&"sent"in e&&!("sessionId"in e));var Se=500,Te=32e3,_t=16e3,yt=128,At=200,wt=2e3,vt=16e3,Mt=80,Ot=200,Lt=16e3;var Ct=2e3;var Ve=2e4,_e=1024,ye=2e6;var Ae=64e3,we=8e3,je=4e3;var qe=9e4;var Rt=999,ve=500,Gt=["HOST_UNAVAILABLE","HOST_TIMEOUT","HOST_REJECTED","DISCONNECTED","DISABLED","BAD_PATH","NO_INTEGRATION","NO_SERVICE","SERVICE_FAILED","NO_SESSION","SESSION_BUSY","NOT_GRANTED","NO_DIRECTORY","NOT_FOUND","FILE_TOO_LARGE","DENIED","NO_MODEL","MODEL_FAILED"],It=["stopped","starting","ready","failed"],ro=new Set(Gt),$t=e=>ro.has(e),Nt=e=>e&&$t(e)?e:"HOST_REJECTED",re=e=>e===void 0?!1:e===null||e===!0||e===!1||String(e)===e?!0:Number(e)===e?Number.isFinite(e):Array.isArray(e)?e.every(re):Object(e)===e?Object.values(e).every(re):!1,Ut=e=>re(e)&&JSON.stringify(e).length<=Lt,St=e=>e?.trim().slice(0,Ot)??"",se=e=>{let t=e.id.trim().slice(0,yt),r=e.title.trim().slice(0,At),a=e.url.trim().slice(0,wt),l=e.text?.trim().slice(0,vt),p=e.author?.trim().slice(0,Mt),s=e.kind==="pull"?"pull":"issue",c={providerId:e.providerId.trim(),id:t,title:r||t,url:a,kind:s};if(l&&(c.text=l),p&&(c.author=p),s==="pull"){let h=St(e.branches?.head),A=St(e.branches?.base);h&&A&&(c.branches={head:h,base:A})}return Ut(e.data)&&(c.data=e.data),c},Ye=e=>{let t=se(e);return e.projectId&&(t.projectId=e.projectId),e.navigation&&(t.navigation=e.navigation),e.worktree&&(t.worktree=e.worktree),t},Je=e=>{let t={text:e.text.trim().slice(0,_t)};return e.send&&(t.send=!0),t},We=e=>e===null||!Number.isFinite(e)?null:Math.min(Rt,Math.max(0,Math.round(e)));var ie=e=>e.length>0&&e.length<=_e&&!e.includes("\0")&&!e.includes("\\");var Me=e=>!e.startsWith("/")||e.includes("\0")||e.includes("\\")||e.includes("://")||e.length>Ct?!1:!e.split("/").some(r=>r==="."||r===".."),so=new Set(It),Qe=e=>!!(e&&"status"in e&&so.has(String(e.status))&&!("body"in e)),Oe=e=>!!(e&&"status"in e&&"body"in e&&Number.isInteger(e.status)),Ze=e=>!!(e&&"content"in e&&String(e.content)===e.content),et=e=>!!(e&&"written"in e&&e.written===!0),tt=e=>!!(e&&"entries"in e&&Array.isArray(e.entries)),io=new Set(Tt),ot=e=>!!(e&&"kind"in e&&"size"in e&&io.has(String(e.kind))&&Number.isFinite(e.size)),nt=e=>!!(e&&"text"in e&&String(e.text)===e.text&&!("status"in e)),ao=new Set(["workspace","ready","directory","session","connection","settings","session-lifecycle","item","resolve","action"]),co=e=>Object(e)===e?e:null,Fe=e=>String(e)===e&&e.length>0,lo=e=>{if(!Fe(e.id))return null;if(e.ok===!0){let t={channel:k,v:1,type:"result",id:e.id,ok:!0};return Object(e.payload)===e.payload&&(t.payload=e.payload),t}return e.ok===!1&&Fe(e.error)?{channel:k,v:1,type:"result",id:e.id,ok:!1,error:e.error,code:Nt(Fe(e.code)?e.code:void 0)}:null},rt=e=>{let t=co(e);return!t||t.channel!==k||t.v!==1?null:t.type==="result"?lo(t):!ao.has(String(t.type))||Object(t.payload)!==t.payload?null:t};var u=class extends Error{code;constructor(t,r){super(r),this.name="HostRequestError",this.code=t}},Dt=()=>Promise.reject(new u("BAD_PATH",'Request path must start with "/" and stay on the declared origin.')),Le=()=>Promise.reject(new u("BAD_PATH",`File path must be 1 to ${_e} characters without NUL or backslash.`)),S=e=>(e.value+=1,`oc-${e.value}`),st=(e={})=>{let t=e.target??("window"in globalThis?window:null);if(!t)throw new u("HOST_UNAVAILABLE","No window. connectHost runs in a browser frame.");let r=e.acceptSource??(o=>o===t.parent),a=e.requestTimeoutMs??Ve,l=new Set,p=new Set,s=new Set,c=new Set,h=new Set,A=new Set,i=new Set,g=null,f=null,x=new Map,I=new Map,W=!1,E={value:0},T=null,q=null,xt=o=>o?{sessionId:o.id,phase:o.busy?"started":"completed"}:null,Q=o=>{t.parent.postMessage(o,"*")},N=(o,n)=>{for(let m of o)try{m(n)}catch(w){console.error(w)}},bt=o=>{if(!(o instanceof MessageEvent)||!r(o.source))return;let n=rt(o.data);if(!n)return;if(n.type==="workspace"){let w=I.get(n.payload.subscriptionId);w&&N([w],n.payload.snapshot);return}if(n.type==="ready"){T=n.payload,q=xt(n.payload.session),N(l,n.payload),N(p,n.payload.directory),N(s,n.payload.session),q&&N(c,q),N(h,n.payload.connection),N(A,n.payload.settings),N(i,n.payload.item);return}if(n.type==="directory"){T&&(T={...T,directory:n.payload.directory}),N(p,n.payload.directory);return}if(n.type==="session"){T&&(T={...T,session:n.payload.session}),n.payload.session?q?.sessionId!==n.payload.session.id&&(q=xt(n.payload.session)):q=null,N(s,n.payload.session);return}if(n.type==="session-lifecycle"){q=n.payload,N(c,n.payload);return}if(n.type==="connection"){T&&(T={...T,connection:n.payload.connection}),N(h,n.payload.connection);return}if(n.type==="settings"){T&&(T={...T,settings:n.payload.settings}),N(A,n.payload.settings);return}if(n.type==="item"){T&&(T={...T,item:n.payload.item}),N(i,n.payload.item);return}if(n.type==="action"){let w=R=>{W||Q({channel:k,v:1,type:"action-result",id:n.id,payload:R})},F=f;if(!F){w({ok:!1,error:"This extension does not handle background actions."});return}Promise.resolve().then(()=>F(n.payload)).then(()=>w({ok:!0}),R=>{let Xe=(R instanceof Error?R.message:String(R)).trim();w({ok:!1,error:(Xe||"Action failed.").slice(0,ve)})});return}if(n.type==="resolve"){let w=R=>{Q({channel:k,v:1,type:"resolve-result",id:n.id,payload:R})},F=g;if(!F){w({error:"This extension does not resolve commands."});return}Promise.resolve().then(()=>F(n.payload)).then(R=>w({item:R?se(R):null}),R=>{let Xe=(R instanceof Error?R.message:String(R)).trim();w({error:(Xe||"Command failed.").slice(0,ve)})});return}let m=x.get(n.id);if(m){if(clearTimeout(m.timer),x.delete(n.id),n.ok){m.resolve(n.payload);return}m.reject(new u(n.code,n.error))}};t.addEventListener("message",bt),Q({channel:k,v:1,type:"hello"});let H=(o,n=a)=>W||t.parent===t?Promise.reject(new u("HOST_UNAVAILABLE","No host frame. This page is not in an iframe.")):new Promise((m,w)=>{let F=setTimeout(()=>{x.delete(o.id),w(new u("HOST_TIMEOUT","Host did not answer in time."))},n);x.set(o.id,{resolve:m,reject:w,timer:F}),Q(o)}),P=o=>H(o).then(()=>{}),Z={channel:k,v:1},ee=(o,n=1024)=>{if(!o.trim()||o.length>n)throw new u("HOST_REJECTED",`Identity must contain 1 to ${n} characters.`)},He=async o=>{o.kind!=="projects"&&ee(o.projectId);let n=await H({...Z,type:"workspace-read",id:S(E),payload:o});if(!n||!("kind"in n)||!("state"in n)||n.kind!==o.kind)throw new u("HOST_REJECTED","Host did not return workspace data.");return n},Pe=async(o,n)=>{o.kind!=="projects"&&ee(o.projectId);let m=S(E);I.set(m,n);try{await P({...Z,type:"workspace-subscribe",id:S(E),payload:{subscriptionId:m,query:o}})}catch(w){throw I.delete(m),W||Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:m}}),w}return()=>{!I.delete(m)||W||Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:m}})}},be=async o=>{if("key"in o&&(o.key.length===0||o.key.length>128))throw new u("HOST_REJECTED","Storage key must contain 1 to 128 characters.");if(o.op==="set"&&!re(o.value))throw new u("HOST_REJECTED","Storage values must be JSON.");if(o.op==="set"&&new TextEncoder().encode(JSON.stringify(o.value)).length>65536)throw new u("HOST_REJECTED","Storage value exceeds 64 KiB.");let n=await H({...Z,type:"storage",id:S(E),payload:o});if(!n||!("storage"in n)||n.op!==o.op)throw new u("HOST_REJECTED","Host did not return storage data.");return n};return{onAction:o=>(f=o,()=>{f===o&&(f=null)}),listProjects:async()=>{let o=await He({kind:"projects"});if(o.kind!=="projects")throw new u("HOST_REJECTED","Expected projects.");return o},listWorktrees:async o=>{let n=await He({kind:"worktrees",projectId:o});if(n.kind!=="worktrees")throw new u("HOST_REJECTED","Expected worktrees.");return n},listSessions:async o=>{let n=await He({kind:"sessions",projectId:o});if(n.kind!=="sessions")throw new u("HOST_REJECTED","Expected sessions.");return n},onProjects:o=>Pe({kind:"projects"},n=>{n.kind==="projects"&&o(n)}),onWorktrees:(o,n)=>Pe({kind:"worktrees",projectId:o},m=>{m.kind==="worktrees"&&n(m)}),onSessions:(o,n)=>Pe({kind:"sessions",projectId:o},m=>{m.kind==="sessions"&&n(m)}),openSession:async o=>{ee(o),await P({...Z,type:"open-session",id:S(E),payload:{sessionId:o}})},storage:{get:async o=>{let n=await be({op:"get",key:o});return n.op==="get"&&n.found?n.value:void 0},set:async(o,n)=>{await be({op:"set",key:o,value:n})},delete:async o=>{await be({op:"delete",key:o})},keys:async()=>{let o=await be({op:"keys"});if(o.op!=="keys")throw new u("HOST_REJECTED","Expected storage keys.");return o.keys}},onReady:o=>(l.add(o),T&&o(T),()=>{l.delete(o)}),onDirectory:o=>(p.add(o),T&&o(T.directory),()=>{p.delete(o)}),onSession:o=>(s.add(o),T&&o(T.session),()=>{s.delete(o)}),onSessionLifecycle:o=>(c.add(o),q&&o(q),()=>{c.delete(o)}),onConnection:o=>(h.add(o),T&&o(T.connection),()=>{h.delete(o)}),onSettings:o=>(A.add(o),T&&o(T.settings),()=>{A.delete(o)}),onItem:o=>(i.add(o),T&&o(T.item),()=>{i.delete(o)}),onResolve:o=>(g=o,()=>{g===o&&(g=null)}),toast:o=>{let n=o.message.trim();return!n||n.length>Se?Promise.reject(new u("HOST_REJECTED",`Toast message must contain 1 to ${Se} characters.`)):o.copy&&o.copy!==!0&&(!o.copy.text.length||o.copy.text.length>Te)?Promise.reject(new u("HOST_REJECTED",`Toast copy text must contain 1 to ${Te} characters.`)):P({channel:k,v:1,type:"toast",id:S(E),payload:{...o,message:n}})},openUrl:o=>P({channel:k,v:1,type:"open-url",id:S(E),payload:{url:o}}),openSurface:o=>P({channel:k,v:1,type:"open-surface",id:S(E),payload:{surfaceId:o}}),writeClipboard:o=>P({channel:k,v:1,type:"clipboard-write",id:S(E),payload:{text:o}}),compose:o=>P({channel:k,v:1,type:"compose",id:S(E),payload:o}),attach:o=>P({channel:k,v:1,type:"attach",id:S(E),payload:se(o)}),startSession:async o=>{o.projectId!==void 0&&ee(o.projectId);let n=o.worktree;n&&n!==!0&&(n.kind==="existing"?ee(n.directory):(n.name!==void 0&&ee(n.name,200),n.baseBranch!==void 0&&ee(n.baseBranch,200)));let m=await H({channel:k,v:1,type:"start-session",id:S(E),payload:Ye(o)},e.requestTimeoutMs??18e4);if(!ze(m))throw new u("HOST_REJECTED","Host did not return a session.");return m},prompt:o=>H({channel:k,v:1,type:"prompt",id:S(E),payload:Je(o)}).then(n=>{if(!Ke(n))throw new u("HOST_REJECTED","Host did not return a prompt result.");return n}),sessionLink:o=>P({channel:k,v:1,type:"session-link",id:S(E),payload:se(o)}),close:()=>P({channel:k,v:1,type:"close",id:S(E)}),oauthStart:()=>P({channel:k,v:1,type:"oauth-start",id:S(E)}),oauthDisconnect:()=>P({channel:k,v:1,type:"oauth-disconnect",id:S(E)}),request:o=>(Me(o.path)?H({channel:k,v:1,type:"request",id:S(E),payload:o}):Dt()).then(n=>{if(!Oe(n))throw new u("HOST_REJECTED","Host request result was empty.");return n}),serviceRequest:o=>(Me(o.path)?H({channel:k,v:1,type:"service-request",id:S(E),payload:o}):Dt()).then(n=>{if(!Oe(n))throw new u("HOST_REJECTED","Host service request result was empty.");return n}),serviceStatus:()=>H({channel:k,v:1,type:"service-status",id:S(E)}).then(o=>{if(!Qe(o))throw new u("HOST_REJECTED","Host did not return service status.");return o}),readFile:o=>(ie(o)?H({channel:k,v:1,type:"file-read",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!Ze(n))throw new u("HOST_REJECTED","Host did not return file content.");return n}),writeFile:(o,n)=>ie(o)?n.length>ye?Promise.reject(new u("FILE_TOO_LARGE",`Content is over ${ye} characters.`)):H({channel:k,v:1,type:"file-write",id:S(E),payload:{path:o,content:n}}).then(m=>{if(!et(m))throw new u("HOST_REJECTED","Host did not confirm the write.");return m}):Le(),listDir:o=>(ie(o)?H({channel:k,v:1,type:"file-list",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!tt(n))throw new u("HOST_REJECTED","Host did not return directory entries.");return n}),stat:o=>(ie(o)?H({channel:k,v:1,type:"file-stat",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!ot(n))throw new u("HOST_REJECTED","Host did not return file status.");return n}),generate:o=>{let n=o.prompt.trim(),m=o.system?.trim();if(n.length===0||n.length>Ae)return Promise.reject(new u("HOST_REJECTED",`Prompt must be 1 to ${Ae} characters.`));if(m!==void 0&&(m.length===0||m.length>we))return Promise.reject(new u("HOST_REJECTED",`System prompt must be 1 to ${we} characters.`));let w=o.maxOutputTokens===void 0?void 0:Math.min(je,Math.max(1,Math.floor(o.maxOutputTokens)));if(w!==void 0&&!Number.isFinite(w))return Promise.reject(new u("HOST_REJECTED","maxOutputTokens must be a number."));let F={prompt:n};return m!==void 0&&(F.system=m),w!==void 0&&(F.maxOutputTokens=w),H({channel:k,v:1,type:"generate",id:S(E),payload:F},e.requestTimeoutMs??qe).then(R=>{if(!nt(R))throw new u("HOST_REJECTED","Host did not return generated text.");return R})},setBadge:o=>P({channel:k,v:1,type:"badge",id:S(E),payload:{count:We(o)}}),dispose:()=>{for(let o of I.keys())Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:o}});I.clear(),W=!0,g=null,f=null,t.removeEventListener("message",bt);for(let o of x.values())clearTimeout(o.timer),o.reject(new u("HOST_UNAVAILABLE","Host client was disposed."));x.clear(),l.clear(),p.clear(),s.clear(),c.clear(),h.clear(),A.clear(),i.clear()}}};var po=[["--oc-bg","background"],["--oc-elevated","elevated"],["--oc-fg","foreground"],["--oc-muted","muted"],["--oc-subtle","subtle"],["--oc-border","border"],["--oc-hover","hover"],["--oc-selection","selection"],["--oc-focus","focus"],["--oc-primary","primary"],["--oc-muted-surface","mutedSurface"],["--oc-elevated-fg","elevatedForeground"],["--oc-active","active"],["--oc-selection-fg","selectionForeground"],["--oc-primary-fg","primaryForeground"],["--oc-primary-text","primaryText"],["--oc-success-text","successText"],["--oc-warning-text","warningText"],["--oc-error-text","errorText"],["--oc-info-text","infoText"],["--oc-success","success"],["--oc-warning","warning"],["--oc-error","error"],["--oc-info","info"],["--oc-font","font"],["--oc-mono","mono"],["--oc-radius","radius"],["--surface-background","background"],["--surface-elevated","elevated"],["--surface-foreground","foreground"],["--surface-muted-foreground","muted"],["--surface-subtle","subtle"],["--interactive-border","border"],["--interactive-hover","hover"],["--interactive-selection","selection"],["--interactive-focus-ring","focus"],["--primary","primary"],["--surface-muted","mutedSurface"],["--surface-elevated-foreground","elevatedForeground"],["--interactive-active","active"],["--interactive-selection-foreground","selectionForeground"],["--primary-foreground","primaryForeground"],["--primary-text","primaryText"],["--success-text","successText"],["--warning-text","warningText"],["--error-text","errorText"],["--info-text","infoText"],["--status-success","success"],["--status-warning","warning"],["--status-error","error"],["--status-info","info"],["--font-sans","font"],["--font-mono","mono"],["--radius","radius"]],Xt=(e,t)=>{t.style.colorScheme=e.mode;for(let[r,a]of po)t.style.setProperty(r,e.tokens[a]);t.style.setProperty("font-family",e.tokens.font),t.style.setProperty("font-size","0.875rem"),t.style.setProperty("line-height","1.45"),t.style.setProperty("color",e.tokens.foreground)},it=(e,t)=>{Xt(e.theme,t),t.dataset&&(t.dataset.ocSurface=e.surface,t.dataset.ocTheme=e.theme.mode)};var uo={"surface-background":"bg","surface-elevated":"elevated","surface-elevated-foreground":"elevated-fg","surface-foreground":"fg","surface-muted-foreground":"muted","surface-muted":"muted-surface","surface-subtle":"subtle","interactive-border":"border","interactive-hover":"hover","interactive-active":"active","interactive-selection":"selection","interactive-selection-foreground":"selection-fg","interactive-focus-ring":"focus",primary:"primary","primary-foreground":"primary-fg","primary-text":"primary-text","success-text":"success-text","warning-text":"warning-text","error-text":"error-text","info-text":"info-text","status-success":"success","status-warning":"warning","status-error":"error","status-info":"info","font-sans":"font","font-mono":"mono",radius:"radius"},y=(e,t)=>`var(--${e}, var(--oc-${uo[e]}, ${t}))`,te=y("surface-background","transparent"),Ce=y("surface-elevated","transparent"),pe=y("surface-elevated-foreground","inherit"),ue=y("surface-foreground","inherit"),v=y("surface-muted-foreground","gray"),fo=y("surface-muted","transparent"),B=y("interactive-border","currentColor"),X=y("interactive-hover","transparent"),oe=y("interactive-active","transparent"),at=y("interactive-selection","transparent"),ct=y("interactive-selection-foreground","inherit"),zt=y("interactive-focus-ring","currentColor"),z=y("primary","currentColor"),dt=y("primary-text","inherit"),lt=y("error-text","inherit"),mo=y("font-sans","inherit"),pt=y("font-mono","monospace"),Bt=y("radius","9px"),O=(e,t,r="transparent")=>`color-mix(in srgb, ${e} ${t}%, ${r})`,Ft=`box-shadow: 0 0 0 2px ${zt};`,Re=e=>{let t=y(`status-${e}`,"currentColor");return`
31
+ `;var Tt=["file","directory","other","missing"],ze=e=>!!(e&&"sessionId"in e),Ke=e=>!!(e&&"sent"in e&&!("sessionId"in e));var Se=500,Te=32e3,_t=16e3,yt=128,At=200,wt=2e3,vt=16e3,Mt=80,Ot=200,Lt=16e3;var Ct=2e3;var Ve=2e4,_e=1024,ye=2e6;var Ae=64e3,we=8e3,je=4e3;var qe=9e4;var Rt=999,ve=500,Gt=["HOST_UNAVAILABLE","HOST_TIMEOUT","HOST_REJECTED","DISCONNECTED","DISABLED","BAD_PATH","NO_INTEGRATION","NO_SERVICE","SERVICE_FAILED","NO_SESSION","SESSION_BUSY","NOT_GRANTED","NO_DIRECTORY","NOT_FOUND","FILE_TOO_LARGE","DENIED","NO_MODEL","MODEL_FAILED"],It=["stopped","starting","ready","failed"],ro=new Set(Gt),$t=e=>ro.has(e),Nt=e=>e&&$t(e)?e:"HOST_REJECTED",re=e=>e===void 0?!1:e===null||e===!0||e===!1||String(e)===e?!0:Number(e)===e?Number.isFinite(e):Array.isArray(e)?e.every(re):Object(e)===e?Object.values(e).every(re):!1,Ut=e=>re(e)&&JSON.stringify(e).length<=Lt,St=e=>e?.trim().slice(0,Ot)??"",se=e=>{let t=e.id.trim().slice(0,yt),r=e.title.trim().slice(0,At),a=e.url.trim().slice(0,wt),p=e.text?.trim().slice(0,vt),l=e.author?.trim().slice(0,Mt),s=e.kind==="pull"?"pull":"issue",c={providerId:e.providerId.trim(),id:t,title:r||t,url:a,kind:s};if(p&&(c.text=p),l&&(c.author=l),s==="pull"){let h=St(e.branches?.head),A=St(e.branches?.base);h&&A&&(c.branches={head:h,base:A})}return Ut(e.data)&&(c.data=e.data),c},Ye=e=>{let t=se(e);return e.projectId&&(t.projectId=e.projectId),e.navigation&&(t.navigation=e.navigation),e.worktree&&(t.worktree=e.worktree),t},Je=e=>{let t={text:e.text.trim().slice(0,_t)};return e.send&&(t.send=!0),t},We=e=>e===null||!Number.isFinite(e)?null:Math.min(Rt,Math.max(0,Math.round(e)));var ie=e=>e.length>0&&e.length<=_e&&!e.includes("\0")&&!e.includes("\\");var Me=e=>!e.startsWith("/")||e.includes("\0")||e.includes("\\")||e.includes("://")||e.length>Ct?!1:!e.split("/").some(r=>r==="."||r===".."),so=new Set(It),Qe=e=>!!(e&&"status"in e&&so.has(String(e.status))&&!("body"in e)),Oe=e=>!!(e&&"status"in e&&"body"in e&&Number.isInteger(e.status)),Ze=e=>!!(e&&"content"in e&&String(e.content)===e.content),et=e=>!!(e&&"written"in e&&e.written===!0),tt=e=>!!(e&&"entries"in e&&Array.isArray(e.entries)),io=new Set(Tt),ot=e=>!!(e&&"kind"in e&&"size"in e&&io.has(String(e.kind))&&Number.isFinite(e.size)),nt=e=>!!(e&&"text"in e&&String(e.text)===e.text&&!("status"in e)),ao=new Set(["workspace","ready","directory","session","connection","settings","session-lifecycle","item","resolve","action"]),co=e=>Object(e)===e?e:null,Fe=e=>String(e)===e&&e.length>0,lo=e=>{if(!Fe(e.id))return null;if(e.ok===!0){let t={channel:k,v:1,type:"result",id:e.id,ok:!0};return Object(e.payload)===e.payload&&(t.payload=e.payload),t}return e.ok===!1&&Fe(e.error)?{channel:k,v:1,type:"result",id:e.id,ok:!1,error:e.error,code:Nt(Fe(e.code)?e.code:void 0)}:null},rt=e=>{let t=co(e);return!t||t.channel!==k||t.v!==1?null:t.type==="result"?lo(t):!ao.has(String(t.type))||Object(t.payload)!==t.payload?null:t};var u=class extends Error{code;constructor(t,r){super(r),this.name="HostRequestError",this.code=t}},Dt=()=>Promise.reject(new u("BAD_PATH",'Request path must start with "/" and stay on the declared origin.')),Le=()=>Promise.reject(new u("BAD_PATH",`File path must be 1 to ${_e} characters without NUL or backslash.`)),S=e=>(e.value+=1,`oc-${e.value}`),st=(e={})=>{let t=e.target??("window"in globalThis?window:null);if(!t)throw new u("HOST_UNAVAILABLE","No window. connectHost runs in a browser frame.");let r=e.acceptSource??(o=>o===t.parent),a=e.requestTimeoutMs??Ve,p=new Set,l=new Set,s=new Set,c=new Set,h=new Set,A=new Set,i=new Set,g=null,f=null,x=new Map,I=new Map,W=!1,E={value:0},T=null,q=null,xt=o=>o?{sessionId:o.id,phase:o.busy?"started":"completed"}:null,Q=o=>{t.parent.postMessage(o,"*")},N=(o,n)=>{for(let m of o)try{m(n)}catch(w){console.error(w)}},bt=o=>{if(!(o instanceof MessageEvent)||!r(o.source))return;let n=rt(o.data);if(!n)return;if(n.type==="workspace"){let w=I.get(n.payload.subscriptionId);w&&N([w],n.payload.snapshot);return}if(n.type==="ready"){T=n.payload,q=xt(n.payload.session),N(p,n.payload),N(l,n.payload.directory),N(s,n.payload.session),q&&N(c,q),N(h,n.payload.connection),N(A,n.payload.settings),N(i,n.payload.item);return}if(n.type==="directory"){T&&(T={...T,directory:n.payload.directory}),N(l,n.payload.directory);return}if(n.type==="session"){T&&(T={...T,session:n.payload.session}),n.payload.session?q?.sessionId!==n.payload.session.id&&(q=xt(n.payload.session)):q=null,N(s,n.payload.session);return}if(n.type==="session-lifecycle"){q=n.payload,N(c,n.payload);return}if(n.type==="connection"){T&&(T={...T,connection:n.payload.connection}),N(h,n.payload.connection);return}if(n.type==="settings"){T&&(T={...T,settings:n.payload.settings}),N(A,n.payload.settings);return}if(n.type==="item"){T&&(T={...T,item:n.payload.item}),N(i,n.payload.item);return}if(n.type==="action"){let w=G=>{W||Q({channel:k,v:1,type:"action-result",id:n.id,payload:G})},F=f;if(!F){w({ok:!1,error:"This extension does not handle background actions."});return}Promise.resolve().then(()=>F(n.payload)).then(()=>w({ok:!0}),G=>{let Xe=(G instanceof Error?G.message:String(G)).trim();w({ok:!1,error:(Xe||"Action failed.").slice(0,ve)})});return}if(n.type==="resolve"){let w=G=>{Q({channel:k,v:1,type:"resolve-result",id:n.id,payload:G})},F=g;if(!F){w({error:"This extension does not resolve commands."});return}Promise.resolve().then(()=>F(n.payload)).then(G=>w({item:G?se(G):null}),G=>{let Xe=(G instanceof Error?G.message:String(G)).trim();w({error:(Xe||"Command failed.").slice(0,ve)})});return}let m=x.get(n.id);if(m){if(clearTimeout(m.timer),x.delete(n.id),n.ok){m.resolve(n.payload);return}m.reject(new u(n.code,n.error))}};t.addEventListener("message",bt),Q({channel:k,v:1,type:"hello"});let H=(o,n=a)=>W||t.parent===t?Promise.reject(new u("HOST_UNAVAILABLE","No host frame. This page is not in an iframe.")):new Promise((m,w)=>{let F=setTimeout(()=>{x.delete(o.id),w(new u("HOST_TIMEOUT","Host did not answer in time."))},n);x.set(o.id,{resolve:m,reject:w,timer:F}),Q(o)}),P=o=>H(o).then(()=>{}),Z={channel:k,v:1},ee=(o,n=1024)=>{if(!o.trim()||o.length>n)throw new u("HOST_REJECTED",`Identity must contain 1 to ${n} characters.`)},He=async o=>{o.kind!=="projects"&&ee(o.projectId);let n=await H({...Z,type:"workspace-read",id:S(E),payload:o});if(!n||!("kind"in n)||!("state"in n)||n.kind!==o.kind)throw new u("HOST_REJECTED","Host did not return workspace data.");return n},Pe=async(o,n)=>{o.kind!=="projects"&&ee(o.projectId);let m=S(E);I.set(m,n);try{await P({...Z,type:"workspace-subscribe",id:S(E),payload:{subscriptionId:m,query:o}})}catch(w){throw I.delete(m),W||Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:m}}),w}return()=>{!I.delete(m)||W||Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:m}})}},be=async o=>{if("key"in o&&(o.key.length===0||o.key.length>128))throw new u("HOST_REJECTED","Storage key must contain 1 to 128 characters.");if(o.op==="set"&&!re(o.value))throw new u("HOST_REJECTED","Storage values must be JSON.");if(o.op==="set"&&new TextEncoder().encode(JSON.stringify(o.value)).length>65536)throw new u("HOST_REJECTED","Storage value exceeds 64 KiB.");let n=await H({...Z,type:"storage",id:S(E),payload:o});if(!n||!("storage"in n)||n.op!==o.op)throw new u("HOST_REJECTED","Host did not return storage data.");return n};return{onAction:o=>(f=o,()=>{f===o&&(f=null)}),listProjects:async()=>{let o=await He({kind:"projects"});if(o.kind!=="projects")throw new u("HOST_REJECTED","Expected projects.");return o},listWorktrees:async o=>{let n=await He({kind:"worktrees",projectId:o});if(n.kind!=="worktrees")throw new u("HOST_REJECTED","Expected worktrees.");return n},listSessions:async o=>{let n=await He({kind:"sessions",projectId:o});if(n.kind!=="sessions")throw new u("HOST_REJECTED","Expected sessions.");return n},onProjects:o=>Pe({kind:"projects"},n=>{n.kind==="projects"&&o(n)}),onWorktrees:(o,n)=>Pe({kind:"worktrees",projectId:o},m=>{m.kind==="worktrees"&&n(m)}),onSessions:(o,n)=>Pe({kind:"sessions",projectId:o},m=>{m.kind==="sessions"&&n(m)}),openSession:async o=>{ee(o),await P({...Z,type:"open-session",id:S(E),payload:{sessionId:o}})},storage:{get:async o=>{let n=await be({op:"get",key:o});return n.op==="get"&&n.found?n.value:void 0},set:async(o,n)=>{await be({op:"set",key:o,value:n})},delete:async o=>{await be({op:"delete",key:o})},keys:async()=>{let o=await be({op:"keys"});if(o.op!=="keys")throw new u("HOST_REJECTED","Expected storage keys.");return o.keys}},onReady:o=>(p.add(o),T&&o(T),()=>{p.delete(o)}),onDirectory:o=>(l.add(o),T&&o(T.directory),()=>{l.delete(o)}),onSession:o=>(s.add(o),T&&o(T.session),()=>{s.delete(o)}),onSessionLifecycle:o=>(c.add(o),q&&o(q),()=>{c.delete(o)}),onConnection:o=>(h.add(o),T&&o(T.connection),()=>{h.delete(o)}),onSettings:o=>(A.add(o),T&&o(T.settings),()=>{A.delete(o)}),onItem:o=>(i.add(o),T&&o(T.item),()=>{i.delete(o)}),onResolve:o=>(g=o,()=>{g===o&&(g=null)}),toast:o=>{let n=o.message.trim();return!n||n.length>Se?Promise.reject(new u("HOST_REJECTED",`Toast message must contain 1 to ${Se} characters.`)):o.copy&&o.copy!==!0&&(!o.copy.text.length||o.copy.text.length>Te)?Promise.reject(new u("HOST_REJECTED",`Toast copy text must contain 1 to ${Te} characters.`)):P({channel:k,v:1,type:"toast",id:S(E),payload:{...o,message:n}})},openUrl:o=>P({channel:k,v:1,type:"open-url",id:S(E),payload:{url:o}}),openSurface:o=>P({channel:k,v:1,type:"open-surface",id:S(E),payload:{surfaceId:o}}),writeClipboard:o=>P({channel:k,v:1,type:"clipboard-write",id:S(E),payload:{text:o}}),compose:o=>P({channel:k,v:1,type:"compose",id:S(E),payload:o}),attach:o=>P({channel:k,v:1,type:"attach",id:S(E),payload:se(o)}),startSession:async o=>{o.projectId!==void 0&&ee(o.projectId);let n=o.worktree;n&&n!==!0&&(n.kind==="existing"?ee(n.directory):(n.name!==void 0&&ee(n.name,200),n.baseBranch!==void 0&&ee(n.baseBranch,200)));let m=await H({channel:k,v:1,type:"start-session",id:S(E),payload:Ye(o)},e.requestTimeoutMs??18e4);if(!ze(m))throw new u("HOST_REJECTED","Host did not return a session.");return m},prompt:o=>H({channel:k,v:1,type:"prompt",id:S(E),payload:Je(o)}).then(n=>{if(!Ke(n))throw new u("HOST_REJECTED","Host did not return a prompt result.");return n}),sessionLink:o=>P({channel:k,v:1,type:"session-link",id:S(E),payload:se(o)}),close:()=>P({channel:k,v:1,type:"close",id:S(E)}),oauthStart:()=>P({channel:k,v:1,type:"oauth-start",id:S(E)}),oauthDisconnect:()=>P({channel:k,v:1,type:"oauth-disconnect",id:S(E)}),request:o=>(Me(o.path)?H({channel:k,v:1,type:"request",id:S(E),payload:o}):Dt()).then(n=>{if(!Oe(n))throw new u("HOST_REJECTED","Host request result was empty.");return n}),serviceRequest:o=>(Me(o.path)?H({channel:k,v:1,type:"service-request",id:S(E),payload:o}):Dt()).then(n=>{if(!Oe(n))throw new u("HOST_REJECTED","Host service request result was empty.");return n}),serviceStatus:()=>H({channel:k,v:1,type:"service-status",id:S(E)}).then(o=>{if(!Qe(o))throw new u("HOST_REJECTED","Host did not return service status.");return o}),readFile:o=>(ie(o)?H({channel:k,v:1,type:"file-read",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!Ze(n))throw new u("HOST_REJECTED","Host did not return file content.");return n}),writeFile:(o,n)=>ie(o)?n.length>ye?Promise.reject(new u("FILE_TOO_LARGE",`Content is over ${ye} characters.`)):H({channel:k,v:1,type:"file-write",id:S(E),payload:{path:o,content:n}}).then(m=>{if(!et(m))throw new u("HOST_REJECTED","Host did not confirm the write.");return m}):Le(),listDir:o=>(ie(o)?H({channel:k,v:1,type:"file-list",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!tt(n))throw new u("HOST_REJECTED","Host did not return directory entries.");return n}),stat:o=>(ie(o)?H({channel:k,v:1,type:"file-stat",id:S(E),payload:{path:o}}):Le()).then(n=>{if(!ot(n))throw new u("HOST_REJECTED","Host did not return file status.");return n}),generate:o=>{let n=o.prompt.trim(),m=o.system?.trim();if(n.length===0||n.length>Ae)return Promise.reject(new u("HOST_REJECTED",`Prompt must be 1 to ${Ae} characters.`));if(m!==void 0&&(m.length===0||m.length>we))return Promise.reject(new u("HOST_REJECTED",`System prompt must be 1 to ${we} characters.`));let w=o.maxOutputTokens===void 0?void 0:Math.min(je,Math.max(1,Math.floor(o.maxOutputTokens)));if(w!==void 0&&!Number.isFinite(w))return Promise.reject(new u("HOST_REJECTED","maxOutputTokens must be a number."));let F={prompt:n};return m!==void 0&&(F.system=m),w!==void 0&&(F.maxOutputTokens=w),H({channel:k,v:1,type:"generate",id:S(E),payload:F},e.requestTimeoutMs??qe).then(G=>{if(!nt(G))throw new u("HOST_REJECTED","Host did not return generated text.");return G})},setBadge:o=>P({channel:k,v:1,type:"badge",id:S(E),payload:{count:We(o)}}),dispose:()=>{for(let o of I.keys())Q({...Z,type:"workspace-unsubscribe",id:S(E),payload:{subscriptionId:o}});I.clear(),W=!0,g=null,f=null,t.removeEventListener("message",bt);for(let o of x.values())clearTimeout(o.timer),o.reject(new u("HOST_UNAVAILABLE","Host client was disposed."));x.clear(),p.clear(),l.clear(),s.clear(),c.clear(),h.clear(),A.clear(),i.clear()}}};var po=[["--oc-bg","background"],["--oc-elevated","elevated"],["--oc-fg","foreground"],["--oc-muted","muted"],["--oc-subtle","subtle"],["--oc-border","border"],["--oc-hover","hover"],["--oc-selection","selection"],["--oc-focus","focus"],["--oc-primary","primary"],["--oc-muted-surface","mutedSurface"],["--oc-elevated-fg","elevatedForeground"],["--oc-active","active"],["--oc-selection-fg","selectionForeground"],["--oc-primary-fg","primaryForeground"],["--oc-primary-text","primaryText"],["--oc-success-text","successText"],["--oc-warning-text","warningText"],["--oc-error-text","errorText"],["--oc-info-text","infoText"],["--oc-success","success"],["--oc-warning","warning"],["--oc-error","error"],["--oc-info","info"],["--oc-font","font"],["--oc-mono","mono"],["--oc-radius","radius"],["--surface-background","background"],["--surface-elevated","elevated"],["--surface-foreground","foreground"],["--surface-muted-foreground","muted"],["--surface-subtle","subtle"],["--interactive-border","border"],["--interactive-hover","hover"],["--interactive-selection","selection"],["--interactive-focus-ring","focus"],["--primary","primary"],["--surface-muted","mutedSurface"],["--surface-elevated-foreground","elevatedForeground"],["--interactive-active","active"],["--interactive-selection-foreground","selectionForeground"],["--primary-foreground","primaryForeground"],["--primary-text","primaryText"],["--success-text","successText"],["--warning-text","warningText"],["--error-text","errorText"],["--info-text","infoText"],["--status-success","success"],["--status-warning","warning"],["--status-error","error"],["--status-info","info"],["--font-sans","font"],["--font-mono","mono"],["--radius","radius"]],Xt=(e,t)=>{t.style.colorScheme=e.mode;for(let[r,a]of po)t.style.setProperty(r,e.tokens[a]);t.style.setProperty("font-family",e.tokens.font),t.style.setProperty("font-size","0.875rem"),t.style.setProperty("line-height","1.45"),t.style.setProperty("color",e.tokens.foreground)},it=(e,t)=>{Xt(e.theme,t),t.dataset&&(t.dataset.ocSurface=e.surface,t.dataset.ocTheme=e.theme.mode)};var uo={"surface-background":"bg","surface-elevated":"elevated","surface-elevated-foreground":"elevated-fg","surface-foreground":"fg","surface-muted-foreground":"muted","surface-muted":"muted-surface","surface-subtle":"subtle","interactive-border":"border","interactive-hover":"hover","interactive-active":"active","interactive-selection":"selection","interactive-selection-foreground":"selection-fg","interactive-focus-ring":"focus",primary:"primary","primary-foreground":"primary-fg","primary-text":"primary-text","success-text":"success-text","warning-text":"warning-text","error-text":"error-text","info-text":"info-text","status-success":"success","status-warning":"warning","status-error":"error","status-info":"info","font-sans":"font","font-mono":"mono",radius:"radius"},y=(e,t)=>`var(--${e}, var(--oc-${uo[e]}, ${t}))`,te=y("surface-background","transparent"),Ce=y("surface-elevated","transparent"),pe=y("surface-elevated-foreground","inherit"),ue=y("surface-foreground","inherit"),v=y("surface-muted-foreground","gray"),fo=y("surface-muted","transparent"),B=y("interactive-border","currentColor"),X=y("interactive-hover","transparent"),oe=y("interactive-active","transparent"),at=y("interactive-selection","transparent"),ct=y("interactive-selection-foreground","inherit"),zt=y("interactive-focus-ring","currentColor"),z=y("primary","currentColor"),dt=y("primary-text","inherit"),lt=y("error-text","inherit"),mo=y("font-sans","inherit"),pt=y("font-mono","monospace"),Bt=y("radius","9px"),O=(e,t,r="transparent")=>`color-mix(in srgb, ${e} ${t}%, ${r})`,Ft=`box-shadow: 0 0 0 2px ${zt};`,Re=e=>{let t=y(`status-${e}`,"currentColor");return`
32
32
  .oc-sdk[data-tone="${e}"], .oc-sdk [data-tone="${e}"] { --oc-sdk-tone: ${t}; --oc-sdk-tone-text: ${y(`${e}-text`,"inherit")}; }`},$=`
33
33
  ${Be}
34
34
  .oc-sdk { box-sizing: border-box; color: ${ue}; font-family: ${mo}; font-size: 0.875rem; line-height: 1.45; }
@@ -173,4 +173,4 @@ textarea.oc-sdk-input { height: auto; padding: 8px 12px; resize: vertical; }
173
173
  .oc-sdk-text { white-space: pre-wrap; overflow-wrap: anywhere; }
174
174
  .oc-sdk-text a { color: ${dt}; text-decoration: underline; text-underline-offset: 2px; }
175
175
  .oc-sdk-text img { display: block; max-width: 100%; margin: 8px 0; border-radius: 8px; border: 1px solid ${O(B,60)}; }
176
- `;var xo=e=>24+e*308;function jt(e){let t=new Map,r=new Map;for(let i of e.nodes)r.set(i.column,[...r.get(i.column)??[],i]);let a=[...r.keys()].sort((i,g)=>i-g),l=new Map(a.map(i=>[i,xo(i)])),p=new Map,s=0;for(let i of a)(r.get(i)??[]).slice().sort((f,x)=>(p.get(f.parentId??"")??-1)-(p.get(x.parentId??"")??-1)||(f.createdAt??0)-(x.createdAt??0)||f.id.localeCompare(x.id)).forEach((f,x)=>{let I=58+x*94;t.set(f.id,{x:l.get(i)??0,y:I}),p.set(f.id,p.size),s=Math.max(s,I+80+24)});let c=t.get(e.rootId),h=e.nodes.filter(i=>i.parentId===e.rootId).map(i=>t.get(i.id)).filter(i=>i!==void 0);if(c&&h.length>0&&(r.get(0)?.length??0)===1){let i=Math.min(...h.map(f=>f.y)),g=Math.max(...h.map(f=>f.y));c.y=Math.round((i+g)/2)}let A=24+(Math.max(0,...a)+1)*308-76+24;return{boxes:t,edges:bo(e.edges,t),columnX:l,width:A,height:Math.max(s,162)}}function Vt(e,t){if(t<=1)return 80/2;let r=Math.min(12,80*.44/(t-1));return 80/2+(e-(t-1)/2)*r}function bo(e,t){let r=e.filter(s=>t.has(s.from)&&t.has(s.to)),a=s=>(t.get(s)?.y??0)+80/2,l=new Map,p=new Map;for(let s of r)l.set(s.from,[...l.get(s.from)??[],s]),p.set(s.to,[...p.get(s.to)??[],s]);for(let s of l.values())s.sort((c,h)=>a(c.to)-a(h.to));for(let s of p.values())s.sort((c,h)=>a(c.from)-a(h.from));return r.map(s=>{let c=t.get(s.from),h=t.get(s.to),A=l.get(s.from)??[],i=p.get(s.to)??[],g=c.x+232,f=c.y+Vt(A.indexOf(s),A.length),x=h.x,I=h.y+Vt(i.indexOf(s),i.length),W=x>g?Math.max(32,(x-g)*.5):90,E=`M ${g} ${f} C ${g+W} ${f}, ${x-W} ${I}, ${x} ${I}`;return{...s,d:E,sx:g,sy:f,tx:x,ty:I}})}function ft(e,t,r){let a=new Set,l=[t];for(;l.length>0;){let p=l.pop();for(let s of e){let c=r==="down"?s.from===p?s.to:void 0:s.to===p?s.from:void 0;c!==void 0&&!a.has(c)&&c!==t&&(a.add(c),l.push(c))}}return a}function qt(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let r=Math.floor(t/60);return r<60?`${r}m ${String(t%60).padStart(2,"0")}s`:`${Math.floor(r/60)}h ${String(r%60).padStart(2,"0")}m`}function mt(e){if(e)return e.includes("/")?e.slice(e.indexOf("/")+1):e}function Yt(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(e)}var So={running:"running",queued:"queued",completed:"completed",failed:"failed",cancelled:"cancelled",idle:"idle"};function fe(e,t){if(e.kind==="root")return e.rawStatus;let r=e.rawStatus===e.state?So[e.state]:e.rawStatus,a=e.startedAt;return a===void 0?r:e.state==="running"?`${r} \xB7 ${qt(t-a)}`:e.endedAt!==void 0?`${r} \xB7 ${qt(e.endedAt-a)}`:r}var To=new Set(["running","queued","completed","failed","cancelled","idle"]),he=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),J=e=>typeof e=="number"&&Number.isFinite(e),me=e=>e===void 0||typeof e=="string",ce=e=>e===void 0||J(e),_o=e=>typeof e=="string"&&To.has(e);function yo(e){return he(e)?typeof e.id=="string"&&(e.kind==="root"||e.kind==="task")&&typeof e.sessionId=="string"&&typeof e.title=="string"&&_o(e.state)&&typeof e.rawStatus=="string"&&me(e.agent)&&me(e.model)&&ce(e.createdAt)&&ce(e.startedAt)&&ce(e.endedAt)&&me(e.summary)&&me(e.error)&&ce(e.tokens)&&ce(e.costUsd)&&ce(e.toolCalls)&&me(e.parentId)&&J(e.column)&&J(e.wave):!1}function Ao(e){return he(e)&&typeof e.id=="string"&&typeof e.from=="string"&&typeof e.to=="string"&&(e.kind==="spawn"||e.kind==="uses")}function wo(e){return he(e)&&J(e.column)&&J(e.total)&&J(e.done)&&J(e.running)}function vo(e){return he(e)&&J(e.omittedNodes)&&Number.isInteger(e.omittedNodes)&&e.omittedNodes>=0}function Mo(e){return he(e)&&(e.status==="ok"||e.status==="unknown-session")&&typeof e.rootId=="string"&&typeof e.focusId=="string"&&typeof e.title=="string"&&Array.isArray(e.nodes)&&e.nodes.every(yo)&&Array.isArray(e.edges)&&e.edges.every(Ao)&&Array.isArray(e.columns)&&e.columns.every(wo)&&J(e.models)&&(e.truncated===void 0||vo(e.truncated))}function Oo(e){let t=JSON.parse(e);if(!Mo(t))throw new Error("service answered with an unexpected graph shape");return t}var Ue=class{constructor(t){this.deps=t}deps;session=void 0;generation=0;inFlight=!1;pending=!1;signature="";setSession(t){t!==this.session&&(this.session=t,this.invalidate())}invalidate(){this.generation++,this.signature="",this.refresh()}async refresh(){let t=this.session;if(t===void 0)return;if(t===null){this.signature="",this.deps.show({kind:"no-session"});return}if(this.deps.isHidden()){this.deps.schedule(5e3,()=>{this.refresh()});return}if(this.inFlight){this.pending=!0;return}this.inFlight=!0;let r=this.generation;try{let a=await this.deps.fetch(t);if(r!==this.generation)return;if(a.status!==200)throw new Error(`service answered HTTP ${a.status}: ${a.body.slice(0,200)}`);let l=Oo(a.body);l.status!=="ok"||l.nodes.length<=1?(this.signature="",this.deps.show({kind:"empty"})):a.body!==this.signature&&(this.signature=a.body,this.deps.show({kind:"graph",graph:l}));let p=l.nodes.some(s=>s.state==="running"||s.state==="queued");this.deps.schedule(p?1500:5e3,()=>{this.refresh()})}catch(a){if(r!==this.generation)return;this.signature="",this.deps.show({kind:"error",error:a}),this.deps.schedule(1e4,()=>{this.refresh()})}finally{this.inFlight=!1,(this.pending||r!==this.generation)&&(this.pending=!1,this.refresh())}}};var Jt="http://www.w3.org/2000/svg";document.documentElement.style.setProperty("--node-card-width",`${232}px`);document.documentElement.style.setProperty("--node-card-height",`${80}px`);var j=st(),G=e=>document.querySelector(e),d={app:G("#app"),title:G("#title"),titleDot:G("#title-dot"),meta:G("#meta"),viewport:G("#viewport"),world:G("#world"),edges:G("#edges"),bands:G("#bands"),nodes:G("#nodes"),empty:G("#empty"),inspector:G("#inspector"),settings:G("#settings")},Ee=null,to=!1,M,C,Wt="",V,ne={},ht,_={x:0,y:0,k:1},de=new Map;function L(e,t="",r=""){let a=document.createElement(e);return t&&(a.className=t),r&&(a.textContent=r),a}function gt(e,t=""){d.empty.replaceChildren(L("strong","",e),...t?[L("p","",t)]:[]),d.empty.hidden=!1,d.world.hidden=!0,d.inspector.hidden=!0}function oo(e){return e instanceof u?e.code==="NO_SERVICE"?["Local service not allowed yet","Approve OmO Graph in Settings \u2192 Extensions so it can read the OmO task store."]:e.code==="SERVICE_FAILED"?["The local service did not start","Check Settings \u2192 Extensions; the next refresh retries it."]:e.code==="DISABLED"?["OmO Graph is paused","Resume it in Settings \u2192 Extensions."]:[`Request failed (${e.code})`,e.message]:["Request failed",e instanceof Error?e.message:String(e)]}function le(){d.world.style.transform=`translate(${_.x}px, ${_.y}px) scale(${_.k})`,d.app.style.setProperty("--zoom",String(_.k))}function De(){if(!C)return;let e=d.viewport.clientWidth,t=d.viewport.clientHeight,r=Math.min(1,Math.max(.62,Math.min((e-16)/C.width,(t-16)/C.height)));_.k=r,_.x=Math.round(C.width*r<=e?(e-C.width*r)/2:8),_.y=Math.round(Math.max(8,(t-C.height*r)/2)),le()}function no(e){let t=e?C?.boxes.get(e):void 0;if(!t)return De();_.k=Math.max(_.k,.8),_.x=Math.round(d.viewport.clientWidth/2-(t.x+232/2)*_.k),_.y=Math.round(d.viewport.clientHeight/2-(t.y+80/2)*_.k),le()}function ke(e,t=d.viewport.clientWidth/2,r=d.viewport.clientHeight/2){let a=Math.min(2,Math.max(.2,_.k*e));_.x=t-(t-_.x)*a/_.k,_.y=r-(r-_.y)*a/_.k,_.k=a,le()}var K,Et=!1;d.viewport.addEventListener("pointerdown",e=>{e.button===0&&(K={x:e.clientX,y:e.clientY,vx:_.x,vy:_.y,moved:!1})});window.addEventListener("pointermove",e=>{if(!K)return;let t=e.clientX-K.x,r=e.clientY-K.y;!K.moved&&Math.hypot(t,r)<4||(K.moved||(K.moved=!0,d.viewport.setPointerCapture?.(e.pointerId),d.app.classList.add("is-panning")),_.x=K.vx+t,_.y=K.vy+r,le())});window.addEventListener("pointerup",()=>{K?.moved&&(Et=!0,setTimeout(()=>{Et=!1},0)),K=void 0,d.app.classList.remove("is-panning")});d.viewport.addEventListener("wheel",e=>{e.preventDefault();let t=d.viewport.getBoundingClientRect();e.ctrlKey||e.metaKey||Math.abs(e.deltaY)>Math.abs(e.deltaX)*2&&!e.shiftKey?ke(Math.exp(-e.deltaY*(e.ctrlKey?.01:.0015)),e.clientX-t.left,e.clientY-t.top):(_.x-=e.deltaX||(e.shiftKey?e.deltaY:0),_.y-=e.shiftKey?0:e.deltaY,le())},{passive:!1});G("#btn-fit").addEventListener("click",De);G("#btn-locate").addEventListener("click",()=>{let e=M?.nodes.find(t=>t.kind==="task"&&t.state==="running");no(M?.focusId!==M?.rootId?M?.focusId:e?.id??M?.rootId)});G("#btn-in").addEventListener("click",()=>ke(1.2));G("#btn-out").addEventListener("click",()=>ke(1/1.2));window.addEventListener("keydown",e=>{e.target instanceof HTMLInputElement||(e.key==="0"?De():e.key==="+"||e.key==="="?ke(1.2):e.key==="-"&&ke(1/1.2))});function ge(e){if(V=e,!M)return;let t=M.edges,r=new Set(e?t.filter(s=>s.to===e).map(s=>s.from):[]),a=new Set(e?t.filter(s=>s.from===e).map(s=>s.to):[]),l=e?ft(t,e,"up"):new Set,p=e?ft(t,e,"down"):new Set;d.app.classList.toggle("has-hover",e!==void 0);for(let[s,c]of de)c.classList.toggle("is-hover",s===e),c.classList.toggle("is-input",r.has(s)),c.classList.toggle("is-output",a.has(s)),c.classList.toggle("is-reach",l.has(s)||p.has(s));for(let s of d.edges.querySelectorAll("path[data-edge]")){let c=s.dataset.from??"",h=s.dataset.to??"";s.classList.toggle("is-in",e!==void 0&&h===e),s.classList.toggle("is-out",e!==void 0&&c===e),s.classList.toggle("is-reach",e!==void 0&&(l.has(c)&&(l.has(h)||h===e)||p.has(h)&&(p.has(c)||c===e)))}Lo()}function Lo(){let e=M?.nodes.find(A=>A.id===(V??(M?.focusId!==M?.rootId?M?.focusId:void 0)));if(!e||!M){d.inspector.hidden=!0;return}let t=M.edges.filter(A=>A.to===e.id).length,r=M.edges.filter(A=>A.from===e.id).length,a=[fe(e,Date.now())];e.agent&&a.push(e.agent);let l=mt(e.model);l&&a.push(l),e.toolCalls!==void 0&&a.push(`${e.toolCalls} tools`),e.tokens!==void 0&&a.push(`${Yt(e.tokens)} tok`),e.costUsd!==void 0&&a.push(`$${e.costUsd.toFixed(2)}`);let p=L("div","i-head");p.append(L("span",`dot s-${e.state}`),L("strong","",e.title));let s=L("div","i-flow");s.append(L("span","i-in",`\u2190 ${t} input${t===1?"":"s"}`),L("span","i-out",`${r} output${r===1?"":"s"} \u2192`));let c=[p,L("div","i-facts",a.join(" \xB7 ")),s],h=e.error??e.summary;h&&c.push(L("p",e.error?"i-text is-error":"i-text",h)),c.push(L("div","i-hint",e.kind==="root"?"Click to open the orchestrating session":"Click to open this sub-agent's session")),d.inspector.replaceChildren(...c),d.inspector.hidden=!1}function Co(e){let t=de.get(e.id);return t||(t=L("button","node"),t.type="button",t.dataset.id=e.id,t.append(L("div","n-head"),L("div","n-status"),L("div","n-meta"),L("div","n-sub")),t.querySelector(".n-head")?.append(L("span","dot"),L("span","n-title")),t.addEventListener("pointerenter",()=>ge(e.id)),t.addEventListener("pointerleave",()=>{V===e.id&&ge(void 0)}),t.addEventListener("focus",()=>ge(e.id)),t.addEventListener("blur",()=>{V===e.id&&ge(void 0)}),t.addEventListener("click",()=>{Et||$o(e.id)}),de.set(e.id,t),d.nodes.append(t)),t}function Ro(e,t,r){e.className=`node k-${t.kind} s-${t.state}${M?.focusId===t.id&&t.kind==="task"?" is-current":""}`,V===t.id&&e.classList.add("is-hover"),e.setAttribute("aria-label",`${t.title}, ${fe(t,r)}. Open session.`);let a=(l,p)=>{let s=e.querySelector(l);s&&s.textContent!==p&&(s.textContent=p)};a(".n-title",t.title),a(".n-status",fe(t,r)),a(".n-meta",t.kind==="root"?`${M?.nodes.length?M.nodes.length-1:0} sub-agents`:[t.agent,mt(t.model)].filter(Boolean).join(" \xB7 ")),a(".n-sub",t.error??t.summary??""),e.querySelector(".dot")?.setAttribute("class",`dot s-${t.state}`)}function Go(e){M=e,C=jt(e),d.world.hidden=!1,d.empty.hidden=!0,d.world.style.width=`${C.width}px`,d.world.style.height=`${C.height}px`,d.edges.setAttribute("width",String(C.width)),d.edges.setAttribute("height",String(C.height)),d.edges.setAttribute("viewBox",`0 0 ${C.width} ${C.height}`);let t=new Map(e.nodes.map(i=>[i.id,i.state])),r=document.createDocumentFragment();for(let i of C.edges){let g=document.createElementNS(Jt,"path");g.setAttribute("d",i.d),g.dataset.edge=i.id,g.dataset.from=i.from,g.dataset.to=i.to;let f=t.get(i.to)==="running"||t.get(i.to)==="queued";if(g.setAttribute("class",`edge k-${i.kind}${f?" is-live":""}`),g.setAttribute("marker-end","url(#arrow)"),r.append(g),f){let x=document.createElementNS(Jt,"path");x.setAttribute("d",i.d),x.setAttribute("class","flow"),x.setAttribute("pathLength","1"),r.append(x)}}d.edges.querySelectorAll("path.edge, path.flow").forEach(i=>i.remove()),d.edges.append(r),d.bands.replaceChildren(...[...C.columnX].map(([i,g])=>{let f=L("div","band");f.style.left=`${g}px`,f.style.width=`${232}px`;let x=e.columns.find(I=>I.column===i);return i===0?f.textContent="Orchestrator":x&&(f.textContent=`Wave ${i} \xB7 ${x.done}/${x.total} done${x.running?` \xB7 ${x.running} running`:""}`),x?.running&&f.classList.add("is-live"),f}));let a=Date.now(),l=new Set;for(let i of e.nodes){let g=Co(i),f=C.boxes.get(i.id);f&&(g.style.transform=`translate(${f.x}px, ${f.y}px)`),Ro(g,i,a),l.add(i.id)}for(let[i,g]of de)l.has(i)||(g.remove(),de.delete(i));let p=e.nodes.filter(i=>i.kind==="task"),s=p.filter(i=>i.state==="completed"||i.state==="failed"||i.state==="cancelled").length,c=p.filter(i=>i.state==="running").length,h=e.columns.length,A=e.columns.find(i=>i.running>0||i.done<i.total)?.column;d.title.textContent=e.title,d.titleDot.className=`dot s-${c>0?"running":p.some(i=>i.state==="failed")?"failed":s===p.length?"completed":"idle"}`,d.meta.textContent=[A!==void 0?`wave ${A}/${h}`:`${h} wave${h===1?"":"s"}`,`${s}/${p.length} done`,...c?[`${c} running`]:[],...e.models?[`${e.models} model${e.models===1?"":"s"}`]:[],...e.truncated?[`${e.truncated.omittedNodes} omitted`]:[]].join(" \xB7 "),j.setBadge(c>0?c:null).catch(()=>{}),Wt!==e.rootId&&(Wt=e.rootId,requestAnimationFrame(()=>e.focusId!==e.rootId?no(e.focusId):De())),V&&!e.nodes.some(i=>i.id===V)&&(V=void 0),ge(V)}function Io(){if(!M)return;let e=Date.now();for(let t of M.nodes){if(t.state!=="running")continue;let a=de.get(t.id)?.querySelector(".n-status");a&&(a.textContent=fe(t,e))}}setInterval(Io,1e3);async function $o(e){let t=M?.nodes.find(r=>r.id===e);if(t)try{await j.openSession(t.sessionId)}catch(r){let[a,l]=oo(r);await j.toast({kind:"error",message:`${a}: ${l}`}).catch(()=>{})}}var xe=new Ue({fetch:e=>{let t={session:e};return ne.agentDir&&(t.agentDir=ne.agentDir),ne.stateDir&&(t.stateDir=ne.stateDir),j.serviceRequest({method:"GET",path:"/graph",query:t})},show:e=>{if(e.kind==="graph"){Go(e.graph);return}M=void 0,j.setBadge(null).catch(()=>{}),e.kind==="no-session"?gt("No session open","Open a chat to see the OmO agents working behind it."):e.kind==="empty"?gt("No sub-agents yet","When this session launches OmO tasks or workflows, they appear here as a live graph."):gt(...oo(e.error))},schedule:(e,t)=>{ht&&clearTimeout(ht),ht=setTimeout(t,e)},isHidden:()=>document.hidden}),Qt=G("#btn-settings");Qt.addEventListener("click",()=>{d.settings.hidden=!d.settings.hidden,Qt.setAttribute("aria-expanded",String(!d.settings.hidden)),d.settings.hidden||d.settings.querySelector("input")?.focus()});d.settings.addEventListener("submit",e=>{e.preventDefault();let t=new FormData(d.settings),r={},a=String(t.get("agentDir")??"").trim(),l=String(t.get("stateDir")??"").trim();a&&(r.agentDir=a),l&&(r.stateDir=l),ne=r,d.settings.hidden=!0,j.storage.set("paths",r).catch(()=>{}),xe.invalidate()});function No(){for(let e of["agentDir","stateDir"]){let t=d.settings.querySelector(`input[name="${e}"]`);t&&(t.value=ne[e]??"")}}var Zt=!1;j.onReady(e=>{it(e,document.documentElement),d.app.dataset.surface=e.surface,!Zt&&(Zt=!0,Ee=e.session?.id??Ee,j.storage.get("paths").then(t=>{if(t&&typeof t=="object"&&!Array.isArray(t)){let r=t;ne={...typeof r.agentDir=="string"?{agentDir:r.agentDir}:{},...typeof r.stateDir=="string"?{stateDir:r.stateDir}:{}}}No()}).catch(()=>{}).finally(()=>{to=!0,xe.setSession(Ee)}))});j.onSession(e=>{let t=e?.id??null;t!==Ee&&(V=void 0),Ee=t,to&&xe.setSession(t)});j.onSessionLifecycle(()=>{xe.refresh()});document.addEventListener("visibilitychange",()=>{document.hidden||xe.refresh()});window.addEventListener("resize",()=>{C&&_.k<=1&&le()});})();
176
+ `;var xo=e=>24+e*308;function jt(e){let t=new Map,r=new Map;for(let i of e.nodes)r.set(i.column,[...r.get(i.column)??[],i]);let a=[...r.keys()].sort((i,g)=>i-g),p=new Map(a.map(i=>[i,xo(i)])),l=new Map,s=0;for(let i of a)(r.get(i)??[]).slice().sort((f,x)=>(l.get(f.parentId??"")??-1)-(l.get(x.parentId??"")??-1)||(f.createdAt??0)-(x.createdAt??0)||f.id.localeCompare(x.id)).forEach((f,x)=>{let I=58+x*94;t.set(f.id,{x:p.get(i)??0,y:I}),l.set(f.id,l.size),s=Math.max(s,I+80+24)});let c=t.get(e.rootId),h=e.nodes.filter(i=>i.parentId===e.rootId).map(i=>t.get(i.id)).filter(i=>i!==void 0);if(c&&h.length>0&&(r.get(0)?.length??0)===1){let i=Math.min(...h.map(f=>f.y)),g=Math.max(...h.map(f=>f.y));c.y=Math.round((i+g)/2)}let A=24+(Math.max(0,...a)+1)*308-76+24;return{boxes:t,edges:bo(e.edges,t),columnX:p,width:A,height:Math.max(s,162)}}function Vt(e,t){if(t<=1)return 80/2;let r=Math.min(12,80*.44/(t-1));return 80/2+(e-(t-1)/2)*r}function bo(e,t){let r=e.filter(s=>t.has(s.from)&&t.has(s.to)),a=s=>(t.get(s)?.y??0)+80/2,p=new Map,l=new Map;for(let s of r)p.set(s.from,[...p.get(s.from)??[],s]),l.set(s.to,[...l.get(s.to)??[],s]);for(let s of p.values())s.sort((c,h)=>a(c.to)-a(h.to));for(let s of l.values())s.sort((c,h)=>a(c.from)-a(h.from));return r.map(s=>{let c=t.get(s.from),h=t.get(s.to),A=p.get(s.from)??[],i=l.get(s.to)??[],g=c.x+232,f=c.y+Vt(A.indexOf(s),A.length),x=h.x,I=h.y+Vt(i.indexOf(s),i.length),W=x>g?Math.max(32,(x-g)*.5):90,E=`M ${g} ${f} C ${g+W} ${f}, ${x-W} ${I}, ${x} ${I}`;return{...s,d:E,sx:g,sy:f,tx:x,ty:I}})}function ft(e,t,r){let a=new Set,p=[t];for(;p.length>0;){let l=p.pop();for(let s of e){let c=r==="down"?s.from===l?s.to:void 0:s.to===l?s.from:void 0;c!==void 0&&!a.has(c)&&c!==t&&(a.add(c),p.push(c))}}return a}function qt(e){let t=Math.max(0,Math.round(e/1e3));if(t<60)return`${t}s`;let r=Math.floor(t/60);return r<60?`${r}m ${String(t%60).padStart(2,"0")}s`:`${Math.floor(r/60)}h ${String(r%60).padStart(2,"0")}m`}function mt(e){if(e)return e.includes("/")?e.slice(e.indexOf("/")+1):e}function Yt(e){return e>=1e6?`${(e/1e6).toFixed(1)}M`:e>=1e3?`${(e/1e3).toFixed(e>=1e4?0:1)}k`:String(e)}var So={running:"running",queued:"queued",completed:"completed",failed:"failed",cancelled:"cancelled",idle:"idle"};function fe(e,t){if(e.kind==="root")return e.rawStatus;let r=e.rawStatus===e.state?So[e.state]:e.rawStatus,a=e.startedAt;return a===void 0?r:e.state==="running"?`${r} \xB7 ${qt(t-a)}`:e.endedAt!==void 0?`${r} \xB7 ${qt(e.endedAt-a)}`:r}var To=new Set(["running","queued","completed","failed","cancelled","idle"]),he=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),J=e=>typeof e=="number"&&Number.isFinite(e),me=e=>e===void 0||typeof e=="string",ce=e=>e===void 0||J(e),_o=e=>typeof e=="string"&&To.has(e);function yo(e){return he(e)?typeof e.id=="string"&&(e.kind==="root"||e.kind==="task")&&typeof e.sessionId=="string"&&typeof e.title=="string"&&_o(e.state)&&typeof e.rawStatus=="string"&&me(e.agent)&&me(e.model)&&ce(e.createdAt)&&ce(e.startedAt)&&ce(e.endedAt)&&me(e.summary)&&me(e.error)&&ce(e.tokens)&&ce(e.costUsd)&&ce(e.toolCalls)&&me(e.parentId)&&J(e.column)&&J(e.wave):!1}function Ao(e){return he(e)&&typeof e.id=="string"&&typeof e.from=="string"&&typeof e.to=="string"&&(e.kind==="spawn"||e.kind==="uses")}function wo(e){return he(e)&&J(e.column)&&J(e.total)&&J(e.done)&&J(e.running)}function vo(e){return he(e)&&J(e.omittedNodes)&&Number.isInteger(e.omittedNodes)&&e.omittedNodes>=0}function Mo(e){return he(e)&&(e.status==="ok"||e.status==="unknown-session")&&typeof e.rootId=="string"&&typeof e.focusId=="string"&&typeof e.title=="string"&&Array.isArray(e.nodes)&&e.nodes.every(yo)&&Array.isArray(e.edges)&&e.edges.every(Ao)&&Array.isArray(e.columns)&&e.columns.every(wo)&&J(e.models)&&(e.truncated===void 0||vo(e.truncated))}function Oo(e){let t=JSON.parse(e);if(!Mo(t))throw new Error("service answered with an unexpected graph shape");return t}var Ue=class{constructor(t){this.deps=t}deps;session=void 0;generation=0;inFlight=!1;pending=!1;signature="";setSession(t){t!==this.session&&(this.session=t,this.invalidate())}invalidate(){this.generation++,this.signature="",this.refresh()}async refresh(){let t=this.session;if(t===void 0)return;if(t===null){this.signature="",this.deps.show({kind:"no-session"});return}if(this.deps.isHidden()){this.deps.schedule(5e3,()=>{this.refresh()});return}if(this.inFlight){this.pending=!0;return}this.inFlight=!0;let r=this.generation;try{let a=await this.deps.fetch(t);if(r!==this.generation)return;if(a.status!==200)throw new Error(`service answered HTTP ${a.status}: ${a.body.slice(0,200)}`);let p=Oo(a.body);p.status!=="ok"||p.nodes.length<=1?(this.signature="",this.deps.show({kind:"empty"})):a.body!==this.signature&&(this.signature=a.body,this.deps.show({kind:"graph",graph:p}));let l=p.nodes.some(s=>s.state==="running"||s.state==="queued");this.deps.schedule(l?1500:5e3,()=>{this.refresh()})}catch(a){if(r!==this.generation)return;this.signature="",this.deps.show({kind:"error",error:a}),this.deps.schedule(1e4,()=>{this.refresh()})}finally{this.inFlight=!1,(this.pending||r!==this.generation)&&(this.pending=!1,this.refresh())}}};var Jt="http://www.w3.org/2000/svg";document.documentElement.style.setProperty("--node-card-width",`${232}px`);document.documentElement.style.setProperty("--node-card-height",`${80}px`);var j=st(),C=e=>document.querySelector(e),d={app:C("#app"),title:C("#title"),titleDot:C("#title-dot"),meta:C("#meta"),viewport:C("#viewport"),world:C("#world"),edges:C("#edges"),bands:C("#bands"),nodes:C("#nodes"),empty:C("#empty"),inspector:C("#inspector"),settings:C("#settings")},Ee=null,to=!1,M,R,Wt="",V,ne={},ht,_={x:0,y:0,k:1},de=new Map;function L(e,t="",r=""){let a=document.createElement(e);return t&&(a.className=t),r&&(a.textContent=r),a}function gt(e,t=""){d.empty.replaceChildren(L("strong","",e),...t?[L("p","",t)]:[]),d.empty.hidden=!1,d.world.hidden=!0,d.inspector.hidden=!0}function oo(e){return e instanceof u?e.code==="NO_SERVICE"?["Local service not allowed yet","Approve OmO Graph in Settings \u2192 Extensions so it can read the OmO task store."]:e.code==="SERVICE_FAILED"?["The local service did not start","Check Settings \u2192 Extensions; the next refresh retries it."]:e.code==="DISABLED"?["OmO Graph is paused","Resume it in Settings \u2192 Extensions."]:[`Request failed (${e.code})`,e.message]:["Request failed",e instanceof Error?e.message:String(e)]}function le(){d.world.style.transform=`translate(${_.x}px, ${_.y}px) scale(${_.k})`,d.app.style.setProperty("--zoom",String(_.k))}function De(){if(!R)return;let e=d.viewport.clientWidth,t=d.viewport.clientHeight,r=Math.min(1,Math.max(.62,Math.min((e-16)/R.width,(t-16)/R.height)));_.k=r,_.x=Math.round(R.width*r<=e?(e-R.width*r)/2:8),_.y=Math.round(Math.max(8,(t-R.height*r)/2)),le()}function no(e){let t=e?R?.boxes.get(e):void 0;if(!t)return De();_.k=Math.max(_.k,.8),_.x=Math.round(d.viewport.clientWidth/2-(t.x+232/2)*_.k),_.y=Math.round(d.viewport.clientHeight/2-(t.y+80/2)*_.k),le()}function ke(e,t=d.viewport.clientWidth/2,r=d.viewport.clientHeight/2){let a=Math.min(2,Math.max(.2,_.k*e));_.x=t-(t-_.x)*a/_.k,_.y=r-(r-_.y)*a/_.k,_.k=a,le()}var K,Et=!1;d.viewport.addEventListener("pointerdown",e=>{e.button===0&&(K={x:e.clientX,y:e.clientY,vx:_.x,vy:_.y,moved:!1})});window.addEventListener("pointermove",e=>{if(!K)return;let t=e.clientX-K.x,r=e.clientY-K.y;!K.moved&&Math.hypot(t,r)<4||(K.moved||(K.moved=!0,d.viewport.setPointerCapture?.(e.pointerId),d.app.classList.add("is-panning")),_.x=K.vx+t,_.y=K.vy+r,le())});window.addEventListener("pointerup",()=>{K?.moved&&(Et=!0,setTimeout(()=>{Et=!1},0)),K=void 0,d.app.classList.remove("is-panning")});d.viewport.addEventListener("wheel",e=>{e.preventDefault();let t=d.viewport.getBoundingClientRect();e.ctrlKey||e.metaKey||Math.abs(e.deltaY)>Math.abs(e.deltaX)*2&&!e.shiftKey?ke(Math.exp(-e.deltaY*(e.ctrlKey?.01:.0015)),e.clientX-t.left,e.clientY-t.top):(_.x-=e.deltaX||(e.shiftKey?e.deltaY:0),_.y-=e.shiftKey?0:e.deltaY,le())},{passive:!1});C("#btn-fit").addEventListener("click",De);C("#btn-locate").addEventListener("click",()=>{let e=M?.nodes.find(t=>t.kind==="task"&&t.state==="running");no(M?.focusId!==M?.rootId?M?.focusId:e?.id??M?.rootId)});C("#btn-in").addEventListener("click",()=>ke(1.2));C("#btn-out").addEventListener("click",()=>ke(1/1.2));window.addEventListener("keydown",e=>{e.target instanceof HTMLInputElement||(e.key==="0"?De():e.key==="+"||e.key==="="?ke(1.2):e.key==="-"&&ke(1/1.2))});function ge(e){if(V=e,!M)return;let t=M.edges,r=new Set(e?t.filter(s=>s.to===e).map(s=>s.from):[]),a=new Set(e?t.filter(s=>s.from===e).map(s=>s.to):[]),p=e?ft(t,e,"up"):new Set,l=e?ft(t,e,"down"):new Set;d.app.classList.toggle("has-hover",e!==void 0);for(let[s,c]of de)c.classList.toggle("is-hover",s===e),c.classList.toggle("is-input",r.has(s)),c.classList.toggle("is-output",a.has(s)),c.classList.toggle("is-reach",p.has(s)||l.has(s));for(let s of d.edges.querySelectorAll("path[data-edge]")){let c=s.dataset.from??"",h=s.dataset.to??"";s.classList.toggle("is-in",e!==void 0&&h===e),s.classList.toggle("is-out",e!==void 0&&c===e),s.classList.toggle("is-reach",e!==void 0&&(p.has(c)&&(p.has(h)||h===e)||l.has(h)&&(l.has(c)||c===e)))}Lo()}function Lo(){let e=M?.nodes.find(A=>A.id===(V??(M?.focusId!==M?.rootId?M?.focusId:void 0)));if(!e||!M){d.inspector.hidden=!0;return}let t=M.edges.filter(A=>A.to===e.id).length,r=M.edges.filter(A=>A.from===e.id).length,a=[fe(e,Date.now())];e.agent&&a.push(e.agent);let p=mt(e.model);p&&a.push(p),e.toolCalls!==void 0&&a.push(`${e.toolCalls} tools`),e.tokens!==void 0&&a.push(`${Yt(e.tokens)} tok`),e.costUsd!==void 0&&a.push(`$${e.costUsd.toFixed(2)}`);let l=L("div","i-head");l.append(L("span",`dot s-${e.state}`),L("strong","",e.title));let s=L("div","i-flow");s.append(L("span","i-in",`\u2190 ${t} input${t===1?"":"s"}`),L("span","i-out",`${r} output${r===1?"":"s"} \u2192`));let c=[l,L("div","i-facts",a.join(" \xB7 ")),s],h=e.error??e.summary;h&&c.push(L("p",e.error?"i-text is-error":"i-text",h)),c.push(L("div","i-hint",e.kind==="root"?"Click to open the orchestrating session":"Click to open this sub-agent's session")),d.inspector.replaceChildren(...c),d.inspector.hidden=!1}function Co(e){let t=de.get(e.id);return t||(t=L("button","node"),t.type="button",t.dataset.id=e.id,t.append(L("div","n-head"),L("div","n-status"),L("div","n-meta"),L("div","n-sub")),t.querySelector(".n-head")?.append(L("span","dot"),L("span","n-title")),t.addEventListener("pointerenter",()=>ge(e.id)),t.addEventListener("pointerleave",()=>{V===e.id&&ge(void 0)}),t.addEventListener("focus",()=>ge(e.id)),t.addEventListener("blur",()=>{V===e.id&&ge(void 0)}),t.addEventListener("click",()=>{Et||$o(e.id)}),de.set(e.id,t),d.nodes.append(t)),t}function Ro(e,t,r){e.className=`node k-${t.kind} s-${t.state}${M?.focusId===t.id&&t.kind==="task"?" is-current":""}`,V===t.id&&e.classList.add("is-hover"),e.setAttribute("aria-label",`${t.title}, ${fe(t,r)}. Open session.`);let a=(p,l)=>{let s=e.querySelector(p);s&&s.textContent!==l&&(s.textContent=l)};a(".n-title",t.title),a(".n-status",fe(t,r)),a(".n-meta",t.kind==="root"?`${M?.nodes.length?M.nodes.length-1:0} sub-agents`:[t.agent,mt(t.model)].filter(Boolean).join(" \xB7 ")),a(".n-sub",t.error??t.summary??""),e.querySelector(".dot")?.setAttribute("class",`dot s-${t.state}`)}function Go(e){M=e,R=jt(e),d.world.hidden=!1,d.empty.hidden=!0,d.world.style.width=`${R.width}px`,d.world.style.height=`${R.height}px`,d.edges.setAttribute("width",String(R.width)),d.edges.setAttribute("height",String(R.height)),d.edges.setAttribute("viewBox",`0 0 ${R.width} ${R.height}`);let t=new Map(e.nodes.map(i=>[i.id,i.state])),r=document.createDocumentFragment();for(let i of R.edges){let g=document.createElementNS(Jt,"path");g.setAttribute("d",i.d),g.dataset.edge=i.id,g.dataset.from=i.from,g.dataset.to=i.to;let f=t.get(i.to)==="running"||t.get(i.to)==="queued";if(g.setAttribute("class",`edge k-${i.kind}${f?" is-live":""}`),g.setAttribute("marker-end","url(#arrow)"),r.append(g),f){let x=document.createElementNS(Jt,"path");x.setAttribute("d",i.d),x.setAttribute("class","flow"),x.setAttribute("pathLength","1"),r.append(x)}}d.edges.querySelectorAll("path.edge, path.flow").forEach(i=>i.remove()),d.edges.append(r),d.bands.replaceChildren(...[...R.columnX].map(([i,g])=>{let f=L("div","band");f.style.left=`${g}px`,f.style.width=`${232}px`;let x=e.columns.find(I=>I.column===i);return i===0?f.textContent="Orchestrator":x&&(f.textContent=`Wave ${i} \xB7 ${x.done}/${x.total} done${x.running?` \xB7 ${x.running} running`:""}`),x?.running&&f.classList.add("is-live"),f}));let a=Date.now(),p=new Set;for(let i of e.nodes){let g=Co(i),f=R.boxes.get(i.id);f&&(g.style.transform=`translate(${f.x}px, ${f.y}px)`),Ro(g,i,a),p.add(i.id)}for(let[i,g]of de)p.has(i)||(g.remove(),de.delete(i));let l=e.nodes.filter(i=>i.kind==="task"),s=l.filter(i=>i.state==="completed"||i.state==="failed"||i.state==="cancelled").length,c=l.filter(i=>i.state==="running").length,h=e.columns.length,A=e.columns.find(i=>i.running>0||i.done<i.total)?.column;d.title.textContent=e.title,d.titleDot.className=`dot s-${c>0?"running":l.some(i=>i.state==="failed")?"failed":s===l.length?"completed":"idle"}`,d.meta.textContent=[A!==void 0?`wave ${A}/${h}`:`${h} wave${h===1?"":"s"}`,`${s}/${l.length} done`,...c?[`${c} running`]:[],...e.models?[`${e.models} model${e.models===1?"":"s"}`]:[],...e.truncated?[`${e.truncated.omittedNodes} omitted`]:[]].join(" \xB7 "),j.setBadge(c>0?c:null).catch(()=>{}),Wt!==e.rootId&&(Wt=e.rootId,requestAnimationFrame(()=>e.focusId!==e.rootId?no(e.focusId):De())),V&&!e.nodes.some(i=>i.id===V)&&(V=void 0),ge(V)}function Io(){if(!M)return;let e=Date.now();for(let t of M.nodes){if(t.state!=="running")continue;let a=de.get(t.id)?.querySelector(".n-status");a&&(a.textContent=fe(t,e))}}setInterval(Io,1e3);async function $o(e){let t=M?.nodes.find(r=>r.id===e);if(t)try{await j.openSession(t.sessionId)}catch(r){let[a,p]=oo(r);await j.toast({kind:"error",message:`${a}: ${p}`}).catch(()=>{})}}var xe=new Ue({fetch:e=>{let t={session:e};return ne.agentDir&&(t.agentDir=ne.agentDir),ne.stateDir&&(t.stateDir=ne.stateDir),j.serviceRequest({method:"GET",path:"/graph",query:t})},show:e=>{if(e.kind==="graph"){Go(e.graph);return}M=void 0,j.setBadge(null).catch(()=>{}),e.kind==="no-session"?gt("No session open","Open a chat to see the OmO agents working behind it."):e.kind==="empty"?gt("No sub-agents yet","When this session launches OmO tasks or workflows, they appear here as a live graph."):gt(...oo(e.error))},schedule:(e,t)=>{ht&&clearTimeout(ht),ht=setTimeout(t,e)},isHidden:()=>document.hidden}),Qt=C("#btn-settings");Qt.addEventListener("click",()=>{d.settings.hidden=!d.settings.hidden,Qt.setAttribute("aria-expanded",String(!d.settings.hidden)),d.settings.hidden||d.settings.querySelector("input")?.focus()});C("#btn-save").addEventListener("click",()=>{let e=new FormData(d.settings),t={},r=String(e.get("agentDir")??"").trim(),a=String(e.get("stateDir")??"").trim();r&&(t.agentDir=r),a&&(t.stateDir=a),ne=t,d.settings.hidden=!0,j.storage.set("paths",t).catch(()=>{}),xe.invalidate()});function No(){for(let e of["agentDir","stateDir"]){let t=d.settings.querySelector(`input[name="${e}"]`);t&&(t.value=ne[e]??"")}}var Zt=!1;j.onReady(e=>{it(e,document.documentElement),d.app.dataset.surface=e.surface,!Zt&&(Zt=!0,Ee=e.session?.id??Ee,j.storage.get("paths").then(t=>{if(t&&typeof t=="object"&&!Array.isArray(t)){let r=t;ne={...typeof r.agentDir=="string"?{agentDir:r.agentDir}:{},...typeof r.stateDir=="string"?{stateDir:r.stateDir}:{}}}No()}).catch(()=>{}).finally(()=>{to=!0,xe.setSession(Ee)}))});j.onSession(e=>{let t=e?.id??null;t!==Ee&&(V=void 0),Ee=t,to&&xe.setSession(t)});j.onSessionLifecycle(()=>{xe.refresh()});document.addEventListener("visibilitychange",()=>{document.hidden||xe.refresh()});window.addEventListener("resize",()=>{R&&_.k<=1&&le()});})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "OpenCode-compatible HTTP/SSE server that lets OpenChamber drive the pi coding agent (and OmO) over RPC",
5
5
  "type": "module",
6
6
  "license": "MIT",