@pasko70/pibo 1.16.6 → 2.0.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 (145) hide show
  1. package/dist/agent-runtime/auth.js +28 -0
  2. package/dist/agent-runtime/capabilities.js +221 -0
  3. package/dist/agent-runtime/context-build.js +313 -0
  4. package/dist/agent-runtime/contract.js +45 -0
  5. package/dist/agent-runtime/errors.js +57 -0
  6. package/dist/agent-runtime/events.js +1 -0
  7. package/dist/agent-runtime/history.js +1 -0
  8. package/dist/agent-runtime/profile-validation.js +109 -0
  9. package/dist/agent-runtime/registry.js +608 -0
  10. package/dist/agent-runtime/resource-files.js +87 -0
  11. package/dist/agent-runtime/resource-service.js +635 -0
  12. package/dist/agent-runtime/resources.js +1 -0
  13. package/dist/agent-runtime/routed-session.js +931 -0
  14. package/dist/agent-runtime/testing/contract.js +51 -0
  15. package/dist/agent-runtime/testing/fake-adapter.js +192 -0
  16. package/dist/agent-runtime/types.js +3 -0
  17. package/dist/agent-runtimes/codex-native/adapter.js +905 -0
  18. package/dist/agent-runtimes/codex-native/auth.js +476 -0
  19. package/dist/agent-runtimes/codex-native/client.js +734 -0
  20. package/dist/agent-runtimes/codex-native/config.js +152 -0
  21. package/dist/agent-runtimes/codex-native/history.js +283 -0
  22. package/dist/agent-runtimes/codex-native/mcp-stdio-launcher.js +89 -0
  23. package/dist/agent-runtimes/codex-native/models.js +581 -0
  24. package/dist/agent-runtimes/codex-native/process.js +459 -0
  25. package/dist/agent-runtimes/codex-native/protocol-types.js +1 -0
  26. package/dist/agent-runtimes/codex-native/protocol-version.js +7 -0
  27. package/dist/agent-runtimes/codex-native/redaction.js +31 -0
  28. package/dist/agent-runtimes/codex-native/requests.js +378 -0
  29. package/dist/agent-runtimes/codex-native/resource-delivery.js +445 -0
  30. package/dist/agent-runtimes/codex-native/thread.js +388 -0
  31. package/dist/agent-runtimes/codex-native/turn.js +849 -0
  32. package/dist/agent-runtimes/pi/adapter.js +653 -0
  33. package/dist/agent-runtimes/pi/auth.js +455 -0
  34. package/dist/agent-runtimes/pi/history.js +640 -0
  35. package/dist/agent-runtimes/pi/model-catalog.js +87 -0
  36. package/dist/agent-runtimes/pi/routed-session.js +1464 -0
  37. package/dist/agent-runtimes/pi/runtime.js +534 -0
  38. package/dist/agent-runtimes/pi/tool-compiler.js +69 -0
  39. package/dist/apps/chat/agent-profiles.js +40 -23
  40. package/dist/apps/chat/agent-store.js +111 -64
  41. package/dist/apps/chat/chat-api-routes.js +1 -1
  42. package/dist/apps/chat/chat-request-normalizers.js +47 -0
  43. package/dist/apps/chat/data/chat-data-mappers.js +25 -9
  44. package/dist/apps/chat/data/history-query-service.js +108 -0
  45. package/dist/apps/chat/data/session-query-service.js +24 -3
  46. package/dist/apps/chat/data/timeline-query-service.js +3 -3
  47. package/dist/apps/chat/model-catalog.js +1 -55
  48. package/dist/apps/chat/provider-auth-actions.js +171 -23
  49. package/dist/apps/chat/stream.js +12 -0
  50. package/dist/apps/chat/trace-v2.js +1 -0
  51. package/dist/apps/chat/trace.js +126 -415
  52. package/dist/apps/chat/web-app.js +582 -157
  53. package/dist/apps/chat-ui/assets/{dist-BTLtT_Vf.js → dist-9v2ZXT6V.js} +1 -1
  54. package/dist/apps/chat-ui/assets/{dist-CMk1aPug.js → dist-B1LVAytl.js} +1 -1
  55. package/dist/apps/chat-ui/assets/{dist-CcwA_IWN.js → dist-C6LPDFXU.js} +1 -1
  56. package/dist/apps/chat-ui/assets/{dist-DKOthbxr.js → dist-DVtXxfHc.js} +1 -1
  57. package/dist/apps/chat-ui/assets/{dist-ls2I6BIw.js → dist-DW4Qrfbd.js} +1 -1
  58. package/dist/apps/chat-ui/assets/{dist-D1rAtRWs.js → dist-DmbsHdMC.js} +1 -1
  59. package/dist/apps/chat-ui/assets/{dist-C1JQRskW.js → dist-DyvwuSFO.js} +1 -1
  60. package/dist/apps/chat-ui/assets/{dist-DfpaylQ-.js → dist-I26VB0QO.js} +1 -1
  61. package/dist/apps/chat-ui/assets/{dist-D10bTFD5.js → dist-RtHbJRPV.js} +1 -1
  62. package/dist/apps/chat-ui/assets/{dist-5KpmmBBZ.js → dist-sZgJkWZb.js} +1 -1
  63. package/dist/apps/chat-ui/assets/index-BPbNvcZe.css +1 -0
  64. package/dist/apps/chat-ui/assets/index-CIOf2uL2.js +237 -0
  65. package/dist/apps/chat-ui/index.html +2 -2
  66. package/dist/apps/chat-vscode-web/assets/index-1ivRyjFs.js +41 -0
  67. package/dist/apps/chat-vscode-web/index.html +1 -1
  68. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  69. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.0.0.vsix +0 -0
  70. package/dist/auth/login-actions.js +5 -251
  71. package/dist/cli-session/localSessionSource.js +8 -1
  72. package/dist/core/context-build.js +124 -20
  73. package/dist/core/default-profile.js +8 -0
  74. package/dist/core/profiles.js +30 -3
  75. package/dist/core/routed-session.js +5 -1446
  76. package/dist/core/runtime.js +5 -561
  77. package/dist/core/session-router.js +637 -83
  78. package/dist/data/cli.js +20 -3
  79. package/dist/data/schema.js +104 -1
  80. package/dist/data/session-store.js +24 -1
  81. package/dist/debug/events.js +25 -10
  82. package/dist/debug/failures.js +23 -3
  83. package/dist/debug/index.js +24 -6
  84. package/dist/debug/messages.js +87 -7
  85. package/dist/debug/persisted-payloads.js +31 -0
  86. package/dist/debug/pty.js +7 -6
  87. package/dist/debug/runtime-binding.js +29 -0
  88. package/dist/debug/session.js +137 -0
  89. package/dist/debug/summary.js +12 -0
  90. package/dist/debug/telemetry.js +28 -0
  91. package/dist/debug/tools.js +12 -2
  92. package/dist/debug/trace.js +154 -76
  93. package/dist/gateway/server.js +87 -2
  94. package/dist/gateway/tool.js +5 -5
  95. package/dist/index.js +21 -2
  96. package/dist/loops/tools.js +13 -12
  97. package/dist/mcp/agent-context.js +13 -4
  98. package/dist/mcp/client.js +10 -6
  99. package/dist/mcp/config.js +5 -2
  100. package/dist/mcp/runtime-session.js +166 -0
  101. package/dist/plugins/builtin.js +191 -53
  102. package/dist/plugins/codex-native.js +29 -0
  103. package/dist/plugins/registry.js +66 -2
  104. package/dist/runs/tools.js +26 -25
  105. package/dist/session-ui/terminalRows.js +2 -2
  106. package/dist/sessions/pibo-data-store.js +125 -16
  107. package/dist/sessions/runtime-binding.js +107 -0
  108. package/dist/sessions/sqlite-store.js +239 -18
  109. package/dist/sessions/store.js +86 -7
  110. package/dist/shared/trace-engine.js +18 -9
  111. package/dist/shared/trace-event-projection.js +29 -11
  112. package/dist/shared/{trace-transcript.js → trace-history.js} +193 -195
  113. package/dist/shared/trace-order.js +9 -4
  114. package/dist/shared/trace-page-merge.js +1 -1
  115. package/dist/signals/projector.js +20 -2
  116. package/dist/signals/registry.js +11 -0
  117. package/dist/subagents/tool.js +8 -6
  118. package/dist/tools/codex-browser.js +20 -19
  119. package/dist/tools/codex-compat.js +10 -9
  120. package/dist/tools/codex-image-generation.js +8 -6
  121. package/dist/tools/contract.js +45 -0
  122. package/dist/tools/credential-registry.js +182 -0
  123. package/dist/tools/mcp-bridge.js +511 -0
  124. package/dist/tools/payload-writer.js +17 -0
  125. package/dist/tools/runtime/tool.js +10 -9
  126. package/dist/tools/schema.js +9 -0
  127. package/dist/tools/session-service.js +193 -0
  128. package/dist/tools/session-tool-set.js +85 -0
  129. package/dist/web-annotations/tools.js +17 -16
  130. package/docs/README.md +11 -0
  131. package/package.json +3 -2
  132. package/skills/builtin/pibo-agent-runtime-adapter/SKILL.md +183 -0
  133. package/skills/builtin/pibo-agent-runtime-adapter/evals/README.md +10 -0
  134. package/skills/builtin/pibo-agent-runtime-adapter/evals/evals.json +45 -0
  135. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/orion-full-harness.md +60 -0
  136. package/skills/builtin/pibo-agent-runtime-adapter/evals/fixtures/relay-partial-harness.md +52 -0
  137. package/skills/builtin/pibo-agent-runtime-adapter/references/capabilities-and-designer.md +194 -0
  138. package/skills/builtin/pibo-agent-runtime-adapter/references/history-debug-and-security.md +213 -0
  139. package/skills/builtin/pibo-agent-runtime-adapter/references/interfaces-and-registration.md +214 -0
  140. package/skills/builtin/pibo-agent-runtime-adapter/references/lifecycle-bindings-and-events.md +193 -0
  141. package/skills/builtin/pibo-agent-runtime-adapter/references/portable-delivery-and-native-behavior.md +226 -0
  142. package/skills/builtin/pibo-agent-runtime-adapter/references/testing-migration-and-validation.md +256 -0
  143. package/dist/apps/chat-ui/assets/index-C2YI0xfw.js +0 -237
  144. package/dist/apps/chat-ui/assets/index-E0RmuCkY.css +0 -1
  145. package/dist/apps/chat-vscode-web/assets/index-D-tSsXLG.js +0 -41
