antechamber 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +17 -14
  3. package/bin/opencode-antechamber +10 -1
  4. package/bin/opencode-pi +11 -1
  5. package/dist/approvals.d.ts +4 -1
  6. package/dist/approvals.js +51 -33
  7. package/dist/catalog.d.ts +1 -1
  8. package/dist/catalog.js +1 -1
  9. package/dist/commands/context.d.ts +0 -1
  10. package/dist/commands/context.js +1 -1
  11. package/dist/commands/doctor.d.ts +3 -2
  12. package/dist/commands/serve.d.ts +2 -1
  13. package/dist/commands/setup.d.ts +2 -3
  14. package/dist/commands/setup.js +16 -4
  15. package/dist/http.d.ts +8 -1
  16. package/dist/http.js +42 -19
  17. package/dist/pi-event-translation.d.ts +9 -5
  18. package/dist/pi-event-translation.js +233 -79
  19. package/dist/pi-rpc.d.ts +20 -2
  20. package/dist/pi-rpc.js +96 -29
  21. package/dist/prompt-parts.d.ts +40 -0
  22. package/dist/prompt-parts.js +157 -0
  23. package/dist/route-services.d.ts +45 -2
  24. package/dist/route-services.js +70 -3
  25. package/dist/routes-v1.js +110 -65
  26. package/dist/routes-v2.js +376 -73
  27. package/dist/server.d.ts +9 -1
  28. package/dist/server.js +109 -9
  29. package/dist/session-commands.d.ts +12 -15
  30. package/dist/session-commands.js +52 -11
  31. package/dist/session-diff.d.ts +1 -1
  32. package/dist/session-diff.js +10 -2
  33. package/dist/session-history.js +108 -42
  34. package/dist/session-hosts.js +2 -2
  35. package/dist/session-store.d.ts +8 -4
  36. package/dist/session-store.js +183 -33
  37. package/dist/session-tree.d.ts +9 -1
  38. package/dist/session-tree.js +51 -26
  39. package/dist/sessions.d.ts +32 -5
  40. package/dist/sessions.js +330 -119
  41. package/dist/sse.d.ts +16 -6
  42. package/dist/sse.js +77 -21
  43. package/dist/subagents.d.ts +5 -0
  44. package/dist/subagents.js +84 -71
  45. package/dist/tool-map.d.ts +3 -2
  46. package/dist/types.d.ts +152 -1
  47. package/dist/v2.d.ts +18 -4
  48. package/dist/v2.js +128 -44
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +1 -1
  51. package/extensions/omo-dag/README.md +2 -0
  52. package/extensions/omo-dag/docs/DESIGN.md +10 -1
  53. package/extensions/omo-dag/panel/index.html +1 -1
  54. package/extensions/omo-dag/panel/main.js +60 -60
  55. package/extensions/omo-dag/service/main.js +72 -8
  56. package/package.json +1 -1
package/dist/v2.d.ts CHANGED
@@ -92,7 +92,7 @@ interface CatalogProviderLike {
92
92
  name: string;
93
93
  models: Record<string, CatalogModelLike>;
94
94
  }
