@pasko70/pibo 2.2.4 → 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 (61) hide show
  1. package/dist/agent-runtime/capabilities.js +9 -1
  2. package/dist/agent-runtime/context-build.js +5 -2
  3. package/dist/agent-runtimes/codex-native/adapter.js +5 -0
  4. package/dist/agent-runtimes/omp/adapter.js +5 -0
  5. package/dist/agent-runtimes/omp/turn.js +2 -0
  6. package/dist/agent-runtimes/pi/adapter.js +28 -7
  7. package/dist/agent-runtimes/pi/intent-tracing.js +120 -0
  8. package/dist/agent-runtimes/pi/routed-session.js +25 -8
  9. package/dist/agent-runtimes/pi/runtime.js +28 -16
  10. package/dist/apps/chat/chat-settings-routes.js +44 -3
  11. package/dist/apps/chat/chat-transcription.js +85 -0
  12. package/dist/apps/chat/data/chat-data-mappers.js +4 -4
  13. package/dist/apps/chat/stream.js +23 -9
  14. package/dist/apps/chat/trace-v2.js +1 -0
  15. package/dist/apps/chat/web-app.js +49 -0
  16. package/dist/apps/chat-ui/assets/{dist-luJVFsmq.js → dist-Byygd1lH.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-ltfz1vFX.js → dist-C9BrS7sL.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-rf4HYdoa.js → dist-CUcAofmV.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-CImWCF2M.js → dist-D4RU6xu3.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-BFwTuvyi.js → dist-DusFwy0L.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{index-nZQXUpxE.css → index-BJ56TREg.css} +1 -1
  22. package/dist/apps/chat-ui/assets/index-Bifi_kjN.js +228 -0
  23. package/dist/apps/chat-ui/index.html +2 -2
  24. package/dist/apps/chat-vscode-web/assets/index-WsLm1mo3.js +43 -0
  25. package/dist/apps/chat-vscode-web/index.html +1 -1
  26. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  27. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.3.0.vsix +0 -0
  28. package/dist/core/codex-compat.js +1 -3
  29. package/dist/core/context-build.js +16 -7
  30. package/dist/core/gateway-resource-guard.js +35 -14
  31. package/dist/core/gateway-settings.js +64 -0
  32. package/dist/core/session-router.js +226 -46
  33. package/dist/core/user-settings.js +20 -0
  34. package/dist/data/ingest-service.js +14 -2
  35. package/dist/debug/agents.js +391 -0
  36. package/dist/debug/index.js +14 -2
  37. package/dist/gateway/server.js +2 -0
  38. package/dist/index.js +6 -1
  39. package/dist/plugins/builtin.js +4 -2
  40. package/dist/plugins/openai-chatgpt-transcription.js +12 -0
  41. package/dist/plugins/openai-transcription.js +12 -0
  42. package/dist/plugins/registry.js +23 -0
  43. package/dist/session-ui/delegation.js +4 -2
  44. package/dist/session-ui/terminalRows.js +51 -1
  45. package/dist/shared/trace-async-agent-runs.js +4 -2
  46. package/dist/shared/trace-event-projection.js +12 -2
  47. package/dist/shared/trace-live-reducer.js +4 -0
  48. package/dist/shared/trace-patch-nodes.js +1 -0
  49. package/dist/shared/trace-subagent-links.js +19 -6
  50. package/dist/subagents/observations.js +149 -0
  51. package/dist/subagents/tool.js +177 -46
  52. package/dist/tools/session-service.js +1 -0
  53. package/dist/tools/session-tool-set.js +9 -5
  54. package/dist/transcription/openai-chatgpt.js +148 -0
  55. package/dist/transcription/openai.js +72 -0
  56. package/dist/transcription/types.js +8 -0
  57. package/npm-shrinkwrap.json +2 -2
  58. package/package.json +1 -1
  59. package/dist/apps/chat-ui/assets/index-D2Maa2v1.js +0 -226
  60. package/dist/apps/chat-vscode-web/assets/index-BpurWVnX.js +0 -41
  61. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.2.4.vsix +0 -0
@@ -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";
@@ -15,6 +15,7 @@ import { AgentRuntimeBindingMissingError, AgentRuntimeUnavailableError } from ".
15
15
  import { validateAgentRuntimeProfileCapabilities } from "../agent-runtime/profile-validation.js";
16
16
  import { getDefaultPiboWorkspace } from "./workspace.js";
17
17
  import { loadPiboModelDefaults, selectRequestedFastMode } from "./model-defaults.js";
18
+ import { loadPiboGatewaySettings } from "./gateway-settings.js";
18
19
  import { loadPiboUserSettings } from "./user-settings.js";
19
20
  import { resolvePiboSessionActiveModel } from "./session-model.js";
20
21
  import { isPiboThinkingLevel } from "./thinking.js";
@@ -31,6 +32,7 @@ import { PORTABLE_HISTORY_HANDOFF_METADATA_KEY, PORTABLE_HISTORY_LAST_IMPORT_MET
31
32
  const DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS = 10 * 60 * 1000;
32
33
  const DEFAULT_SUBAGENT_MAX_DEPTH = 1;
33
34
  const MAX_SUBAGENT_THREAD_KEY_BYTES = 512;
35
+ const MAX_AGENT_OBSERVATIONS = 5_000;
34
36
  const DEFAULT_ROUTED_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;
35
37
  const DEFAULT_ROUTED_SESSION_DISPOSE_TIMEOUT_MS = 30 * 1000;
36
38
  export const LOOP_RUNTIME_RETRY_DEFAULTS = {
@@ -234,6 +236,9 @@ export class PiboSessionRouter {
234
236
  runtimeResourceSessions = new Map();
235
237
  runtimeAuthFingerprints = new Map();
236
238
  activeSubagentChildren = new Map();
239
+ agentObservations = [];
240
+ agentObservationEvictedThroughByParent = new Map();
241
+ nextAgentObservationSequence = 1;
237
242
  scheduledRunReminders = new Map();
238
243
  runReminderGenerations = new Map();
239
244
  runCancellationHandlers = new Map();
@@ -562,32 +567,40 @@ export class PiboSessionRouter {
562
567
  }
563
568
  }
564
569
  descendantSessionIds(parentId) {
565
- const output = [];
570
+ const childrenByParent = new Map();
566
571
  for (const session of this.sessionStore.list?.() ?? []) {
567
- if (session.parentId !== parentId)
572
+ if (!session.parentId)
573
+ continue;
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))
568
584
  continue;
569
- output.push(session.id, ...this.descendantSessionIds(session.id));
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());
570
590
  }
571
591
  return output;
572
592
  }
