antechamber 0.1.0 → 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 +51 -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 +21 -3
  18. package/dist/pi-event-translation.js +317 -65
  19. package/dist/pi-rpc.d.ts +26 -2
  20. package/dist/pi-rpc.js +102 -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 +20 -15
  30. package/dist/session-commands.js +61 -11
  31. package/dist/session-diff.d.ts +1 -1
  32. package/dist/session-diff.js +10 -2
  33. package/dist/session-history.js +113 -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 -2
  38. package/dist/session-tree.js +51 -63
  39. package/dist/sessions.d.ts +34 -6
  40. package/dist/sessions.js +340 -146
  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 +86 -69
  45. package/dist/tool-map.d.ts +3 -2
  46. package/dist/types.d.ts +164 -1
  47. package/dist/v2.d.ts +18 -4
  48. package/dist/v2.js +141 -50
  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/types.d.ts CHANGED
@@ -12,14 +12,42 @@ export interface OCSession {
12
12
  projectID: string;
13
13
  directory: string;
14
14
  parentID?: string;
15
+ parentMessageID?: string;
16
+ agent?: string;
17
+ model?: {
18
+ id: string;
19
+ providerID: string;
20
+ variant?: string;
21
+ };
22
+ metadata?: Record<string, unknown>;
23
+ outcome?: "succeeded" | "failed" | "interrupted";
24
+ slug?: string;
25
+ path?: string;
26
+ summary?: {
27
+ additions: number;
28
+ deletions: number;
29
+ files: number;
30
+ };
31
+ tokens?: {
32
+ input: number;
33
+ output: number;
34
+ reasoning: number;
35
+ cache: {
36
+ read: number;
37
+ write: number;
38
+ };
39
+ };
15
40
  title: string;
16
41
  version: string;
17
42
  revert?: {
18
43
  messageID: string;
19
44
  };
45
+ /** USD spent by this session's own assistant messages; OpenChamber adds child sessions via parentID. */
46
+ cost?: number;
20
47
  time: {
21
48
  created: number;
22
49
  updated: number;
50
+ idle?: number;
23
51
  compacting?: number;
24
52
  /** Epoch ms — presence marks the session archived (SDK v2 session.update). */
25
53
  archived?: number;
@@ -34,6 +62,10 @@ export interface OCUserMessage {
34
62
  };
35
63
  agent: string;
36
64
  model: OCModelRef;
65
+ /** No-reply context admitted without a turn (v2 lists it as `type: "synthetic"`). */
66
+ synthetic?: boolean;
67
+ /** Client metadata sent with the prompt (OpenChamber 2.x). */
68
+ metadata?: Record<string, unknown>;
37
69
  }
38
70
  export interface OCAssistantMessage {
39
71
  id: string;
@@ -68,6 +100,8 @@ export interface OCAssistantMessage {
68
100
  message: string;
69
101
  };
70
102
  };
103
+ /** Marks a compaction summary (OpenCode `AssistantMessage.summary`). */
104
+ summary?: boolean;
71
105
  }
72
106
  export type OCMessage = OCUserMessage | OCAssistantMessage;
