antechamber 0.2.25 → 0.2.26

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,17 @@ and this project uses [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.26] - 2026-10-17
11
+
12
+ ### Changed
13
+
14
+ - Name a subagent card for an `agent()` call made inside eval after its agent type (for example "Explore Agent"),
15
+ and describe it with the call's label, the task's summary, or the first line of its prompt, instead of a bare "agent".
16
+ - Show subagents an eval cell starts with `tool.task(...)` as subagent cards that open their session, live and after
17
+ reloading history, including ones started in the background.
18
+ - Show an `agent()` call that OmO refused (for example one with no agent type) as a failed subagent card. OmO
19
+ records it as a success but starts no task.
20
+
10
21
  ## [0.2.25] - 2026-10-17
11
22
 
12
23
  - feat(tools): show subagents and monitors started inside eval (#30)
@@ -1,5 +1,5 @@
1
1
  import { type SessionStore } from "./session-store.js";
2
- import { type ToolResult } from "./tool-map.js";
2
+ import { type EvalAgent, type ToolResult } from "./tool-map.js";
3
3
  import type { ManagedSession } from "./sessions.js";
4
4
  import type { OCEvent, OCMessageWithParts, OCToolPart } from "./types.js";
5
5
  export declare function stripTaskSuffix(text: string): string;
@@ -17,9 +17,10 @@ export declare function findTaskBySession(sessionId: string): {
17
17
  /**
18
18
  * The session of a subagent an eval cell started with `agent()`. The recorded call keeps only its
19
19
  * arguments, so the task record is found by parent, prompt and label: the oldest one created since
20
- * the cell started and not yet claimed by an earlier call of the same cell.
20
+ * the cell started and not yet claimed by an earlier call of the same cell. `null` means the parent
21
+ * was searched and no record matched; `undefined` means it could not be searched.
21
22
  */
22
- export declare function evalAgentSession(parentPiId: string | undefined, args: Record<string, unknown>, claimed: Set<string>, after: number): string | undefined;
23
+ export declare function evalAgentSession(parentPiId: string | undefined, args: Record<string, unknown>, claimed: Set<string>, after: number): EvalAgent | null | undefined;
23
24
  export declare function findTask(id: string, parentPiId: string): {
24
25
  path: string;
25
26
  data: Record<string, unknown>;
package/dist/subagents.js CHANGED
@@ -193,10 +193,23 @@ export function findTaskBySession(sessionId) {
193
193
  }
194
194
  return undefined;
195
195
  }
196
+ /**
197
+ * Whether a recorded argument names a task record's value. Codemode caps recorded strings at 512 code
198
+ * points and ends a cut one with "…" (call-capture.ts), and a schema agent() call sends its prompt with
199
+ * JSON instructions appended (agent-bridge.ts toTaskParams), so both match as prefixes.
200
+ */
201
+ function recordedMatch(recorded, stored) {
202
+ if (typeof stored !== "string")
203
+ return false;
204
+ if (stored === recorded)
205
+ return true;
206
+ return recorded.endsWith("…") ? stored.startsWith(recorded.slice(0, -1)) : stored.startsWith(`${recorded}\n\n`);
207
+ }
196
208
  /**
197
209
  * The session of a subagent an eval cell started with `agent()`. The recorded call keeps only its
198
210
  * arguments, so the task record is found by parent, prompt and label: the oldest one created since
199
- * the cell started and not yet claimed by an earlier call of the same cell.
211
+ * the cell started and not yet claimed by an earlier call of the same cell. `null` means the parent
212
+ * was searched and no record matched; `undefined` means it could not be searched.
200
213
  */
201
214
  export function evalAgentSession(parentPiId, args, claimed, after) {
202
215
  if (parentPiId === undefined || typeof args.prompt !== "string")
@@ -209,21 +222,31 @@ export function evalAgentSession(parentPiId, args, claimed, after) {
209
222
  if (data?.parent_session_id !== parentPiId)
210
223
  continue;
211
224
  const spec = record(data.spawn_spec) ? data.spawn_spec : {};
212
- if (spec.prompt !== args.prompt)
225
+ if (!recordedMatch(args.prompt, spec.prompt))
213
226
  continue;
214
- if (typeof args.label === "string" && data.name !== args.label)
227
+ if (typeof args.label === "string" && !recordedMatch(args.label, data.name))
215
228
  continue;
216
229
  const created = typeof data.created_at === "string" ? Date.parse(data.created_at) : Number.NaN;
217
230
  // The cell's start is taken when its card opens, a moment after OmO starts it.
218
231
  if (!Number.isFinite(created) || created < after - 5_000)
219
232
  continue;
220
233
  if (best === undefined || created < best.created)
221
- best = { id, created };
234
+ best = { id, created, data };
222
235
  }
223
236
  if (best === undefined)
224
- return undefined;
237
+ return null;
225
238
  claimed.add(best.id);
226
- return ocIdFor(best.id);
239
+ const text = (value) => (typeof value === "string" && value !== "" ? value : undefined);
240
+ // OmO names an unlabelled task after its id, which says nothing.
241
+ const name = text(best.data.name);
242
+ const summary = text(best.data.task_summary);
243
+ const agentType = text(best.data.agent_type);
244
+ return {
245
+ sessionID: ocIdFor(best.id),
246
+ ...(name !== undefined && name !== best.id ? { name } : {}),
247
+ ...(summary !== undefined ? { summary } : {}),
248
+ ...(agentType !== undefined ? { agentType } : {}),
249
+ };
227
250
  }
228
251
  export function findTask(id, parentPiId) {
229
252
  if (!taskId(id))
@@ -32,8 +32,15 @@ export declare function mapTool(omoName: string, args: unknown, result?: ToolRes
32
32
  export interface NestedSessions {
33
33
  /** The monitor child session for a recorded `monitor` call. */
34
34
  monitor?: (args: Record<string, unknown>) => string | undefined;
35
- /** The subagent session for a recorded `agent()` call; `claimed` keeps repeated calls apart. */
36
- agent?: (args: Record<string, unknown>, claimed: Set<string>, after: number) => string | undefined;
35
+ /** The subagent a recorded `agent()` call started; `claimed` keeps repeated calls apart. */
36
+ agent?: (args: Record<string, unknown>, claimed: Set<string>, after: number) => EvalAgent | null | undefined;
37
+ }
38
+ /** The task record behind an `agent()` call made inside eval. */
39
+ export interface EvalAgent {
40
+ sessionID: string;
41
+ name?: string;
42
+ summary?: string;
43
+ agentType?: string;
37
44
  }
38
45
  /**
39
46
  * Finished eval calls, from the raw RPC capture of installed omo-ai 5.1.33 / senpi runtime, 2026-10-17.
package/dist/tool-map.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { ocIdFor } from "./session-store.js";
1
2
  function record(value) {
2
3
  return value !== null && typeof value === "object" && !Array.isArray(value);
3
4
  }
@@ -134,13 +135,41 @@ export function mapTool(omoName, args, result) {
134
135
  ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
135
136
  };
136
137
  }
137
- /** A subagent-style card, as OpenChamber draws `task`: it opens `sessionID` when there is one. */
138
- function nestedSubagent(description, prompt, type, sessionID) {
138
+ /** "Explore Agent" for an agent type or category, "Subagent" when there is none. */
139
+ function agentTitle(type) {
140
+ return type === "subagent" ? "Subagent" : `${type.charAt(0).toUpperCase()}${type.slice(1)} Agent`;
141
+ }
142
+ /**
143
+ * The task id OmO puts in a `task` result: "[task_id: st_x - " when it waited, "(st_x, running" in the
144
+ * background. The preview is capped at 160 code points, so only an id followed by its delimiter is whole.
145
+ */
146
+ const TASK_ID = /\[task_id: (st_[A-Za-z0-9_-]+) - |\((st_[A-Za-z0-9_-]+), running\b/;
147
+ /** The prompt's first line, cut at a word boundary near 60 characters. */
148
+ function shortPrompt(prompt) {
149
+ const line = prompt
150
+ .split("\n")
151
+ .find((text) => text.trim() !== "")
152
+ ?.trim();
153
+ if (line === undefined || line.length <= 60)
154
+ return line;
155
+ const cut = line.slice(0, 60);
156
+ const space = cut.lastIndexOf(" ");
157
+ return `${(space > 30 ? cut.slice(0, space) : cut).trimEnd()}…`;
158
+ }
159
+ /**
160
+ * A subagent-style card, as OpenChamber draws `task`: it opens `sessionID` when there is one.
161
+ * `agentTitle`/`agentSubtitle` feed the OmO Compatibility extension's `task` header rule.
162
+ */
163
+ function nestedSubagent(heading, input, sessionID) {
139
164
  return {
140
165
  tool: "task",
141
- input: { description, prompt, subagent_type: type },
142
- title: description.slice(0, 60),
143
- metadata: sessionID !== undefined ? { sessionID } : {},
166
+ input: { description: input.description, prompt: input.prompt, subagent_type: input.type },
167
+ title: input.description.slice(0, 60),
168
+ metadata: {
169
+ agentTitle: heading.title,
170
+ agentSubtitle: heading.subtitle,
171
+ ...(sessionID !== undefined ? { sessionID } : {}),
172
+ },
144
173
  };
145
174
  }
146
175
  /**
@@ -154,8 +183,9 @@ export function nestedToolParts(parent, details, sessions = {}) {
154
183
  const start = parent.state.status === "pending" ? 0 : parent.state.time.start;
155
184
  const claimed = new Set();
156
185
  details.toolCalls.forEach((entry, index) => {
186
+ let failure;
157
187
  if (!record(entry) ||
158
- (entry.name !== "monitor" && entry.name !== "__agent__") ||
188
+ (entry.name !== "monitor" && entry.name !== "__agent__" && entry.name !== "task") ||
159
189
  typeof entry.ok !== "boolean" ||
160
190
  typeof entry.durationMs !== "number" ||
161
191
  !Number.isFinite(entry.durationMs) ||
@@ -169,14 +199,44 @@ export function nestedToolParts(parent, details, sessions = {}) {
169
199
  return; // nothing to open; the cell's code already shows the call
170
200
  const command = typeof args.command === "string" ? args.command : "";
171
201
  const label = typeof args.description === "string" && args.description !== "" ? args.description : command;
172
- mapped = nestedSubagent(`Monitor: ${label}`, command, "monitor", sessionID);
202
+ mapped = nestedSubagent({ title: "Monitor", subtitle: label }, { description: `Monitor: ${label}`, prompt: command, type: "monitor" }, sessionID);
203
+ }
204
+ else if (entry.name === "task") {
205
+ const text = (value) => (typeof value === "string" && value !== "" ? value : undefined);
206
+ const prompt = text(args.prompt) ?? "";
207
+ const match = typeof entry.resultPreview === "string" ? TASK_ID.exec(entry.resultPreview) : null;
208
+ const taskID = match?.[1] ?? match?.[2];
209
+ if (taskID !== undefined)
210
+ claimed.add(taskID);
211
+ // A long reply pushes the id out of the preview; the task record still names the session.
212
+ const sessionID = !entry.ok
213
+ ? undefined
214
+ : taskID !== undefined
215
+ ? ocIdFor(taskID)
216
+ : (sessions.agent?.({ prompt, ...(text(args.name) ? { label: args.name } : {}) }, claimed, start) ??
217
+ undefined)?.sessionID;
218
+ const description = text(args.task_summary) ?? text(args.description) ?? shortPrompt(prompt) ?? "subagent";
219
+ const type = text(args.subagent_type) ?? text(args.category) ?? "subagent";
220
+ mapped = nestedSubagent({ title: agentTitle(type), subtitle: description }, { description, prompt, type }, sessionID);
173
221
  }
174
222
  else {
175
223
  const prompt = typeof args.prompt === "string" ? args.prompt : "";
176
- const label = [args.label, args.description, prompt.split("\n")[0]].find((value) => typeof value === "string" && value !== "");
177
- const sessionID = entry.ok ? sessions.agent?.(args, claimed, start) : undefined;
178
- const type = typeof args.agent === "string" && args.agent !== "" ? args.agent : "subagent";
179
- mapped = nestedSubagent(label ?? "subagent", prompt, type, sessionID);
224
+ const found = entry.ok ? sessions.agent?.(args, claimed, start) : undefined;
225
+ const text = (value) => (typeof value === "string" && value !== "" ? value : undefined);
226
+ const description = text(args.label) ??
227
+ text(args.description) ??
228
+ found?.summary ??
229
+ found?.name ??
230
+ shortPrompt(prompt) ??
231
+ "subagent";
232
+ const type = text(args.agent) ?? found?.agentType ?? "subagent";
233
+ mapped = nestedSubagent({
234
+ title: agentTitle(type),
235
+ subtitle: description,
236
+ }, { description, prompt, type }, found?.sessionID);
237
+ // OmO writes the task record before agent() returns, but records a refused spawn as ok with no record.
238
+ if (entry.ok && found === null)
239
+ failure = "OmO started no subagent for this call (agent() needs an agent type or a category)";
180
240
  }
181
241
  const key = typeof entry.callId === "string" ? `id:${encodeURIComponent(entry.callId)}` : `index:${index}`;
182
242
  const callID = `${encodeURIComponent(parent.callID)}:${key}`;
@@ -187,19 +247,22 @@ export function nestedToolParts(parent, details, sessions = {}) {
187
247
  metadata: { ...mapped.metadata, nested: true, parentCallID: parent.callID, durationMs: entry.durationMs },
188
248
  time: { start, end: start + entry.durationMs },
189
249
  };
250
+ const failed = !entry.ok || failure !== undefined;
190
251
  parts.push({
191
252
  id: `prt_${parent.sessionID}_nested_${callID}`,
192
253
  sessionID: parent.sessionID,
193
254
  messageID: parent.messageID,
194
255
  type: "tool",
195
256
  callID: `nested:${callID}`,
196
- tool: mapped.tool,
197
- state: entry.ok
257
+ // OpenChamber 1.24 never styles a task card as failed or shows its error (ToolPart.tsx isTaskTool),
258
+ // and a failed spawn has no session to open, so it goes out as a plain card that does both.
259
+ tool: failed && mapped.tool === "task" ? "agent" : mapped.tool,
260
+ state: !failed
198
261
  ? { ...state, status: "completed" }
199
262
  : {
200
263
  ...state,
201
264
  status: "error",
202
- error: typeof entry.error === "string" && entry.error !== "" ? entry.error : "failed",
265
+ error: failure ?? (typeof entry.error === "string" && entry.error !== "" ? entry.error : "failed"),
203
266
  },
204
267
  });
205
268
  });
@@ -6,6 +6,10 @@ extension declare. It has no panel, page, service or permissions.
6
6
 
7
7
  What it does today:
8
8
 
9
+ - **Subagent card headers.** A subagent an eval cell started with `agent()` is shown as a `task` card. Its header
10
+ reads the agent type and a short description (the call's label, the task's summary, or the prompt's first line)
11
+ instead of the generic "Agent Task".
12
+
9
13
  - **Eval cell headers.** OpenChamber 1.24 streams live output only for shell
10
14
  cards, so antechamber shows OmO eval cells as shell cards. With this extension
11
15
  their header reads `JS Eval` (or `Python Eval`) and the cell's summary instead
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-compat",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
4
4
  "description": "OpenChamber extension: displays OmO tool calls the way OmO means them when OpenChamber runs on antechamber",
5
5
  "private": true,
6
6
  "type": "module",
@@ -21,6 +21,16 @@
21
21
  "match": "bash",
22
22
  "title": "{metadata.evalTitle}",
23
23
  "subtitle": "{metadata.evalSubtitle}"
24
+ },
25
+ {
26
+ "match": "task",
27
+ "title": "{metadata.agentTitle}",
28
+ "subtitle": "{metadata.agentSubtitle}"
29
+ },
30
+ {
31
+ "match": "agent",
32
+ "title": "{metadata.agentTitle}",
33
+ "subtitle": "{metadata.agentSubtitle}"
24
34
  }
25
35
  ]
26
36
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber-omo-graph",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antechamber",
3
- "version": "0.2.25",
3
+ "version": "0.2.26",
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",