573
593
  async killChildSessions(parentId, options) {
574
594
  const killed = [];
575
595
  const cancelledRuns = [];
576
- const allSessions = this.sessionStore.list?.() ?? [];
577
- for (const session of allSessions) {
578
- if (session.parentId === parentId) {
579
- const childSession = this.sessions.get(session.id);
580
- if (childSession) {
581
- killed.push(await childSession.kill());
582
- }
583
- if (options?.includeRuns) {
584
- const runs = this.runRegistry.cancelControllerRuns(session.id);
585
- cancelledRuns.push(...runs.map((r) => r.runId));
586
- await this.invokeRunCancellationHandlers(runs);
587
- }
588
- const nested = await this.killChildSessions(session.id, options);
589
- killed.push(...nested.killed);
590
- 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);
591
604
  }
592
605
  }
593
606
  return { killed, cancelledRuns };
@@ -934,6 +947,8 @@ export class PiboSessionRouter {
934
947
  await this.runtimeResourceService.dispose();
935
948
  this.runtimeResourceSessions.clear();
936
949
  this.activeSubagentChildren.clear();
950
+ this.agentObservations.length = 0;
951
+ this.agentObservationEvictedThroughByParent.clear();
937
952
  await this.telemetryWriter?.dispose();
938
953
  const lifecycleFailures = [...authDisposals, ...webAppDisposals]
939
954
  .flatMap((result) => result.status === "rejected" ? [result.reason] : []);
@@ -1098,7 +1113,7 @@ export class PiboSessionRouter {
1098
1113
  historyHandoff = { mode: "fresh" };
1099
1114
  }
1100
1115
  const initialFastMode = resolvePiboSessionInitialFastMode(piboSession) ?? selectRequestedFastMode(sessionProfile, modelDefaults) ?? false;
1101
- const subagentRunner = this.createSubagentRunner(piboSession.id);
1116
+ const agentsController = this.createAgentsController(piboSession.id);
1102
1117
  const runToolController = this.createRunToolController(piboSession.id);
1103
1118
  const codeRuntimeToolController = this.runtimeRegistry.createController(piboSession.id);
1104
1119
  const sessionGeneration = randomUUID();
@@ -1115,7 +1130,7 @@ export class PiboSessionRouter {
1115
1130
  profile: sessionProfile,
1116
1131
  cwd: workspace,
1117
1132
  getActiveMessage: () => session?.getActiveMessage(),
1118
- subagentRunner,
1133
+ agentsController,
1119
1134
  runToolController,
1120
1135
  runtimeToolController: codeRuntimeToolController,
1121
1136
  });