73
107
  export interface OCTextPart {
@@ -144,7 +178,42 @@ export interface OCToolPart {
144
178
  tool: string;
145
179
  state: OCToolState;
146
180
  }
147
- export type OCPart = OCTextPart | OCReasoningPart | OCToolPart;
181
+ /** OpenChamber renders a user message holding this part as `/compact`. */
182
+ export interface OCCompactionPart {
183
+ id: string;
184
+ sessionID: string;
185
+ messageID: string;
186
+ type: "compaction";
187
+ auto: boolean;
188
+ }
189
+ export type OCPromptPart = ({
190
+ type: "text";
191
+ text: string;
192
+ synthetic?: boolean;
193
+ } | {
194
+ type: "file";
195
+ mime: string;
196
+ url: string;
197
+ filename?: string;
198
+ } | {
199
+ type: "agent";
200
+ name: string;
201
+ source?: {
202
+ value: string;
203
+ start: number;
204
+ end: number;
205
+ };
206
+ }) & {
207
+ id?: string;
208
+ };
209
+ export type OCInputPart = OCPromptPart & {
210
+ id: string;
211
+ sessionID: string;
212
+ messageID: string;
213
+ };
214
+ export type OCPart = OCTextPart | OCReasoningPart | OCToolPart | OCCompactionPart | Exclude<OCInputPart, {
215
+ type: "text";
216
+ }>;
148
217
  export interface OCMessageWithParts {
149
218
  info: OCMessage;
150
219
  parts: OCPart[];
@@ -169,6 +238,15 @@ export type OCEvent = {
169
238
  delivery: "steer" | "queue";
170
239
  payload: {
171
240
  text: string;
241
+ files?: Array<Record<string, unknown>>;
242
+ agents?: Array<Record<string, unknown>>;
243
+ metadata?: Record<string, unknown>;
244
+ };
245
+ } | {
246
+ type: "synthetic";
247
+ payload: {
248
+ text: string;
249
+ metadata?: Record<string, unknown>;
172
250
  };
173
251
  };
174
252
  };
@@ -197,6 +275,79 @@ export type OCEvent = {
197
275
  properties: {
198
276
  info: OCSession;
199
277
  };
278
+ } | {
279
+ type: "session.revert.staged";
280
+ properties: {
281
+ sessionID: string;
282
+ revert: {
283
+ messageID: string;
284
+ };
285
+ };
286
+ } | {
287
+ type: "session.revert.cleared";
288
+ properties: {
289
+ sessionID: string;
290
+ };
291
+ } | {
292
+ type: "session.revert.committed";
293
+ properties: {
294
+ sessionID: string;
295
+ to: string;
296
+ };
297
+ } | {
298
+ type: "session.compaction.started";
299
+ properties: {
300
+ sessionID: string;
301
+ reason: string;
302
+ inputID: string;
303
+ };
304
+ } | {
305
+ type: "session.compaction.ended";
306
+ properties: {
307
+ sessionID: string;
308
+ reason: string;
309
+ inputID: string;
310
+ text: string;
311
+ recent: string;
312
+ };
313
+ } | {
314
+ type: "session.compaction.failed";
315
+ properties: {
316
+ sessionID: string;
317
+ reason: string;
318
+ inputID: string;
319
+ error: {
320
+ name: string;
321
+ data: {
322
+ message: string;
323
+ };
324
+ };
325
+ };
326
+ } | {
327
+ type: "session.shell.started" | "session.shell.ended";
328
+ properties: {
329
+ sessionID: string;
330
+ shell: {
331
+ id: string;
332
+ command: string;
333
+ cwd: string;
334
+ shell: string;
335
+ file: string;
336
+ status: "running" | "exited" | "timeout" | "killed";
337
+ exit?: number;
338
+ metadata: Record<string, unknown>;
339
+ time: {
340
+ started: number;
341
+ completed?: number;
342
+ };
343
+ };
344
+ output?: {
345
+ output: string;
346
+ cursor: number;
347
+ size: number;
348
+ truncated: boolean;
349
+ };
350
+ };
200
351
  } | {
201
352
  type: "session.deleted";
202
353
  properties: {
@@ -241,6 +392,15 @@ export type OCEvent = {
241
392
  part: OCPart;
242
393
  delta?: string;
243
394
  };
395
+ } | {
396
+ type: "message.part.delta";
397
+ properties: {
398
+ sessionID: string;
399
+ messageID: string;
400
+ partID: string;
401
+ field: "text";
402
+ delta: string;
403
+ };
244
404
  } | {
245
405
  type: "permission.asked";
246
406
  properties: {
@@ -320,12 +480,15 @@ export interface OCProject {
320
480
  vcs?: "git";
321
481
  time: {
322
482
  created: number;
483
+ updated?: number;
323
484
  initialized?: number;
324
485
  };
486
+ sandboxes?: string[];
325
487
  }
326
488
  /** Incoming prompt body for POST /session/{id}/message and /prompt_async */
327
489
  export interface OCPromptBody {
328
490
  messageID?: string;
491
+ delivery?: "steer" | "queue";
329
492
  model?: OCModelRef;
330
493
  agent?: string;
331
494
  noReply?: boolean;
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
  }
@@ -89,35 +90,51 @@ export function toV2Session(info, opts = {}) {
89
90
  id: info.id,
90
91
  projectID: info.projectID,
91
92
  agent: opts.agent ?? getAgentName(),
92
- cost: 0,
93
- tokens: zeroTokens(),
93
+ cost: info.cost ?? 0,
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) {
112
- const text = info.parts
113
- .filter((p) => p.type === "text")
114
- .map((p) => p.text)
115
- .join("");
123
+ function toV2UserMessage(info) {
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] : []));
116
131
  return {
117
- id: info.info.id,
118
- time: { created: info.info.time.created },
132
+ id: user.id,
133
+ time: { created: user.time.created },
119
134
  type: "user",
120
135
  text,
136
+ ...v2PromptAttachments(inputs),
137
+ ...metadata,
121
138
  };
122
139
  }
123
140
  /**
@@ -126,7 +143,7 @@ export function toV2UserMessage(info) {
126
143
  */
127
144
  export function toV2AssistantMessage(info) {
128
145
  const i = info.info;
129
- const model = { id: i.modelID, providerID: i.providerID };
146
+ const model = { id: i.modelID, providerID: i.providerID, variant: "default" };
130
147
  const content = [];
131
148
  for (const part of info.parts) {
132
149
  if (part.type === "text") {
@@ -142,13 +159,16 @@ export function toV2AssistantMessage(info) {
142
159
  },
143
160
  });
144
161
  }
145
- else {
162
+ else if (part.type === "tool") {
146
163
  content.push(toolContent(part));
147
164
  }
148
165
  }
149
166
  const msg = { id: i.id, time: { created: i.time.created }, type: "assistant", content };
150
167
  if (i.time.completed !== undefined)
151
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);
152
172
  msg.agent = i.mode;
153
173
  msg.model = model;
154
174
  if (i.finish !== undefined)
@@ -217,10 +237,19 @@ export function toV2Message(info) {
217
237
  */
218
238
  export class V2EventTranslator {
219
239
  sessions = new Map();
240
+ sequences = new Map();
220
241
  translate(directory, ev) {
221
242
  switch (ev.type) {
222
243
  case "session.inbox.enqueued":
223
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":
224
253
  return [this.frame(directory, ev.type, ev.properties)];
225
254
  case "message.removed":
226
255
  return [
@@ -238,12 +267,22 @@ export class V2EventTranslator {
238
267
  location: { directory: info.directory },
239
268
  subpath: subpathOf(info.directory),
240
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 } : {}),
241
274
  }),
242
275
  ];
243
276
  }
244
277
  case "session.updated": {
245
278
  const info = ev.properties.info;
246
- 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
+ ];
247
286
  }