@@ -2,24 +2,28 @@ import { createHash, randomUUID } from "node:crypto";
2
2
  import os from "node:os";
3
3
  import { monitorEventLoopDelay } from "node:perf_hooks";
4
4
  import { PiboSteeringUnavailableError } from "../../core/events.js";
5
+ import { PI_AGENT_RUNTIME_CAPABILITIES } from "../../agent-runtimes/pi/adapter.js";
6
+ import { AgentRuntimeBindingMissingError } from "../../agent-runtime/errors.js";
7
+ import { buildPortableRuntimeContextSnapshot, profileWithRuntimeInstance, uniqueRuntimeDiagnostics, } from "../../agent-runtime/context-build.js";
8
+ import { PiboRuntimeResourceService } from "../../agent-runtime/resource-service.js";
5
9
  import { summarizeSessionSignalStatus } from "../../signals/status.js";
6
10
  import { PiboWebHttpError, readJsonBody, responseJson } from "../../web/http.js";
7
11
  import { OutputCompactor } from "./output-compactor.js";
8
12
  import { isLiveOnlyOutputEvent, isPersistableOutputEvent } from "./output-event-policy.js";
9
13
  import { chatRoomIdFromMetadata, isDefaultPiboRoom, isPiboRoomArchived, roomWorkspaceFromMetadata, withChatRoomId, withPiboRoomWorkspace, } from "./types/rooms.js";
10
14
  import { chatStreamFramesFromOutputEvent, createChatStreamState, nextTransientChatStreamFrameId } from "./stream.js";
11
- import { buildSessionNodes, buildTraceView, createTraceViewVersion, loadPiSessionFastMetadata, loadPiSessionMetadata, readTailEntries, readTranscriptHistoryPage } from "./trace.js";
15
+ import { buildSessionNodes, buildTraceView } from "./trace.js";
12
16
  import { DEFAULT_TRACE_EVENTS_PAGE_SIZE, annotateTracePage, etagForVersion, estimateTraceViewBytes, traceCacheEstimatedBytes, liveSnapshotVersion, requestMatchesVersion, setTraceCache, traceProjectionStatus, traceCacheKey, withLiveSnapshots, withRawTraceTail, } from "./chat-trace-helpers.js";
13
17
  import { TRACE_V2_DEFAULT_TIMELINE_LIMIT, TRACE_V2_MAX_TIMELINE_LIMIT, TRACE_V2_PAYLOAD_DEFAULT_LIMIT_BYTES, TRACE_V2_PAYLOAD_MAX_LIMIT_BYTES, TRACE_V2_RAW_EVENTS_DEFAULT_LIMIT, TRACE_V2_RAW_EVENTS_MAX_LIMIT, TRACE_V2_TIMELINE_HARD_BYTES, parseTracePayloadRef, readTracePayloadChunk, traceRawEventsPageFromEvents, traceTimelinePageFromView, } from "./trace-v2.js";
14
18
  import { isChatWebSessionArchived } from "./session-metadata.js";
15
19
  import { withWorkflowSessionKind } from "../../sessions/workflow-session-kind.js";
16
- import { CustomAgentStore, createDefaultCustomAgentStore, } from "./agent-store.js";
20
+ import { CustomAgentStore, createDefaultCustomAgentStore, previewCustomAgentCreate, previewCustomAgentUpdate, } from "./agent-store.js";
17
21
  import { loadPiboModelDefaults, } from "../../core/model-defaults.js";
18
22
  import { inspectPiboContextBuild } from "../../core/context-build.js";
19
23
  import { loadPiboUserSettings, updateTelemetryRetentionLastPrunedAt } from "../../core/user-settings.js";
20
24
  import { isTelemetryRetentionMaintenanceDue, maybeRunTelemetryRetentionMaintenance } from "./telemetry-retention-service.js";
21
25
  import { loadModelCatalog } from "./model-catalog.js";
22
- import { createCustomAgentProfileDefinition } from "./agent-profiles.js";
26
+ import { createCustomAgentProfileDefinition, createCustomAgentRuntimeValidationProfile } from "./agent-profiles.js";
23
27
  import { createDefaultPiboReliabilityStore, PiboReliabilityStore } from "../../reliability/store.js";
24
28
  import { listMcpServerInfos } from "../../mcp/agent-context.js";
25
29
  import { getDefaultPiboWorkspace } from "../../core/workspace.js";
@@ -31,6 +35,7 @@ import { ChatReadStateService } from "./data/read-state-service.js";
31
35
  import { ChatRoomService } from "./data/room-service.js";
32
36
  import { ChatSessionQueryService } from "./data/session-query-service.js";
33
37
  import { ChatTimelineQueryService } from "./data/timeline-query-service.js";
38
+ import { ChatHistoryQueryService } from "./data/history-query-service.js";
34
39
  import { ChatProjectService } from "./data/project-service.js";
35
40
  import { PiboDataStore } from "../../data/pibo-store.js";
36
41
  import { createDefaultPiboCronStore } from "../../cron/store.js";
@@ -40,7 +45,7 @@ import { handleChatLoopApiRequest } from "./loop-api.js";
40
45
  import { prepareWebAnnotationMessageAttachments } from "../../web-annotations/attachments.js";
41
46
  import { createDefaultWebAnnotationStore } from "../../web-annotations/store.js";
42
47
  import { CHAT_WEB_MOUNT_PATH, isChatAppPath, responseBuiltChatAsset, responseBuiltChatPublicFile, responseChatAppShell, isVscodeAppPath, responseBuiltVscodeAsset, responseVscodeAppShell } from "./static-assets.js";
43
- import { executeProviderAuthAction, isProviderAuthAction, providerAuthActionResponse } from "./provider-auth-actions.js";
48
+ import { executeProviderAuthAction, isProviderAuthAction, providerAuthActionResponse, readProviderAuthCatalog, } from "./provider-auth-actions.js";
44
49
  import { prepareChatFileAttachments, resolveDownloadPath, responseChatFileDownload, saveUploadedChatFiles } from "./chat-files.js";
45
50
  import { chatSettingsRoute, chatSettingsRouteInvalidatesBootstrapCatalog, chatSettingsRouteRequiresSameOrigin, handleChatSettingsRoute, } from "./chat-settings-routes.js";
46
51
  import { chatCapabilityRoute, chatCapabilityRouteRequiresSameOrigin, handleChatCapabilityRoute, } from "./chat-capability-routes.js";
@@ -331,6 +336,7 @@ function createFastTraceV2Version(input) {
331
336
  profile: input.session.profile,
332
337
  title: input.session.title ?? null,
333
338
  updatedAt: input.session.updatedAt,
339
+ runtimeBinding: traceRuntimeBindingFromSession(input.session),
334
340
  },
335
341
  status: input.status ?? "idle",
336
342
  events: {
@@ -338,11 +344,13 @@ function createFastTraceV2Version(input) {
338
344
  lastActivityAt: input.lastActivityAt ?? null,
339
345
  latestStreamId: input.latestStreamId ?? null,
340
346
  },
341
- transcript: {
342
- sessionSize: input.transcript?.sessionSize ?? null,
343
- sessionMtimeMs: input.transcript?.sessionMtimeMs ?? null,
344
- modified: input.transcript?.modified ?? null,
347
+ productHistory: {
348
+ messageCount: input.productHistory?.messageCount ?? 0,
349
+ firstEventSequence: input.productHistory?.firstEventSequence ?? null,
350
+ lastEventSequence: input.productHistory?.lastEventSequence ?? null,
351
+ lastCreatedAt: input.productHistory?.lastCreatedAt ?? null,
345
352
  },
353
+ nativeHistoryVersion: input.nativeHistoryVersion ?? null,
346
354
  sessions: relevantSessions,
347
355
  }))
348
356
  .digest("hex");