@@ -1161,7 +1176,7 @@ export class PiboSessionRouter {
1161
1176
  getActiveMessage: () => session?.getActiveMessage(),
1162
1177
  },
1163
1178
  services: {
1164
- subagentRunner,
1179
+ agentsController,
1165
1180
  runToolController,
1166
1181
  codeRuntimeToolController,
1167
1182
  portableTools,
@@ -1582,12 +1597,12 @@ export class PiboSessionRouter {
1582
1597
  id: randomUUID(),
1583
1598
  })));
1584
1599
  }
1585
- createSubagentRunner(parentPiboSessionId) {
1600
+ createAgentsController(parentPiboSessionId) {
1586
1601
  return {
1587
- runSubagent: async ({ subagent, message, threadKey, toolCallId, signal }) => {
1602
+ sendMessage: async ({ subagent, message, threadKey, toolCallId, signal }) => {
1588
1603
  this.assertSubagentDepth(parentPiboSessionId, subagent);
1589
1604
  const child = this.resolveSubagentSession(parentPiboSessionId, subagent, threadKey);
1590
- const toolName = createSubagentToolName(subagent.name);
1605
+ const resolvedThreadKey = typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : "";
1591
1606
  const event = {
1592
1607
  type: "message",
1593
1608
  piboSessionId: child.id,
@@ -1599,22 +1614,154 @@ export class PiboSessionRouter {
1599
1614
  type: "subagent_session",
1600
1615
  piboSessionId: parentPiboSessionId,
1601
1616
  toolCallId,
1602
- toolName,
1617
+ toolName: "pibo_agents_send_message",
1603
1618
  subagentName: subagent.name,
1604
1619
  childPiboSessionId: child.id,
1605
- threadKey: typeof child.metadata?.threadKey === "string" ? child.metadata.threadKey : undefined,
1620
+ threadKey: resolvedThreadKey,
1606
1621
  });
1607
1622
  const untrack = this.trackActiveSubagent(parentPiboSessionId, child.id);
1608
1623
  try {
1609
1624
  const reply = await this.emitMessageAndWaitForReply(event, subagent.timeoutMs ?? DEFAULT_SUBAGENT_REPLY_TIMEOUT_MS, signal);
1610
- 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
+ };
1611
1633
  }
1612
1634
  finally {
1613
1635
  untrack();
1614
1636
  }
1615
1637
  },
1638
+ listAgents: () => this.listManagedAgents(parentPiboSessionId),
1639
+ observe: (input) => this.observeManagedAgents(parentPiboSessionId, input),
1640
+ killAgent: async (agentId) => await this.killManagedAgent(parentPiboSessionId, agentId),
1616
1641
  };
1617
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,
1741
+ };
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
+ }
1618
1765
  async invokeRunCancellationHandlers(runs) {
1619
1766
  const results = await Promise.allSettled(runs.map(async (run) => {
1620
1767
  const cancel = this.runCancellationHandlers.get(run.runId);
@@ -1630,7 +1777,10 @@ export class PiboSessionRouter {
1630
1777
  createRunToolController(parentPiboSessionId) {
1631
1778
  return {
1632
1779
  startToolRun: ({ toolName, params, completionPolicy, retryable, maxAttempts, timeoutMs, serviceWarning, resources, execute, cancel }) => {
1633
- const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}`);
1780
+ const admission = this.gatewayWorkAdmission.reserve(`yielded run ${toolName}`, {
1781
+ sessionId: parentPiboSessionId,
1782
+ gatewaySettings: loadPiboGatewaySettings(),
1783
+ });
1634
1784
  if (resources)
1635
1785
  resources.admission = admission.admission;
1636
1786
  const reminderGeneration = this.runReminderGeneration(parentPiboSessionId);
@@ -1731,35 +1881,33 @@ export class PiboSessionRouter {
1731
1881
  const targetProfile = resolvePiboProfileNameFromRegistryOrDefault(this.pluginRegistry, subagent.targetProfile);
1732
1882
  const parent = this.resolvePiboSession(parentPiboSessionId);
1733
1883
  const resolvedThreadKey = resolveSubagentThreadKey(threadKey);
1734
- const baseMetadata = {
1884
+ const identityMetadata = {
1735
1885
  subagentName: subagent.name,
1736
- subagentToolName: createSubagentToolName(subagent.name),
1737
1886
  threadKey: resolvedThreadKey,
1738
1887
  };
1739
- const metadata = withWorkflowSessionKind(baseMetadata, "subagent");
1888
+ const metadata = withWorkflowSessionKind({
1889
+ ...identityMetadata,
1890
+ subagentToolName: "pibo_agents_send_message",
1891
+ agentStatus: "active",
1892
+ }, "subagent");
1740
1893
  const parentChatRoomId = typeof parent.metadata?.chatRoomId === "string" ? parent.metadata.chatRoomId : undefined;
1741
1894
  if (parentChatRoomId)
1742
1895
  metadata.chatRoomId = parentChatRoomId;
1743
1896
  const newSessionMetadata = subagent.thinkingLevel
1744
1897
  ? { ...metadata, initialThinkingLevel: subagent.thinkingLevel }
1745
1898
  : metadata;
1746
- const legacyMetadata = { ...baseMetadata };
1747
- const legacyMetadataWithChatRoom = parentChatRoomId
1748
- ? { ...baseMetadata, chatRoomId: parentChatRoomId }
1749
- : undefined;
1750
- const findExisting = (candidate) => candidate
1751
- ? this.sessionStore.find({
1752
- channel: "pibo.subagents",
1753
- kind: "subagent",
1754
- parentId: parent.id,
1755
- profile: targetProfile,
1756
- metadata: candidate,
1757
- })[0]
1758
- : undefined;
1759
- 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");
1760
1906
  if (existing) {
1761
1907
  const updatedMetadata = withWorkflowSessionKind({
1762
1908
  ...(existing.metadata ?? {}),
1909
+ subagentToolName: "pibo_agents_send_message",
1910
+ agentStatus: "active",
1763
1911
  ...(parentChatRoomId ? { chatRoomId: parentChatRoomId } : {}),
1764
1912
  }, "subagent");
1765
1913
  if (JSON.stringify(updatedMetadata) !== JSON.stringify(existing.metadata ?? {})) {
@@ -1789,8 +1937,40 @@ export class PiboSessionRouter {
1789
1937
  });
1790
1938
  return activeModel ? this.sessionStore.update(childSession.id, { activeModel }) ?? childSession : childSession;
1791
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
+ }
1792
1971
  emitOutput = (event) => {
1793
1972
  const session = this.sessionStore.get(event.piboSessionId);
1973
+ this.recordAgentObservation(event, session);
1794
1974
  this.telemetryRecorder?.recordOutput(event, { session, status: this.sessions.get(event.piboSessionId)?.getStatus() });
1795
1975
  this.signalRegistry.project({ type: "pibo_output", event, session });
1796
1976
  this.pluginRegistry.notifyEvent(event);
@@ -5,6 +5,8 @@ import { sanitizeTelemetryRetentionSettings } from "./telemetry-retention-settin
5
5
  import { sanitizeTelemetryStaleThresholdSettings } from "./telemetry-staleness.js";
6
6
  export const DEFAULT_USER_TIMEZONE = "UTC";
7
7
  export const DEFAULT_WEB_ANNOTATIONS_TOGGLE_SHORTCUT = "Alt+Shift+A";
8
+ export const DEFAULT_TRANSCRIPTION_PROVIDER_ID = "openai-chatgpt";
9
+ const LEGACY_OPENAI_TRANSCRIPTION_PROVIDER_ID = "openai";
8
10
  export const DEFAULT_PIBO_USER_SETTINGS_PATH = "user-settings.json";
9
11
  export function loadPiboUserSettings() {
10
12
  return sanitizeUserSettings(readState().settings);
@@ -37,6 +39,23 @@ export function sanitizeShortcut(value) {
37
39
  const shortcut = value.replace(/[\u0000-\u001f\u007f]/g, "").trim();
38
40
  return shortcut && shortcut.length <= 80 ? shortcut : undefined;
39
41
  }
42
+ export function sanitizeTranscriptionProviderId(value) {
43
+ if (typeof value !== "string")
44
+ return undefined;
45
+ const providerId = value.trim();
46
+ return /^[a-z0-9][a-z0-9._-]{0,79}$/i.test(providerId) ? providerId : undefined;
47
+ }
48
+ export function sanitizeTranscriptionSettings(value) {
49
+ const raw = value && typeof value === "object" && !Array.isArray(value)
50
+ ? value
51
+ : {};
52
+ const providerId = sanitizeTranscriptionProviderId(raw.providerId);
53
+ return {
54
+ providerId: providerId === LEGACY_OPENAI_TRANSCRIPTION_PROVIDER_ID
55
+ ? DEFAULT_TRANSCRIPTION_PROVIDER_ID
56
+ : providerId ?? DEFAULT_TRANSCRIPTION_PROVIDER_ID,
57
+ };
58
+ }
40
59
  export function sanitizeTimezone(value) {
41
60
  if (typeof value !== "string")
42
61
  return undefined;
@@ -58,6 +77,7 @@ function sanitizeUserSettings(value) {
58
77
  return {
59
78
  timezone: sanitizeTimezone(raw.timezone) ?? DEFAULT_USER_TIMEZONE,
60
79
  shortcuts: sanitizeShortcutSettings(raw.shortcuts),
80
+ transcription: sanitizeTranscriptionSettings(raw.transcription),
61
81
  telemetryStaleThresholds: sanitizeTelemetryStaleThresholdSettings(raw.telemetryStaleThresholds),
62
82
  telemetryRetention: sanitizeTelemetryRetentionSettings(raw.telemetryRetention),
63
83
  };
@@ -322,14 +322,26 @@ 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")
330
- return { toolCallId: event.toolCallId, toolName: event.toolName, argsComplete: event.argsComplete };
342
+ return { toolCallId: event.toolCallId, toolName: event.toolName, argsComplete: event.argsComplete, intent: event.intent };
331
343
  if (event.type === "tool_execution_started" || event.type === "tool_execution_updated" || event.type === "tool_execution_finished")
332
- return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined };
344
+ return { toolCallId: event.toolCallId, toolName: event.toolName, isError: "isError" in event ? event.isError : undefined, intent: event.intent };
333
345
  if (event.type === "subagent_session")
334
346
  return { toolCallId: event.toolCallId, toolName: event.toolName, subagentName: event.subagentName, childPiboSessionId: event.childPiboSessionId, threadKey: event.threadKey };
335
347
  if (event.type === "execution_result")