95
- export interface CatalogLike {
95
+ interface CatalogLike {
96
96
  providers: CatalogProviderLike[];
97
97
  defaults: Record<string, string>;
98
98
  /** The model the agent reported as active (see catalog.ts). */
@@ -108,6 +108,7 @@ export declare function defaultV2Model(cat: CatalogLike): V2Model | undefined;
108
108
  export interface V2Session {
109
109
  id: string;
110
110
  parentID?: string;
111
+ parentMessageID?: string;
111
112
  projectID: string;
112
113
  agent: string;
113
114
  model?: {
@@ -127,6 +128,12 @@ export interface V2Session {
127
128
  location: {
128
129
  directory: string;
129
130
  };
131
+ directory: string;
132
+ metadata?: Record<string, unknown>;
133
+ outcome?: OCSession["outcome"];
134
+ revert?: {
135
+ messageID: string;
136
+ };
130
137
  }
131
138
  export declare function toV2Session(info: OCSession, opts?: {
132
139
  model?: OCModelRef | undefined;
@@ -139,8 +146,11 @@ export interface V2Message {
139
146
  completed?: number;
140
147
  streamed?: number;
141
148
  };
142
- type: "user" | "assistant";
149
+ type: "user" | "assistant" | "synthetic";
143
150
  text?: string;
151
+ files?: Array<Record<string, unknown>>;
152
+ agents?: Array<Record<string, unknown>>;
153
+ metadata?: Record<string, unknown>;
144
154
  agent?: string;
145
155
  model?: {
146
156
  id: string;
@@ -153,8 +163,6 @@ export interface V2Message {
153
163
  tokens?: V2Tokens;
154
164
  error?: OCAssistantMessage["error"];
155
165
  }
156
- /** v2 user message (list + prompt response): the UI reads `.text`. */
157
- export declare function toV2UserMessage(info: OCMessageWithParts): V2Message;
158
166
  /**
159
167
  * v2 assistant message: parts become `content` blocks. The UI rebuilds
160
168
  * text/reasoning part ids from per-type indices into this array.
@@ -162,6 +170,11 @@ export declare function toV2UserMessage(info: OCMessageWithParts): V2Message;
162
170
  export declare function toV2AssistantMessage(info: OCMessageWithParts): V2Message;
163
171
  export declare function toV2Message(info: OCMessageWithParts): V2Message;
164
172
  export interface V2Envelope {
173
+ durable?: {
174
+ aggregateID: string;
175
+ seq: number;
176
+ version: number;
177
+ };
165
178
  id: string;
166
179
  created: number;
167
180
  type: string;
@@ -177,6 +190,7 @@ export interface V2Envelope {
177
190
  */
178
191
  export declare class V2EventTranslator {
179
192
  private sessions;
193
+ private sequences;
180
194
  translate(directory: string, ev: OCEvent): V2Envelope[];
181
195
  private onStatus;
182
196
  private onMessageUpdated;
package/dist/v2.js CHANGED
@@ -24,6 +24,7 @@
24
24
  import { randomUUID } from "node:crypto";
25
25
  import { getAgentName } from "./agent-name.js";
26
26
  import { questionFormID, toQuestionForm } from "./question-forms.js";
27
+ import { v2PromptAttachments } from "./prompt-parts.js";
27
28
  function genId(prefix) {
28
29
  return `${prefix}_${randomUUID().replaceAll("-", "")}`;
29
30
  }
@@ -90,31 +91,50 @@ export function toV2Session(info, opts = {}) {
90
91
  projectID: info.projectID,
91
92
  agent: opts.agent ?? getAgentName(),
92
93
  cost: info.cost ?? 0,
93
- tokens: zeroTokens(),
94
+ tokens: info.tokens ?? zeroTokens(),
94
95
  time: {
95
96
  created: info.time.created,
96
97
  updated: info.time.updated,
98
+ ...(info.time.idle !== undefined ? { idle: info.time.idle } : {}),
97
99
  ...(info.time.archived !== undefined ? { archived: info.time.archived } : {}),
98
100
  },
99
101
  title: info.title,
100
102
  subpath: subpathOf(info.directory),
101
103
  location: { directory: info.directory },
104
+ directory: info.directory,
105
+ ...(info.metadata !== undefined ? { metadata: info.metadata } : {}),
106
+ ...(info.outcome !== undefined ? { outcome: info.outcome } : {}),
107
+ ...(info.revert !== undefined ? { revert: info.revert } : {}),
102
108
  };
103
109
  if (info.parentID !== undefined)
104
110
  session.parentID = info.parentID;
111
+ if (info.parentMessageID !== undefined)
112
+ session.parentMessageID = info.parentMessageID;
105
113
  if (opts.model !== undefined) {
106
- session.model = { id: opts.model.modelID, providerID: opts.model.providerID };
114
+ session.model = {
115
+ id: opts.model.modelID,
116
+ providerID: opts.model.providerID,
117
+ variant: info.model?.variant ?? "default",
118
+ };
107
119
  }
108
120
  return session;
109
121
  }
110
122
  /** v2 user message (list + prompt response): the UI reads `.text`. */
111
- export function toV2UserMessage(info) {
123
+ function toV2UserMessage(info) {
112
124
  const text = info.parts.map((p) => (p.type === "text" ? p.text : p.type === "compaction" ? "/compact" : "")).join("");
125
+ const user = info.info;
126
+ const metadata = user.metadata !== undefined ? { metadata: user.metadata } : {};
127
+ // Admitted no-reply context lists as OpenCode's synthetic item, not as a user turn.
128
+ if (user.synthetic)
129
+ return { id: user.id, time: { created: user.time.created }, type: "synthetic", text, ...metadata };
130
+ const inputs = info.parts.flatMap((part) => (part.type === "file" || part.type === "agent" ? [part] : []));
113
131
  return {
114
- id: info.info.id,
115
- time: { created: info.info.time.created },
132
+ id: user.id,
133
+ time: { created: user.time.created },
116
134
  type: "user",
117
135
  text,
136
+ ...v2PromptAttachments(inputs),
137
+ ...metadata,
118
138
  };
119
139
  }
120
140
  /**
@@ -123,7 +143,7 @@ export function toV2UserMessage(info) {
123
143
  */
124
144
  export function toV2AssistantMessage(info) {
125
145
  const i = info.info;
126
- const model = { id: i.modelID, providerID: i.providerID };
146
+ const model = { id: i.modelID, providerID: i.providerID, variant: "default" };
127
147
  const content = [];
128
148
  for (const part of info.parts) {
129
149
  if (part.type === "text") {
@@ -146,6 +166,9 @@ export function toV2AssistantMessage(info) {
146
166
  const msg = { id: i.id, time: { created: i.time.created }, type: "assistant", content };
147
167
  if (i.time.completed !== undefined)
148
168
  msg.time.completed = i.time.completed;
169
+ const streamed = info.parts.flatMap((part) => part.type === "text" || part.type === "reasoning" ? (part.time?.start === undefined ? [] : [part.time.start]) : []);
170
+ if (streamed.length > 0)
171
+ msg.time.streamed = Math.min(...streamed);
149
172
  msg.agent = i.mode;
150
173
  msg.model = model;
151
174
  if (i.finish !== undefined)
@@ -214,10 +237,19 @@ export function toV2Message(info) {
214
237
  */
215
238
  export class V2EventTranslator {
216
239
  sessions = new Map();
240
+ sequences = new Map();
217
241
  translate(directory, ev) {
218
242
  switch (ev.type) {
219
243
  case "session.inbox.enqueued":
220
244
  case "session.inbox.delivered":
245
+ case "session.revert.staged":
246
+ case "session.revert.cleared":
247
+ case "session.revert.committed":
248
+ case "session.compaction.started":
249
+ case "session.compaction.ended":
250
+ case "session.compaction.failed":
251
+ case "session.shell.started":
252
+ case "session.shell.ended":
221
253
  return [this.frame(directory, ev.type, ev.properties)];
222
254
  case "message.removed":
223
255
  return [
@@ -235,12 +267,22 @@ export class V2EventTranslator {
235
267
  location: { directory: info.directory },
236
268
  subpath: subpathOf(info.directory),
237
269
  title: info.title,
270
+ agent: info.agent ?? getAgentName(),
271
+ ...(info.parentID !== undefined ? { parentID: info.parentID } : {}),
272
+ ...(info.parentMessageID !== undefined ? { parentMessageID: info.parentMessageID } : {}),
273
+ ...(info.metadata !== undefined ? { metadata: info.metadata } : {}),
238
274
  }),
239
275
  ];
240
276
  }
241
277
  case "session.updated": {
242
278
  const info = ev.properties.info;
243
- return [this.frame(directory, "session.renamed", { sessionID: info.id, title: info.title })];
279
+ return [
280
+ this.frame(directory, "session.renamed", { sessionID: info.id, title: info.title }),
281
+ ...(info.metadata !== undefined
282
+ ? [this.frame(directory, "session.metadata.updated", { sessionID: info.id, metadata: info.metadata })]
283
+ : []),
284
+ this.frame(directory, "session.agent.selected", { sessionID: info.id, agent: info.agent ?? getAgentName() }),
285
+ ];
244
286
  }
245
287
  case "session.diff":
246
288
  return [this.frame(directory, "session.diff", ev.properties)];
@@ -257,7 +299,21 @@ export class V2EventTranslator {
257
299
  const frames = [];
258
300
  if (st?.step && !st.step.ended)
259
301
  frames.push(...this.finalizeStep(directory, st.step));
260
- frames.push(this.frame(directory, "session.execution.succeeded", { sessionID }));
302
+ if (st?.executionEnded) {
303
+ // session.error already published the execution's terminal frame.
304
+ }
305
+ else if (st?.step?.error?.name === "MessageAbortedError") {
306
+ frames.push(this.frame(directory, "session.execution.interrupted", { sessionID, reason: "user" }));
307
+ }
308
+ else if (st?.step?.finish === "error") {
309
+ frames.push(this.frame(directory, "session.execution.failed", {
310
+ sessionID,
311
+ error: st.step.error ?? { name: "UnknownError", data: { message: "Provider error" } },
312
+ }));
313
+ }
314
+ else {
315
+ frames.push(this.frame(directory, "session.execution.succeeded", { sessionID }));
316
+ }
261
317
  frames.push(this.frame(directory, "session.idle", { sessionID }));
262
318
  this.sessions.delete(sessionID);
263
319
  return frames;
@@ -265,24 +321,20 @@ export class V2EventTranslator {
265
321
  case "session.error": {
266
322
  const { sessionID, error } = ev.properties;
267
323
  const frames = [];
268
- const st = sessionID !== undefined ? this.sessions.get(sessionID) : undefined;
324
+ const st = sessionID !== undefined ? this.state(sessionID) : undefined;
269
325
  if (sessionID !== undefined && st?.step && !st.step.ended) {
270
- st.step.ended = true;
271
- frames.push(this.frame(directory, "session.step.failed", {
272
- sessionID,
273
- assistantMessageID: st.step.messageID,
274
- finish: st.step.finish ?? "error",
275
- error: error ?? { name: "UnknownError", data: { message: "session error" } },
276
- cost: st.step.cost,
277
- tokens: st.step.tokens,
278
- }));
279
- this.sessions.delete(sessionID);
326
+ st.step.finish = "error";
327
+ st.step.error = error ?? { name: "UnknownError", data: { message: "session error" } };
328
+ frames.push(...this.finalizeStep(directory, st.step));
280
329
  }
281
- if (sessionID !== undefined) {
282
- frames.push(this.frame(directory, "session.execution.failed", {
283
- sessionID,
284
- error: error ?? { name: "UnknownError", data: { message: "session error" } },
285
- }));
330
+ if (sessionID !== undefined && st !== undefined && !st.executionEnded) {
331
+ st.executionEnded = true;
332
+ frames.push(error?.name === "MessageAbortedError"
333
+ ? this.frame(directory, "session.execution.interrupted", { sessionID, reason: "user" })
334
+ : this.frame(directory, "session.execution.failed", {
335
+ sessionID,
336
+ error: error ?? { name: "UnknownError", data: { message: "session error" } },
337
+ }));
286
338
  }
287
339
  return frames;
288
340
  }
@@ -290,6 +342,20 @@ export class V2EventTranslator {
290
342
  return this.onMessageUpdated(directory, ev.properties.info);
291
343
  case "message.part.updated":
292
344
  return this.onPartUpdated(directory, ev.properties.part, ev.properties.delta);
345
+ case "message.part.delta": {
346
+ const { sessionID, messageID, partID, delta } = ev.properties;
347
+ const part = this.state(sessionID).parts.get(partID);
348
+ if (part === undefined || delta === "")
349
+ return [];
350
+ return [
351
+ this.frame(directory, `session.${part.kind}.delta`, {
352
+ sessionID,
353
+ assistantMessageID: messageID,
354
+ ordinal: part.ordinal,
355
+ delta,
356
+ }),
357
+ ];
358
+ }
293
359
  case "model.changed": {
294
360
  const { sessionID, model, previous } = ev.properties;
295
361
  return [
@@ -302,20 +368,10 @@ export class V2EventTranslator {
302
368
  }
303
369
  case "permission.asked": {
304
370
  const p = ev.properties;
305
- return [
306
- this.frame(directory, "permission.v2.asked", {
307
- id: p.id,
308
- sessionID: p.sessionID,
309
- action: p.permission,
310
- resources: p.patterns,
311
- save: p.always,
312
- metadata: p.metadata,
313
- ...(p.tool ? { source: { type: "tool", ...p.tool } } : {}),
314
- }),
315
- ];
371
+ return [this.frame(directory, "permission.asked", p)];
316
372
  }
317
373
  case "permission.replied":
318
- return [this.frame(directory, "permission.v2.replied", ev.properties)];
374
+ return [this.frame(directory, "permission.replied", ev.properties)];
319
375
  case "question.asked":
320
376
  return [this.frame(directory, "form.created", { form: toQuestionForm(ev.properties) })];
321
377
  case "question.replied":
@@ -368,6 +424,9 @@ export class V2EventTranslator {
368
424
  };
369
425
  if (info.finish !== undefined)
370
426
  step.finish = info.finish;
427
+ if (info.finish === "error") {
428
+ step.error = info.error ?? { name: "UnknownError", data: { message: "Provider error" } };
429
+ }
371
430
  st.step = step;
372
431
  frames.push(this.frame(directory, "session.step.started", {
373
432
  sessionID: info.sessionID,
@@ -381,6 +440,9 @@ export class V2EventTranslator {
381
440
  st.step.tokens = info.tokens;
382
441
  if (info.finish !== undefined)
383
442
  st.step.finish = info.finish;
443
+ if (info.finish === "error") {
444
+ st.step.error = info.error ?? { name: "UnknownError", data: { message: "Provider error" } };
445
+ }
384
446
  if (!st.step.streamed) {
385
447
  st.step.streamed = true;
386
448
  frames.push(this.frame(directory, "session.step.streamed", { sessionID: info.sessionID, assistantMessageID: info.id }));
@@ -399,16 +461,27 @@ export class V2EventTranslator {
399
461
  st.ended = true;
400
462
  return [
401
463
  this.frame(directory, "session.usage.updated", { sessionID: st.sessionID, cost: st.cost, tokens: st.tokens }),
402
- this.frame(directory, "session.step.ended", {
403
- sessionID: st.sessionID,
404
- assistantMessageID: st.messageID,
405
- ...(st.finish !== undefined ? { finish: st.finish } : {}),
406
- cost: st.cost,
407
- tokens: st.tokens,
408
- }),
464
+ st.finish === "error"
465
+ ? this.frame(directory, "session.step.failed", {
466
+ sessionID: st.sessionID,
467
+ assistantMessageID: st.messageID,
468
+ rawFinish: st.finish,
469
+ error: st.error ?? { name: "UnknownError", data: { message: "Provider error" } },
470
+ cost: st.cost,
471
+ tokens: st.tokens,
472
+ })
473
+ : this.frame(directory, "session.step.ended", {
474
+ sessionID: st.sessionID,
475
+ assistantMessageID: st.messageID,
476
+ ...(st.finish !== undefined ? { finish: st.finish } : {}),
477
+ cost: st.cost,
478
+ tokens: st.tokens,
479
+ }),
409
480
  ];
410
481
  }
411
482
  onPartUpdated(directory, part, delta) {
483
+ if (part.type === "file" || part.type === "agent")
484
+ return [];
412
485
  if (part.type === "compaction")
413
486
  // Like shell turns, the server creates this user turn; there is no optimistic prompt.
414
487
  return [
@@ -547,7 +620,18 @@ export class V2EventTranslator {
547
620
  return st;
548
621
  }
549
622
  frame(directory, type, data) {
550
- return { id: genId("evt"), created: Date.now(), type, location: { directory }, data };
623
+ const aggregateID = typeof data.sessionID === "string" ? data.sessionID : undefined;
624
+ const seq = aggregateID === undefined ? undefined : (this.sequences.get(aggregateID) ?? 0) + 1;
625
+ if (aggregateID !== undefined && seq !== undefined)
626
+ this.sequences.set(aggregateID, seq);
627
+ return {
628
+ id: genId("evt"),
629
+ created: Date.now(),
630
+ type,
631
+ location: { directory },
632
+ data,
633
+ ...(aggregateID !== undefined && seq !== undefined ? { durable: { aggregateID, seq, version: 1 } } : {}),
634
+ };
551
635
  }
552
636
  }
553
637
  function safeJson(value) {
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export declare const ADAPTER_VERSION: string;
2
- export declare const OPENCODE_API_VERSION = "2.0.18";
2
+ export declare const OPENCODE_API_VERSION = "2.0.20";
package/dist/version.js CHANGED
@@ -10,4 +10,4 @@ if (typeof manifest !== "object" ||
10
10
  export const ADAPTER_VERSION = manifest.version;
11
11
  // OpenChamber parses --version as an OpenCode version and requires major 2.
12
12
  // This API contract version must not follow the npm package version.
13
- export const OPENCODE_API_VERSION = "2.0.18";
13
+ export const OPENCODE_API_VERSION = "2.0.20";
@@ -25,6 +25,8 @@ sessions on OmO.
25
25
  - **Pan and zoom.** Drag the background, and scroll or pinch to zoom. `0`
26
26
  fits the graph; the target button centres on the current or first running
27
27
  agent.
28
+ - Very large runs are sent within a bounded response size. When tasks are
29
+ omitted to fit that limit, the panel shows the omitted-node count.
28
30
 
29
31
  The panel docks in the right rail. The same page is also available full
30
32
  screen from the sidebar's Extension pages menu.
@@ -28,6 +28,14 @@ does that in ~2 ms with an mtime cache and returns one JSON document.
28
28
  2. **Connector registry**: `<stateDir>/sessions.json`, mapping OpenChamber
29
29
  session ids to OmO session ids and titles.
30
30
 
31
+ Task discovery reads regular `.json` files and skips directories and symlinks.
32
+ The service resolves configured root spellings before using them as cache
33
+ keys; its recency-updated task-store cache holds at most 32 root sets. Graph
34
+ responses are capped at 240,000 serialized characters. If the full graph
35
+ exceeds that bound, the service keeps the root and a valid task prefix, removes
36
+ edges to omitted tasks, and returns `truncated.omittedNodes`; the panel
37
+ validates that field and displays the count.
38
+
31
39
  The connector names sub-agent sessions `ses_<md5(task_id)>` and root sessions
32
40
  `ses_<md5(piSessionId)>` (`ocIdFor` in `src/session-store.ts`). The graph
33
41
  uses the same function, so a node's `sessionId` is exactly the id
@@ -59,7 +67,8 @@ uses the same function, so a node's `sessionId` is exactly the id
59
67
 
60
68
  ## Layout (`src/layout.ts`)
61
69
 
62
- Columns are fixed-width; within a column, cards stack in the order of their
70
+ Columns are fixed-width, and panel CSS dimensions come from the same card-size
71
+ constants used by the layout. Within a column, cards stack in the order of their
63
72
  parents' rows, then by creation time, so a fan-out stays next to the card
64
73
  that launched it. The orchestrator is centred on its direct children. Edges
65
74
  leave a card's right side and enter the left side as horizontal cubic
@@ -76,7 +76,7 @@
76
76
  .has-hover .edge.is-in { opacity: 1; stroke: var(--edge-in); stroke-width: 2.2; }
77
77
  .has-hover .edge.is-out { opacity: 1; stroke: var(--edge-out); stroke-width: 2.2; }
78
78
 
79
- .node { position: absolute; left: 0; top: 0; width: 232px; height: 80px; padding: 8px 10px; text-align: left; display: flex; flex-direction: column; gap: 1px;
79
+ .node { position: absolute; left: 0; top: 0; width: var(--node-card-width); height: var(--node-card-height); padding: 8px 10px; text-align: left; display: flex; flex-direction: column; gap: 1px;
80
80
  background: var(--surface); border: 1px solid var(--border); border-radius: calc(var(--radius) * .6); cursor: pointer;
81
81
  font: 11px/1.35 var(--mono); color: var(--muted); transition: opacity .15s, border-color .15s, box-shadow .15s, background .15s; }
82
82
  .node:hover, .node.is-hover { border-color: color-mix(in srgb, var(--fg) 40%, var(--border)); background: var(--surface-2); }