antechamber 0.2.24 → 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 +20 -0
- package/dist/monitor-sessions.d.ts +6 -0
- package/dist/monitor-sessions.js +18 -0
- package/dist/pi-event-translation.d.ts +2 -1
- package/dist/pi-event-translation.js +32 -3
- package/dist/session-history.js +19 -4
- package/dist/subagents.d.ts +8 -1
- package/dist/subagents.js +55 -0
- package/dist/tool-map.d.ts +22 -0
- package/dist/tool-map.js +134 -0
- package/extensions/omo-compat/README.md +4 -0
- package/extensions/omo-compat/package.json +11 -1
- package/extensions/omo-dag/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ 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
|
+
|
|
21
|
+
## [0.2.25] - 2026-10-17
|
|
22
|
+
|
|
23
|
+
- feat(tools): show subagents and monitors started inside eval (#30)
|
|
24
|
+
|
|
10
25
|
## [0.2.24] - 2026-10-17
|
|
11
26
|
|
|
12
27
|
### Fixed
|
|
@@ -14,6 +29,11 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
14
29
|
- Show completed background bash output and detached eval results in OpenChamber, including after reloading history.
|
|
15
30
|
A card that started background work keeps running until that work finishes.
|
|
16
31
|
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- Show subagents and monitors started inside an eval cell as subagent cards that open their session, live and after
|
|
35
|
+
reloading history. A failed `agent()` call keeps its error.
|
|
36
|
+
|
|
17
37
|
## [0.2.23] - 2026-10-17
|
|
18
38
|
|
|
19
39
|
- feat(monitors): show matched lines in monitor sessions (#32)
|
|
@@ -37,6 +37,12 @@ export declare class MonitorSessions {
|
|
|
37
37
|
observe(parent: ManagedSession, ev: Record<string, unknown>): void;
|
|
38
38
|
/** A hidden `senpi-monitor:notification` custom message: append its lines to each watch it names. */
|
|
39
39
|
notify(parent: ManagedSession, message: Record<string, unknown>): void;
|
|
40
|
+
/**
|
|
41
|
+
* The child session of the newest watch a parent started with these monitor arguments.
|
|
42
|
+
* A monitor call recorded inside eval keeps only its arguments, not the `bash_N` id the
|
|
43
|
+
* child is keyed by, so command and description identify it.
|
|
44
|
+
*/
|
|
45
|
+
findChild(parentID: string, args: Record<string, unknown>): string | undefined;
|
|
40
46
|
/** The agent process is gone: OmO's in-process watches died with it. */
|
|
41
47
|
stopParent(parentID: string): void;
|
|
42
48
|
/** Forget a deleted child, or every child of a deleted parent. */
|
package/dist/monitor-sessions.js
CHANGED
|
@@ -120,6 +120,24 @@ export class MonitorSessions {
|
|
|
120
120
|
this.publish(watch);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* The child session of the newest watch a parent started with these monitor arguments.
|
|
125
|
+
* A monitor call recorded inside eval keeps only its arguments, not the `bash_N` id the
|
|
126
|
+
* child is keyed by, so command and description identify it.
|
|
127
|
+
*/
|
|
128
|
+
findChild(parentID, args) {
|
|
129
|
+
let found;
|
|
130
|
+
for (const watch of this.watches.values()) {
|
|
131
|
+
const { monitor } = watch;
|
|
132
|
+
if (watch.session.info.parentID !== parentID || monitor.command !== args.command)
|
|
133
|
+
continue;
|
|
134
|
+
if (typeof args.description === "string" && monitor.description !== args.description)
|
|
135
|
+
continue;
|
|
136
|
+
if (found === undefined || monitor.startedAtMs > found.monitor.startedAtMs)
|
|
137
|
+
found = watch;
|
|
138
|
+
}
|
|
139
|
+
return found?.session.info.id;
|
|
140
|
+
}
|
|
123
141
|
/** The agent process is gone: OmO's in-process watches died with it. */
|
|
124
142
|
stopParent(parentID) {
|
|
125
143
|
for (const watch of this.watches.values()) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { OCAssistantMessage, OCMessageWithParts, OCModelRef, OCEvent, OCSessionStatus, OCPart } from "./types.js";
|
|
3
3
|
import type { ManagedSession } from "./sessions.js";
|
|
4
4
|
import type { SessionStore } from "./session-store.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type SubagentBridge } from "./subagents.js";
|
|
6
6
|
import { type MonitorSessions } from "./monitor-sessions.js";
|
|
7
7
|
import { type TodoUpdated } from "./todo-bridge.js";
|
|
8
8
|
export interface PiUsage {
|
|
@@ -84,6 +84,7 @@ export declare class PiEventTranslator {
|
|
|
84
84
|
private applyToolResults;
|
|
85
85
|
/** The agent process is gone, and its background shells with it: end cards still waiting on them. */
|
|
86
86
|
settleBackground(s: ManagedSession): void;
|
|
87
|
+
private onNestedTools;
|
|
87
88
|
/** Complete an ended assistant message whose completion waited on its tools; true when it changed. */
|
|
88
89
|
private completeSettled;
|
|
89
90
|
/** Tool parts that never received a result (abort, crash) must not spin forever. */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/** Pi message/part translation and the live event bridge, with explicit manager services. */
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { evalAgentSession } from "./subagents.js";
|
|
3
4
|
import { MONITOR_NOTIFICATION } from "./monitor-sessions.js";
|
|
4
|
-
import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts, resultText, toolFailed, } from "./tool-map.js";
|
|
5
|
+
import { applyProviderToolResults, finishProviderToolParts, mapTool, nestedToolParts, providerToolParts, resultText, toolFailed, } from "./tool-map.js";
|
|
5
6
|
import { TodoBridge } from "./todo-bridge.js";
|
|
6
7
|
import { readSessionTree, recordTurn } from "./session-tree.js";
|
|
7
8
|
import { touchedFiles } from "./session-diff.js";
|
|
@@ -476,6 +477,7 @@ export class PiEventTranslator {
|
|
|
476
477
|
if (output.trim() !== "")
|
|
477
478
|
part.state.metadata = { ...part.state.metadata, output };
|
|
478
479
|
this.emitPart(s, part);
|
|
480
|
+
this.onNestedTools(s, part, partial !== null && typeof partial === "object" && "details" in partial ? partial.details : undefined);
|
|
479
481
|
return;
|
|
480
482
|
}
|
|
481
483
|
case "tool_execution_end": {
|
|
@@ -752,8 +754,11 @@ export class PiEventTranslator {
|
|
|
752
754
|
// Task expansion replaces the streaming placeholder with one or more
|
|
753
755
|
// cards sharing its callID. Order those cards without reconstructing it.
|
|
754
756
|
const indices = new Map([...streaming.parts].map(([index, part]) => [part.type === "tool" ? part.callID : part.id, index]));
|
|
755
|
-
|
|
756
|
-
|
|
757
|
+
const order = (part) => {
|
|
758
|
+
const parent = part.type === "tool" && part.state.status !== "pending" ? part.state.metadata?.parentCallID : undefined;
|
|
759
|
+
return (indices.get(typeof parent === "string" ? parent : part.type === "tool" ? part.callID : part.id) ?? Infinity);
|
|
760
|
+
};
|
|
761
|
+
owner.parts.sort((a, b) => order(a) - order(b));
|
|
757
762
|
}
|
|
758
763
|
}
|
|
759
764
|
/**
|
|
@@ -850,6 +855,12 @@ export class PiEventTranslator {
|
|
|
850
855
|
continue;
|
|
851
856
|
}
|
|
852
857
|
const part = callID !== undefined ? s.toolByCallId.get(callID) : undefined;
|
|
858
|
+
const parent = part ??
|
|
859
|
+
s.messages
|
|
860
|
+
?.flatMap((message) => message.parts)
|
|
861
|
+
.find((item) => item.type === "tool" && item.callID === callID);
|
|
862
|
+
if (parent !== undefined)
|
|
863
|
+
this.onNestedTools(s, parent, tr.details);
|
|
853
864
|
if (!part || callID === undefined)
|
|
854
865
|
continue;
|
|
855
866
|
s.toolByCallId.delete(callID);
|
|
@@ -870,6 +881,24 @@ export class PiEventTranslator {
|
|
|
870
881
|
for (const part of settleBackground(s.messages ?? [], Date.now()))
|
|
871
882
|
this.emitPart(s, part);
|
|
872
883
|
}
|
|
884
|
+
onNestedTools(s, parent, details) {
|
|
885
|
+
const nested = nestedToolParts(parent, details, {
|
|
886
|
+
monitor: (args) => this.services.monitors.findChild(s.info.id, args),
|
|
887
|
+
agent: (args, claimed, after) => evalAgentSession(s.piSessionId ?? s.restored?.piSessionId, args, claimed, after),
|
|
888
|
+
});
|
|
889
|
+
if (nested.length === 0)
|
|
890
|
+
return;
|
|
891
|
+
const owner = s.messages?.find((message) => message.info.id === parent.messageID);
|
|
892
|
+
if (owner === undefined)
|
|
893
|
+
return;
|
|
894
|
+
for (const [index, part] of nested.entries()) {
|
|
895
|
+
const existing = owner.parts.findIndex((item) => item.id === part.id);
|
|
896
|
+
if (existing >= 0)
|
|
897
|
+
owner.parts.splice(existing, 1);
|
|
898
|
+
owner.parts.splice(owner.parts.indexOf(parent) + 1 + index, 0, part);
|
|
899
|
+
this.emitPart(s, part);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
873
902
|
/** Complete an ended assistant message whose completion waited on its tools; true when it changed. */
|
|
874
903
|
completeSettled(s, info) {
|
|
875
904
|
if (!this.awaitingTools.has(info) || info.role !== "assistant" || hasActiveTools(s, info.id))
|
package/dist/session-history.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Reconstruct OpenCode history from pi JSONL session files. */
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { applyProviderToolResults, finishProviderToolParts, mapTool, providerToolParts } from "./tool-map.js";
|
|
3
|
+
import { applyProviderToolResults, finishProviderToolParts, mapTool, nestedToolParts, providerToolParts, } from "./tool-map.js";
|
|
4
4
|
import { TodoBridge } from "./todo-bridge.js";
|
|
5
5
|
import { userMessageModel } from "./thinking-levels.js";
|
|
6
6
|
import { contextMessageID } from "./prompt-parts.js";
|
|
7
7
|
import { bindMessageEntries } from "./session-tree.js";
|
|
8
|
-
import { taskParts, updateTaskParts, taskCompletions } from "./subagents.js";
|
|
8
|
+
import { evalAgentSession, taskParts, updateTaskParts, taskCompletions } from "./subagents.js";
|
|
9
9
|
import { shellTitle, shellUserMessage } from "./session-commands.js";
|
|
10
10
|
import { compactionMessages, piModelId, piStopReason, textOf } from "./pi-event-translation.js";
|
|
11
11
|
import { backgroundID, backgroundMessage, backgroundResult, completeBackgroundEval, finishToolPart, releaseBackground, settleBackground, } from "./background-results.js";
|
|
@@ -407,6 +407,18 @@ export function parsePiSessionText(s, agentName, raw) {
|
|
|
407
407
|
}
|
|
408
408
|
if (callID !== undefined)
|
|
409
409
|
finishToolPart(part, m, callID, created);
|
|
410
|
+
const nested = nestedToolParts(part, m.details, {
|
|
411
|
+
agent: (args, claimed, after) => evalAgentSession(s.piSessionId ?? s.restored?.piSessionId, args, claimed, after),
|
|
412
|
+
});
|
|
413
|
+
const owner = out.find((message) => message.info.id === part.messageID);
|
|
414
|
+
if (owner !== undefined) {
|
|
415
|
+
for (const card of nested) {
|
|
416
|
+
const existing = owner.parts.findIndex((item) => item.id === card.id);
|
|
417
|
+
if (existing >= 0)
|
|
418
|
+
owner.parts.splice(existing, 1);
|
|
419
|
+
}
|
|
420
|
+
owner.parts.splice(owner.parts.indexOf(part) + 1, 0, ...nested);
|
|
421
|
+
}
|
|
410
422
|
}
|
|
411
423
|
// system / custom entries are not rendered
|
|
412
424
|
}
|
|
@@ -475,8 +487,11 @@ export function parsePiSessionText(s, agentName, raw) {
|
|
|
475
487
|
sessionID,
|
|
476
488
|
messageID: message.info.id,
|
|
477
489
|
}));
|
|
478
|
-
const
|
|
479
|
-
|
|
490
|
+
const nestedIDs = new Set(message.parts.flatMap((part) => part.type === "tool" && part.state.status !== "pending" && part.state.metadata?.nested === true
|
|
491
|
+
? [part.id]
|
|
492
|
+
: []));
|
|
493
|
+
const identities = s.tree.messageParts?.[message.info.id]?.filter((identity) => !nestedIDs.has(identity));
|
|
494
|
+
for (const [index, part] of message.parts.filter((part) => !nestedIDs.has(part.id)).entries()) {
|
|
480
495
|
const identity = identities?.[index];
|
|
481
496
|
if (identity !== undefined)
|
|
482
497
|
part.id = identity;
|
package/dist/subagents.d.ts
CHANGED
|
@@ -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;
|
|
@@ -14,6 +14,13 @@ export declare function findTaskBySession(sessionId: string): {
|
|
|
14
14
|
path: string;
|
|
15
15
|
data: Record<string, unknown>;
|
|
16
16
|
} | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* The session of a subagent an eval cell started with `agent()`. The recorded call keeps only its
|
|
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. `null` means the parent
|
|
21
|
+
* was searched and no record matched; `undefined` means it could not be searched.
|
|
22
|
+
*/
|
|
23
|
+
export declare function evalAgentSession(parentPiId: string | undefined, args: Record<string, unknown>, claimed: Set<string>, after: number): EvalAgent | null | undefined;
|
|
17
24
|
export declare function findTask(id: string, parentPiId: string): {
|
|
18
25
|
path: string;
|
|
19
26
|
data: Record<string, unknown>;
|
package/dist/subagents.js
CHANGED
|
@@ -193,6 +193,61 @@ 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
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The session of a subagent an eval cell started with `agent()`. The recorded call keeps only its
|
|
210
|
+
* arguments, so the task record is found by parent, prompt and label: the oldest one created since
|
|
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.
|
|
213
|
+
*/
|
|
214
|
+
export function evalAgentSession(parentPiId, args, claimed, after) {
|
|
215
|
+
if (parentPiId === undefined || typeof args.prompt !== "string")
|
|
216
|
+
return undefined;
|
|
217
|
+
let best;
|
|
218
|
+
for (const { id, path } of taskFiles()) {
|
|
219
|
+
if (claimed.has(id))
|
|
220
|
+
continue;
|
|
221
|
+
const data = childRecord(id, path);
|
|
222
|
+
if (data?.parent_session_id !== parentPiId)
|
|
223
|
+
continue;
|
|
224
|
+
const spec = record(data.spawn_spec) ? data.spawn_spec : {};
|
|
225
|
+
if (!recordedMatch(args.prompt, spec.prompt))
|
|
226
|
+
continue;
|
|
227
|
+
if (typeof args.label === "string" && !recordedMatch(args.label, data.name))
|
|
228
|
+
continue;
|
|
229
|
+
const created = typeof data.created_at === "string" ? Date.parse(data.created_at) : Number.NaN;
|
|
230
|
+
// The cell's start is taken when its card opens, a moment after OmO starts it.
|
|
231
|
+
if (!Number.isFinite(created) || created < after - 5_000)
|
|
232
|
+
continue;
|
|
233
|
+
if (best === undefined || created < best.created)
|
|
234
|
+
best = { id, created, data };
|
|
235
|
+
}
|
|
236
|
+
if (best === undefined)
|
|
237
|
+
return null;
|
|
238
|
+
claimed.add(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
|
+
};
|
|
250
|
+
}
|
|
196
251
|
export function findTask(id, parentPiId) {
|
|
197
252
|
if (!taskId(id))
|
|
198
253
|
return undefined;
|
package/dist/tool-map.d.ts
CHANGED
|
@@ -25,6 +25,28 @@ export declare function resultText(content: unknown): string;
|
|
|
25
25
|
/** Only edit cards need patch metadata; write cards already preview input.content. */
|
|
26
26
|
export declare function resultMetadata(tool: string, result: ToolResult): Record<string, unknown>;
|
|
27
27
|
export declare function mapTool(omoName: string, args: unknown, result?: ToolResult): MappedTool;
|
|
28
|
+
/**
|
|
29
|
+
* What an eval cell started that has a session of its own. Its other calls are not shown:
|
|
30
|
+
* the cell's output already holds what it printed, and OmO records only a one-line preview.
|
|
31
|
+
*/
|
|
32
|
+
export interface NestedSessions {
|
|
33
|
+
/** The monitor child session for a recorded `monitor` call. */
|
|
34
|
+
monitor?: (args: Record<string, unknown>) => 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;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Finished eval calls, from the raw RPC capture of installed omo-ai 5.1.33 / senpi runtime, 2026-10-17.
|
|
47
|
+
* Monitors and `agent()` calls (recorded as `__agent__`) become subagent cards; everything else is skipped.
|
|
48
|
+
*/
|
|
49
|
+
export declare function nestedToolParts(parent: OCToolPart, details: unknown, sessions?: NestedSessions): OCToolPart[];
|
|
28
50
|
/** One card at each call's original block index; result blocks are not cards.
|
|
29
51
|
* Shared by RPC snapshots and history so ids and renderer state survive reloads.
|
|
30
52
|
*/
|
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,6 +135,139 @@ export function mapTool(omoName, args, result) {
|
|
|
134
135
|
...(Object.keys(metadata).length > 0 ? { metadata } : {}),
|
|
135
136
|
};
|
|
136
137
|
}
|
|
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) {
|
|
164
|
+
return {
|
|
165
|
+
tool: "task",
|
|
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
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Finished eval calls, from the raw RPC capture of installed omo-ai 5.1.33 / senpi runtime, 2026-10-17.
|
|
177
|
+
* Monitors and `agent()` calls (recorded as `__agent__`) become subagent cards; everything else is skipped.
|
|
178
|
+
*/
|
|
179
|
+
export function nestedToolParts(parent, details, sessions = {}) {
|
|
180
|
+
if (!record(details) || !Array.isArray(details.toolCalls))
|
|
181
|
+
return [];
|
|
182
|
+
const parts = [];
|
|
183
|
+
const start = parent.state.status === "pending" ? 0 : parent.state.time.start;
|
|
184
|
+
const claimed = new Set();
|
|
185
|
+
details.toolCalls.forEach((entry, index) => {
|
|
186
|
+
let failure;
|
|
187
|
+
if (!record(entry) ||
|
|
188
|
+
(entry.name !== "monitor" && entry.name !== "__agent__" && entry.name !== "task") ||
|
|
189
|
+
typeof entry.ok !== "boolean" ||
|
|
190
|
+
typeof entry.durationMs !== "number" ||
|
|
191
|
+
!Number.isFinite(entry.durationMs) ||
|
|
192
|
+
entry.durationMs < 0)
|
|
193
|
+
return;
|
|
194
|
+
const args = record(entry.args) ? entry.args : {};
|
|
195
|
+
let mapped;
|
|
196
|
+
if (entry.name === "monitor") {
|
|
197
|
+
const sessionID = entry.ok ? sessions.monitor?.(args) : undefined;
|
|
198
|
+
if (sessionID === undefined)
|
|
199
|
+
return; // nothing to open; the cell's code already shows the call
|
|
200
|
+
const command = typeof args.command === "string" ? args.command : "";
|
|
201
|
+
const label = typeof args.description === "string" && args.description !== "" ? args.description : command;
|
|
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);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
const prompt = typeof args.prompt === "string" ? args.prompt : "";
|
|
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)";
|
|
240
|
+
}
|
|
241
|
+
const key = typeof entry.callId === "string" ? `id:${encodeURIComponent(entry.callId)}` : `index:${index}`;
|
|
242
|
+
const callID = `${encodeURIComponent(parent.callID)}:${key}`;
|
|
243
|
+
const state = {
|
|
244
|
+
input: mapped.input,
|
|
245
|
+
title: mapped.title,
|
|
246
|
+
output: "",
|
|
247
|
+
metadata: { ...mapped.metadata, nested: true, parentCallID: parent.callID, durationMs: entry.durationMs },
|
|
248
|
+
time: { start, end: start + entry.durationMs },
|
|
249
|
+
};
|
|
250
|
+
const failed = !entry.ok || failure !== undefined;
|
|
251
|
+
parts.push({
|
|
252
|
+
id: `prt_${parent.sessionID}_nested_${callID}`,
|
|
253
|
+
sessionID: parent.sessionID,
|
|
254
|
+
messageID: parent.messageID,
|
|
255
|
+
type: "tool",
|
|
256
|
+
callID: `nested:${callID}`,
|
|
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
|
|
261
|
+
? { ...state, status: "completed" }
|
|
262
|
+
: {
|
|
263
|
+
...state,
|
|
264
|
+
status: "error",
|
|
265
|
+
error: failure ?? (typeof entry.error === "string" && entry.error !== "" ? entry.error : "failed"),
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
return parts;
|
|
270
|
+
}
|
|
137
271
|
/** JSON first, then the literal-only Python repr used by older session files.
|
|
138
272
|
* Never execute provider content; invalid or incomplete literals are skipped.
|
|
139
273
|
*/
|
|
@@ -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.
|
|
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
|
}
|