antechamber 1.0.40 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +126 -0
  2. package/README.md +88 -141
  3. package/bin/opencode-antechamber +14 -4
  4. package/dist/approvals.d.ts +9 -1
  5. package/dist/approvals.js +10 -2
  6. package/dist/catalog.d.ts +13 -2
  7. package/dist/catalog.js +88 -52
  8. package/dist/cli.d.ts +1 -1
  9. package/dist/cli.js +1 -1
  10. package/dist/commands/context.d.ts +5 -2
  11. package/dist/commands/context.js +8 -4
  12. package/dist/commands/doctor.js +4 -3
  13. package/dist/commands/setup.js +9 -28
  14. package/dist/config.d.ts +6 -2
  15. package/dist/config.js +18 -4
  16. package/dist/file-watch.d.ts +35 -0
  17. package/dist/file-watch.js +163 -0
  18. package/dist/latency.d.ts +10 -0
  19. package/dist/latency.js +115 -0
  20. package/dist/misc-routes.js +14 -12
  21. package/dist/pi-event-translation.js +44 -2
  22. package/dist/route-services.d.ts +8 -3
  23. package/dist/routes-v1.d.ts +1 -1
  24. package/dist/routes-v1.js +29 -418
  25. package/dist/routes-v2.js +252 -34
  26. package/dist/self-update.d.ts +2 -1
  27. package/dist/self-update.js +5 -1
  28. package/dist/server.d.ts +17 -3
  29. package/dist/server.js +191 -18
  30. package/dist/session-commands.d.ts +3 -1
  31. package/dist/session-commands.js +13 -7
  32. package/dist/session-history.js +3 -0
  33. package/dist/session-store.d.ts +19 -10
  34. package/dist/session-store.js +266 -128
  35. package/dist/session-tree.d.ts +15 -1
  36. package/dist/session-tree.js +2 -2
  37. package/dist/sessions.d.ts +85 -1
  38. package/dist/sessions.js +238 -10
  39. package/dist/sse.d.ts +0 -2
  40. package/dist/sse.js +13 -46
  41. package/dist/text-generation.d.ts +20 -0
  42. package/dist/text-generation.js +93 -0
  43. package/dist/tool-map.js +1 -3
  44. package/dist/types.d.ts +60 -0
  45. package/dist/v2.d.ts +8 -1
  46. package/dist/v2.js +24 -4
  47. package/dist/work-status.js +1 -1
  48. package/extensions/antechamber/README.md +100 -0
  49. package/extensions/antechamber/bin/opencode-antechamber +42 -0
  50. package/extensions/{omo-dag/docs/DESIGN.md → antechamber/docs/GRAPH.md} +5 -5
  51. package/extensions/{omo-work/docs/DESIGN.md → antechamber/docs/WORK.md} +3 -3
  52. package/extensions/antechamber/package.json +60 -0
  53. package/extensions/{omo-dag → antechamber}/panel/index.html +8 -1
  54. package/extensions/antechamber/panel/main.js +261 -0
  55. package/extensions/antechamber/runtime/launcher.js +201 -0
  56. package/extensions/antechamber/service/main.js +884 -0
  57. package/package.json +75 -26
  58. package/extensions/omo-compat/README.md +0 -48
  59. package/extensions/omo-compat/icon.svg +0 -1
  60. package/extensions/omo-compat/package.json +0 -43
  61. package/extensions/omo-dag/README.md +0 -167
  62. package/extensions/omo-dag/package.json +0 -33
  63. package/extensions/omo-dag/panel/main.js +0 -176
  64. package/extensions/omo-dag/service/main.js +0 -457
  65. package/extensions/omo-work/README.md +0 -88
  66. package/extensions/omo-work/package.json +0 -30
  67. package/extensions/omo-work/service/main.js +0 -149
  68. /package/extensions/{omo-dag → antechamber}/icon.svg +0 -0
  69. /package/extensions/{omo-work → antechamber}/status/index.html +0 -0
  70. /package/extensions/{omo-work → antechamber}/status/main.js +0 -0
