@pasko70/pibo 2.2.5 → 2.3.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 (31) hide show
  1. package/dist/agent-runtime/context-build.js +5 -2
  2. package/dist/agent-runtimes/pi/adapter.js +1 -1
  3. package/dist/agent-runtimes/pi/runtime.js +25 -16
  4. package/dist/apps/chat-ui/assets/{dist-BK7eaoLT.js → dist-Byygd1lH.js} +1 -1
  5. package/dist/apps/chat-ui/assets/{dist-fNgdnmyE.js → dist-C9BrS7sL.js} +1 -1
  6. package/dist/apps/chat-ui/assets/{dist-wq3P0LZj.js → dist-CUcAofmV.js} +1 -1
  7. package/dist/apps/chat-ui/assets/{dist-BCRR_a0Z.js → dist-D4RU6xu3.js} +1 -1
  8. package/dist/apps/chat-ui/assets/{dist-CTUT484B.js → dist-DusFwy0L.js} +1 -1
  9. package/dist/apps/chat-ui/assets/{index-DsFKL_EZ.js → index-Bifi_kjN.js} +90 -90
  10. package/dist/apps/chat-ui/index.html +1 -1
  11. package/dist/apps/chat-vscode-web/assets/{index-R4FTxr78.js → index-WsLm1mo3.js} +5 -5
  12. package/dist/apps/chat-vscode-web/index.html +1 -1
  13. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  14. package/dist/apps/vscode-artifacts/{pibo-vscode-ext-2.2.5.vsix → pibo-vscode-ext-2.3.0.vsix} +0 -0
  15. package/dist/core/codex-compat.js +1 -3
  16. package/dist/core/context-build.js +16 -7
  17. package/dist/core/session-router.js +221 -45
  18. package/dist/data/ingest-service.js +12 -0
  19. package/dist/debug/agents.js +391 -0
  20. package/dist/debug/index.js +7 -0
  21. package/dist/index.js +1 -1
  22. package/dist/session-ui/delegation.js +4 -2
  23. package/dist/shared/trace-async-agent-runs.js +4 -2
  24. package/dist/shared/trace-event-projection.js +8 -2
  25. package/dist/shared/trace-subagent-links.js +19 -6
  26. package/dist/subagents/observations.js +149 -0
  27. package/dist/subagents/tool.js +177 -46
  28. package/dist/tools/session-service.js +1 -0
  29. package/dist/tools/session-tool-set.js +9 -5
  30. package/npm-shrinkwrap.json +2 -2
  31. package/package.json +1 -1
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <meta name="theme-color" content="#101d22" />
7
7
  <title>Pibo</title>
8
- <script type="module" crossorigin src="/apps/chat-vscode/assets/index-R4FTxr78.js"></script>
8
+ <script type="module" crossorigin src="/apps/chat-vscode/assets/index-WsLm1mo3.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/apps/chat-vscode/assets/index-CF1-9PKU.css">
10
10
  </head>
11
11
  <body>
@@ -1,4 +1,3 @@
1
- const CODEX_COMPAT_SUBAGENTS = ["default", "explorer", "worker"];
2
1
  function currentDate() {
3
2
  return new Date().toISOString().slice(0, 10);
4
3
  }