248
287
  case "session.diff":
249
288
  return [this.frame(directory, "session.diff", ev.properties)];
@@ -260,7 +299,21 @@ export class V2EventTranslator {
260
299
  const frames = [];
261
300
  if (st?.step && !st.step.ended)
262
301
  frames.push(...this.finalizeStep(directory, st.step));
263
- 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
+ }
264
317
  frames.push(this.frame(directory, "session.idle", { sessionID }));
265
318
  this.sessions.delete(sessionID);
266
319
  return frames;
@@ -268,24 +321,20 @@ export class V2EventTranslator {
268
321
  case "session.error": {
269
322
  const { sessionID, error } = ev.properties;
270
323
  const frames = [];
271
- const st = sessionID !== undefined ? this.sessions.get(sessionID) : undefined;
324
+ const st = sessionID !== undefined ? this.state(sessionID) : undefined;
272
325
  if (sessionID !== undefined && st?.step && !st.step.ended) {
273
- st.step.ended = true;
274
- frames.push(this.frame(directory, "session.step.failed", {
275
- sessionID,
276
- assistantMessageID: st.step.messageID,
277
- finish: st.step.finish ?? "error",
278
- error: error ?? { name: "UnknownError", data: { message: "session error" } },
279
- cost: st.step.cost,
280
- tokens: st.step.tokens,
281
- }));
282
- 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));
283
329
  }
