@pasko70/pibo 2.5.2 → 3.0.1

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 (100) hide show
  1. package/context/pibo-native-tooling.md +1 -0
  2. package/dist/agent-runtime/contract.js +2 -1
  3. package/dist/agent-runtime/history.js +53 -1
  4. package/dist/agent-runtime/routed-session.js +100 -26
  5. package/dist/agent-runtimes/codex-native/adapter.js +304 -30
  6. package/dist/agent-runtimes/codex-native/client.js +22 -3
  7. package/dist/agent-runtimes/codex-native/first-use.js +294 -0
  8. package/dist/agent-runtimes/codex-native/history.js +26 -7
  9. package/dist/agent-runtimes/codex-native/process.js +14 -0
  10. package/dist/agent-runtimes/codex-native/thread.js +59 -25
  11. package/dist/agent-runtimes/codex-native/turn.js +27 -0
  12. package/dist/agent-runtimes/history-proof.js +8 -0
  13. package/dist/agent-runtimes/omp/adapter.js +61 -14
  14. package/dist/agent-runtimes/omp/history.js +189 -34
  15. package/dist/agent-runtimes/omp/thread.js +82 -36
  16. package/dist/agent-runtimes/pi/adapter.js +30 -3
  17. package/dist/agent-runtimes/pi/history.js +66 -5
  18. package/dist/agent-runtimes/pi/openai-response-observer.js +102 -0
  19. package/dist/agent-runtimes/pi/routed-session.js +344 -37
  20. package/dist/apps/chat/chat-api-routes.js +1 -1
  21. package/dist/apps/chat/chat-files.js +130 -9
  22. package/dist/apps/chat/chat-settings-routes.js +27 -2
  23. package/dist/apps/chat/chat-speech.js +100 -0
  24. package/dist/apps/chat/data/chat-data-mappers.js +25 -5
  25. package/dist/apps/chat/data/timeline-query-service.js +89 -1
  26. package/dist/apps/chat/trace-v2.js +210 -8
  27. package/dist/apps/chat/trace.js +3 -0
  28. package/dist/apps/chat/web-app.js +108 -3
  29. package/dist/apps/chat-ui/assets/{dist-DTWJf37A.js → dist-BBDMeU5-.js} +1 -1
  30. package/dist/apps/chat-ui/assets/{dist-CkYWKnvy.js → dist-BDIATCQt.js} +1 -1
  31. package/dist/apps/chat-ui/assets/{dist-DZ8Wpj-H.js → dist-JnW4v8UE.js} +1 -1
  32. package/dist/apps/chat-ui/assets/{dist-Dmblz15N.js → dist-lRiLP9rr.js} +1 -1
  33. package/dist/apps/chat-ui/assets/{dist-DjcwoyNY.js → dist-mqCviI-c.js} +1 -1
  34. package/dist/apps/chat-ui/assets/index-0XOOVxLP.js +228 -0
  35. package/dist/apps/chat-ui/assets/index-DV7_CgsC.css +1 -0
  36. package/dist/apps/chat-ui/index.html +2 -2
  37. package/dist/apps/chat-vscode-web/assets/index-Dtw2Z7jz.js +43 -0
  38. package/dist/apps/chat-vscode-web/assets/{index-CF1-9PKU.css → index-SluZr_-r.css} +1 -1
  39. package/dist/apps/chat-vscode-web/index.html +2 -2
  40. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  41. package/dist/apps/vscode-artifacts/pibo-vscode-ext-3.0.1.vsix +0 -0
  42. package/dist/cli.js +49 -5
  43. package/dist/config/config.js +59 -8
  44. package/dist/core/preview-server-settings.js +20 -0
  45. package/dist/core/session-router.js +464 -37
  46. package/dist/core/user-settings.js +10 -0
  47. package/dist/data/payload-store.js +57 -1
  48. package/dist/data/schema.js +18 -1
  49. package/dist/debug/trace.js +5 -0
  50. package/dist/gateway/client.js +368 -65
  51. package/dist/gateway/protocol.js +2 -0
  52. package/dist/gateway/server.js +8 -0
  53. package/dist/gateway/web.js +2 -0
  54. package/dist/index.js +1 -1
  55. package/dist/loops/store.js +6 -4
  56. package/dist/mcp/config.js +89 -17
  57. package/dist/mcp/daemon-client.js +473 -153
  58. package/dist/mcp/daemon.js +401 -37
  59. package/dist/plugins/builtin.js +2 -2
  60. package/dist/plugins/codex-native.js +2 -0
  61. package/dist/plugins/registry.js +259 -1
  62. package/dist/previews/cli.js +345 -0
  63. package/dist/previews/config.js +61 -0
  64. package/dist/previews/manager.js +644 -0
  65. package/dist/previews/network.js +198 -0
  66. package/dist/previews/plugin.js +11 -0
  67. package/dist/previews/process-supervisor.js +38 -0
  68. package/dist/previews/proxy.js +387 -0
  69. package/dist/previews/store.js +750 -0
  70. package/dist/previews/types.js +1 -0
  71. package/dist/previews/web-app.js +446 -0
  72. package/dist/runs/registry.js +22 -0
  73. package/dist/runs/tools.js +46 -7
  74. package/dist/session-ui/terminalRows.js +187 -13
  75. package/dist/sessions/pibo-data-store.js +37 -0
  76. package/dist/sessions/runtime-binding-persistence.js +34 -0
  77. package/dist/sessions/runtime-binding.js +6 -0
  78. package/dist/sessions/sqlite-store.js +37 -0
  79. package/dist/shared/trace-engine.js +12 -64
  80. package/dist/shared/trace-event-projection.js +16 -0
  81. package/dist/shared/trace-history.js +684 -184
  82. package/dist/shared/trace-limits.js +7 -0
  83. package/dist/shared/trace-live-patch.js +61 -0
  84. package/dist/shared/trace-tool-identity.js +43 -0
  85. package/dist/speech/openai-codex-realtime-call-proxy.js +248 -0
  86. package/dist/speech/openai-codex.js +407 -0
  87. package/dist/speech/types.js +8 -0
  88. package/dist/subagents/context.js +5 -3
  89. package/dist/subagents/observations.js +94 -6
  90. package/dist/subagents/tool.js +103 -20
  91. package/dist/tools/codex-image-artifacts.js +24 -0
  92. package/dist/tools/codex-image-generation.js +4 -22
  93. package/dist/web/channel.js +88 -4
  94. package/dist/web/http.js +2 -1
  95. package/npm-shrinkwrap.json +2 -2
  96. package/package.json +1 -1
  97. package/dist/apps/chat-ui/assets/index-0WZI2phJ.css +0 -1
  98. package/dist/apps/chat-ui/assets/index-DsgTm1Aw.js +0 -228
  99. package/dist/apps/chat-vscode-web/assets/index-DvTSSvzN.js +0 -43
  100. package/dist/apps/vscode-artifacts/pibo-vscode-ext-2.5.2.vsix +0 -0