@@ -15,7 +14,7 @@ export function buildCodexCompatSystemPrompt(options) {
15
14
  const compatibilityInstructions = [
16
15
  "# Codex-Compatible Runtime",
17
16
  "You are running in Pibo through the codex-compat profile. Match Codex-style tool use where the exposed Pibo tools support it, while staying truthful about implemented behavior.",
18
- "Use Pibo's pibo_run_* tools and generated pibo_subagent_* tools for parallel work, yielded runs, and child-agent lifecycle management.",
17
+ "Use Pibo's pibo_run_* and pibo_agents_* tools for parallel work, yielded runs, and delegated-agent lifecycle management.",
19
18
  "Use direct execution for normal coding tasks. If a structured planning or user-input tool is not present, ask concise questions in normal chat.",
20
19
  "When web_search is selected by the profile, use it for current or externally sourced information.",
21
20
  childInstructions,
@@ -24,7 +23,6 @@ export function buildCodexCompatSystemPrompt(options) {
24
23
  ` <shell>${options.shell}</shell>`,
25
24
  ` <current_date>${options.currentDate ?? currentDate()}</current_date>`,
26
25
  ` <timezone>${options.timezone ?? currentTimezone()}</timezone>`,
27
- ` <subagents>${CODEX_COMPAT_SUBAGENTS.join(", ")}</subagents>`,
28
26
  "</environment_context>",
29
27
  options.baseSystemPrompt,
30
28
  ];
@@ -139,11 +139,20 @@ function sanitizeNode(input, parentId, order = 0) {
139
139
  function countNodes(nodes) {
140
140
  return nodes.reduce((count, node) => count + 1 + countNodes(node.children ?? []), 0);
141
141
  }
142
- function inspectionSubagentRunner() {
142
+ function inspectionAgentsController() {
143
+ const fail = () => {
144
+ throw new Error("Context build inspection cannot execute delegated-agent tools");
145
+ };
143
146
  return {
144
- async runSubagent() {
145
- throw new Error("Context build inspection cannot execute subagents");
146
- },
147
+ sendMessage: fail,
148
+ listAgents: () => [],
149
+ observe: (input) => ({
150
+ filters: input,
151
+ observations: [],
152
+ nextAfterSequence: input.afterSequence ?? 0,
153
+ truncated: false,
154
+ }),
155
+ killAgent: fail,
147
156
  };
148
157
  }
149
158
  function inspectionRunToolController() {
@@ -196,8 +205,8 @@ function toolDefinitionSchema(definition, toolInfo) {
196
205
  function generatedOriginForTool(name, profile) {
197
206
  if (name === "runtime")
198
207
  return "Generated Pibo runtime tool selected by the profile.";
199
- if (name.startsWith("pibo_subagent_"))
200
- return "Generated subagent tool from the profile's subagent list.";
208
+ if (name.startsWith("pibo_agents_"))
209
+ return "Generated shared agent-management tool from the profile's delegated-agent list.";
201
210
  if (name.startsWith("pibo_run_"))
202
211
  return "Generated run-control tool from the pibo-run-control capability package.";
203
212
  if (PIBO_GOAL_TOOL_NAMES.includes(name))
@@ -298,7 +307,7 @@ export async function inspectPiboContextBuild(options = {}) {
298
307
  profile: inspectionProfile,
299
308
  activeModel: undefined,
300
309
  persistSession: false,
301
- subagentRunner: options.subagentRunner ?? (hasEnabledSubagents ? inspectionSubagentRunner() : undefined),
310
+ agentsController: options.agentsController ?? (hasEnabledSubagents ? inspectionAgentsController() : undefined),
302
311
  runToolController: options.runToolController ?? (hasYieldableTools ? inspectionRunToolController() : undefined),
303
312
  });
304
313
  try {
@@ -3,7 +3,7 @@ import { InitialSessionContext, } from "./profiles.js";
3
3
  import { createDefaultPiboPluginRegistry, createPiboProfileFromRegistryOrDefault, resolvePiboProfileNameFromRegistryOrDefault, selectDefaultPiboProfileName } from "../plugins/builtin.js";
4
4
  import { RuntimeRoutedSession as RoutedSession, } from "../agent-runtime/routed-session.js";
5
5
  import { runtimeSessionErrorDetails } from "./session-errors.js";
6
- import { createSubagentToolName } from "../subagents/tool.js";
6
+ import { normalizePiboAgentObservationCursor, normalizePiboAgentObservationLimit, normalizePiboAgentObservationOrder, parsePiboAgentObservationTimestamp, piboAgentObservationDetails, piboAgentObservationKind, piboAgentObservationRole, piboAgentObservationSourceFromEvent, piboAgentObservationText, } from "../subagents/observations.js";
7
7
  import { PiboRunRegistry } from "../runs/registry.js";
8
8
  import { PiboRunExecutionTimeoutError } from "../runs/lifecycle.js";
9
9
  import { PiboRunResourceLimitError } from "../runs/resource-isolation.js";
@@ -32,6 +32,7 @@ import { PORTABLE_HISTORY_HANDOFF_METADATA_KEY, PORTABLE_HISTORY_LAST_IMPORT_MET
32
32
  const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
33
33
  const DEFAULT_SUBAGENT_MAX_DEPTH = 1;
34
34
  const MAX_SUBAGENT_THREAD_KEY_BYTES = 512;
35
+ const MAX_AGENT_OBSERVATIONS = 5_000;
35
36
  const DEFAULT_ROUTED_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
36
37
  const DEFAULT_ROUTED_SESSION_DISPOSE_TIMEOUT_MS = 30 * 1000;
37
38
  export const LOOP_RUNTIME_RETRY_DEFAULTS = {
@@ -235,6 +236,9 @@ export class PiboSessionRouter {
235
236
  runtimeResourceSessions = new Map();
236
237
  runtimeAuthFingerprints = new Map();
237
238
  activeSubagentChildren = new Map();
239
+ agentObservations = [];
240
+ agentObservationEvictedThroughByParent = new Map();
241
+ nextAgentObservationSequence = 1;
238
242
  scheduledRunReminders = new Map();
239
243
  runReminderGenerations = new Map();
240
244
  runCancellationHandlers = new Map();
@@ -563,32 +567,40 @@ export class PiboSessionRouter {
563
567
  }
564
568
  }
565
569
  descendantSessionIds(parentId) {
566
- const output = [];
570
+ const childrenByParent = new Map();
567
571
  for (const session of this.sessionStore.list?.() ?? []) {
568
- if (session.parentId !== parentId)
572
+ if (!session.parentId)
569
573
  continue;
570
- output.push(session.id, ...this.descendantSessionIds(session.id));
574
+ const children = childrenByParent.get(session.parentId) ?? [];
575
+ children.push(session);
576
+ childrenByParent.set(session.parentId, children);
577
+ }
578
+ const output = [];
579
+ const seen = new Set([parentId]);
580
+ const stack = [...(childrenByParent.get(parentId) ?? [])].reverse();
581
+ while (stack.length > 0) {
582
+ const session = stack.pop();
583
+ if (seen.has(session.id))
584
+ continue;
585
+ seen.add(session.id);
586
+ output.push(session.id);
587
+ const children = childrenByParent.get(session.id);
588
+ if (children)
589
+ stack.push(...[...children].reverse());
571
590
  }
572
591
  return output;
573
592
  }
574
593
  async killChildSessions(parentId, options) {
575
594
  const killed = [];
576
595
  const cancelledRuns = [];
577
- const allSessions = this.sessionStore.list?.() ?? [];
578
- for (const session of allSessions) {
579
- if (session.parentId === parentId) {
580
- const childSession = this.sessions.get(session.id);
581
- if (childSession) {
582
- killed.push(await childSession.kill());
583
- }
584
- if (options?.includeRuns) {
585
- const runs = this.runRegistry.cancelControllerRuns(session.id);
586
- cancelledRuns.push(...runs.map((r) => r.runId));
587
- await this.invokeRunCancellationHandlers(runs);
588
- }
589
- const nested = await this.killChildSessions(session.id, options);
590
- killed.push(...nested.killed);
591
- cancelledRuns.push(...nested.cancelledRuns);
596
+ for (const id of this.descendantSessionIds(parentId)) {
597
+ const childSession = this.sessions.get(id);
598
+ if (childSession)
599
+ killed.push(await childSession.kill());
600
+ if (options?.includeRuns) {
601
+ const runs = this.runRegistry.cancelControllerRuns(id);
602
+ cancelledRuns.push(...runs.map((run) => run.runId));
603
+ await this.invokeRunCancellationHandlers(runs);
592
604
  }
593
605
  }
594
606
  return { killed, cancelledRuns };
@@ -935,6 +947,8 @@ export class PiboSessionRouter {
935
947
  await this.runtimeResourceService.dispose();
936
948
  this.runtimeResourceSessions.clear();
937
949
  this.activeSubagentChildren.clear();
950
+ this.agentObservations.length = 0;
951
+ this.agentObservationEvictedThroughByParent.clear();
938
952
  await this.telemetryWriter?.dispose();
939
953
  const lifecycleFailures = [...authDisposals, ...webAppDisposals]
940
954
  .flatMap((result) => result.status === "rejected" ? [result.reason] : []);
@@ -1099,7 +1113,7 @@ export class PiboSessionRouter {
1099
1113
  historyHandoff = { mode: "fresh" };
1100
1114
  }
1101
1115
  const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(sessionProfile, modelDefaults) ?? false;
1102
- const subagentRunner = this.createSubagentRunner(piboSession.id);
1116
+ const agentsController = this.createAgentsController(piboSession.id);
1103
1117
  const runToolController = this.createRunToolController(piboSession.id);
1104
1118
  const codeRuntimeToolController = this.runtimeRegistry.createController(piboSession.id);
1105
1119
  const sessionGeneration = randomUUID();
@@ -1116,7 +1130,7 @@ export class PiboSessionRouter {
1116
1130
  profile: sessionProfile,
1117
1131
  cwd: workspace,
1118
1132
  getActiveMessage: () => session?.getActiveMessage(),
1119
- subagentRunner,
1133
+ agentsController,
1120
1134
  runToolController,
1121
1135
  runtimeToolController: codeRuntimeToolController,
1122
1136
  });
@@ -1162,7 +1176,7 @@ export class PiboSessionRouter {
1162
1176
  getActiveMessage: () => session?.getActiveMessage(),
1163
1177
  },
1164
1178
  services: {
1165
- subagentRunner,
1179
+ agentsController,
1166
1180
  runToolController,
1167
1181
  codeRuntimeToolController,
1168
1182
  portableTools,
@@ -1583,12 +1597,12 @@ export class PiboSessionRouter {
1583
1597
  id: randomUUID(),
1584
1598
  })));
1585
1599
  }
1586
- createSubagentRunner(parentPiboSessionId) {
1600
+ createAgentsController(parentPiboSessionId) {
1587
1601
  return {
1588
- runSubagent: async ({ subagent, message, threadKey, toolCallId, signal }) => {
1602
+ sendMessage: async ({ subagent, message, threadKey, toolCallId, signal }) => {
1589
1603
  this.assertSubagentDepth(parentPiboSessionId, subagent);
1590
1604
  const child = this.resolveSubagentSession(parentPiboSessionId, subagent, threadKey);
1591
- const toolName = createSubagentToolName(subagent.name);
1605
+ const resolvedThreadKey = typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : "";
1592
1606
  const event = {
1593
1607
  type: "message",
1594
1608
  piboSessionId: child.id,
@@ -1600,22 +1614,154 @@ export class PiboSessionRouter {
1600
1614
  type: "subagent_session",
1601
1615
  piboSessionId: parentPiboSessionId,
1602
1616
  toolCallId,
1603
- toolName,
1617
+ toolName: "pibo_agents_send_message",
1604
1618
  subagentName: subagent.name,
1605
1619
  childPiboSessionId: child.id,
1606
- threadKey: typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : undefined,
1620
+ threadKey: resolvedThreadKey,
1607
1621
  });
1608
1622
  const untrack = this.trackActiveSubagent(parentPiboSessionId, child.id);
1609
1623
  try {
1610
1624
  const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS, signal);
1611
- return { piboSessionId: child.id, eventId: event.id, reply };
1625
+ return {
1626
+ agentId: child.id,
1627
+ name: subagent.name,
1628
+ profile: child.profile,
1629
+ threadKey: resolvedThreadKey,
1630
+ eventId: event.id,
1631
+ reply,
1632
+ };
1612
1633
  }
1613
1634
  finally {
1614
1635
  untrack();
1615
1636
  }
1616
1637
  },
1638
+ listAgents: () => this.listManagedAgents(parentPiboSessionId),
1639
+ observe: (input) => this.observeManagedAgents(parentPiboSessionId, input),
1640
+ killAgent: async (agentId) => await this.killManagedAgent(parentPiboSessionId, agentId),
1641
+ };
1642
+ }
1643
+ listManagedAgents(parentPiboSessionId) {
1644
+ return this.sessionStore.find({
1645
+ channel: "pibo.subagents",
1646
+ kind: "subagent",
1647
+ parentId: parentPiboSessionId,
1648
+ }).map((session) => {
1649
+ const live = this.sessions.get(session.id)?.getStatus();
1650
+ const killed = session.metadata?.agentStatus === "killed";
1651
+ const running = !killed && Boolean(live && (live.processing || live.streaming || live.queuedMessages > 0));
1652
+ return {
1653
+ agentId: session.id,
1654
+ name: typeof session.metadata?.subagentName === "string" ? session.metadata.subagentName : session.profile,
1655
+ profile: session.profile,
1656
+ ...(typeof session.metadata?.threadKey === "string" ? { threadKey: session.metadata.threadKey } : {}),
1657
+ status: killed ? "killed" : running ? "running" : "idle",
1658
+ createdAt: session.createdAt,
1659
+ updatedAt: session.updatedAt,
1660
+ ...(session.activeModel ? { activeModel: { ...session.activeModel } } : {}),
1661
+ };
1662
+ });
1663
+ }
1664
+ requireManagedAgent(parentPiboSessionId, agentId) {
1665
+ const session = this.sessionStore.get(agentId);
1666
+ if (!session
1667
+ || session.parentId !== parentPiboSessionId
1668
+ || session.channel !== "pibo.subagents"
1669
+ || session.kind !== "subagent") {
1670
+ throw new Error(`Agent "${agentId}" is not owned by Pibo session "${parentPiboSessionId}".`);
1671
+ }
1672
+ return session;
1673
+ }
1674
+ observeManagedAgents(parentPiboSessionId, input) {
1675
+ const order = normalizePiboAgentObservationOrder(input.order);
1676
+ const limit = normalizePiboAgentObservationLimit(input.limit);
1677
+ const afterSequence = normalizePiboAgentObservationCursor(input.afterSequence);
1678
+ const since = parsePiboAgentObservationTimestamp(input.since, "since");
1679
+ const until = parsePiboAgentObservationTimestamp(input.until, "until");
1680
+ if (since !== undefined && until !== undefined && since > until) {
1681
+ throw new Error("Agent observation since must not be after until.");
1682
+ }
1683
+ const agentIds = input.agentIds ? new Set(input.agentIds) : undefined;
1684
+ if (agentIds) {
1685
+ for (const agentId of agentIds)
1686
+ this.requireManagedAgent(parentPiboSessionId, agentId);
1687
+ }
1688
+ const names = input.names ? new Set(input.names) : undefined;
1689
+ const threadKeys = input.threadKeys ? new Set(input.threadKeys) : undefined;
1690
+ const eventTypes = input.eventTypes ? new Set(input.eventTypes) : undefined;
1691
+ const kinds = input.kinds ? new Set(input.kinds) : undefined;
1692
+ const textContains = input.textContains?.toLowerCase();
1693
+ const matches = this.agentObservations.filter((observation) => {
1694
+ if (observation.managingParentId !== parentPiboSessionId)
1695
+ return false;
1696
+ if (agentIds && !agentIds.has(observation.agentId))
1697
+ return false;
1698
+ if (names && !names.has(observation.name))
1699
+ return false;
1700
+ if (threadKeys && (!observation.threadKey || !threadKeys.has(observation.threadKey)))
1701
+ return false;
1702
+ if (eventTypes && !eventTypes.has(observation.eventType))
1703
+ return false;
1704
+ if (kinds && !kinds.has(observation.kind))
1705
+ return false;
1706
+ if (afterSequence !== undefined && observation.sequence <= afterSequence)
1707
+ return false;
1708
+ const createdAt = Date.parse(observation.createdAt);
1709
+ if (since !== undefined && createdAt < since)
1710
+ return false;
1711
+ if (until !== undefined && createdAt > until)
1712
+ return false;
1713
+ if (textContains && !(observation.text ?? "").toLowerCase().includes(textContains))
1714
+ return false;
1715
+ return true;
1716
+ });
1717
+ const cursorPolling = afterSequence !== undefined;
1718
+ const ordered = cursorPolling
1719
+ ? matches.slice(0, limit)
1720
+ : [...matches].sort((left, right) => order === "asc" ? left.sequence - right.sequence : right.sequence - left.sequence).slice(0, limit);
1721
+ if (cursorPolling && order === "desc")
1722
+ ordered.reverse();
1723
+ const selected = ordered.map((observation) => {
1724
+ const { managingParentId: _managingParentId, details, ...visible } = observation;
1725
+ return input.includeDetails === true && details !== undefined ? { ...visible, details } : visible;
1726
+ });
1727
+ const evictedThrough = this.agentObservationEvictedThroughByParent.get(parentPiboSessionId) ?? 0;
1728
+ const retentionTruncated = afterSequence === undefined ? evictedThrough > 0 : afterSequence < evictedThrough;
1729
+ const nextAfterSequence = selected.reduce((maximum, observation) => Math.max(maximum, observation.sequence), afterSequence === undefined ? 0 : Math.max(afterSequence, evictedThrough));
1730
+ return {
1731
+ filters: {
1732
+ ...input,
1733
+ ...(afterSequence !== undefined ? { afterSequence } : {}),
1734
+ order,
1735
+ limit,
1736
+ includeDetails: input.includeDetails === true,
1737
+ },
1738
+ observations: selected,
1739
+ nextAfterSequence,
1740
+ truncated: retentionTruncated || matches.length > selected.length,
1617
1741
  };
1618
1742
  }
1743
+ async killManagedAgent(parentPiboSessionId, agentId) {
1744
+ const child = this.requireManagedAgent(parentPiboSessionId, agentId);
1745
+ const ids = [agentId, ...this.descendantSessionIds(agentId)];
1746
+ const idSet = new Set(ids);
1747
+ const cancelledRuns = this.runRegistry.listAll({ includeConsumed: true, includeDetached: true })
1748
+ .filter((run) => idSet.has(run.controllerPiboSessionId) && !isTerminalRunStatus(run.status))
1749
+ .map((run) => run.runId);
1750
+ await Promise.allSettled(ids.flatMap((id) => this.sessions.has(id)
1751
+ ? [this.emit({ type: "execution", piboSessionId: id, action: "abort", id: randomUUID() })]
1752
+ : []));
1753
+ if (child.metadata?.agentStatus !== "killed") {
1754
+ this.sessionStore.update(agentId, {
1755
+ metadata: {
1756
+ ...(child.metadata ?? {}),
1757
+ agentStatus: "killed",
1758
+ killedAt: new Date().toISOString(),
1759
+ },
1760
+ });
1761
+ }
1762
+ await this.disposeSessionSubtree(agentId, `killed by parent ${parentPiboSessionId}`, { cancelRuns: true });
1763
+ return { agentId, killed: ids, cancelledRuns };
1764
+ }
1619
1765
  async invokeRunCancellationHandlers(runs) {
1620
1766
  const results = await Promise.allSettled(runs.map(async (run) => {
1621
1767
  const cancel = this.runCancellationHandlers.get(run.runId);
@@ -1735,35 +1881,33 @@ export class PiboSessionRouter {
1735
1881
  const targetProfile = resolvePiboProfileNameFromRegistryOrDefault(this.pluginRegistry, subagent.targetProfile);
1736
1882
  const parent = this.resolvePiboSession(parentPiboSessionId);
1737
1883
  const resolvedThreadKey = resolveSubagentThreadKey(threadKey);
1738
- const baseMetadata = {
1884
+ const identityMetadata = {
1739
1885
  subagentName: subagent.name,
1740
- subagentToolName: createSubagentToolName(subagent.name),
1741
1886
  threadKey: resolvedThreadKey,
1742
1887
  };
1743
- const metadata = withWorkflowSessionKind(baseMetadata, "subagent");
1888
+ const metadata = withWorkflowSessionKind({
1889
+ ...identityMetadata,
1890
+ subagentToolName: "pibo_agents_send_message",
1891
+ agentStatus: "active",
1892
+ }, "subagent");
1744
1893
  const parentChatRoomId = typeof parent.metadata?.chatRoomId === "string" ? parent.metadata.chatRoomId : undefined;
1745
1894
  if (parentChatRoomId)
1746
1895
  metadata.chatRoomId = parentChatRoomId;
1747
1896
  const newSessionMetadata = subagent.thinkingLevel
1748
1897
  ? { ...metadata, initialThinkingLevel: subagent.thinkingLevel }
1749
1898
  : metadata;
1750
- const legacyMetadata = { ...baseMetadata };
1751
- const legacyMetadataWithChatRoom = parentChatRoomId
1752
- ? { ...baseMetadata, chatRoomId: parentChatRoomId }
1753
- : undefined;
1754
- const findExisting = (candidate) => candidate
1755
- ? this.sessionStore.find({
1756
- channel: "pibo.subagents",
1757
- kind: "subagent",
1758
- parentId: parent.id,
1759
- profile: targetProfile,
1760
- metadata: candidate,
1761
- })[0]
1762
- : undefined;
1763
- const existing = findExisting(metadata) ?? findExisting(legacyMetadataWithChatRoom) ?? findExisting(legacyMetadata);
1899
+ const existing = this.sessionStore.find({
1900
+ channel: "pibo.subagents",
1901
+ kind: "subagent",
1902
+ parentId: parent.id,
1903
+ profile: targetProfile,
1904
+ metadata: identityMetadata,
1905
+ }).find((candidate) => candidate.metadata?.agentStatus !== "killed");
1764
1906
  if (existing) {
1765
1907
  const updatedMetadata = withWorkflowSessionKind({
1766
1908
  ...(existing.metadata ?? {}),
1909
+ subagentToolName: "pibo_agents_send_message",
1910
+ agentStatus: "active",
1767
1911
  ...(parentChatRoomId ? { chatRoomId: parentChatRoomId } : {}),
1768
1912
  }, "subagent");
1769
1913
  if (JSON.stringify(updatedMetadata) !== JSON.stringify(existing.metadata ?? {})) {
@@ -1793,8 +1937,40 @@ export class PiboSessionRouter {
1793
1937
  });
1794
1938
  return activeModel ? this.sessionStore.update(childSession.id, { activeModel }) ?? childSession : childSession;
1795
1939
  }
1940
+ recordAgentObservation(event, session) {
1941
+ if (!session || session.kind !== "subagent" || session.channel !== "pibo.subagents" || !session.parentId)
1942
+ return;
1943
+ const name = typeof session.metadata?.subagentName === "string" ? session.metadata.subagentName : session.profile;
1944
+ const source = piboAgentObservationSourceFromEvent(event);
1945
+ const role = piboAgentObservationRole(source);
1946
+ const text = piboAgentObservationText(source);
1947
+ const observation = {
1948
+ managingParentId: session.parentId,
1949
+ sequence: this.nextAgentObservationSequence++,
1950
+ createdAt: new Date().toISOString(),
1951
+ agentId: session.id,
1952
+ name,
1953
+ ...(typeof session.metadata?.threadKey === "string" ? { threadKey: session.metadata.threadKey } : {}),
1954
+ eventType: event.type,
1955
+ kind: piboAgentObservationKind(event.type),
1956
+ ...(role ? { role } : {}),
1957
+ ...(text ? { text } : {}),
1958
+ ...("toolName" in event && typeof event.toolName === "string" ? { toolName: event.toolName } : {}),
1959
+ ...("toolCallId" in event && typeof event.toolCallId === "string" ? { toolCallId: event.toolCallId } : {}),
1960
+ ...(event.type === "tool_execution_finished" ? { isError: event.isError } : event.type === "session_error" ? { isError: true } : {}),
1961
+ details: piboAgentObservationDetails(event),
1962
+ };
1963
+ this.agentObservations.push(observation);
1964
+ if (this.agentObservations.length > MAX_AGENT_OBSERVATIONS) {
1965
+ const evicted = this.agentObservations.splice(0, this.agentObservations.length - MAX_AGENT_OBSERVATIONS);
1966
+ for (const item of evicted) {
1967
+ this.agentObservationEvictedThroughByParent.set(item.managingParentId, Math.max(this.agentObservationEvictedThroughByParent.get(item.managingParentId) ?? 0, item.sequence));
1968
+ }
1969
+ }
1970
+ }
1796
1971
  emitOutput = (event) => {
1797
1972
  const session = this.sessionStore.get(event.piboSessionId);
1973
+ this.recordAgentObservation(event, session);
1798
1974
  this.telemetryRecorder?.recordOutput(event, { session, status: this.sessions.get(event.piboSessionId)?.getStatus() });
1799
1975
  this.signalRegistry.project({ type: "pibo_output", event, session });
1800
1976
  this.pluginRegistry.notifyEvent(event);
@@ -322,8 +322,20 @@ function attributesForOutputEvent(event) {
322
322
  return { inlineText: event.text, source: event.source, queuedMessages: event.queuedMessages };
323
323
  if (event.type === "message_steered")
324
324
  return { inlineText: event.text, source: event.source, activeEventId: event.activeEventId };
325
+ if (event.type === "message_started")
326
+ return { inlineText: event.text, source: event.source };
327
+ if (event.type === "message_finished")
328
+ return { source: event.source };
325
329
  if (event.type === "assistant_message" || event.type === "assistant_delta")
326
330
  return { assistantIndex: event.assistantIndex, contentIndex: event.contentIndex };
331
+ if (event.type === "assistant_usage")
332
+ return {
333
+ inputTokens: event.inputTokens,
334
+ outputTokens: event.outputTokens,
335
+ cacheReadTokens: event.cacheReadTokens,
336
+ cacheWriteTokens: event.cacheWriteTokens,
337
+ totalTokens: event.totalTokens,
338
+ };
327
339
  if (event.type === "thinking_started" || event.type === "thinking_delta" || event.type === "thinking_finished")
328
340
  return { thinkingIndex: event.thinkingIndex, contentIndex: event.contentIndex };
329
341
  if (event.type === "tool_call")