@@ -896,6 +904,54 @@ function serializeCustomAgent(agent, context) {
896
904
  function serializeCustomAgents(agents, context) {
897
905
  return agents.map((agent) => serializeCustomAgent(agent, context));
898
906
  }
907
+ const RUNTIME_PROFILE_UPDATE_FIELDS = new Set([
908
+ "runtimeInstanceId",
909
+ "runtimeOptions",
910
+ "nativeTools",
911
+ "skills",
912
+ "contextFiles",
913
+ "subagents",
914
+ "mcpServers",
915
+ "piPackages",
916
+ "mainModel",
917
+ "subagentModel",
918
+ "thinkingLevel",
919
+ "mainThinkingLevel",
920
+ "subagentThinkingLevel",
921
+ "fast",
922
+ "mainFast",
923
+ "subagentFast",
924
+ "builtinTools",
925
+ "builtinToolNames",
926
+ "autoContextFiles",
927
+ "runControl",
928
+ "goalControl",
929
+ ]);
930
+ function customAgentUpdateAffectsRuntime(update) {
931
+ return Object.keys(update).some((field) => RUNTIME_PROFILE_UPDATE_FIELDS.has(field));
932
+ }
933
+ async function requireValidCustomAgentRuntime(agent, context) {
934
+ const profile = createCustomAgentRuntimeValidationProfile(agent);
935
+ let diagnostics;
936
+ if (context.channelContext.validateAgentRuntimeProfile) {
937
+ diagnostics = await context.channelContext.validateAgentRuntimeProfile(profile, process.cwd());
938
+ }
939
+ else {
940
+ const runtimes = context.channelContext.getCapabilityCatalog?.().agentRuntimes ?? [];
941
+ const selected = runtimes.find((runtime) => runtime.id === agent.runtimeInstanceId);
942
+ if (!selected && runtimes.length === 0 && agent.runtimeInstanceId === "pi")
943
+ return;
944
+ diagnostics = selected
945
+ ? selected.enabled
946
+ ? []
947
+ : [{ severity: "error", code: "runtime_instance_disabled", message: `Agent runtime instance "${selected.id}" is disabled.` }]
948
+ : [{ severity: "error", code: "runtime_instance_unknown", message: `Unknown agent runtime instance "${agent.runtimeInstanceId}".` }];
949
+ }
950
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === "error");
951
+ if (errors.length === 0)
952
+ return;
953
+ throw new PiboWebHttpError(`Agent runtime selection is invalid: ${errors.map((diagnostic) => diagnostic.message).join(" ")}`, 400);
954
+ }
899
955
  function listBrokenContextFiles(keys, context) {
900
956
  const catalog = context.channelContext.getCapabilityCatalog?.();
901
957
  if (!catalog)
@@ -923,17 +979,48 @@ function requireSharedAgent(agent) {
923
979
  return agent;
924
980
  }
925
981
  async function buildAgentCatalog(context, state) {
982
+ const baseCatalog = context.channelContext.getCapabilityCatalog?.() ?? {
983
+ agentRuntimes: [],
984
+ nativeTools: [],
985
+ skills: [],
986
+ subagents: [],
987
+ contextFiles: [],
988
+ packages: [],
989
+ piboTools: [],
990
+ mcpServers: [],
991
+ piPackages: [],
992
+ };
993
+ let agentRuntimes = (baseCatalog.agentRuntimes ?? []).map((runtime) => {
994
+ const diagnostics = runtime.enabled ? [] : [{
995
+ severity: "error",
996
+ code: "runtime_instance_disabled",
997
+ message: `Agent runtime instance "${runtime.id}" is disabled.`,
998
+ }];
999
+ return {
1000
+ ...runtime,
1001
+ available: runtime.enabled,
1002
+ diagnostics,
1003
+ };
1004
+ });
1005
+ if (context.channelContext.inspectAgentRuntimeInstances) {
1006
+ try {
1007
+ agentRuntimes = await context.channelContext.inspectAgentRuntimeInstances();
1008
+ }
1009
+ catch (error) {
1010
+ agentRuntimes = agentRuntimes.map((runtime) => ({
1011
+ ...runtime,
1012
+ available: false,
1013
+ diagnostics: [{
1014
+ severity: "error",
1015
+ code: "runtime_catalog_diagnostics_failed",
1016
+ message: error instanceof Error ? error.message : String(error),
1017
+ }],
1018
+ }));
1019
+ }
1020
+ }
926
1021
  return {
927
- ...(context.channelContext.getCapabilityCatalog?.() ?? {
928
- nativeTools: [],
929
- skills: [],
930
- subagents: [],
931
- contextFiles: [],
932
- packages: [],
933
- piboTools: [],
934
- mcpServers: [],
935
- piPackages: [],
936
- }),
1022
+ ...baseCatalog,
1023
+ agentRuntimes,
937
1024
  mcpServers: await listMcpServerInfos(),
938
1025
  piPackages: listPiPackages(),
939
1026
  userSkills: state.userSkillManager.list(),
@@ -2036,7 +2123,7 @@ function normalizeAgentDeleteConfirmation(value) {
2036
2123
  }
2037
2124
  return value.trim();
2038
2125
  }
2039
- function deleteSessionsForAgentProfile(state, context, webSession, profileNames) {
2126
+ async function deleteSessionsForAgentProfile(state, context, webSession, profileNames) {
2040
2127
  const deleteSession = context.channelContext.deleteSession;
2041
2128
  if (!deleteSession)
2042
2129
  throw new PiboWebHttpError("Session deletion is not available", 501);
@@ -2055,13 +2142,13 @@ function deleteSessionsForAgentProfile(state, context, webSession, profileNames)
2055
2142
  }
2056
2143
  }
2057
2144
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2145
+ for (const id of orderedIds)
2146
+ await deleteSession(id);
2058
2147
  state.sessionQuery.deleteSessions(orderedIds);
2059
2148
  state.eventCommands.deleteSessions(orderedIds);
2060
- for (const id of orderedIds)
2061
- deleteSession(id);
2062
2149
  return orderedIds;
2063
2150
  }
2064
- function deleteSessionSubtree(state, context, webSession, rootSession) {
2151
+ async function deleteSessionSubtree(state, context, webSession, rootSession) {
2065
2152
  const deleteSession = context.channelContext.deleteSession;
2066
2153
  if (!deleteSession)
2067
2154
  throw new PiboWebHttpError("Session deletion is not available", 501);
@@ -2079,13 +2166,13 @@ function deleteSessionSubtree(state, context, webSession, rootSession) {
2079
2166
  }
2080
2167
  }
2081
2168
  const orderedIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2169
+ for (const id of orderedIds)
2170
+ await deleteSession(id);
2082
2171
  state.sessionQuery.deleteSessions(orderedIds);
2083
2172
  state.eventCommands.deleteSessions(orderedIds);
2084
- for (const id of orderedIds)
2085
- deleteSession(id);
2086
2173
  return orderedIds;
2087
2174
  }
2088
- function deleteRoomTree(state, context, webSession, room, confirmName) {
2175
+ async function deleteRoomTree(state, context, webSession, room, confirmName) {
2089
2176
  if (isDefaultPiboRoom(room))
2090
2177
  throw new PiboWebHttpError("Default chat cannot be deleted", 400);
2091
2178
  if (!isPiboRoomArchived(room))
@@ -2117,11 +2204,11 @@ function deleteRoomTree(state, context, webSession, room, confirmName) {
2117
2204
  }
2118
2205
  }
2119
2206
  const orderedSessionIds = [...ids].sort((left, right) => sessionDepth(sessionsById.get(right), sessionsById) - sessionDepth(sessionsById.get(left), sessionsById));
2207
+ for (const id of orderedSessionIds)
2208
+ await deleteSession(id);
2120
2209
  state.sessionQuery.deleteSessions(orderedSessionIds);
2121
2210
  state.eventCommands.deleteSessions(orderedSessionIds);
2122
2211
  state.eventCommands.deleteRooms(roomIds);
2123
- for (const id of orderedSessionIds)
2124
- deleteSession(id);
2125
2212
  const orderedRoomIds = [...roomIds].reverse();
2126
2213
  state.roomService.deleteRooms(orderedRoomIds);
2127
2214
  return { deletedRoomIds: orderedRoomIds, deletedSessionIds: orderedSessionIds };
@@ -2142,6 +2229,29 @@ function requireSharedSession(context, piboSessionId) {
2142
2229
  }
2143
2230
  return canonicalizeSessionProfile(context, session);
2144
2231
  }
2232
+ function defaultRuntimeInstanceId(context, defaultProfile) {
2233
+ if (context.channelContext.createProfile) {
2234
+ try {
2235
+ return context.channelContext.createProfile(defaultProfile).runtimeInstanceId;
2236
+ }
2237
+ catch {
2238
+ // Fall through to the registered runtime order when the profile is unavailable.
2239
+ }
2240
+ }
2241
+ return context.channelContext.getCapabilityCatalog?.().agentRuntimes?.[0]?.id;
2242
+ }
2243
+ function sessionRuntimeInstanceId(context, session) {
2244
+ return context.channelContext.getSessionRuntimeBinding?.(session.id)?.runtimeInstanceId
2245
+ ?? session.runtimeBinding?.runtimeInstanceId
2246
+ ?? (() => {
2247
+ try {
2248
+ return context.channelContext.createProfile?.(session.profile).runtimeInstanceId;
2249
+ }
2250
+ catch {
2251
+ return undefined;
2252
+ }
2253
+ })();
2254
+ }
2145
2255
  function resolveRequestedSession(state, context, webSession, defaultProfile, piboSessionId, roomId) {
2146
2256
  if (!piboSessionId)
2147
2257
  return ensureDefaultChatSession(state, context, webSession, defaultProfile, roomId);
@@ -2162,20 +2272,125 @@ async function buildContextBuildSnapshotForRequest(input) {
2162
2272
  if (!input.piboSessionId)
2163
2273
  throw new PiboWebHttpError("piboSessionId is required", 400);
2164
2274
  const selectedSession = requireSharedSession(input.context, input.piboSessionId);
2165
- const profile = createProfile(selectedSession.profile);
2275
+ const configuredProfile = createProfile(selectedSession.profile);
2276
+ const runtimeInstanceId = selectedSession.runtimeBinding?.runtimeInstanceId ?? configuredProfile.runtimeInstanceId;
2277
+ const profile = profileWithRuntimeInstance(configuredProfile, runtimeInstanceId);
2278
+ const cwd = selectedSession.workspace ?? getDefaultPiboWorkspace();
2279
+ const runtime = await resolveContextBuildRuntime(input.context, runtimeInstanceId);
2280
+ const validationDiagnostics = input.context.channelContext.validateAgentRuntimeProfile
2281
+ ? await input.context.channelContext.validateAgentRuntimeProfile(profile, cwd)
2282
+ : [];
2283
+ const bindingMismatchDiagnostic = selectedSession.runtimeBinding && selectedSession.runtimeBinding.adapterId !== runtime.adapterId
2284
+ ? [{
2285
+ severity: "error",
2286
+ code: "runtime_binding_adapter_mismatch",
2287
+ message: `Session binding expects adapter "${selectedSession.runtimeBinding.adapterId}", but runtime instance "${runtime.id}" uses "${runtime.adapterId}".`,
2288
+ }]
2289
+ : [];
2290
+ const diagnostics = uniqueRuntimeDiagnostics([
2291
+ ...runtime.diagnostics,
2292
+ ...validationDiagnostics,
2293
+ ...bindingMismatchDiagnostic,
2294
+ ]);
2295
+ const runtimeInfo = {
2296
+ runtimeInstanceId: runtime.id,
2297
+ adapterId: runtime.adapterId,
2298
+ available: runtime.available && !diagnostics.some((diagnostic) => diagnostic.severity === "error"),
2299
+ transport: runtime.transport,
2300
+ bindingState: selectedSession.runtimeBinding?.state,
2301
+ protocol: runtime.protocol,
2302
+ capabilities: runtime.capabilities,
2303
+ diagnostics,
2304
+ };
2166
2305
  const userSettings = loadPiboUserSettings();
2167
- const cwd = selectedSession?.workspace ?? getDefaultPiboWorkspace();
2168
- return inspectPiboContextBuild({
2169
- cwd,
2306
+ const resourceService = new PiboRuntimeResourceService();
2307
+ const resources = await resourceService.createSession({
2308
+ piboSessionId: selectedSession.id,
2309
+ piboRoomId: chatRoomIdFromMetadata(selectedSession.metadata),
2310
+ runtimeInstanceId: runtime.id,
2311
+ adapterId: runtime.adapterId,
2312
+ sessionGeneration: `inspection-${randomUUID()}`,
2170
2313
  profile,
2171
- activeModel: selectedSession?.activeModel,
2172
- persistSession: false,
2173
- sessionContext: {
2174
- piboSessionId: selectedSession?.id,
2175
- piboRoomId: selectedSession ? chatRoomIdFromMetadata(selectedSession.metadata) : undefined,
2176
- timezone: userSettings.timezone,
2177
- },
2314
+ cwd,
2315
+ timezone: userSettings.timezone,
2316
+ capabilities: runtime.capabilities,
2317
+ strict: false,
2178
2318
  });
2319
+ try {
2320
+ if (runtime.adapterId === "pi" && runtimeInfo.available) {
2321
+ const snapshot = await inspectPiboContextBuild({
2322
+ cwd,
2323
+ profile,
2324
+ activeModel: selectedSession.activeModel,
2325
+ persistSession: false,
2326
+ resources,
2327
+ sessionContext: {
2328
+ piboSessionId: selectedSession.id,
2329
+ piboRoomId: chatRoomIdFromMetadata(selectedSession.metadata),
2330
+ timezone: userSettings.timezone,
2331
+ },
2332
+ });
2333
+ const runtimeIssues = diagnostics.filter((diagnostic) => diagnostic.severity !== "info");
2334
+ return {
2335
+ ...snapshot,
2336
+ runtime: runtimeInfo,
2337
+ diagnostics: [
2338
+ ...snapshot.diagnostics,
2339
+ ...runtimeIssues.map((diagnostic) => ({ type: diagnostic.severity, message: diagnostic.message })),
2340
+ ],
2341
+ summary: {
2342
+ ...snapshot.summary,
2343
+ warnings: snapshot.summary.warnings + runtimeIssues.filter((diagnostic) => diagnostic.severity === "warning").length,
2344
+ errors: snapshot.summary.errors + runtimeIssues.filter((diagnostic) => diagnostic.severity === "error").length,
2345
+ },
2346
+ };
2347
+ }
2348
+ return buildPortableRuntimeContextSnapshot({
2349
+ profile,
2350
+ runtime: runtimeInfo,
2351
+ cwd,
2352
+ piboSessionId: selectedSession.id,
2353
+ piboRoomId: chatRoomIdFromMetadata(selectedSession.metadata),
2354
+ activeModel: selectedSession.activeModel,
2355
+ resources: resources.getInspection(),
2356
+ });
2357
+ }
2358
+ finally {
2359
+ await resourceService.dispose();
2360
+ }
2361
+ }
2362
+ async function resolveContextBuildRuntime(context, runtimeInstanceId) {
2363
+ if (context.channelContext.inspectAgentRuntimeInstances) {
2364
+ const runtime = (await context.channelContext.inspectAgentRuntimeInstances()).find((candidate) => candidate.id === runtimeInstanceId);
2365
+ if (runtime)
2366
+ return runtime;
2367
+ }
2368
+ const runtime = context.channelContext.getCapabilityCatalog?.().agentRuntimes?.find((candidate) => candidate.id === runtimeInstanceId);
2369
+ if (!runtime && runtimeInstanceId === "pi") {
2370
+ return {
2371
+ id: "pi",
2372
+ adapterId: "pi",
2373
+ displayName: "Pi Coding Agent",
2374
+ enabled: true,
2375
+ available: true,
2376
+ transport: "embedded",
2377
+ capabilities: structuredClone(PI_AGENT_RUNTIME_CAPABILITIES),
2378
+ configSchema: { type: "object", additionalProperties: false },
2379
+ protocol: { name: "pi-sdk", supportedRange: "0.80.6" },
2380
+ diagnostics: [],
2381
+ };
2382
+ }
2383
+ if (!runtime)
2384
+ throw new PiboWebHttpError(`Unknown agent runtime instance "${runtimeInstanceId}"`, 400);
2385
+ return {
2386
+ ...runtime,
2387
+ available: runtime.enabled,
2388
+ diagnostics: runtime.enabled ? [] : [{
2389
+ severity: "error",
2390
+ code: "runtime_instance_disabled",
2391
+ message: `Agent runtime instance "${runtime.id}" is disabled.`,
2392
+ }],
2393
+ };
2179
2394
  }
2180
2395
  function indexSharedSessions(sessionQuery, sessions) {
2181
2396
  sessionQuery.upsertSessionsIfChanged(sessions);
@@ -2369,63 +2584,106 @@ function parseTimelineBeforeCursor(url) {
2369
2584
  const cursor = url.searchParams.get("before") ?? url.searchParams.get("cursor");
2370
2585
  if (!cursor || cursor === "tail")
2371
2586
  return { kind: "tail" };
2372
- const transcript = parseTranscriptTimelineCursor(cursor);
2373
- if (transcript)
2374
- return transcript;
2587
+ const history = parseRuntimeHistoryTimelineCursor(cursor);
2588
+ if (history)
2589
+ return history;
2375
2590
  const parsed = Number.parseInt(cursor, 10);
2376
2591
  if (!Number.isFinite(parsed) || parsed <= 0)
2377
- throw new PiboWebHttpError("Trace cursor must be tail, transcript, or a positive sequence", 400);
2592
+ throw new PiboWebHttpError("Trace cursor must be tail, runtime-history, or a positive sequence", 400);
2378
2593
  return { kind: "event", beforeSequence: parsed, raw: String(parsed) };
2379
2594
  }
2380
- function parseTranscriptTimelineCursor(cursor) {
2381
- if (!cursor.startsWith("transcript:"))
2382
- return undefined;
2383
- const [, byteValue, encodedTimestamp] = cursor.split(":");
2384
- const beforeByte = Number.parseInt(byteValue ?? "", 10);
2385
- if (!Number.isFinite(beforeByte) || beforeByte < 0)
2386
- throw new PiboWebHttpError("Invalid transcript trace cursor", 400);
2387
- let beforeTimestamp;
2388
- if (encodedTimestamp) {
2595
+ function parseRuntimeHistoryTimelineCursor(cursor) {
2596
+ if (cursor.startsWith("transcript:")) {
2597
+ const [, , encodedTimestamp] = cursor.split(":");
2598
+ let beforeTimestamp;
2389
2599
  try {
2390
- const decoded = Buffer.from(encodedTimestamp, "base64url").toString("utf8");
2391
- if (decoded)
2392
- beforeTimestamp = decoded;
2600
+ beforeTimestamp = encodedTimestamp ? Buffer.from(encodedTimestamp, "base64url").toString("utf8") || undefined : undefined;
2393
2601
  }
2394
2602
  catch {
2395
- throw new PiboWebHttpError("Invalid transcript trace cursor", 400);
2603
+ throw new PiboWebHttpError("Invalid legacy transcript trace cursor", 400);
2604
+ }
2605
+ return { kind: "history", providerCursor: cursor, beforeTimestamp, raw: cursor };
2606
+ }
2607
+ if (!cursor.startsWith("runtime-history:"))
2608
+ return undefined;
2609
+ try {
2610
+ const payload = JSON.parse(Buffer.from(cursor.slice("runtime-history:".length), "base64url").toString("utf8"));
2611
+ if (payload.v !== 1)
2612
+ throw new Error("unsupported version");
2613
+ for (const value of [payload.piboSessionId, payload.runtimeInstanceId, payload.adapterId, payload.providerCursor, payload.beforeTimestamp]) {
2614
+ if (value !== undefined && typeof value !== "string")
2615
+ throw new Error("invalid field");
2396
2616
  }
2617
+ return { kind: "history", ...payload, raw: cursor };
2618
+ }
2619
+ catch (error) {
2620
+ throw new PiboWebHttpError(`Invalid runtime history trace cursor: ${error instanceof Error ? error.message : String(error)}`, 400);
2397
2621
  }
2398
- return { kind: "transcript", beforeByte, beforeTimestamp, raw: cursor };
2399
2622
  }
2400
- function encodeTranscriptTimelineCursor(beforeByte, beforeTimestamp) {
2401
- const encodedTimestamp = beforeTimestamp ? Buffer.from(beforeTimestamp, "utf8").toString("base64url") : "";
2402
- return `transcript:${Math.max(0, Math.floor(beforeByte))}:${encodedTimestamp}`;
2623
+ function encodeRuntimeHistoryTimelineCursor(session, input = {}) {
2624
+ const binding = session.runtimeBinding;
2625
+ const payload = {
2626
+ v: 1,
2627
+ piboSessionId: session.id,
2628
+ runtimeInstanceId: binding?.runtimeInstanceId,
2629
+ adapterId: binding?.adapterId,
2630
+ providerCursor: input.providerCursor,
2631
+ beforeTimestamp: input.beforeTimestamp,
2632
+ };
2633
+ return `runtime-history:${Buffer.from(JSON.stringify(payload), "utf8").toString("base64url")}`;
2634
+ }
2635
+ function validateRuntimeHistoryCursor(session, cursor) {
2636
+ const binding = session.runtimeBinding;
2637
+ if (cursor.piboSessionId && cursor.piboSessionId !== session.id) {
2638
+ throw new PiboWebHttpError("Runtime history cursor belongs to a different Pibo session", 409);
2639
+ }
2640
+ if (cursor.runtimeInstanceId && cursor.runtimeInstanceId !== binding?.runtimeInstanceId) {
2641
+ throw new PiboWebHttpError("Runtime history cursor belongs to a different runtime instance", 409);
2642
+ }
2643
+ if (cursor.adapterId && cursor.adapterId !== binding?.adapterId) {
2644
+ throw new PiboWebHttpError("Runtime history cursor belongs to a different runtime adapter", 409);
2645
+ }
2646
+ }
2647
+ function traceRuntimeBindingFromSession(session) {
2648
+ const binding = session.runtimeBinding;
2649
+ return binding ? {
2650
+ runtimeInstanceId: binding.runtimeInstanceId,
2651
+ adapterId: binding.adapterId,
2652
+ nativeSessionId: binding.nativeSessionId,
2653
+ state: binding.state,
2654
+ protocol: binding.protocol,
2655
+ protocolVersion: binding.protocolVersion,
2656
+ adapterVersion: binding.adapterVersion,
2657
+ revision: binding.revision,
2658
+ } : undefined;
2659
+ }
2660
+ function requiresNativeHistoryCompatibility(session) {
2661
+ const metadata = session.runtimeBinding?.metadata;
2662
+ return session.originId !== undefined
2663
+ || metadata?.nativeHistoryFallback === true
2664
+ || typeof metadata?.migrationSource === "string";
2665
+ }
2666
+ function sessionNodeHistoryOptions(context) {
2667
+ return {
2668
+ loadHistoryInspection: async (session) => requiresNativeHistoryCompatibility(session) && context.channelContext.inspectSessionRuntimeHistory
2669
+ ? await context.channelContext.inspectSessionRuntimeHistory(session.id).catch(() => undefined)
2670
+ : undefined,
2671
+ };
2672
+ }
2673
+ function runtimeHistorySupported(context, session) {
2674
+ if (!context.channelContext.readSessionRuntimeHistory)
2675
+ return false;
2676
+ const runtimeInstanceId = session.runtimeBinding?.runtimeInstanceId;
2677
+ if (!runtimeInstanceId)
2678
+ return false;
2679
+ return context.channelContext.getCapabilityCatalog?.().agentRuntimes.find((runtime) => runtime.id === runtimeInstanceId)?.capabilities.maintenance.history === true;
2403
2680
  }
2404
2681
  function compactionCutoffTimestamp(events) {
2405
2682
  const compaction = events.find((event) => event.type === "execution_result" && event.payload.type === "execution_result" && event.payload.action === "compact")
2406
2683
  ?? events.find((event) => event.type === "compaction_end" || event.type === "compaction_start");
2407
2684
  return compaction?.createdAt;
2408
2685
  }
2409
- function originTranscriptTailCursor(input) {
2410
- if (!input.session.originId)
2411
- return undefined;
2412
- const sessionPath = input.metadata?.sessionPath;
2413
- const sessionSize = input.metadata?.sessionSize;
2414
- if (!sessionPath || !sessionSize || sessionSize <= 0)
2415
- return undefined;
2416
- const firstVisibleTranscript = firstVisibleTailTranscriptNode(input.trace.nodes, input.limit);
2417
- if (!firstVisibleTranscript?.startedAt)
2418
- return undefined;
2419
- const probe = readTranscriptHistoryPage(sessionPath, {
2420
- beforeByte: sessionSize,
2421
- beforeTimestamp: firstVisibleTranscript.startedAt,
2422
- limit: 1,
2423
- });
2424
- if (probe.entries.length === 0)
2425
- return undefined;
2426
- return encodeTranscriptTimelineCursor(sessionSize, firstVisibleTranscript.startedAt);
2427
- }
2428
- function firstVisibleTailTranscriptNode(nodes, limit) {
2686
+ function firstVisibleTailHistoryNode(nodes, limit) {
2429
2687
  const flat = [];
2430
2688
  const visit = (items) => {
2431
2689
  for (const item of items) {
@@ -2434,7 +2692,7 @@ function firstVisibleTailTranscriptNode(nodes, limit) {
2434
2692
  }
2435
2693
  };
2436
2694
  visit(nodes);
2437
- return flat.slice(-Math.max(1, limit)).find((node) => node.source === "transcript" && node.startedAt);
2695
+ return flat.slice(-Math.max(1, limit)).find((node) => (node.source === "transcript" || node.source === "product-history") && node.startedAt);
2438
2696
  }
2439
2697
  function transientReplayScopeKeys(input) {
2440
2698
  const keys = [];
@@ -2855,7 +3113,7 @@ async function sendProjectMessage(input) {
2855
3113
  return responseJson({ accepted, output });
2856
3114
  }
2857
3115
  catch (error) {
2858
- if (error instanceof PiboSteeringUnavailableError) {
3116
+ if (error instanceof PiboSteeringUnavailableError || error instanceof AgentRuntimeBindingMissingError) {
2859
3117
  throw new PiboWebHttpError(error.message, 409);
2860
3118
  }
2861
3119
  throw error;
@@ -3057,7 +3315,7 @@ async function sendChatMessage(input) {
3057
3315
  });
3058
3316
  for (const listener of input.state.liveListeners)
3059
3317
  listener(failed);
3060
- if (error instanceof PiboSteeringUnavailableError) {
3318
+ if (error instanceof PiboSteeringUnavailableError || error instanceof AgentRuntimeBindingMissingError) {
3061
3319
  throw new PiboWebHttpError(error.message, 409);
3062
3320
  }
3063
3321
  throw error;
@@ -3073,6 +3331,7 @@ export function createChatWebApp(options = {}) {
3073
3331
  const state = {
3074
3332
  sessionQuery: new ChatSessionQueryService(dataStore),
3075
3333
  timelineQuery: new ChatTimelineQueryService(dataStore),
3334
+ historyQuery: new ChatHistoryQueryService(dataStore),
3076
3335
  eventCommands: new ChatEventCommandService(dataStore),
3077
3336
  readState: new ChatReadStateService(dataStore),
3078
3337
  roomService: new ChatRoomService(dataStore),
@@ -3227,7 +3486,7 @@ export function createChatWebApp(options = {}) {
3227
3486
  indexSharedSessions(state.sessionQuery, roomSessions);
3228
3487
  const sessionUnreadCounts = buildSessionUnreadCounts(state, ownedSessions);
3229
3488
  const [sessions, catalog] = await Promise.all([
3230
- buildSessionNodes(roomSessions, sessionIndexItemsWithSignalState(context, roomSessions, state.sessionQuery.listSessions(), sessionUnreadCounts), process.cwd(), sessionUnreadCounts),
3489
+ buildSessionNodes(roomSessions, sessionIndexItemsWithSignalState(context, roomSessions, state.sessionQuery.listSessions(), sessionUnreadCounts), process.cwd(), sessionUnreadCounts, sessionNodeHistoryOptions(context)),
3231
3490
  loadBootstrapCatalog(state, context, webSession),
3232
3491
  ]);
3233
3492
  const roomTree = state.roomService.listRoomTree();
@@ -3988,6 +4247,7 @@ export function createChatWebApp(options = {}) {
3988
4247
  const body = await readJsonBody(request);
3989
4248
  const input = createAgentInput(body);
3990
4249
  requireAgentProfileNameAvailable(state, context, input.displayName);
4250
+ await requireValidCustomAgentRuntime(previewCustomAgentCreate(input), context);
3991
4251
  const agent = state.agentStore.create(input);
3992
4252
  context.channelContext.upsertProfile?.(createCustomAgentProfileDefinition(agent));
3993
4253
  invalidateBootstrapCatalogCache(state);
@@ -4003,6 +4263,9 @@ export function createChatWebApp(options = {}) {
4003
4263
  const archived = normalizeAgentArchived(body.archived);
4004
4264
  if (update.displayName)
4005
4265
  requireAgentProfileNameAvailable(state, context, update.displayName, existing.id);
4266
+ if (customAgentUpdateAffectsRuntime(update)) {
4267
+ await requireValidCustomAgentRuntime(previewCustomAgentUpdate(existing, update), context);
4268
+ }
4006
4269
  const updated = Object.keys(update).length ? state.agentStore.update(patchAgentId, update) : existing;
4007
4270
  const afterUpdate = requireSharedAgent(updated);
4008
4271
  const agent = archived === undefined ? afterUpdate : state.agentStore.setArchived(patchAgentId, archived);
@@ -4029,7 +4292,7 @@ export function createChatWebApp(options = {}) {
4029
4292
  if (confirmName !== agent.profileName) {
4030
4293
  throw new PiboWebHttpError(`Type "${agent.profileName}" to permanently delete this agent and its sessions.`, 400);
4031
4294
  }
4032
- const deletedSessionIds = deleteSessionsForAgentProfile(state, context, webSession, [agent.profileName, ...agent.profileAliases]);
4295
+ const deletedSessionIds = await deleteSessionsForAgentProfile(state, context, webSession, [agent.profileName, ...agent.profileAliases]);
4033
4296
  state.agentStore.delete(agent.id);
4034
4297
  context.channelContext.removeProfile?.(agent.profileName);
4035
4298
  invalidateBootstrapCatalogCache(state);
@@ -4069,7 +4332,7 @@ export function createChatWebApp(options = {}) {
4069
4332
  includeArchived,
4070
4333
  });
4071
4334
  indexSharedSessions(state.sessionQuery, roomSessions);
4072
- const nodes = await buildSessionNodes(roomSessions, state.sessionQuery.listSessions(), process.cwd(), new Map());
4335
+ const nodes = await buildSessionNodes(roomSessions, state.sessionQuery.listSessions(), process.cwd(), new Map(), sessionNodeHistoryOptions(context));
4073
4336
  if (!wantsPage)
4074
4337
  return responseJson(nodes);
4075
4338
  const page = paginateSessionNodes(nodes, { archived: archivedPage, cursor, limit });
@@ -4130,7 +4393,7 @@ export function createChatWebApp(options = {}) {
4130
4393
  indexSharedSessions(state.sessionQuery, ownedSessions);
4131
4394
  return responseJson({
4132
4395
  room,
4133
- sessions: await buildSessionNodes(ownedSessions, state.sessionQuery.listSessions(), process.cwd(), new Map()),
4396
+ sessions: await buildSessionNodes(ownedSessions, state.sessionQuery.listSessions(), process.cwd(), new Map(), sessionNodeHistoryOptions(context)),
4134
4397
  });
4135
4398
  }
4136
4399
  if (roomResource && roomResource.child === undefined && request.method === "PATCH") {
@@ -4151,7 +4414,7 @@ export function createChatWebApp(options = {}) {
4151
4414
  const webSession = await requireSession(request, context);
4152
4415
  const room = requireRoom(state, roomResource.roomId, webSession, "admin");
4153
4416
  const body = await readJsonBody(request);
4154
- const deleted = deleteRoomTree(state, context, webSession, room, normalizeRoomDeleteConfirmation(body.confirmName));
4417
+ const deleted = await deleteRoomTree(state, context, webSession, room, normalizeRoomDeleteConfirmation(body.confirmName));
4155
4418
  return responseJson(deleted);
4156
4419
  }
4157
4420
  if (roomResource && roomResource.child === "events" && request.method === "GET") {
@@ -4196,6 +4459,62 @@ export function createChatWebApp(options = {}) {
4196
4459
  markSessionsRead(state, sessionSubtree(listSharedSessions(context), selectedSession.id));
4197
4460
  return responseJson({ ok: true, piboSessionId: selectedSession.id });
4198
4461
  }
4462
+ if (sessionAction?.action === "runtime-binding" && request.method === "GET") {
4463
+ const webSession = await requireSession(request, context);
4464
+ const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, sessionAction.piboSessionId);
4465
+ const binding = context.channelContext.getSessionRuntimeBinding?.(selectedSession.id) ?? selectedSession.runtimeBinding;
4466
+ if (!binding)
4467
+ throw new PiboWebHttpError("Runtime binding not found", 404);
4468
+ return responseJson({ binding });
4469
+ }
4470
+ if (sessionAction?.action === "runtime-binding" && request.method === "PATCH") {
4471
+ requireSameOriginJsonRequest(request);
4472
+ const webSession = await requireSession(request, context);
4473
+ const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, sessionAction.piboSessionId);
4474
+ if (!context.channelContext.rebindSessionRuntime) {
4475
+ throw new PiboWebHttpError("Runtime binding repair is not available", 501);
4476
+ }
4477
+ const body = await readJsonBody(request);
4478
+ if (typeof body.runtimeInstanceId !== "string" || !body.runtimeInstanceId.trim()) {
4479
+ throw new PiboWebHttpError("runtimeInstanceId is required", 400);
4480
+ }
4481
+ if (!Number.isInteger(body.expectedRevision) || Number(body.expectedRevision) < 1) {
4482
+ throw new PiboWebHttpError("expectedRevision must be a positive integer", 400);
4483
+ }
4484
+ if (body.nativeSessionId !== undefined && typeof body.nativeSessionId !== "string") {
4485
+ throw new PiboWebHttpError("nativeSessionId must be a string", 400);
4486
+ }
4487
+ if (body.state !== undefined && body.state !== "unbound" && body.state !== "bound") {
4488
+ throw new PiboWebHttpError("state must be unbound or bound", 400);
4489
+ }
4490
+ const locator = body.locator;
4491
+ const locatorKinds = new Set(["local-file", "local-directory", "uri", "remote", "adapter-resolved"]);
4492
+ if (locator !== undefined
4493
+ && (!isJsonObject(locator)
4494
+ || typeof locator.kind !== "string"
4495
+ || !locatorKinds.has(locator.kind)
4496
+ || (locator.value !== undefined && typeof locator.value !== "string"))) {
4497
+ throw new PiboWebHttpError("locator must contain a supported kind and optional string value", 400);
4498
+ }
4499
+ try {
4500
+ const binding = await context.channelContext.rebindSessionRuntime(selectedSession.id, {
4501
+ runtimeInstanceId: body.runtimeInstanceId.trim(),
4502
+ nativeSessionId: typeof body.nativeSessionId === "string" && body.nativeSessionId.trim() ? body.nativeSessionId.trim() : undefined,
4503
+ state: body.state,
4504
+ locator: locator,
4505
+ expectedRevision: Number(body.expectedRevision),
4506
+ });
4507
+ const updated = context.channelContext.getSession(selectedSession.id);
4508
+ if (updated)
4509
+ state.sessionQuery.upsertSession(updated);
4510
+ return responseJson({ binding });
4511
+ }
4512
+ catch (error) {
4513
+ const message = error instanceof Error ? error.message : String(error);
4514
+ const status = /concurrently|idle/i.test(message) ? 409 : 400;
4515
+ throw new PiboWebHttpError(message, status);
4516
+ }
4517
+ }
4199
4518
  const patchSessionId = sessionResourceId(url.pathname);
4200
4519
  if (patchSessionId && request.method === "PATCH") {
4201
4520
  requireSameOriginJsonRequest(request);
@@ -4241,7 +4560,7 @@ export function createChatWebApp(options = {}) {
4241
4560
  if (confirmText !== "Delete this session") {
4242
4561
  throw new PiboWebHttpError('Type "Delete this session" to permanently delete this session.', 400);
4243
4562
  }
4244
- const deletedSessionIds = deleteSessionSubtree(state, context, webSession, selectedSession);
4563
+ const deletedSessionIds = await deleteSessionSubtree(state, context, webSession, selectedSession);
4245
4564
  return responseJson({ deletedSessionIds });
4246
4565
  }
4247
4566
  if ((sessionAction?.action === "kill" || sessionAction?.action === "kill-all") && request.method === "POST") {
@@ -4262,27 +4581,30 @@ export function createChatWebApp(options = {}) {
4262
4581
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, url.searchParams.get("piboSessionId") || undefined);
4263
4582
  state.sessionQuery.upsertSession(selectedSession);
4264
4583
  const indexedSession = state.sessionQuery.getSession(selectedSession.id);
4265
- const metadataStartedAt = performance.now();
4266
- const metadata = await loadPiSessionFastMetadata(selectedSession, selectedSession.workspace ?? process.cwd());
4267
- const metadataMs = performance.now() - metadataStartedAt;
4584
+ const historyStartedAt = performance.now();
4585
+ const productHistory = state.historyQuery.getProductHistoryCoverage(selectedSession.id);
4586
+ const historyInspection = requiresNativeHistoryCompatibility(selectedSession) && context.channelContext.inspectSessionRuntimeHistory
4587
+ ? await context.channelContext.inspectSessionRuntimeHistory(selectedSession.id).catch(() => undefined)
4588
+ : undefined;
4589
+ const historyMs = performance.now() - historyStartedAt;
4268
4590
  const lastEventSequence = state.timelineQuery.getLatestEventSequence(selectedSession.id);
4269
4591
  const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: selectedSession.id });
4270
- const version = createTraceViewVersion({
4592
+ const version = createFastTraceV2Version({
4271
4593
  session: selectedSession,
4272
4594
  sessions: listSharedSessions(context),
4273
- events: lastEventSequence > 0
4274
- ? [{ id: `seq:${lastEventSequence}`, eventSequence: lastEventSequence, createdAt: indexedSession?.lastActivityAt ?? "" }]
4275
- : [],
4595
+ lastEventSequence,
4596
+ lastActivityAt: indexedSession?.lastActivityAt,
4276
4597
  status: indexedSession?.status,
4277
- metadata,
4278
4598
  latestStreamId,
4599
+ productHistory,
4600
+ nativeHistoryVersion: historyInspection?.version,
4279
4601
  });
4280
4602
  const headers = {
4281
4603
  etag: etagForVersion(version),
4282
4604
  "x-pibo-trace-version": version,
4283
4605
  "server-timing": [
4284
4606
  `trace_summary;dur=${(performance.now() - startedAt).toFixed(1)}`,
4285
- `trace_metadata;dur=${metadataMs.toFixed(1)}`,
4607
+ `trace_history;dur=${historyMs.toFixed(1)}`,
4286
4608
  `trace_events;desc="${lastEventSequence}"`,
4287
4609
  ].join(", "),
4288
4610
  };
@@ -4292,7 +4614,8 @@ export function createChatWebApp(options = {}) {
4292
4614
  const summary = {
4293
4615
  piboSessionId: selectedSession.id,
4294
4616
  piSessionId: selectedSession.piSessionId,
4295
- title: selectedSession.title ?? "Untitled Session",
4617
+ runtimeBinding: traceRuntimeBindingFromSession(selectedSession),
4618
+ title: selectedSession.title ?? historyInspection?.title ?? historyInspection?.firstMessage ?? "Untitled Session",
4296
4619
  version,
4297
4620
  latestStreamId,
4298
4621
  eventCount: lastEventSequence,
@@ -4307,10 +4630,13 @@ export function createChatWebApp(options = {}) {
4307
4630
  const beforeSequence = timelineCursor.kind === "event" ? timelineCursor.beforeSequence : undefined;
4308
4631
  const limit = parsePositiveIntSearchParam(url, "limit", TRACE_V2_DEFAULT_TIMELINE_LIMIT, TRACE_V2_MAX_TIMELINE_LIMIT);
4309
4632
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, url.searchParams.get("piboSessionId") || undefined);
4633
+ if (timelineCursor.kind === "history")
4634
+ validateRuntimeHistoryCursor(selectedSession, timelineCursor);
4310
4635
  state.sessionQuery.upsertSession(selectedSession);
4311
4636
  const ownedSessions = listSharedSessions(context);
4312
4637
  const indexedSession = state.sessionQuery.getSession(selectedSession.id);
4313
- let metadataMs = 0;
4638
+ let historyMs = 0;
4639
+ const productHistory = state.historyQuery.getProductHistoryCoverage(selectedSession.id);
4314
4640
  const lastEventSequence = state.timelineQuery.getLatestEventSequence(selectedSession.id);
4315
4641
  const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: selectedSession.id });
4316
4642
  const turnTimings = state.timelineQuery.listMessageTurnTimings(selectedSession.id);
@@ -4319,11 +4645,12 @@ export function createChatWebApp(options = {}) {
4319
4645
  ? context.channelContext.getSessionRuntimeStatus(selectedSession.id) ?? null
4320
4646
  : undefined;
4321
4647
  const traceStatus = traceProjectionStatus(liveSnapshots, indexedSession?.status, turnTimings, runtimeStatus);
4322
- const metadataStartedAt = performance.now();
4323
- const transcriptMetadata = timelineCursor.kind === "tail" || timelineCursor.kind === "transcript"
4324
- ? await loadPiSessionFastMetadata(selectedSession, selectedSession.workspace ?? process.cwd())
4325
- : undefined;
4326
- metadataMs += performance.now() - metadataStartedAt;
4648
+ let historyInspection;
4649
+ if (timelineCursor.kind === "tail" && requiresNativeHistoryCompatibility(selectedSession) && context.channelContext.inspectSessionRuntimeHistory) {
4650
+ const historyStartedAt = performance.now();
4651
+ historyInspection = await context.channelContext.inspectSessionRuntimeHistory(selectedSession.id).catch(() => undefined);
4652
+ historyMs += performance.now() - historyStartedAt;
4653
+ }
4327
4654
  const baseVersion = createFastTraceV2Version({
4328
4655
  session: selectedSession,
4329
4656
  sessions: ownedSessions,
@@ -4331,7 +4658,8 @@ export function createChatWebApp(options = {}) {
4331
4658
  lastActivityAt: indexedSession?.lastActivityAt,
4332
4659
  status: traceStatus,
4333
4660
  latestStreamId,
4334
- transcript: transcriptMetadata,
4661
+ productHistory,
4662
+ nativeHistoryVersion: historyInspection?.version,
4335
4663
  });
4336
4664
  const snapshotVersion = liveSnapshotVersion(liveSnapshots);
4337
4665
  const version = snapshotVersion ? `${baseVersion}:live:${snapshotVersion}` : baseVersion;
@@ -4354,7 +4682,7 @@ export function createChatWebApp(options = {}) {
4354
4682
  ...baseHeaders,
4355
4683
  "server-timing": [
4356
4684
  `trace_timeline;dur=${(performance.now() - startedAt).toFixed(1)}`,
4357
- `trace_metadata;dur=${metadataMs.toFixed(1)}`,
4685
+ `trace_history;dur=${historyMs.toFixed(1)}`,
4358
4686
  `trace_events;desc="0"`,
4359
4687
  `trace_cache;desc="page-hit"`,
4360
4688
  ].join(", "),
@@ -4364,22 +4692,32 @@ export function createChatWebApp(options = {}) {
4364
4692
  let trace = cached;
4365
4693
  let eventCount = 0;
4366
4694
  if (!trace) {
4367
- if (timelineCursor.kind === "transcript") {
4368
- const history = transcriptMetadata?.sessionPath
4369
- ? readTranscriptHistoryPage(transcriptMetadata.sessionPath, {
4370
- beforeByte: timelineCursor.beforeByte,
4695
+ if (timelineCursor.kind === "history") {
4696
+ if (!context.channelContext.readSessionRuntimeHistory) {
4697
+ throw new PiboWebHttpError("Runtime history is unavailable for this gateway", 501);
4698
+ }
4699
+ const historyStartedAt = performance.now();
4700
+ let history;
4701
+ try {
4702
+ history = await context.channelContext.readSessionRuntimeHistory(selectedSession.id, {
4703
+ cursor: timelineCursor.providerCursor,
4371
4704
  beforeTimestamp: timelineCursor.beforeTimestamp,
4372
4705
  limit,
4373
- })
4374
- : { entries: [], hasOlder: false, scannedBytes: 0, startByte: 0, endByte: 0 };
4706
+ });
4707
+ }
4708
+ catch {
4709
+ throw new PiboWebHttpError("Runtime history could not be read for this page", 502);
4710
+ }
4711
+ historyMs += performance.now() - historyStartedAt;
4712
+ historyInspection = history.inspection;
4375
4713
  trace = await buildTraceView({
4376
4714
  session: selectedSession,
4377
4715
  sessions: ownedSessions,
4378
4716
  events: [],
4379
4717
  status: traceStatus,
4380
- metadata: transcriptMetadata ?? {},
4381
- transcriptEntries: history.entries,
4382
- transcriptOrderOffset: history.startByte,
4718
+ historyEntries: history.entries,
4719
+ historyInspection: history.inspection,
4720
+ historyOrderOffset: history.orderOffset,
4383
4721
  turnTimings,
4384
4722
  includeRawEvents: false,
4385
4723
  latestStreamId,
@@ -4390,10 +4728,13 @@ export function createChatWebApp(options = {}) {
4390
4728
  eventLimit: limit,
4391
4729
  pageSize: limit,
4392
4730
  beforeCursor: timelineCursor.raw,
4393
- nextBeforeCursor: history.hasOlder && history.nextBeforeByte !== undefined
4394
- ? encodeTranscriptTimelineCursor(history.nextBeforeByte, timelineCursor.beforeTimestamp)
4731
+ nextBeforeCursor: history.hasMore
4732
+ ? encodeRuntimeHistoryTimelineCursor(selectedSession, {
4733
+ providerCursor: history.nextCursor,
4734
+ beforeTimestamp: timelineCursor.beforeTimestamp,
4735
+ })
4395
4736
  : undefined,
4396
- hasOlderEvents: history.hasOlder,
4737
+ hasOlderEvents: history.hasMore,
4397
4738
  };
4398
4739
  }
4399
4740
  else {
@@ -4402,17 +4743,32 @@ export function createChatWebApp(options = {}) {
4402
4743
  limit,
4403
4744
  ...(beforeSequence !== undefined ? { beforeSequence } : {}),
4404
4745
  });
4405
- const transcriptEntries = timelineCursor.kind === "tail" && transcriptMetadata?.sessionPath
4406
- ? readTailEntries(transcriptMetadata.sessionPath)
4407
- : [];
4408
4746
  eventCount = events.length;
4747
+ let nativeHistory;
4748
+ if (timelineCursor.kind === "tail"
4749
+ && requiresNativeHistoryCompatibility(selectedSession)
4750
+ && runtimeHistorySupported(context, selectedSession)
4751
+ && context.channelContext.readSessionRuntimeHistory) {
4752
+ const historyStartedAt = performance.now();
4753
+ nativeHistory = await context.channelContext.readSessionRuntimeHistory(selectedSession.id, { limit: Math.min(limit * 4, 500) }).catch(() => undefined);
4754
+ historyMs += performance.now() - historyStartedAt;
4755
+ historyInspection = nativeHistory?.inspection ?? historyInspection;
4756
+ }
4757
+ const historyEntries = nativeHistory?.entries.length
4758
+ ? nativeHistory.entries
4759
+ : state.historyQuery.listProductHistoryEntries({
4760
+ piboSessionId: selectedSession.id,
4761
+ limit: Math.min(limit * 2, 500),
4762
+ ...(beforeSequence !== undefined ? { beforeSequence } : {}),
4763
+ });
4409
4764
  trace = await buildTraceView({
4410
4765
  session: selectedSession,
4411
4766
  sessions: ownedSessions,
4412
4767
  events,
4413
4768
  status: traceStatus,
4414
- metadata: transcriptMetadata ?? {},
4415
- transcriptEntries,
4769
+ historyEntries,
4770
+ historyInspection,
4771
+ historyOrderOffset: nativeHistory?.orderOffset,
4416
4772
  turnTimings,
4417
4773
  includeRawEvents: false,
4418
4774
  latestStreamId,
@@ -4423,19 +4779,24 @@ export function createChatWebApp(options = {}) {
4423
4779
  beforeSequence,
4424
4780
  beforeCursor: timelineCursor.kind === "event" ? timelineCursor.raw : undefined,
4425
4781
  });
4426
- if (trace.hasOlderEvents !== true && events.length > 0) {
4427
- const cutoff = compactionCutoffTimestamp(events);
4782
+ if (trace.hasOlderEvents !== true && nativeHistory?.hasMore) {
4783
+ trace = {
4784
+ ...trace,
4785
+ nextBeforeCursor: encodeRuntimeHistoryTimelineCursor(selectedSession, {
4786
+ providerCursor: nativeHistory.nextCursor,
4787
+ }),
4788
+ hasOlderEvents: true,
4789
+ };
4790
+ }
4791
+ else if (trace.hasOlderEvents !== true && events.length > 0 && runtimeHistorySupported(context, selectedSession)) {
4792
+ const cutoff = compactionCutoffTimestamp(events)
4793
+ ?? (requiresNativeHistoryCompatibility(selectedSession) ? events[0]?.createdAt : undefined);
4428
4794
  if (cutoff) {
4429
- const metadataStartedAt = performance.now();
4430
- const metadata = transcriptMetadata ?? await loadPiSessionMetadata(selectedSession, selectedSession.workspace ?? process.cwd());
4431
- metadataMs += performance.now() - metadataStartedAt;
4432
- if (metadata.sessionPath && metadata.sessionSize && metadata.sessionSize > 0) {
4433
- trace = {
4434
- ...trace,
4435
- nextBeforeCursor: encodeTranscriptTimelineCursor(metadata.sessionSize, cutoff),
4436
- hasOlderEvents: true,
4437
- };
4438
- }
4795
+ trace = {
4796
+ ...trace,
4797
+ nextBeforeCursor: encodeRuntimeHistoryTimelineCursor(selectedSession, { beforeTimestamp: cutoff }),
4798
+ hasOlderEvents: true,
4799
+ };
4439
4800
  }
4440
4801
  }
4441
4802
  }
@@ -4447,21 +4808,12 @@ export function createChatWebApp(options = {}) {
4447
4808
  status: traceStatus,
4448
4809
  });
4449
4810
  trace = { ...trace, version };
4450
- const transcriptTailCursor = timelineCursor.kind === "tail"
4451
- ? originTranscriptTailCursor({
4452
- session: selectedSession,
4453
- trace,
4454
- metadata: transcriptMetadata,
4455
- limit,
4456
- })
4457
- : undefined;
4458
4811
  const page = traceTimelinePageFromView({
4459
4812
  trace,
4460
4813
  payloadStore: state.dataStore.payloads,
4461
4814
  limit,
4462
4815
  byteLimit: TRACE_V2_TIMELINE_HARD_BYTES,
4463
4816
  fromTail: timelineCursor.kind === "tail",
4464
- transcriptTailCursor,
4465
4817
  });
4466
4818
  setTraceTimelinePageCache(state.traceTimelinePageCache, pageCacheKey, page);
4467
4819
  return responseJson(page, {
@@ -4469,7 +4821,7 @@ export function createChatWebApp(options = {}) {
4469
4821
  ...baseHeaders,
4470
4822
  "server-timing": [
4471
4823
  `trace_timeline;dur=${(performance.now() - startedAt).toFixed(1)}`,
4472
- `trace_metadata;dur=${metadataMs.toFixed(1)}`,
4824
+ `trace_history;dur=${historyMs.toFixed(1)}`,
4473
4825
  `trace_events;desc="${eventCount}"`,
4474
4826
  `trace_cache;desc="${cached ? "hit" : "miss"}"`,
4475
4827
  ].join(", "),
@@ -4493,7 +4845,7 @@ export function createChatWebApp(options = {}) {
4493
4845
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/trace/raw-events` && request.method === "GET") {
4494
4846
  const webSession = await requireSession(request, context);
4495
4847
  const rawCursor = parseTimelineBeforeCursor(url);
4496
- if (rawCursor.kind === "transcript")
4848
+ if (rawCursor.kind === "history")
4497
4849
  throw new PiboWebHttpError("Raw event cursor must be tail or a positive sequence", 400);
4498
4850
  const beforeSequence = rawCursor.kind === "event" ? rawCursor.beforeSequence : undefined;
4499
4851
  const limit = parsePositiveIntSearchParam(url, "limit", TRACE_V2_RAW_EVENTS_DEFAULT_LIMIT, TRACE_V2_RAW_EVENTS_MAX_LIMIT);
@@ -4523,9 +4875,17 @@ export function createChatWebApp(options = {}) {
4523
4875
  state.sessionQuery.upsertSession(selectedSession);
4524
4876
  const ownedSessions = listSharedSessions(context);
4525
4877
  const indexedSession = state.sessionQuery.getSession(selectedSession.id);
4526
- const metadataStartedAt = performance.now();
4527
- const metadata = await loadPiSessionMetadata(selectedSession, selectedSession.workspace ?? process.cwd());
4528
- const metadataMs = performance.now() - metadataStartedAt;
4878
+ let historyMs = 0;
4879
+ const productHistory = state.historyQuery.getProductHistoryCoverage(selectedSession.id);
4880
+ let nativeHistory;
4881
+ if (beforeSequence === undefined
4882
+ && requiresNativeHistoryCompatibility(selectedSession)
4883
+ && runtimeHistorySupported(context, selectedSession)
4884
+ && context.channelContext.readSessionRuntimeHistory) {
4885
+ const historyStartedAt = performance.now();
4886
+ nativeHistory = await context.channelContext.readSessionRuntimeHistory(selectedSession.id, { limit: Math.min(eventLimit * 4, 500) }).catch(() => undefined);
4887
+ historyMs += performance.now() - historyStartedAt;
4888
+ }
4529
4889
  const lastEventSequence = state.timelineQuery.getLatestEventSequence(selectedSession.id);
4530
4890
  const latestStreamId = state.timelineQuery.getLatestStreamId({ piboSessionId: selectedSession.id });
4531
4891
  const turnTimings = state.timelineQuery.listMessageTurnTimings(selectedSession.id);
@@ -4534,15 +4894,15 @@ export function createChatWebApp(options = {}) {
4534
4894
  ? context.channelContext.getSessionRuntimeStatus(selectedSession.id) ?? null
4535
4895
  : undefined;
4536
4896
  const traceStatus = traceProjectionStatus(liveSnapshots, indexedSession?.status, turnTimings, runtimeStatus);
4537
- const baseVersion = createTraceViewVersion({
4897
+ const baseVersion = createFastTraceV2Version({
4538
4898
  session: selectedSession,
4539
4899
  sessions: ownedSessions,
4540
- events: lastEventSequence > 0
4541
- ? [{ id: `seq:${lastEventSequence}`, eventSequence: lastEventSequence, createdAt: indexedSession?.lastActivityAt ?? "" }]
4542
- : [],
4900
+ lastEventSequence,
4901
+ lastActivityAt: indexedSession?.lastActivityAt,
4543
4902
  status: traceStatus,
4544
- metadata,
4545
4903
  latestStreamId,
4904
+ productHistory,
4905
+ nativeHistoryVersion: nativeHistory?.inspection?.version,
4546
4906
  });
4547
4907
  const snapshotVersion = liveSnapshotVersion(liveSnapshots);
4548
4908
  const version = snapshotVersion ? `${baseVersion}:live:${snapshotVersion}` : baseVersion;
@@ -4552,7 +4912,7 @@ export function createChatWebApp(options = {}) {
4552
4912
  const serverTiming = (cacheState, eventCount = 0) => ({
4553
4913
  "server-timing": [
4554
4914
  `trace;dur=${(performance.now() - startedAt).toFixed(1)}`,
4555
- `trace_metadata;dur=${metadataMs.toFixed(1)}`,
4915
+ `trace_history;dur=${historyMs.toFixed(1)}`,
4556
4916
  `trace_events;desc="${eventCount}"`,
4557
4917
  `trace_cache;desc="${cacheState}"`,
4558
4918
  ].join(", "),
@@ -4570,12 +4930,21 @@ export function createChatWebApp(options = {}) {
4570
4930
  ...(beforeSequence !== undefined ? { beforeSequence } : {}),
4571
4931
  });
4572
4932
  eventCount = events.length;
4933
+ const historyEntries = nativeHistory?.entries.length
4934
+ ? nativeHistory.entries
4935
+ : state.historyQuery.listProductHistoryEntries({
4936
+ piboSessionId: selectedSession.id,
4937
+ limit: Math.min(eventLimit * 2, 1000),
4938
+ ...(beforeSequence !== undefined ? { beforeSequence } : {}),
4939
+ });
4573
4940
  trace = await buildTraceView({
4574
4941
  session: selectedSession,
4575
4942
  sessions: ownedSessions,
4576
4943
  events,
4577
4944
  status: traceStatus,
4578
- metadata,
4945
+ historyEntries,
4946
+ historyInspection: nativeHistory?.inspection,
4947
+ historyOrderOffset: nativeHistory?.orderOffset,
4579
4948
  turnTimings,
4580
4949
  includeRawEvents: false,
4581
4950
  latestStreamId,
@@ -4637,6 +5006,11 @@ export function createChatWebApp(options = {}) {
4637
5006
  session,
4638
5007
  sessions: ownedSessions,
4639
5008
  events: state.timelineQuery.listTraceEvents({ piboSessionId, beforeOrAtSequence: eventSequence, limit: DEFAULT_TRACE_EVENTS_PAGE_SIZE }),
5009
+ historyEntries: state.historyQuery.listProductHistoryEntries({
5010
+ piboSessionId,
5011
+ limit: DEFAULT_TRACE_EVENTS_PAGE_SIZE,
5012
+ beforeSequence: eventSequence + 1,
5013
+ }),
4640
5014
  status: indexedSession?.status,
4641
5015
  turnTimings: state.timelineQuery.listMessageTurnTimings(piboSessionId),
4642
5016
  });
@@ -4654,6 +5028,34 @@ export function createChatWebApp(options = {}) {
4654
5028
  const body = await readJsonBody(request);
4655
5029
  return sendChatMessage({ state, context, webSession, defaultProfile, body });
4656
5030
  }
5031
+ if (url.pathname === `${CHAT_WEB_API_PREFIX}/provider-auth` && request.method === "GET") {
5032
+ await requireSession(request, context);
5033
+ return responseJson(await readProviderAuthCatalog(context.channelContext, defaultRuntimeInstanceId(context, defaultProfile)), { headers: { "cache-control": "no-store" } });
5034
+ }
5035
+ if (url.pathname === `${CHAT_WEB_API_PREFIX}/provider-auth` && request.method === "POST") {
5036
+ requireSameOriginJsonRequest(request);
5037
+ await requireSession(request, context);
5038
+ const body = await readJsonBody(request);
5039
+ const action = typeof body.action === "string" ? body.action : "";
5040
+ const mappedAction = action === "start"
5041
+ ? "login.start"
5042
+ : action === "complete"
5043
+ ? "login.complete"
5044
+ : action === "api_key"
5045
+ ? "login.apikey"
5046
+ : action === "cancel"
5047
+ ? "login.cancel"
5048
+ : action === "logout"
5049
+ ? "logout"
5050
+ : isProviderAuthAction(action) && action !== "login.status"
5051
+ ? action
5052
+ : undefined;
5053
+ if (!mappedAction)
5054
+ throw new PiboWebHttpError("Unsupported provider authentication action", 400);
5055
+ const result = await executeProviderAuthAction(context.channelContext, mappedAction, body);
5056
+ invalidateBootstrapCatalogCache(state);
5057
+ return responseJson({ result }, { headers: { "cache-control": "no-store" } });
5058
+ }
4657
5059
  if (url.pathname === `${CHAT_WEB_API_PREFIX}/status` && request.method === "GET") {
4658
5060
  const webSession = await requireSession(request, context);
4659
5061
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, url.searchParams.get("piboSessionId") || undefined);
@@ -4677,7 +5079,30 @@ export function createChatWebApp(options = {}) {
4677
5079
  }
4678
5080
  const piboSessionId = typeof body.piboSessionId === "string" ? body.piboSessionId : undefined;
4679
5081
  if (isProviderAuthAction(body.action)) {
4680
- return providerAuthActionResponse({ piboSessionId, action: body.action, result: await executeProviderAuthAction(body.action, body.params) });
5082
+ const authParams = isJsonObject(body.params) ? { ...body.params } : {};
5083
+ const explicitRuntimeInstanceId = typeof authParams.runtimeInstanceId === "string" && authParams.runtimeInstanceId.trim()
5084
+ ? authParams.runtimeInstanceId.trim()
5085
+ : undefined;
5086
+ const selectedSession = piboSessionId
5087
+ ? resolveRequestedSession(state, context, webSession, defaultProfile, piboSessionId)
5088
+ : undefined;
5089
+ const boundRuntimeInstanceId = selectedSession ? sessionRuntimeInstanceId(context, selectedSession) : undefined;
5090
+ if (explicitRuntimeInstanceId && boundRuntimeInstanceId && explicitRuntimeInstanceId !== boundRuntimeInstanceId) {
5091
+ throw new PiboWebHttpError("Provider authentication target conflicts with the selected session runtime", 409);
5092
+ }
5093
+ const selectedRuntimeInstanceId = explicitRuntimeInstanceId ?? boundRuntimeInstanceId;
5094
+ if (!selectedRuntimeInstanceId) {
5095
+ throw new PiboWebHttpError("Provider authentication requires an explicit runtimeInstanceId or a valid bound Pibo Session", 400);
5096
+ }
5097
+ authParams.runtimeInstanceId = selectedRuntimeInstanceId;
5098
+ const result = await executeProviderAuthAction(context.channelContext, body.action, authParams);
5099
+ if (body.action !== "login.status")
5100
+ invalidateBootstrapCatalogCache(state);
5101
+ return providerAuthActionResponse({
5102
+ piboSessionId: selectedSession?.id,
5103
+ action: body.action,
5104
+ result,
5105
+ });
4681
5106
  }
4682
5107
  const selectedSession = resolveRequestedSession(state, context, webSession, defaultProfile, piboSessionId);
4683
5108
  const room = ensureSessionRoom(state, context, selectedSession, webSession);