@@ -8,6 +8,7 @@ import { PIBO_PROVIDER_RECOVERY_MESSAGE_TYPE, PIBO_PROVIDER_RECOVERY_PROMPT, Pib
8
8
  import { PiAgentRuntimeAuthController } from "./auth.js";
9
9
  import { piIntentTracingInstalled, piToolIntentField, splitPiToolIntentArguments } from "./intent-tracing.js";
10
10
  import { loadModelCatalog as loadPiModelCatalog } from "./model-catalog.js";
11
+ import { withOpenAiResponseEventObserver } from "./openai-response-observer.js";
11
12
  import { PIBO_TRANSCRIPT_INTEGRITY_RESUME_MESSAGE_TYPE, PIBO_TRANSCRIPT_INTEGRITY_RESUME_PROMPT, PiboTranscriptIntegrityError, claimPiboTranscriptIntegrityContinuation, settlePiboTranscriptIntegrityContinuation, } from "../../core/transcript-integrity.js";
12
13
  const FAST_SERVICE_TIER = "priority";
13
14
  const RUN_REMINDER_CAPABILITY_TOOLS = new Set([
@@ -24,6 +25,11 @@ function modelSupportsFastServiceTier(model) {
24
25
  return true;
25
26
  return model.api === "openai-responses" && (model.provider === "openai" || model.provider === "openai-codex");
26
27
  }
28
+ function modelUsesOpenAiResponses(model) {
29
+ if (model?.api === "openai-codex-responses")
30
+ return true;
31
+ return model?.api === "openai-responses" && (model.provider === "openai" || model.provider === "openai-codex");
32
+ }
27
33
  function withFastServiceTier(payload) {
28
34
  if (!payload || typeof payload !== "object" || Array.isArray(payload))
29
35
  return payload;
@@ -250,6 +256,9 @@ function normalizeToolExecutionEvent(piboSessionId, candidate, context) {
250
256
  }
251
257
  return undefined;
252
258
  }
259
+ function providerWebSearchToolCallId(rawId) {
260
+ return rawId.startsWith("provider:web_search:") ? rawId : `provider:web_search:${rawId}`;
261
+ }
253
262
  function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
254
263
  const item = recordValue(candidate.item) ?? recordValue(candidate.assistantMessageEvent?.item);
255
264
  const itemType = stringValue(item?.type);
@@ -269,14 +278,22 @@ function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
269
278
  ? `content-${candidate.assistantMessageEvent.contentIndex}`
270
279
  : undefined) ??
271
280
  "active";
272
- const toolCallId = rawId.startsWith("provider:web_search:") ? rawId : `provider:web_search:${rawId}`;
273
- const query = stringValue(candidate.query) ??
274
- stringValue(candidate.assistantMessageEvent?.query) ??
275
- stringValue(action?.query) ??
276
- stringValue(item?.query) ??
277
- stringValue(item?.search_query);
278
- const sources = firstArray(candidate.sources, candidate.assistantMessageEvent?.sources, item?.sources, item?.results, item?.citations);
279
- const args = { providerTool: "web_search", ...(query ? { query } : {}) };
281
+ const toolCallId = providerWebSearchToolCallId(rawId);
282
+ const query = firstTrimmedString(candidate.query, candidate.assistantMessageEvent?.query, action?.query, item?.query, item?.search_query);
283
+ const queries = firstStringArray(action?.queries, item?.queries);
284
+ const url = normalizeProviderWebUrl(firstTrimmedString(action?.url, item?.url));
285
+ const pattern = firstTrimmedString(action?.pattern, item?.pattern);
286
+ const actionType = normalizeProviderWebSearchActionType(action?.type, { query, queries, url, pattern });
287
+ const rawSources = firstArray(candidate.sources, candidate.assistantMessageEvent?.sources, action?.sources, item?.sources, item?.results, item?.citations);
288
+ const sources = normalizeProviderWebSearchSources(rawSources);
289
+ const details = {
290
+ ...(actionType ? { actionType } : {}),
291
+ ...(query ? { query } : {}),
292
+ ...(queries ? { queries } : {}),
293
+ ...(url ? { url } : {}),
294
+ ...(pattern ? { pattern } : {}),
295
+ };
296
+ const args = { providerTool: "web_search", ...details };
280
297
  if (status === "running") {
281
298
  return {
282
299
  type: "tool_execution_started",
@@ -295,12 +312,106 @@ function normalizeProviderWebSearchEvent(piboSessionId, candidate) {
295
312
  result: status === "error"
296
313
  ? (error ?? "Web search failed")
297
314
  : {
298
- ...(query ? { query } : {}),
299
- ...(sources ? { sources, sourceCount: sources.length } : {}),
315
+ ...details,
316
+ ...(rawSources ? { sources, sourceCount: sources.length } : {}),
300
317
  },
301
318
  isError: status === "error",
302
319
  };
303
320
  }
321
+ function inferProviderWebSearchActionType(details) {
322
+ if (details.pattern)
323
+ return "find_in_page";
324
+ if (details.url)
325
+ return "open_page";
326
+ if (details.query || details.queries)
327
+ return "search";
328
+ return undefined;
329
+ }
330
+ function normalizeProviderWebSearchActionType(value, details) {
331
+ const explicit = firstTrimmedString(value)?.toLowerCase();
332
+ if (explicit === "search")
333
+ return "search";
334
+ if (explicit === "open" || explicit === "open_page")
335
+ return "open_page";
336
+ if (explicit === "find" || explicit === "find_in_page")
337
+ return "find_in_page";
338
+ if (explicit === "other")
339
+ return "other";
340
+ return inferProviderWebSearchActionType(details);
341
+ }
342
+ function normalizeProviderWebSearchSources(candidates) {
343
+ if (!candidates)
344
+ return [];
345
+ const sources = new Map();
346
+ for (const candidate of candidates) {
347
+ const record = recordValue(candidate);
348
+ const rawUrl = typeof candidate === "string"
349
+ ? candidate
350
+ : firstTrimmedString(record?.url, record?.href, record?.link);
351
+ const url = normalizeProviderWebUrl(rawUrl, true);
352
+ if (!url)
353
+ continue;
354
+ const title = firstTrimmedString(record?.title, record?.name);
355
+ const existing = sources.get(url);
356
+ if (!existing || (!existing.title && title))
357
+ sources.set(url, { url, ...(title ? { title } : {}) });
358
+ }
359
+ return [...sources.values()];
360
+ }
361
+ function normalizeProviderWebUrl(value, stripHash = false) {
362
+ if (!value)
363
+ return undefined;
364
+ try {
365
+ const url = new URL(value);
366
+ if ((url.protocol !== "https:" && url.protocol !== "http:") || url.username || url.password)
367
+ return undefined;
368
+ if (stripHash)
369
+ url.hash = "";
370
+ return url.href;
371
+ }
372
+ catch {
373
+ return undefined;
374
+ }
375
+ }
376
+ function providerWebSearchCompletionIsProvisional(candidate) {
377
+ const rawEventType = (stringValue(candidate.type) ?? stringValue(candidate.assistantMessageEvent?.type))?.toLowerCase();
378
+ if (!rawEventType?.includes("web_search_call.completed"))
379
+ return false;
380
+ return !recordValue(candidate.item) && !recordValue(candidate.assistantMessageEvent?.item);
381
+ }
382
+ function providerResponseCompleted(candidate) {
383
+ const rawEventType = (stringValue(candidate.type) ?? stringValue(candidate.assistantMessageEvent?.type))?.toLowerCase();
384
+ return rawEventType === "response.completed";
385
+ }
386
+ function normalizeNativeProviderWebSearchEvent(event) {
387
+ if (!event ||
388
+ (event.type !== "tool_execution_started" &&
389
+ event.type !== "tool_execution_updated" &&
390
+ event.type !== "tool_execution_finished") ||
391
+ event.toolName !== "web_search") {
392
+ return undefined;
393
+ }
394
+ return { ...event, toolCallId: providerWebSearchToolCallId(event.toolCallId) };
395
+ }
396
+ function providerWebSearchArgsFromResult(result) {
397
+ const details = recordValue(result);
398
+ return {
399
+ providerTool: "web_search",
400
+ ...(details?.actionType ? { actionType: details.actionType } : {}),
401
+ ...(details?.query ? { query: details.query } : {}),
402
+ ...(details?.queries ? { queries: details.queries } : {}),
403
+ ...(details?.url ? { url: details.url } : {}),
404
+ ...(details?.pattern ? { pattern: details.pattern } : {}),
405
+ };
406
+ }
407
+ function providerWebSearchArgsKey(args) {
408
+ try {
409
+ return JSON.stringify(args);
410
+ }
411
+ catch {
412
+ return String(args);
413
+ }
414
+ }
304
415
  function providerWebSearchStatus(rawType, rawStatus) {
305
416
  const type = rawType.toLowerCase();
306
417
  const status = stringValue(rawStatus)?.toLowerCase();
@@ -322,6 +433,29 @@ function firstArray(...values) {
322
433
  }
323
434
  return undefined;
324
435
  }
436
+ function firstStringArray(...values) {
437
+ for (const value of values) {
438
+ if (!Array.isArray(value))
439
+ continue;
440
+ const strings = [...new Set(value.flatMap((item) => {
441
+ const normalized = firstTrimmedString(item);
442
+ return normalized ? [normalized] : [];
443
+ }))];
444
+ if (strings.length > 0)
445
+ return strings;
446
+ }
447
+ return undefined;
448
+ }
449
+ function firstTrimmedString(...values) {
450
+ for (const value of values) {
451
+ if (typeof value !== "string")
452
+ continue;
453
+ const trimmed = value.trim();
454
+ if (trimmed)
455
+ return trimmed;
456
+ }
457
+ return undefined;
458
+ }
325
459
  export function normalizePiEvent(piboSessionId, event, context) {
326
460
  if (!event || typeof event !== "object")
327
461
  return undefined;
@@ -462,13 +596,16 @@ export class RoutedSession {
462
596
  drainPromise;
463
597
  inFlightMessage;
464
598
  fastMode = false;
465
- fastModePatchedAgents = new WeakSet();
599
+ providerRequestPatchedAgents = new WeakSet();
600
+ providerWebSearchObservationEnabled = false;
601
+ providerWebSearchLifecycle = new Map();
466
602
  activeMessage;
467
603
  activeExecutionEvent;
468
604
  activeAssistantIndex;
469
605
  nextAssistantIndex = 0;
470
606
  activeThinkingIndex;
471
607
  nextThinkingIndex = 0;
608
+ sessionIdentityOperationInFlight = false;
472
609
  pendingAssistantError;
473
610
  pendingAssistantErrorRetryable = false;
474
611
  activeMessageFailed = false;
@@ -493,27 +630,43 @@ export class RoutedSession {
493
630
  this.messagePreflight = messagePreflight;
494
631
  this.fastMode = initialFastMode && this.fastModeSupported();
495
632
  this.bindRuntimeSession();
496
- this.patchFastModeProviderRequest();
633
+ this.patchProviderRequest();
497
634
  this.patchAgentContinue();
498
635
  this.runtime.setRebindSession(async () => {
499
636
  this.bindRuntimeSession();
500
- this.patchFastModeProviderRequest();
637
+ this.patchProviderRequest();
501
638
  this.patchAgentContinue();
502
639
  });
503
640
  }
504
- patchFastModeProviderRequest() {
641
+ enableProviderWebSearchObservation() {
642
+ this.providerWebSearchObservationEnabled = true;
643
+ }
644
+ patchProviderRequest() {
505
645
  const agent = this.runtime.session.agent;
506
646
  if (!agent || typeof agent !== "object")
507
647
  return;
508
- if (this.fastModePatchedAgents.has(agent))
648
+ if (this.providerRequestPatchedAgents.has(agent))
509
649
  return;
510
- this.fastModePatchedAgents.add(agent);
511
- const originalStreamFn = typeof agent.streamFn === "function" ? agent.streamFn.bind(agent) : undefined;
512
- if (originalStreamFn) {
513
- agent.streamFn = (model, context, options) => {
514
- const nextOptions = this.shouldUseFastServiceTier(model) ? withFastServiceTierOption(options) : options;
650
+ this.providerRequestPatchedAgents.add(agent);
651
+ const streamFunctionKey = typeof agent.streamFunction === "function"
652
+ ? "streamFunction"
653
+ : typeof agent.streamFn === "function"
654
+ ? "streamFn"
655
+ : undefined;
656
+ const originalStreamFn = streamFunctionKey ? agent[streamFunctionKey]?.bind(agent) : undefined;
657
+ if (streamFunctionKey && originalStreamFn) {
658
+ const wrappedStreamFn = (model, context, options) => {
659
+ let nextOptions = this.shouldUseFastServiceTier(model) ? withFastServiceTierOption(options) : options;
660
+ if (this.providerWebSearchObservationEnabled && modelUsesOpenAiResponses(model)) {
661
+ const activeEventId = this.activeMessage?.id;
662
+ nextOptions = withOpenAiResponseEventObserver(nextOptions, (event) => this.observeProviderResponseEvent(event, activeEventId));
663
+ }
515
664
  return originalStreamFn(model, context, nextOptions);
516
665
  };
666
+ if (streamFunctionKey === "streamFunction")
667
+ agent.streamFunction = wrappedStreamFn;
668
+ else
669
+ agent.streamFn = wrappedStreamFn;
517
670
  }
518
671
  const originalOnPayload = agent.onPayload?.bind(agent);
519
672
  agent.onPayload = async (payload, model) => {
@@ -523,6 +676,111 @@ export class RoutedSession {
523
676
  return useFastTier ? withFastServiceTier(nextPayload) : nextPayload;
524
677
  };
525
678
  }
679
+ observeProviderResponseEvent(event, activeEventId) {
680
+ if (!event || typeof event !== "object")
681
+ return;
682
+ if (activeEventId && this.activeMessage?.id !== activeEventId)
683
+ return;
684
+ const candidate = event;
685
+ if (providerResponseCompleted(candidate)) {
686
+ this.flushPendingProviderWebSearchFinishes();
687
+ return;
688
+ }
689
+ this.emitProviderWebSearchEvent(candidate, undefined, "provider");
690
+ }
691
+ emitProviderWebSearchEvent(candidate, normalized = normalizeProviderWebSearchEvent(this.piboSessionId, candidate), source = "provider") {
692
+ if (!normalized || (normalized.type !== "tool_execution_started" &&
693
+ normalized.type !== "tool_execution_updated" &&
694
+ normalized.type !== "tool_execution_finished"))
695
+ return false;
696
+ const lifecycle = this.providerWebSearchLifecycle.get(normalized.toolCallId) ?? { started: false, finished: false };
697
+ if (source === "provider")
698
+ lifecycle.providerObserved = true;
699
+ else if (lifecycle.providerObserved) {
700
+ if (normalized.type === "tool_execution_finished" && !lifecycle.finished && !lifecycle.pendingFinish) {
701
+ lifecycle.pendingFinish = normalized;
702
+ this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
703
+ }
704
+ return true;
705
+ }
706
+ if (normalized.type === "tool_execution_started" || normalized.type === "tool_execution_updated") {
707
+ if (lifecycle.finished)
708
+ return true;
709
+ const argsKey = providerWebSearchArgsKey(normalized.args);
710
+ if (!lifecycle.started) {
711
+ lifecycle.started = true;
712
+ lifecycle.argsKey = argsKey;
713
+ this.emit(this.withActiveMessage(normalized.type === "tool_execution_started" ? normalized : {
714
+ type: "tool_execution_started",
715
+ piboSessionId: normalized.piboSessionId,
716
+ toolCallId: normalized.toolCallId,
717
+ toolName: normalized.toolName,
718
+ args: normalized.args,
719
+ }));
720
+ }
721
+ else if (argsKey !== lifecycle.argsKey) {
722
+ lifecycle.argsKey = argsKey;
723
+ this.emit(this.withActiveMessage({
724
+ type: "tool_execution_updated",
725
+ piboSessionId: this.piboSessionId,
726
+ toolCallId: normalized.toolCallId,
727
+ toolName: normalized.toolName,
728
+ args: normalized.args,
729
+ partialResult: normalized.args,
730
+ }));
731
+ }
732
+ this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
733
+ return true;
734
+ }
735
+ if (providerWebSearchCompletionIsProvisional(candidate) || (source === "pi" && this.providerWebSearchObservationEnabled)) {
736
+ if (!lifecycle.started) {
737
+ const args = providerWebSearchArgsFromResult(normalized.result);
738
+ lifecycle.started = true;
739
+ lifecycle.argsKey = providerWebSearchArgsKey(args);
740
+ this.emit(this.withActiveMessage({
741
+ type: "tool_execution_started",
742
+ piboSessionId: this.piboSessionId,
743
+ toolCallId: normalized.toolCallId,
744
+ toolName: normalized.toolName,
745
+ args,
746
+ }));
747
+ }
748
+ lifecycle.pendingFinish = normalized;
749
+ this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
750
+ return true;
751
+ }
752
+ if (lifecycle.finished)
753
+ return true;
754
+ lifecycle.pendingFinish = undefined;
755
+ this.finishProviderWebSearchLifecycle(normalized, lifecycle);
756
+ return true;
757
+ }
758
+ finishProviderWebSearchLifecycle(normalized, lifecycle) {
759
+ if (!lifecycle.started) {
760
+ const args = providerWebSearchArgsFromResult(normalized.result);
761
+ lifecycle.started = true;
762
+ lifecycle.argsKey = providerWebSearchArgsKey(args);
763
+ this.emit(this.withActiveMessage({
764
+ type: "tool_execution_started",
765
+ piboSessionId: this.piboSessionId,
766
+ toolCallId: normalized.toolCallId,
767
+ toolName: normalized.toolName,
768
+ args,
769
+ }));
770
+ }
771
+ lifecycle.finished = true;
772
+ this.providerWebSearchLifecycle.set(normalized.toolCallId, lifecycle);
773
+ this.emit(this.withActiveMessage(normalized));
774
+ }
775
+ flushPendingProviderWebSearchFinishes() {
776
+ for (const lifecycle of this.providerWebSearchLifecycle.values()) {
777
+ const pending = lifecycle.pendingFinish;
778
+ if (!pending || lifecycle.finished)
779
+ continue;
780
+ lifecycle.pendingFinish = undefined;
781
+ this.finishProviderWebSearchLifecycle(pending, lifecycle);
782
+ }
783
+ }
526
784
  shouldUseFastServiceTier(model) {
527
785
  if (this.getFastModeResult().mode !== "fast")
528
786
  return false;
@@ -576,13 +834,17 @@ export class RoutedSession {
576
834
  this.unsubscribe = session.subscribe((event) => {
577
835
  this.onPiEventTelemetry?.(this.piboSessionId, event, { status: this.getStatus(), activeEventId: this.activeMessage?.id ?? this.activeExecutionEvent?.id });
578
836
  const model = this.runtime.session.model;
579
- const normalized = normalizePiEvent(this.piboSessionId, event, {
837
+ const candidate = event && typeof event === "object" ? event : undefined;
838
+ const providerWebSearchEvent = candidate ? normalizeProviderWebSearchEvent(this.piboSessionId, candidate) : undefined;
839
+ const normalized = providerWebSearchEvent ?? normalizePiEvent(this.piboSessionId, event, {
580
840
  contextWindow: numberValue(model?.contextWindow),
581
841
  intentTracing: piIntentTracingInstalled(session),
582
842
  intentFieldForTool: (toolName) => piToolIntentField(session, toolName),
583
843
  });
584
- const candidate = event && typeof event === "object" ? event : undefined;
585
844
  const assistantMessageEnded = candidate?.type === "message_end" && isAssistantMessage(candidate.message);
845
+ const assistantMessageFailed = assistantMessageEnded && normalized?.type === "session_error";
846
+ if (assistantMessageEnded && !assistantMessageFailed)
847
+ this.flushPendingProviderWebSearchFinishes();
586
848
  const usageEvent = assistantMessageEnded ? normalizeAssistantUsageEvent(this.piboSessionId, candidate?.message) : undefined;
587
849
  if (usageEvent)
588
850
  this.emit(this.withActiveMessage(usageEvent));
@@ -597,8 +859,18 @@ export class RoutedSession {
597
859
  this.pendingAssistantError = undefined;
598
860
  this.pendingAssistantErrorRetryable = false;
599
861
  }
600
- if (normalized)
862
+ const nativeProviderWebSearchEvent = this.providerWebSearchObservationEnabled
863
+ ? normalizeNativeProviderWebSearchEvent(normalized)
864
+ : undefined;
865
+ if (providerWebSearchEvent && candidate) {
866
+ this.emitProviderWebSearchEvent(candidate, providerWebSearchEvent, "provider");
867
+ }
868
+ else if (nativeProviderWebSearchEvent && candidate) {
869
+ this.emitProviderWebSearchEvent(candidate, nativeProviderWebSearchEvent, "pi");
870
+ }
871
+ else if (normalized) {
601
872
  this.emit(this.withActiveMessage(normalized));
873
+ }
602
874
  }
603
875
  if (this.forwardPiEvents) {
604
876
  this.emit({ type: "pi_event", piboSessionId: this.piboSessionId, event });
@@ -762,6 +1034,9 @@ export class RoutedSession {
762
1034
  }
763
1035
  enqueueMessage(event) {
764
1036
  this.assertActive();
1037
+ if (this.sessionIdentityOperationInFlight) {
1038
+ throw new Error("Pibo session cannot accept messages while a session identity operation is in progress.");
1039
+ }
765
1040
  this.queue.push({ kind: "message", event });
766
1041
  const output = {
767
1042
  type: "message_queued",
@@ -807,18 +1082,29 @@ export class RoutedSession {
807
1082
  if (event.action === "compact") {
808
1083
  return this.enqueueCompactAction(event);
809
1084
  }
810
- const result = await this.runAction(event);
811
- if (isSessionOperationResult(result))
812
- await this.onSessionOperation?.(result, event);
813
- const output = {
814
- type: "execution_result",
815
- piboSessionId: this.piboSessionId,
816
- eventId: event.id,
817
- action: event.action,
818
- result,
819
- };
820
- this.emit(output);
821
- return output;
1085
+ const changesSessionIdentity = event.action === "session.fork" || event.action === "session.clone";
1086
+ if (changesSessionIdentity) {
1087
+ this.assertSessionIdentityOperationIdle(event.action === "session.fork" ? "fork" : "clone");
1088
+ this.sessionIdentityOperationInFlight = true;
1089
+ }
1090
+ try {
1091
+ const result = await this.runAction(event);
1092
+ if (isSessionOperationResult(result))
1093
+ await this.onSessionOperation?.(result, event);
1094
+ const output = {
1095
+ type: "execution_result",
1096
+ piboSessionId: this.piboSessionId,
1097
+ eventId: event.id,
1098
+ action: event.action,
1099
+ result,
1100
+ };
1101
+ this.emit(output);
1102
+ return output;
1103
+ }
1104
+ finally {
1105
+ if (changesSessionIdentity)
1106
+ this.sessionIdentityOperationInFlight = false;
1107
+ }
822
1108
  }
823
1109
  getActiveMessage() {
824
1110
  if (!this.activeMessage)
@@ -919,10 +1205,12 @@ export class RoutedSession {
919
1205
  }));
920
1206
  }
921
1207
  getForkCandidates() {
1208
+ this.assertSessionIdentityOperationIdle("inspect fork candidates");
922
1209
  return this.runtime.session.getUserMessagesForForking();
923
1210
  }
924
1211
  async forkSession(entryId) {
925
1212
  this.assertActive();
1213
+ this.assertSessionWorkIdle("fork");
926
1214
  const previous = this.createSessionSnapshot();
927
1215
  const result = await this.runtime.fork(entryId);
928
1216
  return {
@@ -935,6 +1223,7 @@ export class RoutedSession {
935
1223
  }
936
1224
  async cloneSession() {
937
1225
  this.assertActive();
1226
+ this.assertSessionWorkIdle("clone");
938
1227
  const leafId = this.runtime.session.sessionManager.getLeafId();
939
1228
  if (!leafId) {
940
1229
  throw new Error("Cannot clone session: no current entry selected");
@@ -948,6 +1237,18 @@ export class RoutedSession {
948
1237
  cancelled: result.cancelled,
949
1238
  };
950
1239
  }
1240
+ assertSessionIdentityOperationIdle(operation) {
1241
+ if (this.sessionIdentityOperationInFlight) {
1242
+ throw new Error(`Pibo session already has a session identity operation in progress; cannot ${operation}.`);
1243
+ }
1244
+ this.assertSessionWorkIdle(operation);
1245
+ }
1246
+ assertSessionWorkIdle(operation) {
1247
+ const status = this.getStatus();
1248
+ if (status.processing || status.streaming || status.queuedMessages > 0) {
1249
+ throw new Error(`Pibo session must be idle to ${operation}.`);
1250
+ }
1251
+ }
951
1252
  getSessionTree() {
952
1253
  this.assertActive();
953
1254
  return {
@@ -1151,8 +1452,11 @@ export class RoutedSession {
1151
1452
  const drain = this.drain();
1152
1453
  this.drainPromise = drain;
1153
1454
  void drain.finally(() => {
1154
- if (this.drainPromise === drain)
1155
- this.drainPromise = undefined;
1455
+ if (this.drainPromise !== drain)
1456
+ return;
1457
+ this.drainPromise = undefined;
1458
+ if (this.queue.length > 0 && !this.disposed)
1459
+ this.startDrain();
1156
1460
  });
1157
1461
  }
1158
1462
  async drain() {
@@ -1205,6 +1509,7 @@ export class RoutedSession {
1205
1509
  if (this.disposed || inFlight.cancelled)
1206
1510
  return;
1207
1511
  this.activeMessage = event;
1512
+ this.providerWebSearchLifecycle.clear();
1208
1513
  this.providerRecoveryCancelled = false;
1209
1514
  this.pendingAssistantError = undefined;
1210
1515
  this.pendingAssistantErrorRetryable = false;
@@ -1239,6 +1544,7 @@ export class RoutedSession {
1239
1544
  return;
1240
1545
  this.flushPendingAssistantError();
1241
1546
  if (!this.activeMessageFailed) {
1547
+ this.flushPendingProviderWebSearchFinishes();
1242
1548
  this.emit({
1243
1549
  type: "message_finished",
1244
1550
  piboSessionId: this.piboSessionId,
@@ -1272,6 +1578,7 @@ export class RoutedSession {
1272
1578
  this.nextAssistantIndex = 0;
1273
1579
  this.activeThinkingIndex = undefined;
1274
1580
  this.nextThinkingIndex = 0;
1581
+ this.providerWebSearchLifecycle.clear();
1275
1582
  if (this.inFlightMessage === inFlight)
1276
1583
  this.inFlightMessage = undefined;
1277
1584
  inFlight.resolveSettled();
@@ -314,7 +314,7 @@ export function sessionActionResource(pathname) {
314
314
  if (parts.length !== 2 || !parts[0])
315
315
  return undefined;
316
316
  const action = parts[1];
317
- if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding")
317
+ if (action !== "read" && action !== "kill" && action !== "kill-all" && action !== "runtime-binding" && action !== "fork-candidates")
318
318
  return undefined;
319
319
  try {
320
320
  return { piboSessionId: decodeURIComponent(parts[0]), action };