@@ -0,0 +1,93 @@
1
+ import { PiRpcClient } from "./pi-rpc.js";
2
+ /**
3
+ * A hang guard, not a budget: OpenChamber sizes its own small-model deadline by prompt length up to 900 s
4
+ * (server/lib/small-model/index.js generationBudgetMs) and its disconnect cancels the run here, and OpenCode's
5
+ * generate.text has no server deadline. This only has to outlast the longest budget a caller may give.
6
+ */
7
+ export const GENERATION_DEADLINE_MS = 15 * 60 * 1000;
8
+ /** Own the auxiliary agents separately from user sessions, with no admission queue. */
9
+ export class TextGeneration {
10
+ active = new Map();
11
+ stopped = false;
12
+ async generate(directory, prompt, model, signal) {
13
+ if (this.stopped)
14
+ throw new Error("Text generation is stopping");
15
+ if (this.active.size >= 2)
16
+ throw new Error("Text generation is at capacity");
17
+ const cancellation = new AbortController();
18
+ const result = generateText(directory, prompt, model, signal ? AbortSignal.any([signal, cancellation.signal]) : cancellation.signal);
19
+ this.active.set(cancellation, result);
20
+ try {
21
+ return await result;
22
+ }
23
+ finally {
24
+ this.active.delete(cancellation);
25
+ }
26
+ }
27
+ async dispose() {
28
+ this.stopped = true;
29
+ for (const cancellation of this.active.keys())
30
+ cancellation.abort(new Error("Text generation is stopping"));
31
+ await Promise.allSettled(this.active.values());
32
+ }
33
+ }
34
+ /** Run a model turn in an agent with no persistent session, never in a user's chat. */
35
+ export async function generateText(directory, prompt, model, caller) {
36
+ caller?.throwIfAborted();
37
+ // Startup overrides initialize agent state without persisting the default, unlike set_model.
38
+ // senpi dist/cli/args.js:93-104 -> main.js:561-576,843 -> core/sdk.js:125,292-298.
39
+ const pi = new PiRpcClient({
40
+ cwd: directory,
41
+ args: ["--no-session", "--no-tools", ...(model ? ["--provider", model.providerID, "--model", model.id] : [])],
42
+ });
43
+ const cancellation = new AbortController();
44
+ const signal = caller ? AbortSignal.any([caller, cancellation.signal]) : cancellation.signal;
45
+ // This budget covers startup/model resolution, admission, the turn, and the result read.
46
+ const timer = setTimeout(() => cancellation.abort(new Error("Text generation timed out")), GENERATION_DEADLINE_MS);
47
+ try {
48
+ const finished = new Promise((resolve, reject) => {
49
+ const cleanup = () => {
50
+ pi.off("event", onEvent);
51
+ signal.removeEventListener("abort", onAbort);
52
+ };
53
+ const onAbort = () => {
54
+ cleanup();
55
+ reject(signal.reason);
56
+ };
57
+ const onEvent = (event) => {
58
+ if (event.type !== "agent_end")
59
+ return;
60
+ cleanup();
61
+ resolve();
62
+ };
63
+ pi.onEvent(onEvent);
64
+ signal.addEventListener("abort", onAbort, { once: true });
65
+ });
66
+ // A prompt RPC may acknowledge admission before the agent finishes; agent_end
67
+ // is the completion boundary before reading the resulting entries.
68
+ const admitted = pi.command({ type: "prompt", message: prompt }, undefined, signal).then((response) => {
69
+ if (!response.success)
70
+ throw new Error(response.error ?? "Text generation failed");
71
+ });
72
+ await Promise.all([admitted, finished]);
73
+ const history = await pi.command({ type: "get_entries" }, undefined, signal);
74
+ if (!history.success || !Array.isArray(history.data?.entries))
75
+ throw new Error("Text generation produced no readable history");
76
+ const assistant = history.data.entries.findLast((entry) => entry.type === "message" && entry.message?.role === "assistant");
77
+ const content = assistant?.message?.content;
78
+ const text = typeof content === "string"
79
+ ? content
80
+ : content
81
+ ?.filter((part) => part.type === "text")
82
+ .map((part) => part.text ?? "")
83
+ .join("");
84
+ if (!text)
85
+ throw new Error("Text generation produced no text");
86
+ return text;
87
+ }
88
+ finally {
89
+ clearTimeout(timer);
90
+ cancellation.abort();
91
+ await pi.dispose();
92
+ }
93
+ }
package/dist/tool-map.js CHANGED
@@ -257,9 +257,7 @@ export function nestedToolParts(parent, details, sessions = {}) {
257
257
  messageID: parent.messageID,
258
258
  type: "tool",
259
259
  callID: `nested:${callID}`,
260
- // OpenChamber 1.24 never styles a task card as failed or shows its error (ToolPart.tsx isTaskTool),
261
- // and a failed spawn has no session to open, so it goes out as a plain card that does both.
262
- tool: failed && mapped.tool === "task" ? "agent" : mapped.tool,
260
+ tool: mapped.tool,
263
261
  state: !failed
264
262
  ? { ...state, status: "completed" }
265
263
  : {
package/dist/types.d.ts CHANGED
@@ -48,6 +48,7 @@ export interface OCSession {
48
48
  created: number;
49
49
  updated: number;
50
50
  idle?: number;
51
+ viewed?: number;
51
52
  compacting?: number;
52
53
  /** Epoch ms — presence marks the session archived (SDK v2 session.update). */
53
54
  archived?: number;
@@ -67,6 +68,8 @@ export interface OCUserMessage {
67
68
  };
68
69
  /** No-reply context admitted without a turn (v2 lists it as `type: "synthetic"`). */
69
70
  synthetic?: boolean;
71
+ /** Human-readable label for a v2 synthetic notice. */
72
+ description?: string;
70
73
  /** Client metadata sent with the prompt (OpenChamber 2.x). */
71
74
  metadata?: Record<string, unknown>;
72
75
  }
@@ -291,6 +294,33 @@ export type OCEvent = {
291
294
  properties: {
292
295
  server: string;
293
296
  };
297
+ } | {
298
+ type: "filesystem.changed";
299
+ properties: {
300
+ file: string;
301
+ event: "change" | "unlink";
302
+ };
303
+ } | {
304
+ type: "project.updated";
305
+ properties: {
306
+ id: string;
307
+ canonical: string;
308
+ time: {
309
+ created: number;
310
+ updated: number;
311
+ active: number;
312
+ };
313
+ sandboxes: string[];
314
+ vcs: string;
315
+ };
316
+ } | {
317
+ type: "provider.updated" | "model.updated" | "command.updated";
318
+ properties: Record<string, never>;
319
+ } | {
320
+ type: "vcs.branch.updated";
321
+ properties: {
322
+ branch?: string;
323
+ };
294
324
  } | {
295
325
  type: "session.created";
296
326
  properties: {
@@ -301,6 +331,30 @@ export type OCEvent = {
301
331
  properties: {
302
332
  info: OCSession;
303
333
  };
334
+ } | {
335
+ type: "session.renamed";
336
+ properties: {
337
+ sessionID: string;
338
+ title: string;
339
+ };
340
+ } | {
341
+ type: "session.agent.selected";
342
+ properties: {
343
+ sessionID: string;
344
+ agent: string;
345
+ previous?: string;
346
+ };
347
+ } | {
348
+ type: "session.archived" | "session.unarchived";
349
+ properties: {
350
+ sessionID: string;
351
+ };
352
+ } | {
353
+ type: "session.viewed";
354
+ properties: {
355
+ sessionID: string;
356
+ idle: number;
357
+ };
304
358
  } | {
305
359
  type: "session.revert.staged";
306
360
  properties: {
@@ -327,6 +381,12 @@ export type OCEvent = {
327
381
  reason: string;
328
382
  inputID: string;
329
383
  };
384
+ } | {
385
+ type: "session.compaction.delta";
386
+ properties: {
387
+ sessionID: string;
388
+ text: string;
389
+ };
330
390
  } | {
331
391
  type: "session.compaction.ended";
332
392
  properties: {
package/dist/v2.d.ts CHANGED
@@ -123,6 +123,8 @@ export interface V2Session {
123
123
  time: {
124
124
  created: number;
125
125
  updated: number;
126
+ idle?: number;
127
+ viewed?: number;
126
128
  archived?: number;
127
129
  };
128
130
  title: string;
@@ -148,7 +150,12 @@ export interface V2Message {
148
150
  completed?: number;
149
151
  streamed?: number;
150
152
  };
151
- type: "user" | "assistant" | "synthetic";
153
+ type: "user" | "assistant" | "synthetic" | "idle" | "compaction";
154
+ status?: "running" | "completed" | "failed";
155
+ reason?: string;
156
+ summary?: string;
157
+ outcome?: "succeeded" | "failed" | "interrupted";
158
+ description?: string;
152
159
  text?: string;
153
160
  files?: Array<Record<string, unknown>>;
154
161
  agents?: Array<Record<string, unknown>>;
package/dist/v2.js CHANGED
@@ -23,6 +23,7 @@
23
23
  */
24
24
  import { randomUUID } from "node:crypto";
25
25
  import { getAgentName } from "./agent-name.js";
26
+ import { toV2Permission } from "./approvals.js";
26
27
  import { questionFormID, toQuestionForm } from "./question-forms.js";
27
28
  import { v2PromptAttachments } from "./prompt-parts.js";
28
29
  function genId(prefix) {
@@ -97,6 +98,7 @@ export function toV2Session(info, opts = {}) {
97
98
  created: info.time.created,
98
99
  updated: info.time.updated,
99
100
  ...(info.time.idle !== undefined ? { idle: info.time.idle } : {}),
101
+ ...(info.time.viewed !== undefined ? { viewed: info.time.viewed } : {}),
100
102
  ...(info.time.archived !== undefined ? { archived: info.time.archived } : {}),
101
103
  },
102
104
  title: info.title,
@@ -127,7 +129,14 @@ function toV2UserMessage(info) {
127
129
  const metadata = user.metadata !== undefined ? { metadata: user.metadata } : {};
128
130
  // Admitted no-reply context lists as OpenCode's synthetic item, not as a user turn.
129
131
  if (user.synthetic)
130
- return { id: user.id, time: { created: user.time.created }, type: "synthetic", text, ...metadata };
132
+ return {
133
+ id: user.id,
134
+ time: { created: user.time.created },
135
+ type: "synthetic",
136
+ text,
137
+ ...(user.description !== undefined ? { description: user.description } : {}),
138
+ ...metadata,
139
+ };
131
140
  const inputs = info.parts.flatMap((part) => (part.type === "file" || part.type === "agent" ? [part] : []));
132
141
  return {
133
142
  id: user.id,
@@ -290,16 +299,28 @@ export class V2EventTranslator {
290
299
  switch (ev.type) {
291
300
  case "session.inbox.enqueued":
292
301
  case "session.inbox.delivered":
302
+ case "session.renamed":
303
+ case "session.agent.selected":
304
+ case "session.archived":
305
+ case "session.unarchived":
306
+ case "session.viewed":
293
307
  case "session.revert.staged":
294
308
  case "session.revert.cleared":
295
309
  case "session.revert.committed":
296
310
  case "session.compaction.started":
311
+ case "session.compaction.delta":
297
312
  case "session.compaction.ended":
298
313
  case "session.compaction.failed":
299
314
  case "session.shell.started":
300
315
  case "session.shell.ended":
301
316
  case "shell.created":
302
317
  case "shell.exited":
318
+ case "filesystem.changed":
319
+ case "project.updated":
320
+ case "provider.updated":
321
+ case "model.updated":
322
+ case "command.updated":
323
+ case "vcs.branch.updated":
303
324
  return [this.frame(directory, ev.type, ev.properties)];
304
325
  case "message.removed":
305
326
  return [
@@ -318,6 +339,7 @@ export class V2EventTranslator {
318
339
  subpath: subpathOf(info.directory),
319
340
  title: info.title,
320
341
  agent: info.agent ?? getAgentName(),
342
+ ...(info.model !== undefined ? { model: info.model } : {}),
321
343
  ...(info.parentID !== undefined ? { parentID: info.parentID } : {}),
322
344
  ...(info.parentMessageID !== undefined ? { parentMessageID: info.parentMessageID } : {}),
323
345
  ...(info.metadata !== undefined ? { metadata: info.metadata } : {}),
@@ -327,11 +349,9 @@ export class V2EventTranslator {
327
349
  case "session.updated": {
328
350
  const info = ev.properties.info;
329
351
  return [
330
- this.frame(directory, "session.renamed", { sessionID: info.id, title: info.title }),
331
352
  ...(info.metadata !== undefined
332
353
  ? [this.frame(directory, "session.metadata.updated", { sessionID: info.id, metadata: info.metadata })]
333
354
  : []),
334
- this.frame(directory, "session.agent.selected", { sessionID: info.id, agent: info.agent ?? getAgentName() }),
335
355
  ...(info.cost !== undefined
336
356
  ? [
337
357
  this.frame(directory, "session.usage.updated", {
@@ -427,7 +447,7 @@ export class V2EventTranslator {
427
447
  }
428
448
  case "permission.asked": {
429
449
  const p = ev.properties;
430
- return [this.frame(directory, "permission.asked", p)];
450
+ return [this.frame(directory, "permission.asked", toV2Permission(p))];
431
451
  }
432
452
  case "permission.replied":
433
453
  return [this.frame(directory, "permission.replied", ev.properties)];
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Background work of one session for the OmO Work status section (extensions/omo-work):
2
+ * Background work of one session for the OmO Work status section (extensions/antechamber):
3
3
  * todo phases, terminal monitors, detached eval cells, background terminal sessions and the
4
4
  * goal continuation timer. OpenChamber 2.x has no native surface for any of these.
5
5
  *
@@ -0,0 +1,100 @@
1
+ # antechamber: OpenChamber extension
2
+
3
+ One OpenChamber 2.x extension that installs [antechamber](../../README.md) and OmO, keeps them at the versions this
4
+ release pins, and adds the OmO views to OpenChamber:
5
+
6
+ - **OmO Graph**, a rail panel (and a full-screen page) drawing the OmO sub-agents behind the open session as a live
7
+ graph. See [docs/GRAPH.md](docs/GRAPH.md).
8
+ - **OmO Work**, a section of the Work Status panel with the session's todo phases, goal timer and other wake
9
+ sources. See [docs/WORK.md](docs/WORK.md).
10
+ - **OmO tool headers**: eval cells read `JS Eval` (or `Python Eval`) and their summary instead of "Shell Command",
11
+ and a sub-agent card reads its agent type and description instead of "Agent Task".
12
+ - **The runtime**: `bin/opencode-antechamber`, the program OpenChamber runs as its OpenCode server. Its first start
13
+ installs, with Bun, the antechamber release whose version is this extension's version, and the `omo-ai` that
14
+ release pins, into `~/.antechamber/runtime/<version>`.
15
+
16
+ It replaces the separate OmO Graph, OmO Work and OmO Compatibility extensions of antechamber 1.x.
17
+
18
+ ## Requirements
19
+
20
+ - OpenChamber **2.3.2 or newer**, web or desktop. VS Code and mobile load no extensions.
21
+ - [Bun](https://bun.sh) 1.4 or newer on the machine OpenChamber runs on, on `PATH` or in `~/.bun/bin`.
22
+ - Network access to the npm registry the first time each version starts.
23
+
24
+ ## Install
25
+
26
+ 1. In OpenChamber, open **Settings → Extensions → Add → URL** and enter `https://github.com/eliottness/antechamber`.
27
+ The repository root carries this extension's manifest (paths rebased into `extensions/antechamber/`) and the
28
+ built files are committed, so the clone is the extension. `https://github.com/eliottness/antechamber#extension/antechamber`
29
+ installs the same extension from a branch holding only its files.
30
+ 2. Approve what it asks for: **sessions**, to follow the open session and open sub-agent sessions, and **a local
31
+ service that runs `bun`**, which installs the runtime and reads the OmO task store and antechamber's run records.
32
+ 3. Open the **OmO** rail panel and its settings (the sliders icon). The **Runtime** block shows the install and the
33
+ launcher's path. The service starts installing as soon as a panel or the Work section opens.
34
+ 4. In **Settings → OpenCode CLI**, set the path to that launcher: the path the Runtime block shows,
35
+ `<OpenChamber data dir>/extensions/antechamber/extensions/antechamber/bin/opencode-antechamber` for the repository URL. OpenChamber restarts its server on
36
+ antechamber.
37
+
38
+ Other ways to add it: a **Folder** (`extensions/antechamber` of an npm install or a built checkout; `antechamber
39
+ setup` prints it), or the release **ZIP**
40
+ (`https://github.com/eliottness/antechamber/releases/latest/download/antechamber-openchamber.zip`). OpenChamber
41
+ checks only Git installs for updates.
42
+
43
+ For a server you reach over HTTP:
44
+
45
+ ```bash
46
+ curl -sX POST "$OC/api/guests" -H 'content-type: application/json' \
47
+ -d '{"url":"https://github.com/eliottness/antechamber"}'
48
+ curl -sX PUT "$OC/api/guests/antechamber/capabilities" -H 'content-type: application/json' \
49
+ -d '{"granted":["sessions","service"]}'
50
+ ```
51
+
52
+ ## Updating
53
+
54
+ Every antechamber release publishes the extension at its own version to the `extension/antechamber` branch, and
55
+ OpenChamber offers it under **Update** in Settings → Extensions. Updating the extension is updating antechamber and
56
+ OmO: the next time OpenChamber starts its server, the launcher installs the new pair. OpenChamber's own OpenCode
57
+ Update action runs the launcher's `upgrade`, which only points here.
58
+
59
+ The previous runtime stays installed for an adapter that is still running from it; older ones are removed.
60
+
61
+ ## Settings
62
+
63
+ The launcher reads these from OpenChamber's environment (the service never sees them, because OpenChamber starts
64
+ extension services with a minimal environment):
65
+
66
+ | Variable | Default | Effect |
67
+ |---|---|---|
68
+ | `ANTECHAMBER_RUNTIME_DIR` | `~/.antechamber/runtime` | Where runtimes are installed |
69
+ | `ANTECHAMBER_RUNTIME_SPEC` | unset | A local antechamber tarball or folder to run instead of the pinned release (development) |
70
+
71
+ Every other antechamber setting (`ANTECHAMBER_PI_BINARY`, `ANTECHAMBER_STATE_DIR`, ...) works as documented in
72
+ [docs/configuration.md](../../docs/configuration.md). If your deployment moves the state or agent directories, enter the
73
+ same paths in the Graph panel's settings and the Work section's **Data path**.
74
+
75
+ ## How it works
76
+
77
+ ```
78
+ OpenChamber --spawn--> bin/opencode-antechamber --exec--> bun <runtime>/node_modules/antechamber/dist/cli.js serve
79
+ | `--spawn--> <runtime>/node_modules/omo-ai/bin/omo.js --mode rpc
80
+ `--bun runtime/launcher.js resolve (install when missing)
81
+
82
+ panel / section (sandboxed iframes) --serviceRequest--> OpenChamber host --loopback + token--> service/main.js
83
+ /graph reads the OmO task store and the session registry
84
+ /status relays antechamber's /antechamber/work-status routes
85
+ /runtime reports and starts the runtime install
86
+ ```
87
+
88
+ The launcher and the service share one install: a lock directory beside the runtime lets whichever starts first
89
+ install, and the other waits. `bun install` runs in a staging directory with `omo-ai` trusted (its postinstall
90
+ prepares OmO's engine), and the runtime is moved into place only once antechamber's CLI is there.
91
+
92
+ ## Development
93
+
94
+ From the repository root: `npm run build:extension` syncs the root `package.json` manifest and bundles the panel and the section (browser IIFEs), the service
95
+ (ESM for OpenChamber's runtime) and the launcher (ESM for Bun); `npm test`, `npm run typecheck`, `npm run lint` and
96
+ `npm run format:check` cover it; `npm run pack:extension -- out.zip` writes the release archive. To run a local
97
+ build through the launcher, `npm pack` antechamber and point `ANTECHAMBER_RUNTIME_SPEC` at the tarball.
98
+
99
+ Sources: `src/graph/` (panel, layout, task store, `/graph`), `src/work/` (section, `/status` relay),
100
+ `src/runtime/` (install manager and launcher), `src/service.ts` (the one service).
@@ -0,0 +1,42 @@
1
+ #!/bin/sh
2
+ # OpenChamber launcher: point Settings -> OpenCode CLI (settings.opencodeBinary) at this file.
3
+ # OpenChamber runs `<binary> serve --hostname H --port P` and `<binary> --version`; its Update action
4
+ # runs `<binary> upgrade`. The first `serve` installs the antechamber release this extension pins (and
5
+ # the OmO it pins) with Bun; updating the extension in Settings -> Extensions updates both.
6
+ set -eu
7
+ SCRIPT_PATH=$0
8
+ while [ -h "$SCRIPT_PATH" ]; do
9
+ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
10
+ LINK="$(readlink "$SCRIPT_PATH")"
11
+ case "$LINK" in
12
+ /*) SCRIPT_PATH=$LINK ;;
13
+ *) SCRIPT_PATH="$SCRIPT_DIR/$LINK" ;;
14
+ esac
15
+ done
16
+ SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$SCRIPT_PATH")" && pwd)"
17
+ LAUNCHER="$SCRIPT_DIR/../runtime/launcher.js"
18
+ # GUI apps and services often have a PATH without Bun's install directory.
19
+ BUN=$(command -v bun || true)
20
+ for CANDIDATE in "${BUN_INSTALL:-${HOME:-}/.bun}/bin/bun" "${HOME:-}/.bun/bin/bun"; do
21
+ if [ -z "$BUN" ] && [ -x "$CANDIDATE" ]; then BUN=$CANDIDATE; fi
22
+ done
23
+ if [ -z "$BUN" ]; then
24
+ echo "opencode-antechamber: bun not found on PATH or in ~/.bun/bin; install it from https://bun.sh" >&2
25
+ exit 1
26
+ fi
27
+ case "${1:-}" in
28
+ serve)
29
+ shift
30
+ CLI=$("$BUN" "$LAUNCHER" resolve)
31
+ ANTECHAMBER_MANAGED=openchamber-extension
32
+ export ANTECHAMBER_MANAGED
33
+ exec "$BUN" "$CLI" serve "$@"
34
+ ;;
35
+ --version|-v) exec "$BUN" "$LAUNCHER" version ;;
36
+ upgrade)
37
+ echo "antechamber is managed by its OpenChamber extension: update it in Settings -> Extensions."
38
+ exit 0
39
+ ;;
40
+ esac
41
+ echo "opencode-antechamber: unsupported command '${1:-}' (only 'serve', '--version' and 'upgrade' are implemented)" >&2
42
+ exit 1
@@ -25,7 +25,7 @@ does that in ~2 ms with an mtime cache and returns one JSON document.
25
25
  `description` / `name`, `agent_type` / `category`, `model`, the
26
26
  `created_at` / `started_at` / `terminal_at` timestamps,
27
27
  `final_response`, `error_message`, `run_stats`, `spawn_spec.prompt`.
28
- 2. **Connector registry**: `<stateDir>/sessions.json`, mapping OpenChamber
28
+ 2. **Connector registry**: `<stateDir>/sessions.sqlite` (`sessions.json` before antechamber 2.0), mapping OpenChamber
29
29
  session ids to OmO session ids and titles.
30
30
 
31
31
  Task discovery reads regular `.json` files and skips directories and symlinks.
@@ -45,7 +45,7 @@ uses the same identity rules so `openSession` opens the correct child.
45
45
  Legacy deletion tombstones retain their owning parent's bare identity, even
46
46
  when they predate task metadata in the registry.
47
47
 
48
- ## Graph model (`src/graph.ts`)
48
+ ## Graph model (`src/graph/graph.ts`)
49
49
 
50
50
  - **Run**: every task whose `root_session_id` equals the resolved root. The
51
51
  root is resolved from the open session: it is either a root itself, a
@@ -69,7 +69,7 @@ when they predate task metadata in the registry.
69
69
  failed, `cancelled`/`canceled`/`killed` → cancelled. The raw status is kept
70
70
  for the label, so `lost` reads as `lost`.
71
71
 
72
- ## Layout (`src/layout.ts`)
72
+ ## Layout (`src/graph/layout.ts`)
73
73
 
74
74
  Columns are fixed-width, and panel CSS dimensions come from the same card-size
75
75
  constants used by the layout. Within a column, cards stack in the order of their
@@ -80,7 +80,7 @@ Béziers. Several edges sharing one side are spread over the card's middle
80
80
  band (Archify's "spread shared endpoints deterministically" rule) instead of
81
81
  piling onto one point.
82
82
 
83
- ## Interaction (`src/panel.ts`, `panel/index.html`)
83
+ ## Interaction (`src/graph/panel.ts`, `panel/index.html`)
84
84
 
85
85
  - Hover or focus a card: direct inputs (amber) and outputs (accent) are fully
86
86
  lit, the transitive upstream/downstream reach stays half-lit, and
@@ -102,7 +102,7 @@ piling onto one point.
102
102
  and binds to `127.0.0.1` on the port the host assigns.
103
103
  - The optional `agentDir` / `stateDir` query parameters let the panel point
104
104
  it at other directories. It reads only `*.json` task records and
105
- `sessions.json` under them. The service runs with the user's permissions
105
+ `sessions.sqlite` (or `sessions.json`) under them. The service runs with the user's permissions
106
106
  (OpenChamber's approval dialog says so), so this is not a sandbox. It just
107
107
  keeps the surface small.
108
108
  - Prompts and final responses stay local. They reach only the panel the user
@@ -30,11 +30,11 @@ OpenChamber host ── loopback + OPENCHAMBER_SERVICE_TOKEN ──▶ service/m
30
30
  `service`, and a `panel` block with no entry. OpenChamber 2.x's manifest
31
31
  parser requires a `panel` object, but without `panel.entry` there is no rail
32
32
  icon or panel; the section is the only surface.
33
- - **Frame** (`src/status.ts`, `src/view.ts`): renders the snapshot. It follows
33
+ - **Frame** (`src/work/status.ts`, `src/work/view.ts`): renders the snapshot. It follows
34
34
  the open session through `host.onSession`, sizes itself through
35
35
  `host.setHeight`, and publishes one host header control, **Data path**, when
36
36
  the host supports status controls.
37
- - **Service** (`src/service.ts`, `src/discovery.ts`): stateless relay. It finds
37
+ - **Service** (`src/work/routes.ts`, `src/work/discovery.ts`): stateless relay. It finds
38
38
  running antechamber servers through their run records and forwards the
39
39
  frame's requests.
40
40
  - **Adapter** (`src/work-status.ts` in antechamber): builds the snapshot and
@@ -58,7 +58,7 @@ The frame shows `todo`, `goal` and `wakeSources`. `monitors`, `evalCells` and
58
58
  `background` stay in the snapshot, and the adapter keeps its
59
59
  `/antechamber/work-status/<session>/eval/<cell>[/stop]` routes, for section
60
60
  versions installed before these moved to OpenChamber's background commands
61
- (ZIP installs never update). The frame checks the envelope (`src/model.ts`) and
61
+ (ZIP installs never update). The frame checks the envelope (`src/work/model.ts`) and
62
62
  imports these types from the adapter, so a shape change on either side fails
63
63
  the typecheck.
64
64
 
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "antechamber-openchamber",
3
+ "version": "2.0.1",
4
+ "description": "OpenChamber extension for antechamber: installs and pins the antechamber server and OmO, draws the OmO Graph and the OmO Work section, and titles OmO tool cards",
5
+ "private": true,
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "openchamber": {
9
+ "apiVersion": 1,
10
+ "engines": {
11
+ "openchamber": ">=2.3.2"
12
+ },
13
+ "contributes": {
14
+ "panel": {
15
+ "id": "antechamber",
16
+ "name": "OmO",
17
+ "icon": "icon.svg",
18
+ "entry": "panel/index.html"
19
+ },
20
+ "page": {
21
+ "entry": "panel/index.html",
22
+ "title": "OmO Graph"
23
+ },
24
+ "statusSection": {
25
+ "entry": "status/index.html",
26
+ "title": "OmO Work",
27
+ "height": 120
28
+ },
29
+ "capabilities": [
30
+ "sessions"
31
+ ],
32
+ "service": {
33
+ "entry": "service/main.js",
34
+ "runtime": "host",
35
+ "permissions": {
36
+ "exec": [
37
+ "bun"
38
+ ]
39
+ }
40
+ },
41
+ "tools": [
42
+ {
43
+ "match": "shell",
44
+ "title": "{metadata.evalTitle}",
45
+ "subtitle": "{metadata.evalSubtitle}"
46
+ },
47
+ {
48
+ "match": "task",
49
+ "title": "{metadata.agentTitle}",
50
+ "subtitle": "{metadata.agentSubtitle}"
51
+ },
52
+ {
53
+ "match": "agent",
54
+ "title": "{metadata.agentTitle}",
55
+ "subtitle": "{metadata.agentSubtitle}"
56
+ }
57
+ ]
58
+ }
59
+ }
60
+ }
@@ -51,6 +51,12 @@
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
53
  #settings #btn-save { background: var(--accent); color: var(--oc-primary-fg, #001); border-color: transparent; }
54
+ #runtime { display: grid; gap: 4px; padding-bottom: 8px; border-bottom: 1px solid var(--border); font-size: 11px; color: var(--muted); }
55
+ #runtime strong { color: var(--fg); font-weight: 600; }
56
+ #runtime code { font: 11px var(--mono); color: var(--fg); word-break: break-all; user-select: all; -webkit-user-select: all; }
57
+ #runtime[data-state="failed"] { color: var(--bad); }
58
+ #runtime[data-state="installing"] { color: var(--run); }
59
+ #runtime .row { justify-content: flex-start; }
54
60
 
55
61
  #viewport { position: relative; flex: 1; overflow: hidden; cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none;
56
62
  background-image: radial-gradient(color-mix(in srgb, var(--muted) 22%, transparent) 1px, transparent 1px);
@@ -151,12 +157,13 @@
151
157
  <button class="tool" id="btn-in" type="button" title="Zoom in (+)" aria-label="Zoom in">
152
158
  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5v14"/></svg>
153
159
  </button>
154
- <button class="tool" id="btn-settings" type="button" title="Data paths" aria-label="Data paths" aria-expanded="false" aria-controls="settings">
160
+ <button class="tool" id="btn-settings" type="button" title="Settings" aria-label="Settings" aria-expanded="false" aria-controls="settings">
155
161
  <svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7h10M18 7h2M4 17h4M12 17h8"/><circle cx="16" cy="7" r="2"/><circle cx="10" cy="17" r="2"/></svg>
156
162
  </button>
157
163
  </div>
158
164
  </header>
159
165
  <form id="settings" hidden>
166
+ <section id="runtime" aria-live="polite"><strong>Runtime</strong><span id="runtime-state">Checking…</span></section>
160
167
  <label>OmO agent directory <input name="agentDir" placeholder="~/.omo/agent (default, then ~/.pi/agent)" autocomplete="off" spellcheck="false" /></label>
161
168
  <label>Connector state directory <input name="stateDir" placeholder="~/.config/antechamber (default)" autocomplete="off" spellcheck="false" /></label>
162
169
  <div class="row"><button id="btn-save" type="button">Save</button></div>