284
- if (sessionID !== undefined) {
285
- frames.push(this.frame(directory, "session.execution.failed", {
286
- sessionID,
287
- error: error ?? { name: "UnknownError", data: { message: "session error" } },
288
- }));
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
+ }));
289
338
  }
290
339
  return frames;
291
340
  }
@@ -293,6 +342,20 @@ export class V2EventTranslator {
293
342
  return this.onMessageUpdated(directory, ev.properties.info);
294
343
  case "message.part.updated":
295
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
+ }
296
359
  case "model.changed": {
297
360
  const { sessionID, model, previous } = ev.properties;
298
361
  return [
@@ -305,20 +368,10 @@ export class V2EventTranslator {
305
368
  }
306
369
  case "permission.asked": {
307
370
  const p = ev.properties;
308
- return [
309
- this.frame(directory, "permission.v2.asked", {
310
- id: p.id,
311
- sessionID: p.sessionID,
312
- action: p.permission,
313
- resources: p.patterns,
314
- save: p.always,
315
- metadata: p.metadata,
316
- ...(p.tool ? { source: { type: "tool", ...p.tool } } : {}),
317
- }),
318
- ];
371
+ return [this.frame(directory, "permission.asked", p)];
319
372
  }
320
373
  case "permission.replied":
321
- return [this.frame(directory, "permission.v2.replied", ev.properties)];
374
+ return [this.frame(directory, "permission.replied", ev.properties)];
322
375
  case "question.asked":
323
376
  return [this.frame(directory, "form.created", { form: toQuestionForm(ev.properties) })];
324
377
  case "question.replied":
@@ -371,6 +424,9 @@ export class V2EventTranslator {
371
424
  };
372
425
  if (info.finish !== undefined)
373
426
  step.finish = info.finish;
427
+ if (info.finish === "error") {
428
+ step.error = info.error ?? { name: "UnknownError", data: { message: "Provider error" } };
429
+ }
374
430
  st.step = step;
375
431
  frames.push(this.frame(directory, "session.step.started", {
376
432
  sessionID: info.sessionID,
@@ -384,6 +440,9 @@ export class V2EventTranslator {
384
440
  st.step.tokens = info.tokens;
385
441
  if (info.finish !== undefined)
386
442
  st.step.finish = info.finish;
443
+ if (info.finish === "error") {
444
+ st.step.error = info.error ?? { name: "UnknownError", data: { message: "Provider error" } };
445
+ }
387
446
  if (!st.step.streamed) {
388
447
  st.step.streamed = true;
389
448
  frames.push(this.frame(directory, "session.step.streamed", { sessionID: info.sessionID, assistantMessageID: info.id }));
@@ -402,16 +461,37 @@ export class V2EventTranslator {
402
461
  st.ended = true;
403
462
  return [
404
463
  this.frame(directory, "session.usage.updated", { sessionID: st.sessionID, cost: st.cost, tokens: st.tokens }),
405
- this.frame(directory, "session.step.ended", {
406
- sessionID: st.sessionID,
407
- assistantMessageID: st.messageID,
408
- ...(st.finish !== undefined ? { finish: st.finish } : {}),
409
- cost: st.cost,
410
- tokens: st.tokens,
411
- }),
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
+ }),
412
480
  ];
413
481
  }
414
482
  onPartUpdated(directory, part, delta) {
483
+ if (part.type === "file" || part.type === "agent")
484
+ return [];
485
+ if (part.type === "compaction")
486
+ // Like shell turns, the server creates this user turn; there is no optimistic prompt.
487
+ return [
488
+ this.frame(directory, "session.prompted", {
489
+ sessionID: part.sessionID,
490
+ messageID: part.messageID,
491
+ prompt: { text: "/compact" },
492
+ delivery: "queue",
493
+ }),
494
+ ];
415
495
  return part.type === "tool" ? this.onToolPart(directory, part) : this.onTextPart(directory, part, delta);
416
496
  }
417
497
  onTextPart(directory, part, delta) {
@@ -540,7 +620,18 @@ export class V2EventTranslator {
540
620
  return st;
541
621
  }
542
622
  frame(directory, type, data) {
543
- 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
+ };
544
635
  }
545
636
  }
546
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