@get-bb/plugin-sdk 0.4.15 → 0.4.17

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.
@@ -1,103 +1,3 @@
1
- // ../provider-bridge-protocol/src/conformance/client.ts
2
- function isWireMessage(value) {
3
- return typeof value === "object" && value !== null && !Array.isArray(value);
4
- }
5
- var ConformanceClient = class {
6
- constructor(transport, timeoutMs) {
7
- this.transport = transport;
8
- this.timeoutMs = timeoutMs;
9
- }
10
- transport;
11
- timeoutMs;
12
- nextId = 1;
13
- log = [];
14
- drainIntoLog() {
15
- for (const raw of this.transport.takeMessages()) {
16
- if (isWireMessage(raw)) {
17
- this.log.push(raw);
18
- }
19
- }
20
- }
21
- sendRaw(line) {
22
- this.transport.send(line);
23
- }
24
- notify(method, params) {
25
- this.transport.send(
26
- JSON.stringify({
27
- jsonrpc: "2.0",
28
- method,
29
- ...params !== void 0 ? { params } : {}
30
- })
31
- );
32
- }
33
- request(method, params) {
34
- const id = this.nextId;
35
- this.nextId += 1;
36
- this.transport.send(
37
- JSON.stringify({
38
- jsonrpc: "2.0",
39
- id,
40
- method,
41
- ...params !== void 0 ? { params } : {}
42
- })
43
- );
44
- return id;
45
- }
46
- /** Poll until `resolve` yields a value or the deadline passes (→ null). */
47
- async waitFor(resolve2) {
48
- const deadline = Date.now() + this.timeoutMs;
49
- for (; ; ) {
50
- this.drainIntoLog();
51
- const value = resolve2();
52
- if (value !== void 0) {
53
- return value;
54
- }
55
- if (Date.now() > deadline) {
56
- return null;
57
- }
58
- await new Promise((r) => setTimeout(r, 15));
59
- }
60
- }
61
- async waitForResponse(id) {
62
- return this.waitFor(
63
- () => this.log.find(
64
- (message) => message.id === id && message.method === void 0
65
- )
66
- );
67
- }
68
- /** A settle window: drain for the given quiet period without expectations. */
69
- async settle(quietMs) {
70
- const deadline = Date.now() + quietMs;
71
- while (Date.now() < deadline) {
72
- this.drainIntoLog();
73
- await new Promise((r) => setTimeout(r, 15));
74
- }
75
- this.drainIntoLog();
76
- }
77
- responsesFor(id) {
78
- return this.log.filter(
79
- (message) => message.id === id && message.method === void 0
80
- );
81
- }
82
- notifications(method) {
83
- return this.log.filter(
84
- (message) => message.id === void 0 && typeof message.method === "string" && (method === void 0 || message.method === method)
85
- );
86
- }
87
- };
88
- var clientRequestCounter = 0;
89
- function nextConformanceClientRequestId() {
90
- const alphabet = "23456789abcdefghijkmnpqrstuvwxyz";
91
- clientRequestCounter += 1;
92
- let remaining = clientRequestCounter;
93
- let suffix = "";
94
- while (suffix.length < 10) {
95
- suffix = alphabet[remaining % alphabet.length] + suffix;
96
- remaining = Math.floor(remaining / alphabet.length);
97
- }
98
- return `creq_${suffix}`;
99
- }
100
-
101
1
  // ../domain/src/shared-types.ts
102
2
  import { z as z2 } from "zod";
103
3
 
@@ -431,6 +331,10 @@ import { z as z6 } from "zod";
431
331
 
432
332
  // ../domain/src/plugin-interaction-limits.ts
433
333
  var PLUGIN_INTERACTION_MAX_TITLE_LENGTH = 160;
334
+ var PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES = 64 * 1024;
335
+ function jsonByteLength(value) {
336
+ return new TextEncoder().encode(JSON.stringify(value)).length;
337
+ }
434
338
 
435
339
  // ../domain/src/item-presentation.ts
436
340
  import { z as z4 } from "zod";
@@ -692,26 +596,21 @@ var pluginPendingInteractionPayloadSchema = z6.object({
692
596
  title: z6.string().trim().min(1).max(PLUGIN_INTERACTION_MAX_TITLE_LENGTH),
693
597
  data: jsonValueSchema
694
598
  });
695
- var pendingInteractionPayloadSchema = z6.discriminatedUnion("kind", [
696
- approvalPendingInteractionPayloadSchema,
697
- userQuestionPendingInteractionPayloadSchema
698
- ]);
699
- var planReviewInteractionRequestPayloadSchema = z6.object({
700
- kind: z6.literal("plan_review"),
701
- itemId: z6.string().min(1),
702
- /** The plan body, as Markdown. */
703
- plan: z6.string().min(1),
704
- /** Where the provider saved the plan, or null when it kept it in memory. */
705
- planFilePath: z6.string().min(1).nullable()
706
- });
707
599
  var pluginExtensionInteractionRequestPayloadSchema = z6.object({
708
600
  kind: extensionKindSchema,
709
601
  title: z6.string().trim().min(1).max(PLUGIN_INTERACTION_MAX_TITLE_LENGTH),
710
- data: jsonValueSchema
602
+ data: jsonValueSchema.refine(
603
+ (value) => jsonByteLength(value) <= PLUGIN_INTERACTION_MAX_PAYLOAD_BYTES,
604
+ { message: "Plugin request data exceeds 64 KiB" }
605
+ )
711
606
  });
712
607
  var interactionRequestPayloadSchema = z6.union([
713
608
  userQuestionPendingInteractionPayloadSchema,
714
- planReviewInteractionRequestPayloadSchema,
609
+ pluginExtensionInteractionRequestPayloadSchema
610
+ ]);
611
+ var pendingInteractionPayloadSchema = z6.union([
612
+ approvalPendingInteractionPayloadSchema,
613
+ userQuestionPendingInteractionPayloadSchema,
715
614
  pluginExtensionInteractionRequestPayloadSchema
716
615
  ]);
717
616
  var approvalDecisionDiscriminatorError = "Invalid discriminator value. Expected 'allow_once' | 'allow_for_session' | 'deny'";
@@ -746,14 +645,36 @@ var userQuestionPendingInteractionResolutionSchema = z6.object({
746
645
  var pluginPendingInteractionResolutionSchema = z6.object({
747
646
  kind: z6.literal("plugin_submitted")
748
647
  });
648
+ var pluginExtensionInteractionResolutionSchema = z6.object({
649
+ kind: z6.literal("request_answer"),
650
+ value: jsonValueSchema
651
+ });
749
652
  var pendingInteractionResolutionSchema = z6.union(
750
653
  [
751
654
  approvalPendingInteractionResolutionSchema,
752
655
  userQuestionPendingInteractionResolutionSchema,
753
- pluginPendingInteractionResolutionSchema
656
+ pluginPendingInteractionResolutionSchema,
657
+ pluginExtensionInteractionResolutionSchema
754
658
  ],
755
659
  approvalDecisionDiscriminatorError
756
660
  );
661
+ var approvalInteractionOutcomeSchema = z6.object({
662
+ payload: approvalPendingInteractionPayloadSchema,
663
+ resolution: approvalPendingInteractionResolutionSchema
664
+ });
665
+ var userQuestionInteractionOutcomeSchema = z6.object({
666
+ payload: userQuestionPendingInteractionPayloadSchema,
667
+ resolution: userQuestionPendingInteractionResolutionSchema
668
+ });
669
+ var pluginExtensionInteractionOutcomeSchema = z6.object({
670
+ payload: pluginExtensionInteractionRequestPayloadSchema,
671
+ resolution: pluginExtensionInteractionResolutionSchema
672
+ });
673
+ var providerInteractionOutcomeSchema = z6.union([
674
+ approvalInteractionOutcomeSchema,
675
+ userQuestionInteractionOutcomeSchema,
676
+ pluginExtensionInteractionOutcomeSchema
677
+ ]);
757
678
  var pendingInteractionProviderOriginSchema = z6.object({
758
679
  kind: z6.literal("provider"),
759
680
  providerId: z6.string().min(1),
@@ -771,10 +692,7 @@ var pendingInteractionCreateSchema = z6.object({
771
692
  providerId: z6.string().min(1),
772
693
  providerThreadId: z6.string().min(1),
773
694
  providerRequestId: z6.string().min(1),
774
- payload: z6.union([
775
- approvalPendingInteractionPayloadSchema,
776
- userQuestionPendingInteractionPayloadSchema
777
- ])
695
+ payload: pendingInteractionPayloadSchema
778
696
  });
779
697
  var pendingInteractionBaseSchema = z6.object({
780
698
  id: z6.string().min(1),
@@ -785,21 +703,30 @@ var pendingInteractionBaseSchema = z6.object({
785
703
  expiresAt: z6.number().int().nonnegative().nullable().optional(),
786
704
  resolvedAt: z6.number().int().nonnegative().nullable()
787
705
  });
788
- var providerPendingInteractionSchema = pendingInteractionBaseSchema.extend({
706
+ var providerPendingInteractionBaseSchema = pendingInteractionBaseSchema.extend({
789
707
  turnId: z6.string().min(1),
790
708
  providerId: z6.string().min(1),
791
709
  providerThreadId: z6.string().min(1),
792
710
  providerRequestId: z6.string().min(1),
793
- origin: pendingInteractionProviderOriginSchema.optional(),
794
- payload: z6.union([
795
- approvalPendingInteractionPayloadSchema,
796
- userQuestionPendingInteractionPayloadSchema
797
- ]),
798
- resolution: z6.union([
799
- approvalPendingInteractionResolutionSchema,
800
- userQuestionPendingInteractionResolutionSchema
801
- ]).nullable()
711
+ origin: pendingInteractionProviderOriginSchema.optional()
712
+ });
713
+ var approvalPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
714
+ payload: approvalPendingInteractionPayloadSchema,
715
+ resolution: approvalPendingInteractionResolutionSchema.nullable()
716
+ });
717
+ var userQuestionPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
718
+ payload: userQuestionPendingInteractionPayloadSchema,
719
+ resolution: userQuestionPendingInteractionResolutionSchema.nullable()
802
720
  });
721
+ var pluginExtensionPendingInteractionSchema = providerPendingInteractionBaseSchema.extend({
722
+ payload: pluginExtensionInteractionRequestPayloadSchema,
723
+ resolution: pluginExtensionInteractionResolutionSchema.nullable()
724
+ });
725
+ var providerPendingInteractionSchema = z6.union([
726
+ approvalPendingInteractionSchema,
727
+ userQuestionPendingInteractionSchema,
728
+ pluginExtensionPendingInteractionSchema
729
+ ]);
803
730
  var pluginPendingInteractionSchema = pendingInteractionBaseSchema.extend({
804
731
  turnId: z6.string().min(1).nullable(),
805
732
  origin: pendingInteractionPluginOriginSchema,
@@ -810,6 +737,45 @@ var pendingInteractionSchema = z6.union([
810
737
  providerPendingInteractionSchema,
811
738
  pluginPendingInteractionSchema
812
739
  ]);
740
+ var interactionLifecycleRecordBaseSchema = z6.object({
741
+ id: z6.string().min(1),
742
+ status: pendingInteractionStatusSchema,
743
+ statusReason: z6.string().nullable()
744
+ });
745
+ var interactionLifecycleProviderOriginSchema = z6.object({
746
+ kind: z6.literal("provider"),
747
+ providerId: z6.string().min(1),
748
+ providerRequestId: z6.string().min(1)
749
+ });
750
+ var approvalInteractionLifecycleRecordPayloadSchema = approvalPendingInteractionPayloadSchema.omit({ availableDecisions: true });
751
+ var approvalInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
752
+ origin: interactionLifecycleProviderOriginSchema,
753
+ payload: approvalInteractionLifecycleRecordPayloadSchema,
754
+ resolution: approvalPendingInteractionResolutionSchema.nullable()
755
+ });
756
+ var userQuestionInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
757
+ origin: interactionLifecycleProviderOriginSchema,
758
+ payload: userQuestionPendingInteractionPayloadSchema,
759
+ resolution: userQuestionPendingInteractionResolutionSchema.nullable()
760
+ });
761
+ var pluginInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
762
+ origin: pendingInteractionPluginOriginSchema,
763
+ payload: pluginPendingInteractionPayloadSchema.omit({ data: true }),
764
+ resolution: pluginPendingInteractionResolutionSchema.nullable()
765
+ });
766
+ var pluginExtensionInteractionLifecycleSchema = interactionLifecycleRecordBaseSchema.extend({
767
+ origin: interactionLifecycleProviderOriginSchema,
768
+ payload: pluginExtensionInteractionRequestPayloadSchema.omit({
769
+ data: true
770
+ }),
771
+ resolution: pluginExtensionInteractionResolutionSchema.omit({ value: true }).nullable()
772
+ });
773
+ var interactionLifecycleSchema = z6.union([
774
+ approvalInteractionLifecycleSchema,
775
+ userQuestionInteractionLifecycleSchema,
776
+ pluginInteractionLifecycleSchema,
777
+ pluginExtensionInteractionLifecycleSchema
778
+ ]);
813
779
 
814
780
  // ../domain/src/protocol-ids.ts
815
781
  import { z as z7 } from "zod";
@@ -827,6 +793,11 @@ var systemEventTypeValues = [
827
793
  "system/manager/user_message",
828
794
  "system/thread/interrupted",
829
795
  "system/operation",
796
+ "system/interaction/lifecycle",
797
+ // Legacy persisted per-shape interaction events; every status change now
798
+ // appends one `system/interaction/lifecycle`. Retained for read/decode
799
+ // only: `convertLegacyStoredThreadEvent` projects a stored row into the
800
+ // lifecycle event, so no consumer sees these types.
830
801
  "system/permissionGrant/lifecycle",
831
802
  "system/userQuestion/lifecycle",
832
803
  "system/thread-provisioning",
@@ -956,6 +927,9 @@ var systemOperationEventDataSchema = z8.object({
956
927
  operationId: z8.string(),
957
928
  metadata: z8.record(z8.string(), jsonValueSchema).optional()
958
929
  });
930
+ var systemInteractionLifecycleEventDataSchema = z8.object({
931
+ interaction: interactionLifecycleSchema
932
+ });
959
933
  var systemPermissionGrantLifecycleEventDataSchema = z8.object({
960
934
  interactionId: z8.string(),
961
935
  providerId: z8.string(),
@@ -1163,6 +1137,10 @@ var threadEventScopeDefinitionByType = {
1163
1137
  policy: "thread-or-turn",
1164
1138
  rationale: "Thread-management operations use thread scope outside provider turns; tool-owned operations use turn scope so the operation stays with the tool call that caused it."
1165
1139
  },
1140
+ "system/interaction/lifecycle": {
1141
+ policy: "thread-or-turn",
1142
+ rationale: "A provider interaction belongs to the turn that raised it; a plugin may raise one outside any turn."
1143
+ },
1166
1144
  "system/permissionGrant/lifecycle": { policy: "turn" },
1167
1145
  "system/userQuestion/lifecycle": { policy: "turn" },
1168
1146
  "system/thread-provisioning": {
@@ -1558,8 +1536,6 @@ var threadEventItemSchema = z11.discriminatedUnion("type", [
1558
1536
  server: z11.string().optional(),
1559
1537
  tool: z11.string(),
1560
1538
  arguments: z11.record(z11.string(), z11.unknown()).optional(),
1561
- /** Server-enriched labels for a native plugin tool's timeline row. */
1562
- statusLabels: z11.object({ pending: z11.string(), completed: z11.string() }).optional(),
1563
1539
  status: threadEventItemStatusSchema,
1564
1540
  result: z11.unknown().optional(),
1565
1541
  error: z11.string().optional(),
@@ -1567,8 +1543,7 @@ var threadEventItemSchema = z11.discriminatedUnion("type", [
1567
1543
  truncation: threadEventItemTruncationSchema.optional(),
1568
1544
  /**
1569
1545
  * The escape hatch for tools with no core kind: the bridge says how the
1570
- * row reads. Supersedes the server-enriched `statusLabels` when both are
1571
- * present (WS3 deletes `statusLabels`).
1546
+ * row reads (label, glyph, headline, suppression).
1572
1547
  */
1573
1548
  ...itemPresentationField,
1574
1549
  parentToolCallId: z11.string().optional()
@@ -1905,6 +1880,10 @@ var unscopedSystemEventSchema = z11.discriminatedUnion("type", [
1905
1880
  type: z11.literal("system/operation"),
1906
1881
  threadId: z11.string()
1907
1882
  }).merge(systemOperationEventDataSchema),
1883
+ z11.object({
1884
+ type: z11.literal("system/interaction/lifecycle"),
1885
+ threadId: z11.string()
1886
+ }).merge(systemInteractionLifecycleEventDataSchema),
1908
1887
  z11.object({
1909
1888
  type: z11.literal("system/permissionGrant/lifecycle"),
1910
1889
  threadId: z11.string()
@@ -1949,20 +1928,33 @@ var rejectLegacyClientRequestSequenceSchema = z11.unknown().superRefine((value,
1949
1928
  });
1950
1929
  var threadEventSchema = rejectLegacyClientRequestSequenceSchema.pipe(
1951
1930
  z11.union([providerEventSchema, systemEventSchema]).superRefine((event, ctx) => {
1952
- const result = validateThreadEventScope({
1931
+ const result2 = validateThreadEventScope({
1953
1932
  type: event.type,
1954
1933
  scope: event.scope
1955
1934
  });
1956
- if (!result.valid) {
1935
+ if (!result2.valid) {
1957
1936
  ctx.addIssue({
1958
1937
  code: z11.ZodIssueCode.custom,
1959
- message: result.message ?? "Invalid thread event scope",
1938
+ message: result2.message ?? "Invalid thread event scope",
1960
1939
  path: ["scope"]
1961
1940
  });
1962
1941
  return;
1963
1942
  }
1964
1943
  })
1965
1944
  );
1945
+ function isThreadEventWithItem(event) {
1946
+ switch (event.type) {
1947
+ case "item/started":
1948
+ case "item/completed":
1949
+ case "item/delegation/progress":
1950
+ case "item/delegation/completed":
1951
+ case "item/backgroundTask/progress":
1952
+ case "item/backgroundTask/completed":
1953
+ return true;
1954
+ default:
1955
+ return false;
1956
+ }
1957
+ }
1966
1958
  var threadEventTypeValues = [
1967
1959
  ...providerEventTypeValues,
1968
1960
  ...systemEventTypeValues
@@ -1973,6 +1965,23 @@ var threadEventTypeSchema = z11.string().refine(
1973
1965
  "Invalid thread event type"
1974
1966
  );
1975
1967
 
1968
+ // ../domain/src/plugin-icon.ts
1969
+ var PLUGIN_ICON_MAX_BYTES = 32 * 1024;
1970
+ var NAMESPACED_GLYPH_PATTERN = /^[a-z0-9-]+\/[a-z0-9][a-z0-9-]*$/u;
1971
+ function isNamespacedGlyph(glyph) {
1972
+ return NAMESPACED_GLYPH_PATTERN.test(glyph);
1973
+ }
1974
+ function parseNamespacedGlyph(glyph) {
1975
+ if (!isNamespacedGlyph(glyph)) {
1976
+ return null;
1977
+ }
1978
+ const separator = glyph.indexOf("/");
1979
+ return {
1980
+ pluginId: glyph.slice(0, separator),
1981
+ name: glyph.slice(separator + 1)
1982
+ };
1983
+ }
1984
+
1976
1985
  // ../domain/src/provider-fork.ts
1977
1986
  import { z as z12 } from "zod";
1978
1987
  var PROVIDER_FORK_VALUES = ["none", "tip", "checkpoint"];
@@ -1996,6 +2005,7 @@ var availableModelSchema = z13.object({
1996
2005
  defaultReasoningEffort: reasoningLevelSchema,
1997
2006
  isDefault: z13.boolean()
1998
2007
  });
2008
+ var providerModelCatalogScopeSchema = z13.enum(["host", "workspace"]);
1999
2009
  var providerCapabilitiesSchema = z13.object({
2000
2010
  supportsThreadArchive: z13.boolean(),
2001
2011
  supportsThreadRename: z13.boolean(),
@@ -2008,7 +2018,15 @@ var providerCapabilitiesSchema = z13.object({
2008
2018
  * whole sessions (tip-only) and cannot stop at a checkpoint.
2009
2019
  */
2010
2020
  supportsSessionRewind: z13.boolean(),
2011
- permissionModes: z13.array(permissionModeSchema).min(1)
2021
+ permissionModes: z13.array(permissionModeSchema).min(1),
2022
+ /**
2023
+ * How far one `model/list` answer travels: `"host"` when the bridge answers
2024
+ * from account or agent state and ignores the workspace path, so bb probes
2025
+ * once per machine; `"workspace"` when project configuration can change the
2026
+ * answer. Declared by the provider's plugin — core never infers it from an
2027
+ * id.
2028
+ */
2029
+ modelCatalogScope: providerModelCatalogScopeSchema
2012
2030
  });
2013
2031
  var providerComposerCommandSchema = z13.object({
2014
2032
  trigger: promptMentionCommandTriggerSchema,
@@ -2058,25 +2076,44 @@ var providerExtensionKindsSchema = z13.record(
2058
2076
  );
2059
2077
  var providerInfoSchema = z13.object({
2060
2078
  id: z13.string(),
2079
+ /**
2080
+ * The plugin that registered the provider (`bb.providers.register`). The
2081
+ * owner of the provider's extension-kind namespace, and the bundle the
2082
+ * app loads on the first thread of this provider.
2083
+ */
2084
+ pluginId: z13.string().min(1),
2061
2085
  displayName: z13.string(),
2062
2086
  /**
2063
2087
  * Declared grouping key shared by related providers (the ACP agents).
2064
2088
  * Absent when the provider declared none. Grouping only.
2065
2089
  */
2066
2090
  family: z13.string().min(1).optional(),
2091
+ /**
2092
+ * The declared icon, projected by form. A plugin-relative asset path
2093
+ * (`icon: "./icons/agent.svg"`) is served by the provider-logo route and
2094
+ * arrives here as `logoUrl`; a named host glyph (`icon: "Zap"`) has no
2095
+ * bytes to serve and arrives as `icon.glyph`, the same vocabulary an
2096
+ * item presentation's `icon` uses. A declaration names at most one form,
2097
+ * so at most one of the two is set; `icon` is absent when the declaration
2098
+ * named a path or nothing. Clients draw a vendored brand mark first, then
2099
+ * `logoUrl`, then `icon.glyph`, then the display name's initial.
2100
+ */
2101
+ icon: z13.object({ glyph: z13.string().min(1) }).optional(),
2067
2102
  logoUrl: z13.string().min(1).nullable(),
2068
- /** Sessionless maintenance methods declared by the provider plugin. */
2069
- experimental_providerHealth: z13.boolean(),
2070
- experimental_providerUsage: z13.boolean(),
2071
- experimental_providerInstallation: z13.boolean(),
2103
+ /** Sessionless maintenance requests the provider's bridge implements. */
2104
+ maintenance: z13.object({
2105
+ health: z13.boolean(),
2106
+ usage: z13.boolean(),
2107
+ installation: z13.boolean()
2108
+ }),
2072
2109
  capabilities: providerCapabilitiesSchema,
2073
2110
  composerActions: z13.array(providerComposerActionSchema),
2074
2111
  available: z13.boolean(),
2075
2112
  // -------------------------------------------------------------------------
2076
2113
  // Target-state projection (docs/provider-plugin-api.md §1). Optional and
2077
2114
  // unfilled until WS2a projects them from the plugin declaration; absence
2078
- // means "the provider declared none", never a default. The `experimental_*`
2079
- // and `capabilities.supports*` fields above stay until WS2a stabilizes the
2115
+ // means "the provider declared none", never a default. The
2116
+ // `capabilities.supports*` fields above stay until WS2a stabilizes the
2080
2117
  // surface as one unit.
2081
2118
  // -------------------------------------------------------------------------
2082
2119
  strings: providerStringsSchema.optional(),
@@ -2122,954 +2159,358 @@ var dynamicToolSchema = z13.object({
2122
2159
  presentation: threadEventItemPresentationSchema.optional()
2123
2160
  });
2124
2161
 
2125
- // ../provider-bridge-protocol/src/conformance/scenarios.ts
2126
- import { z as z23 } from "zod";
2127
-
2128
- // ../provider-bridge-protocol/src/version.ts
2129
- var PROVIDER_BRIDGE_PROTOCOL_VERSION = 2;
2130
- var THREAD_DELTA_GRAMMAR_V3 = 3;
2131
-
2132
- // ../provider-bridge-protocol/src/handshake.ts
2162
+ // ../provider-bridge-protocol/src/thread-delta.ts
2133
2163
  import { z as z14 } from "zod";
2134
- var bridgeGrammarVersionsSchema = z14.tuple([z14.number().int().positive(), z14.number().int().positive()]).refine(([min, max]) => min <= max, {
2135
- message: "grammarVersions must be an ascending [min, max] range"
2164
+ var THREAD_DELTA_NOTIFICATION_METHOD = "thread/delta";
2165
+ var deltaPresentationSchema = threadEventItemPresentationSchema;
2166
+ var THREAD_DELTA_KEY_SEPARATOR = "";
2167
+ var deltaKeyPartSchema = z14.string().min(1).refine((value) => !value.includes(THREAD_DELTA_KEY_SEPARATOR), {
2168
+ message: "provider keys must not contain the internal key separator (\\u001f)"
2136
2169
  });
2137
- function negotiateGrammarVersion(runtime, bridge) {
2138
- const min = Math.max(runtime[0], bridge[0]);
2139
- const max = Math.min(runtime[1], bridge[1]);
2140
- return min <= max ? max : null;
2141
- }
2142
- var bridgeSteerModeSchema = z14.enum(["inject", "queue"]);
2143
- var bridgeCapabilitiesSchema = z14.object({
2144
- /**
2145
- * A released session can be re-attached later from its persisted
2146
- * providerThreadId. The per-session `sessionRestorable` flag on
2147
- * thread-identity results refines this (an agent update can drop restore
2148
- * support mid-flight); this handshake value is the default for sessions
2149
- * that do not say.
2150
- */
2151
- sessionRestore: z14.boolean().default(false),
2170
+ var deltaItemKeySchema = z14.object({
2171
+ providerItemId: deltaKeyPartSchema.optional(),
2172
+ channel: deltaKeyPartSchema.optional(),
2173
+ parentRef: deltaKeyPartSchema.optional()
2174
+ });
2175
+ var providerTurnIdSchema = deltaKeyPartSchema;
2176
+ var deltaFileChangeSchema = z14.object({
2177
+ path: z14.string(),
2178
+ /** The bridge states the change kind; the assembler never derives it. */
2179
+ kind: z14.enum(["add", "update", "delete"]),
2180
+ movePath: z14.string().optional(),
2181
+ /** Provider-supplied unified diff; preferred over old/new text building. */
2182
+ diff: z14.string().optional(),
2183
+ oldText: z14.string().optional(),
2184
+ /** When present the assembler builds the unified diff from old/new text. */
2185
+ newText: z14.string().optional()
2186
+ });
2187
+ var deltaBackgroundTaskShapeSchema = z14.object({
2188
+ type: z14.literal("backgroundTask"),
2152
2189
  /**
2153
- * The bridge mirrors bb archive state into the provider's own session
2154
- * list. When false the runtime never sends thread/archive or
2155
- * thread/unarchive.
2190
+ * The provider's stable task id, shared by every generation (restart) of
2191
+ * the same task. Rides through to the canonical item so consumers can
2192
+ * correlate a restarted task with its earlier generations — the assembler
2193
+ * mints fresh item ids per generation, so identity must travel as data,
2194
+ * never as id text.
2156
2195
  */
2157
- threadArchive: z14.boolean().default(false),
2196
+ familyId: z14.string().min(1),
2197
+ taskType: z14.string(),
2198
+ description: z14.string(),
2199
+ status: threadEventItemStatusSchema,
2200
+ taskStatus: backgroundTaskStatusSchema,
2201
+ skipTranscript: z14.boolean(),
2202
+ workflowName: z14.string().optional(),
2203
+ workflow: workflowProgressSnapshotSchema.optional(),
2204
+ usage: backgroundTaskUsageSchema.optional(),
2205
+ summary: z14.string().optional(),
2206
+ error: z14.string().optional(),
2207
+ outputFile: z14.string().optional()
2208
+ });
2209
+ var deltaFileReadShapeSchema = z14.object({
2210
+ type: z14.literal("fileRead"),
2211
+ path: z14.string(),
2212
+ cmd: z14.string().optional()
2213
+ });
2214
+ var deltaSearchShapeSchema = z14.object({
2215
+ type: z14.literal("search"),
2216
+ mode: threadEventSearchModeSchema,
2217
+ query: z14.string(),
2218
+ path: z14.string().optional(),
2219
+ cmd: z14.string().optional()
2220
+ });
2221
+ var deltaDelegationShapeSchema = z14.object({
2222
+ type: z14.literal("delegation"),
2223
+ childRef: deltaKeyPartSchema,
2224
+ label: z14.string(),
2225
+ background: z14.boolean(),
2226
+ summary: z14.string().optional()
2227
+ });
2228
+ var deltaPlanStepsShapeSchema = z14.object({
2229
+ type: z14.literal("planSteps"),
2230
+ steps: z14.array(threadEventPlanStepSchema),
2231
+ explanation: z14.string().optional()
2232
+ });
2233
+ var deltaExtensionShapeSchema = z14.object({
2234
+ type: z14.literal("extension"),
2235
+ kind: extensionKindSchema,
2236
+ payload: jsonValueSchema
2237
+ });
2238
+ var deltaItemShapeSchema = z14.discriminatedUnion("type", [
2239
+ z14.object({
2240
+ type: z14.literal("command"),
2241
+ command: z14.string(),
2242
+ cwd: z14.string(),
2243
+ aggregatedOutput: z14.string().optional(),
2244
+ exitCode: z14.number().optional(),
2245
+ durationMs: z14.number().optional()
2246
+ }),
2247
+ z14.object({
2248
+ type: z14.literal("fileChange"),
2249
+ /** Empty while a path is not yet known, including bare close fallbacks. */
2250
+ changes: z14.array(deltaFileChangeSchema)
2251
+ }),
2158
2252
  /**
2159
- * The bridge pushes bb thread titles to the provider. When false the
2160
- * runtime never sends thread/name/set.
2253
+ * The generic tool call: the escape hatch for tools with no core kind. In
2254
+ * grammar v3 the bridge says how the row reads through the delta's
2255
+ * `presentation` (label, icon, suppression) instead of core keeping a
2256
+ * tool-name table. A `tool` item without presentation is read as legacy
2257
+ * data: `@bb/domain`'s `upgradeLegacyToolItem` reshapes read/grep/glob/
2258
+ * find/ls by name and suppresses the Task- and Todo-family bookkeeping
2259
+ * calls when the stored row is parsed, and any other name renders with
2260
+ * the generic tool row — until the backfill migration, after which
2261
+ * `presentation` is required.
2161
2262
  */
2162
- threadRename: z14.boolean().default(false),
2163
- /** The bridge supports thread/goal/clear. */
2164
- threadGoalClear: z14.boolean().default(false),
2263
+ z14.object({
2264
+ type: z14.literal("tool"),
2265
+ tool: z14.string(),
2266
+ server: z14.string().optional(),
2267
+ args: z14.unknown().optional(),
2268
+ result: z14.unknown().optional(),
2269
+ error: z14.string().optional(),
2270
+ durationMs: z14.number().optional()
2271
+ }),
2272
+ z14.object({ type: z14.literal("compaction") }),
2273
+ z14.object({ type: z14.literal("agentMessage"), text: z14.string() }),
2274
+ z14.object({
2275
+ type: z14.literal("reasoning"),
2276
+ summary: z14.array(z14.string()),
2277
+ content: z14.array(z14.string())
2278
+ }),
2279
+ z14.object({ type: z14.literal("plan"), text: z14.string() }),
2280
+ z14.object({
2281
+ type: z14.literal("webSearch"),
2282
+ queries: z14.array(z14.string()).min(1)
2283
+ }),
2284
+ z14.object({
2285
+ type: z14.literal("webFetch"),
2286
+ url: z14.string(),
2287
+ prompt: z14.string().nullable().optional(),
2288
+ pattern: z14.string().nullable()
2289
+ }),
2290
+ z14.object({ type: z14.literal("imageView"), path: z14.string() }),
2291
+ deltaBackgroundTaskShapeSchema,
2292
+ // Grammar v3 shapes. Every existing shape above is kept unchanged.
2293
+ deltaFileReadShapeSchema,
2294
+ deltaSearchShapeSchema,
2295
+ deltaDelegationShapeSchema,
2296
+ deltaPlanStepsShapeSchema,
2297
+ deltaExtensionShapeSchema
2298
+ ]);
2299
+ var deltaProgressSnapshotSchema = z14.discriminatedUnion("type", [
2300
+ deltaBackgroundTaskShapeSchema,
2301
+ deltaDelegationShapeSchema
2302
+ ]);
2303
+ var deltaTextChannelSchema = z14.enum([
2304
+ "agentMessage",
2305
+ "reasoningSummary",
2306
+ "reasoningText",
2307
+ "plan"
2308
+ ]);
2309
+ var deltaOutputChannelSchema = z14.enum(["command", "fileChange"]);
2310
+ var deltaErrorSchema = z14.object({ message: z14.string() });
2311
+ var deltaAttachSchema = z14.enum(["open", "currentOrLast"]);
2312
+ var deltaNoTurnFallbackSchema = z14.object({
2313
+ raw: providerRawEventSchema,
2314
+ rawType: z14.string()
2315
+ });
2316
+ function requireExtensionPresentation(delta, ctx) {
2317
+ if (delta.item.type === "extension" && delta.presentation === void 0) {
2318
+ ctx.addIssue({
2319
+ code: "custom",
2320
+ message: "extension items require a presentation on item.open/item.close",
2321
+ path: ["presentation"]
2322
+ });
2323
+ }
2324
+ }
2325
+ var threadDeltaSchema = z14.discriminatedUnion("kind", [
2165
2326
  /**
2166
- * Session cloning support ({@link providerForkSchema} the same
2167
- * vocabulary the provider declaration uses). The declaration is a ceiling
2168
- * for UI affordances; this is the operative truth, and it may only narrow
2169
- * the declaration, never widen it.
2327
+ * The provider consumed an input (immediate or steered). The assembler owns
2328
+ * the queue-until-turn-opens behavior and the terminal-turn invariant.
2329
+ * With `providerTurnId` the acceptance is emitted against that vouched turn
2330
+ * directly (codex correlates acceptance to a named native turn).
2170
2331
  */
2171
- fork: providerForkSchema.default("none"),
2332
+ z14.object({
2333
+ kind: z14.literal("input.accepted"),
2334
+ clientRequestId: clientTurnRequestIdSchema,
2335
+ providerTurnId: providerTurnIdSchema.optional()
2336
+ }),
2172
2337
  /**
2173
- * Where the thread's approval policy is enforced. "runtime" bridges
2174
- * forward every approval request and the runtime applies the thread
2175
- * policy (including auto-deny). "provider" bridges enforce policy before
2176
- * forwarding, so every forwarded request is already known to need user
2177
- * input and the runtime must not reclassify it against mutable thread
2178
- * settings.
2338
+ * Input the provider itself injected into the conversation, with no bb
2339
+ * client request behind it (a pi extension's `sendMessage` custom message
2340
+ * that triggered or steered a turn). The assembler records it as a
2341
+ * `userMessage` item in the open turn so the transcript shows what the
2342
+ * model was answering. Dropped silently when no turn is open: the provider
2343
+ * appended it to its own context without running the agent, so there is no
2344
+ * bb turn to attach it to.
2179
2345
  */
2180
- approvalEnforcedBy: z14.enum(["runtime", "provider"]).default("runtime"),
2346
+ z14.object({
2347
+ kind: z14.literal("input.provider"),
2348
+ text: z14.string().min(1),
2349
+ parentRef: deltaKeyPartSchema.optional()
2350
+ }),
2181
2351
  /**
2182
- * The `thread/delta` grammar range this bridge speaks. A bridge that says
2183
- * nothing speaks exactly the protocol version it negotiated — today's
2184
- * bridges all emit v2 so the default is `[2, 2]`, never a wider range
2185
- * it never claimed. A v3-capable bridge reports `[2, 3]` (or `[3, 3]`
2186
- * once the v2 paths are deleted) and emits the highest version inside
2187
- * the intersection with the runtime's `initialize` params range
2188
- * ({@link negotiateGrammarVersion}); the runtime rejects a disjoint
2189
- * range at startup.
2352
+ * An explicit provider signal opened work (pi `agent_start`, codex
2353
+ * `turn/started`). With `providerTurnId` the turn lives in the keyed
2354
+ * provider-turn space: several may be open at once (codex multiplexes
2355
+ * subagent child turns onto one thread) and none of the current-turn
2356
+ * machinery is touched.
2190
2357
  */
2191
- grammarVersions: bridgeGrammarVersionsSchema.default([
2192
- PROVIDER_BRIDGE_PROTOCOL_VERSION,
2193
- PROVIDER_BRIDGE_PROTOCOL_VERSION
2194
- ]),
2358
+ z14.object({
2359
+ kind: z14.literal("turn.open"),
2360
+ providerTurnId: providerTurnIdSchema.optional(),
2361
+ /** Provider-native parent tool-call id for delegated child turns. */
2362
+ parentRef: deltaKeyPartSchema.optional()
2363
+ }),
2195
2364
  /**
2196
- * Mid-turn steer delivery ({@link bridgeSteerModeSchema}). Defaults to
2197
- * `queue`, the conservative reading: absence is the definite "no" the
2198
- * rest of this handshake uses, and `inject` is the stronger promise (the
2199
- * steer reaches the model before the turn ends) a bridge must make
2200
- * explicitly. Nothing in the runtime branches on it yet, so the default
2201
- * changes no behavior today; claude and codex declare `inject` before WS4
2202
- * reads it.
2365
+ * The bridge's conclusion that the turn settled. `claimIfIdle: true` marks
2366
+ * fallback closers that own a turn only if accepted input is pending
2367
+ * (the old bridge-kit terminal-turn rule, applied centrally); an open turn is
2368
+ * always settled. A keyed boundary (`providerTurnId`) always emits the
2369
+ * provider named the turn and settles only that turn.
2203
2370
  */
2204
- steerMode: bridgeSteerModeSchema.default("queue")
2205
- }).passthrough();
2206
- var initializeParamsSchema = z14.object({
2207
- protocolVersion: z14.number().int().positive(),
2208
- client: z14.object({ name: z14.string().min(1), version: z14.string().min(1) }),
2371
+ z14.object({
2372
+ kind: z14.literal("turn.boundary"),
2373
+ status: threadEventTurnStatusSchema,
2374
+ error: deltaErrorSchema.optional(),
2375
+ providerCheckpointId: z14.string().min(1).optional(),
2376
+ claimIfIdle: z14.boolean().optional(),
2377
+ providerTurnId: providerTurnIdSchema.optional()
2378
+ }),
2209
2379
  /**
2210
- * The `thread/delta` grammar range the runtime's assembler accepts (see
2211
- * {@link negotiateGrammarVersion}). A runtime that predates the field
2212
- * reads as speaking exactly its protocol version.
2380
+ * A parsed item opened. `attach: "currentOrLast"` pins the item to the turn
2381
+ * that is open or just closed without opening a new one (pi threshold
2382
+ * compaction); the default attaches to the open turn only. A known
2383
+ * `providerItemId` reuses its minted bb id (an explicit open reopens the
2384
+ * same item, codex's settle/reopen rule).
2213
2385
  */
2214
- grammarVersions: bridgeGrammarVersionsSchema.default([
2215
- PROVIDER_BRIDGE_PROTOCOL_VERSION,
2216
- PROVIDER_BRIDGE_PROTOCOL_VERSION
2217
- ])
2218
- }).passthrough();
2219
- var initializeResultSchema = z14.object({
2220
- protocolVersion: z14.number().int().positive(),
2221
- // An absent capabilities block reads as "no capabilities" via the inner
2222
- // per-field defaults, so older bridges parse to explicit values.
2223
- capabilities: z14.preprocess(
2224
- (value) => value ?? {},
2225
- bridgeCapabilitiesSchema
2226
- )
2227
- }).passthrough();
2228
-
2229
- // ../provider-bridge-protocol/src/execution-options.ts
2230
- import { z as z15 } from "zod";
2231
- var bridgeExecutionOptionsSchema = z15.object({
2232
- model: z15.string().min(1).optional(),
2233
- serviceTier: serviceTierSchema.optional(),
2234
- reasoningLevel: reasoningLevelSchema.optional(),
2386
+ z14.object({
2387
+ kind: z14.literal("item.open"),
2388
+ key: deltaItemKeySchema,
2389
+ item: deltaItemShapeSchema,
2390
+ /**
2391
+ * Grammar v3: how the row reads, persisted with the opened item. The
2392
+ * one place presentation travels. Optional for core shapes while v2
2393
+ * deltas are accepted; REQUIRED for `extension` shapes.
2394
+ */
2395
+ presentation: deltaPresentationSchema.optional(),
2396
+ attach: deltaAttachSchema.optional(),
2397
+ providerTurnId: providerTurnIdSchema.optional(),
2398
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2399
+ }).superRefine(requireExtensionPresentation),
2235
2400
  /**
2236
- * BB prompt mode (`"plan"`), present only when the prompt entered one
2237
- * through the provider's declared composer action. Each bridge maps it
2238
- * onto the agent's native equivalent.
2401
+ * The item settled. `item` is REQUIRED and always carries the full terminal
2402
+ * item shape (Michael's uniform close rule, 2026-08-18): the assembler
2403
+ * builds the completed item from it. With a same-shaped item open under the
2404
+ * key, the terminal shape wins and the opened item contributes only its
2405
+ * minted id; with a different-shaped item open, the assembler closes the
2406
+ * opened shape and then emits the terminal shape (ACP's dual-complete);
2407
+ * with nothing open it builds the bare completed item.
2408
+ *
2409
+ * Provider-identified closes (`key.providerItemId`) dedup: a repeated close
2410
+ * for a settled id is dropped and an explicit `item.open` reopens the id
2411
+ * (codex retries the terminal notification after approvals).
2239
2412
  */
2240
- promptMode: promptModeSchema.optional(),
2241
- /** Frozen for the life of a provider session; applied at construction. */
2242
- instructions: z15.string().optional(),
2243
- envVars: z15.record(z15.string(), z15.string()).optional(),
2244
- /** Provider-scoped session options. Opaque outside the owning bridge. */
2245
- providerOptions: z15.record(z15.string(), z15.unknown()).optional()
2246
- }).and(runtimePermissionPolicySchema);
2247
-
2248
- // ../provider-bridge-protocol/src/provider-maintenance.ts
2249
- import { z as z16 } from "zod";
2250
- var experimental_providerMaintenanceParamsSchema = z16.object({
2251
- providerId: z16.string().min(1),
2252
- cwd: z16.string().min(1).optional(),
2253
- providerOptions: z16.record(z16.string(), z16.unknown()).optional()
2254
- }).passthrough();
2255
- var experimental_providerInstallationRequirementSchema = z16.enum([
2256
- "thread_rewind"
2257
- ]);
2258
- var experimental_providerInstallationStatusParamsSchema = experimental_providerMaintenanceParamsSchema.extend({
2259
- requirement: experimental_providerInstallationRequirementSchema.optional()
2260
- });
2261
- var experimental_providerHealthSchema = z16.object({
2262
- status: z16.enum([
2263
- "ready",
2264
- "not_installed",
2265
- "unauthenticated",
2266
- "expired",
2267
- "unsupported_version",
2268
- "unknown"
2269
- ]),
2270
- statusMessage: z16.string().min(1).nullable(),
2271
- accountEmail: z16.string().nullable(),
2272
- planLabel: z16.string().min(1).nullable(),
2273
- installedVersion: z16.string().min(1).nullable(),
2274
- minimumSupportedVersion: z16.string().min(1).nullable(),
2275
- canInstall: z16.boolean(),
2276
- canUpdate: z16.boolean(),
2277
- loginCommand: z16.string().min(1).nullable()
2278
- }).passthrough();
2279
- var experimental_providerUsageWindowSchema = z16.object({
2280
- label: z16.string().min(1),
2281
- usedPercent: z16.number().min(0).max(100),
2282
- resetsAt: z16.string().min(1).nullable(),
2283
- cost: z16.object({
2284
- usedUsdCents: z16.number().int().nonnegative(),
2285
- limitUsdCents: z16.number().int().positive()
2286
- }).optional()
2287
- }).passthrough();
2288
- var experimental_providerUsageSchema = z16.discriminatedUnion("status", [
2289
- z16.object({
2290
- status: z16.literal("ok"),
2291
- accountEmail: z16.string().email().nullable(),
2292
- planLabel: z16.string().min(1).nullable(),
2293
- windows: z16.array(experimental_providerUsageWindowSchema)
2294
- }).passthrough(),
2295
- z16.object({ status: z16.literal("not_installed") }).passthrough(),
2296
- z16.object({ status: z16.literal("unauthenticated") }).passthrough(),
2297
- z16.object({ status: z16.literal("expired") }).passthrough(),
2298
- z16.object({
2299
- status: z16.literal("error"),
2300
- message: z16.string().min(1),
2301
- planLabel: z16.string().min(1).nullable().default(null),
2302
- accountEmail: z16.string().nullable().default(null)
2303
- }).passthrough()
2304
- ]);
2305
- var experimental_providerHealthResultSchema = z16.discriminatedUnion(
2306
- "supported",
2307
- [
2308
- z16.object({ supported: z16.literal(false) }).passthrough(),
2309
- z16.object({
2310
- supported: z16.literal(true),
2311
- health: experimental_providerHealthSchema
2312
- }).passthrough()
2313
- ]
2314
- );
2315
- var experimental_providerUsageResultSchema = z16.discriminatedUnion(
2316
- "supported",
2317
- [
2318
- z16.object({ supported: z16.literal(false) }).passthrough(),
2319
- z16.object({
2320
- supported: z16.literal(true),
2321
- usage: experimental_providerUsageSchema
2322
- }).passthrough()
2323
- ]
2324
- );
2325
- var experimental_providerInstallationActionKindSchema = z16.enum([
2326
- "install",
2327
- "update"
2328
- ]);
2329
- var experimental_providerInstallationActionSchema = z16.object({
2330
- kind: experimental_providerInstallationActionKindSchema,
2331
- label: z16.enum(["Install", "Update"]),
2332
- command: z16.string().min(1)
2333
- }).passthrough();
2334
- var experimental_providerInstallationSourceSchema = z16.enum([
2335
- "notInstalled",
2336
- "npmGlobal",
2337
- "external"
2338
- ]);
2339
- var experimental_providerInstallationStatusSchema = z16.object({
2340
- executableName: z16.string().min(1),
2341
- executablePath: z16.string().min(1).nullable(),
2342
- installed: z16.boolean(),
2343
- installSource: experimental_providerInstallationSourceSchema,
2344
- currentVersion: z16.string().min(1).nullable(),
2345
- latestVersion: z16.string().min(1).nullable(),
2346
- minimumSupportedVersion: z16.string().min(1).nullable(),
2347
- npmPackageName: z16.string().min(1).nullable(),
2348
- npmGlobalPackageVersion: z16.string().min(1).nullable(),
2349
- installAction: experimental_providerInstallationActionSchema.nullable(),
2350
- needsUpdate: z16.boolean(),
2351
- versionUnsupported: z16.boolean()
2352
- }).passthrough();
2353
- var experimental_providerInstallationRunParamsSchema = experimental_providerMaintenanceParamsSchema.extend({
2354
- action: experimental_providerInstallationActionKindSchema
2355
- });
2356
- var experimental_providerInstallationCommandSchema = z16.object({
2357
- command: z16.string().min(1),
2358
- args: z16.array(z16.string()).max(64),
2359
- displayCommand: z16.string().min(1)
2360
- }).passthrough();
2361
- var experimental_providerInstallationVerificationSchema = z16.discriminatedUnion("kind", [
2362
- z16.object({ kind: z16.literal("installed") }).passthrough(),
2363
- z16.object({
2364
- kind: z16.literal("version_changed"),
2365
- previousVersion: z16.string().min(1)
2366
- }).passthrough(),
2367
- z16.object({
2368
- kind: z16.literal("version_at_least"),
2369
- version: z16.string().min(1)
2370
- }).passthrough()
2371
- ]);
2372
- var experimental_providerInstallationRunResultSchema = z16.discriminatedUnion("available", [
2373
- z16.object({
2374
- available: z16.literal(false),
2375
- message: z16.string().min(1)
2376
- }).passthrough(),
2377
- z16.object({
2378
- available: z16.literal(true),
2379
- command: experimental_providerInstallationCommandSchema,
2380
- verification: experimental_providerInstallationVerificationSchema
2381
- }).passthrough()
2382
- ]);
2383
-
2384
- // ../provider-bridge-protocol/src/requests.ts
2385
- import { z as z17 } from "zod";
2386
- var BRIDGE_REQUEST_METHODS = {
2387
- initialize: "initialize",
2388
- modelList: "model/list",
2389
- experimentalProviderHealth: "provider/health",
2390
- experimentalProviderUsage: "provider/usage",
2391
- experimentalProviderInstallationStatus: "provider/installation/status",
2392
- experimentalProviderInstallationRun: "provider/installation/run",
2393
- threadStart: "thread/start",
2394
- threadResume: "thread/resume",
2395
- threadFork: "thread/fork",
2396
- threadStop: "thread/stop",
2397
- threadDiscard: "thread/discard",
2398
- threadNameSet: "thread/name/set",
2399
- threadArchive: "thread/archive",
2400
- threadUnarchive: "thread/unarchive",
2401
- threadGoalClear: "thread/goal/clear",
2402
- turnStart: "turn/start",
2403
- turnSteer: "turn/steer",
2404
- skillsConfigure: "skills/configure"
2405
- };
2406
- var sessionConstructionFields = {
2407
- threadId: z17.string().min(1),
2408
- cwd: z17.string().min(1),
2409
- options: bridgeExecutionOptionsSchema,
2410
- dynamicTools: z17.array(dynamicToolSchema).optional(),
2411
- disallowedTools: z17.array(z17.string().min(1)).optional(),
2412
- instructionMode: instructionModeSchema
2413
- };
2414
- var modelListParamsSchema = z17.object({ cwd: z17.string().min(1).optional() }).passthrough();
2415
- var threadStartParamsSchema = z17.object({
2416
- ...sessionConstructionFields,
2417
- input: z17.array(promptInputSchema).optional()
2418
- }).passthrough();
2419
- var threadResumeParamsSchema = z17.object({
2420
- ...sessionConstructionFields,
2421
- providerThreadId: z17.string().min(1)
2422
- }).passthrough();
2423
- var threadForkParamsSchema = z17.object({
2424
- ...sessionConstructionFields,
2425
- sourceProviderThreadId: z17.string().min(1),
2413
+ z14.object({
2414
+ kind: z14.literal("item.close"),
2415
+ key: deltaItemKeySchema,
2416
+ status: threadEventItemStatusSchema,
2417
+ resultText: z14.string().optional(),
2418
+ exitCode: z14.number().optional(),
2419
+ aggregatedOutput: z14.string().optional(),
2420
+ /** Terminal approval verdict (codex declined → denied). Default null. */
2421
+ approvalStatus: z14.literal("denied").optional(),
2422
+ item: deltaItemShapeSchema,
2423
+ /**
2424
+ * Grammar v3: the terminal presentation. Like `item`, the close carries
2425
+ * the full terminal form; when absent the opened item's presentation
2426
+ * survives onto the completed item (close-echo). REQUIRED for an
2427
+ * `extension` shape, which has nothing to echo without it.
2428
+ */
2429
+ presentation: deltaPresentationSchema.optional(),
2430
+ providerTurnId: providerTurnIdSchema.optional(),
2431
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2432
+ }).superRefine(requireExtensionPresentation),
2426
2433
  /**
2427
- * Absent means fork at the tip. Bridges whose handshake advertises
2428
- * `fork: "tip"` reject a request carrying a checkpoint instead of
2429
- * silently cloning more history than the bb timeline shows.
2434
+ * Free-form progress on an open item (non-command tool updates), or — with
2435
+ * `snapshot` a re-embedded snapshot of work that outlives its turn: a
2436
+ * background task (`item/backgroundTask/progress`) or, in grammar v3, a
2437
+ * background delegation (`item/delegation/progress`); both thread-scoped,
2438
+ * no turn required.
2439
+ *
2440
+ * Progress is throttled centrally by the assembler (one emission per item
2441
+ * key per policy interval, 500ms default; the newest suppressed snapshot is
2442
+ * flushed trailing-edge on the thread's next traffic once the window
2443
+ * elapses, and an `item.close` supersedes it). `flush: true` bypasses the
2444
+ * throttle and resets the window — status transitions must land immediately.
2430
2445
  */
2431
- sourceProviderCheckpointId: z17.string().min(1).optional()
2432
- }).passthrough();
2433
- var threadStopParamsSchema = z17.object({
2434
- threadId: z17.string().min(1),
2435
- providerThreadId: z17.string().min(1),
2446
+ z14.object({
2447
+ kind: z14.literal("item.progress"),
2448
+ key: deltaItemKeySchema,
2449
+ message: z14.string().optional(),
2450
+ snapshot: deltaProgressSnapshotSchema.optional(),
2451
+ flush: z14.boolean().optional(),
2452
+ providerTurnId: providerTurnIdSchema.optional(),
2453
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2454
+ }),
2436
2455
  /**
2437
- * "interrupt" stops an active turn and settles it as interrupted.
2438
- * "release" detaches an idle session so its resources can be reclaimed;
2439
- * it must never fabricate an interruption. One verb serving both intents
2440
- * is the #1584 incident the field is required.
2456
+ * Streamed text the one streaming dialect. Every text stream is keyed
2457
+ * like every other item: by the provider's own item id when the provider
2458
+ * names its message items (codex), or by a bridge-chosen `key.channel`
2459
+ * (`"assistant"`, `"thinking-2"`) plus `key.parentRef` for providers whose
2460
+ * streams are anonymous (claude, pi, acp). The first delta for an unknown
2461
+ * key synthesizes the channel's `item/started`; later deltas (and deltas
2462
+ * for a provider id already opened or settled) reuse the mapped id. The
2463
+ * assembler accumulates the stream text per open item so `item.textClose`
2464
+ * can settle without a provider-final text.
2441
2465
  */
2442
- intent: z17.enum(["interrupt", "release"]),
2443
- /** Non-null when the stop interrupts an active provider turn. */
2444
- activeTurnId: z17.string().min(1).nullable()
2445
- }).passthrough();
2446
- var threadRefParams = z17.object({
2447
- threadId: z17.string().min(1),
2448
- providerThreadId: z17.string().min(1)
2449
- }).passthrough();
2450
- var threadNameSetParamsSchema = z17.object({
2451
- threadId: z17.string().min(1),
2452
- providerThreadId: z17.string().min(1),
2453
- title: z17.string().min(1)
2454
- }).passthrough();
2455
- var turnInputFields = {
2456
- threadId: z17.string().min(1),
2457
- providerThreadId: z17.string().min(1),
2458
- input: z17.array(promptInputSchema),
2459
- clientRequestId: clientTurnRequestIdSchema,
2460
- options: bridgeExecutionOptionsSchema
2461
- };
2462
- var turnStartParamsSchema = z17.object(turnInputFields).passthrough();
2463
- var turnSteerParamsSchema = z17.object({
2464
- ...turnInputFields,
2465
- expectedTurnId: z17.string().min(1)
2466
- }).passthrough();
2467
- var skillsConfigureRootSchema = z17.object({
2468
- id: z17.string().min(1),
2469
- path: z17.string().min(1),
2470
- skills: z17.array(
2471
- z17.object({
2472
- name: z17.string().min(1),
2473
- description: z17.string()
2474
- }).passthrough()
2475
- )
2476
- }).passthrough();
2477
- var skillsConfigureParamsSchema = z17.object({
2478
- roots: z17.array(skillsConfigureRootSchema)
2479
- }).passthrough();
2480
- var threadIdentityResultSchema = z17.object({
2481
- providerThreadId: z17.string().min(1),
2482
- /** Refines the handshake's `sessionRestore` for this session. */
2483
- sessionRestorable: z17.boolean().optional()
2484
- }).passthrough();
2485
- var modelListResultSchema = z17.object({
2486
- models: z17.array(availableModelSchema),
2487
- selectedOnlyModels: z17.array(availableModelSchema).default([])
2488
- }).passthrough();
2489
-
2490
- // ../provider-bridge-protocol/src/notifications.ts
2491
- import { z as z18 } from "zod";
2492
- var threadIdentityNotificationSchema = z18.object({
2493
- threadId: z18.string().min(1),
2494
- providerThreadId: z18.string().min(1),
2495
- /** Refines the handshake's `sessionRestore` for this session. */
2496
- sessionRestorable: z18.boolean().optional()
2497
- }).passthrough();
2498
- var sessionReplacedNotificationSchema = z18.object({
2499
- threadId: z18.string().min(1),
2500
- /** Identity of the replacement session (may equal the old identity). */
2501
- providerThreadId: z18.string().min(1).nullable(),
2502
- /** Human-readable cause, shown in the timeline. */
2503
- reason: z18.string().min(1),
2504
- /** True when provider-side context did not survive the replacement. */
2505
- contextLost: z18.boolean().default(false)
2506
- }).passthrough();
2507
- var providerRawNotificationSchema = z18.object({
2508
- threadId: z18.string().min(1).optional(),
2509
- coverage: z18.enum(["noise", "unknown"]),
2510
- payload: z18.unknown()
2511
- }).passthrough();
2512
- var providerRecoveryNotificationSchema = z18.object({
2513
- threadId: z18.string().min(1).optional(),
2514
- kind: providerRecoveryKindSchema,
2515
- message: z18.string().min(1),
2516
- retryable: z18.boolean()
2517
- }).passthrough();
2518
- var errorNotificationSchema = z18.object({
2519
- threadId: z18.string().min(1).optional(),
2520
- message: z18.string().min(1)
2521
- }).passthrough();
2522
-
2523
- // ../provider-bridge-protocol/src/bridge-requests.ts
2524
- import { z as z19 } from "zod";
2525
- var toolCallRequestParamsSchema = z19.object({
2526
- providerThreadId: z19.string().min(1),
2527
- threadId: z19.string().min(1).optional(),
2528
- turnId: z19.union([z19.string().min(1), z19.null()]),
2529
- callId: z19.string().min(1),
2530
- tool: z19.string().min(1),
2531
- arguments: z19.unknown()
2532
- }).passthrough();
2533
- var toolCallResultSchema = z19.object({
2534
- success: z19.boolean(),
2535
- contentItems: z19.array(
2536
- z19.discriminatedUnion("type", [
2537
- z19.object({ type: z19.literal("inputText"), text: z19.string() }),
2538
- z19.object({
2539
- type: z19.literal("inputImage"),
2540
- imageUrl: z19.string().min(1)
2541
- })
2542
- ])
2543
- )
2544
- }).passthrough();
2545
- var interactionRequestParamsSchema = z19.object({
2546
- providerThreadId: z19.string().min(1),
2547
- threadId: z19.string().min(1).optional(),
2548
- turnId: z19.union([z19.string().min(1), z19.null()]),
2549
- payload: pendingInteractionPayloadSchema,
2466
+ z14.object({
2467
+ kind: z14.literal("item.textDelta"),
2468
+ key: deltaItemKeySchema,
2469
+ channel: deltaTextChannelSchema,
2470
+ text: z14.string(),
2471
+ providerTurnId: providerTurnIdSchema.optional(),
2472
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2473
+ }),
2550
2474
  /**
2551
- * The request's turn id and approval-subject item ids are in the
2552
- * provider's native id space (a `thread/delta` bridge holds no bb ids):
2553
- * the runtime adapter translates them through the delta assembler's maps
2554
- * before the interaction reaches the app. Omission means the ids are
2555
- * already app-visible (bridges whose approval subjects never referenced
2556
- * timeline ids ACP's approval ids never matched timeline ids).
2475
+ * Settle a text stream. `text` present: the provider's final text, preferred
2476
+ * over the accumulated stream (and enough on its own a close for a key
2477
+ * nothing streamed under completes a fresh item). `text` absent: settle
2478
+ * with the accumulated stream text, completing nothing when the stream only
2479
+ * ever received whitespace. Either way the key is released, so later text
2480
+ * mints a fresh item. `channel` says which item to mint for a bare close
2481
+ * and where a provider-final `text` lands on a reasoning item. Providers
2482
+ * that name their message items may instead settle through `item.close`
2483
+ * with the full terminal shape (the uniform close rule) — that is the same
2484
+ * item lifecycle, not a second streaming dialect.
2557
2485
  */
2558
- providerNativeIds: z19.boolean().optional()
2559
- }).passthrough();
2560
-
2561
- // ../provider-bridge-protocol/src/errors.ts
2562
- var BRIDGE_JSON_RPC_ERRORS = {
2563
- /** Standard JSON-RPC: params failed schema validation. */
2564
- INVALID_PARAMS: -32602,
2565
- /** Standard JSON-RPC: method not implemented by this bridge. */
2566
- METHOD_NOT_FOUND: -32601,
2567
- /** Generic bridge failure. */
2568
- BRIDGE_ERROR: -32e3,
2569
- /** A turn/steer arrived but the session has no active turn. */
2570
- NO_ACTIVE_TURN: -32001,
2571
- /** thread/resume for a session the provider can no longer restore. */
2572
- SESSION_NOT_RESTORABLE: -32002,
2573
- /** thread/fork with a checkpoint on a bridge that only forks at the tip. */
2574
- FORK_CHECKPOINT_UNSUPPORTED: -32003
2575
- };
2576
-
2577
- // ../provider-bridge-protocol/src/thread-event-grammar.ts
2578
- var ITEM_STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
2579
- "item/agentMessage/delta",
2580
- "item/plan/delta",
2581
- "item/commandExecution/outputDelta",
2582
- "item/fileChange/outputDelta",
2583
- "item/reasoning/summaryTextDelta",
2584
- "item/reasoning/textDelta",
2585
- "item/mcpToolCall/progress",
2586
- "item/toolCall/progress"
2587
- ]);
2588
- var MAX_ITEM_IDS_PER_THREAD = 512;
2589
- var THREAD_EVENT_GRAMMAR_RULES = {
2590
- itemOpensBeforeDelta: "item/opens-before-delta",
2591
- itemSettlesOnce: "item/settles-once",
2592
- turnStartsOnce: "turn/starts-once",
2593
- turnSettlesOnce: "turn/settles-once",
2594
- turnKnown: "turn/known"
2595
- };
2596
- var OK = { kind: "ok" };
2597
- var ThreadEventGrammar = class {
2598
- #byThreadId = /* @__PURE__ */ new Map();
2599
- clear() {
2600
- this.#byThreadId.clear();
2601
- }
2602
- clearThread(threadId) {
2603
- this.#byThreadId.delete(threadId);
2604
- }
2605
- observe(event) {
2606
- const state = this.#stateFor(event.threadId);
2607
- switch (event.type) {
2608
- case "turn/started": {
2609
- const turnId = turnIdOf(event);
2610
- if (turnId === void 0) {
2611
- return OK;
2612
- }
2613
- if (state.completedTurnIds.has(turnId)) {
2614
- return violation(
2615
- THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
2616
- `turn/started for turn "${turnId}", which already completed`
2617
- );
2618
- }
2619
- if (state.startedTurnIds.has(turnId)) {
2620
- return violation(
2621
- THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
2622
- `turn/started for turn "${turnId}", which is already open`
2623
- );
2624
- }
2625
- state.startedTurnIds.add(turnId);
2626
- return OK;
2627
- }
2628
- case "turn/completed": {
2629
- const turnId = turnIdOf(event);
2630
- if (turnId === void 0) {
2631
- return OK;
2632
- }
2633
- if (state.completedTurnIds.has(turnId)) {
2634
- return violation(
2635
- THREAD_EVENT_GRAMMAR_RULES.turnSettlesOnce,
2636
- `turn/completed for turn "${turnId}", which already completed`
2637
- );
2638
- }
2639
- if (!state.startedTurnIds.has(turnId)) {
2640
- return violation(
2641
- THREAD_EVENT_GRAMMAR_RULES.turnKnown,
2642
- `turn/completed for turn "${turnId}", which never started`
2643
- );
2644
- }
2645
- state.startedTurnIds.delete(turnId);
2646
- state.completedTurnIds.add(turnId);
2647
- return OK;
2648
- }
2649
- case "item/started": {
2650
- state.openItemIds.add(event.item.id);
2651
- state.settledItemIds.delete(event.item.id);
2652
- trim(state.openItemIds);
2653
- return OK;
2654
- }
2655
- case "item/completed":
2656
- case "item/backgroundTask/completed":
2657
- case "item/delegation/completed": {
2658
- const itemId = event.item.id;
2659
- if (state.settledItemIds.has(itemId)) {
2660
- return violation(
2661
- THREAD_EVENT_GRAMMAR_RULES.itemSettlesOnce,
2662
- `${event.type} for item "${itemId}", which already settled`
2663
- );
2664
- }
2665
- state.openItemIds.delete(itemId);
2666
- state.settledItemIds.add(itemId);
2667
- trim(state.settledItemIds);
2668
- return OK;
2669
- }
2670
- case "item/backgroundTask/progress":
2671
- case "item/delegation/progress": {
2672
- return this.#checkOpenItem(state, event.type, event.item.id);
2673
- }
2674
- default: {
2675
- if (!ITEM_STREAMING_EVENT_TYPES.has(event.type)) {
2676
- return OK;
2677
- }
2678
- if (!("itemId" in event) || typeof event.itemId !== "string") {
2679
- return OK;
2680
- }
2681
- return this.#checkOpenItem(state, event.type, event.itemId);
2682
- }
2683
- }
2684
- }
2685
- #checkOpenItem(state, eventType, itemId) {
2686
- if (state.openItemIds.has(itemId)) {
2687
- return OK;
2688
- }
2689
- return violation(
2690
- THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
2691
- `${eventType} for item "${itemId}" arrived before item/started`
2692
- );
2693
- }
2694
- #stateFor(threadId) {
2695
- const existing = this.#byThreadId.get(threadId);
2696
- if (existing !== void 0) {
2697
- return existing;
2698
- }
2699
- const created = {
2700
- openItemIds: /* @__PURE__ */ new Set(),
2701
- settledItemIds: /* @__PURE__ */ new Set(),
2702
- startedTurnIds: /* @__PURE__ */ new Set(),
2703
- completedTurnIds: /* @__PURE__ */ new Set()
2704
- };
2705
- this.#byThreadId.set(threadId, created);
2706
- return created;
2707
- }
2708
- };
2709
- function violation(rule, reason) {
2710
- return { kind: "violation", rule, reason };
2711
- }
2712
- function turnIdOf(event) {
2713
- return "scope" in event ? getThreadEventScopeTurnId(event.scope) : void 0;
2714
- }
2715
- function trim(itemIds) {
2716
- while (itemIds.size > MAX_ITEM_IDS_PER_THREAD) {
2717
- const oldest = itemIds.values().next();
2718
- if (oldest.done === true) {
2719
- return;
2720
- }
2721
- itemIds.delete(oldest.value);
2722
- }
2723
- }
2724
-
2725
- // ../provider-bridge-protocol/src/thread-delta.ts
2726
- import { z as z20 } from "zod";
2727
- var THREAD_DELTA_NOTIFICATION_METHOD = "thread/delta";
2728
- var deltaPresentationSchema = threadEventItemPresentationSchema;
2729
- var THREAD_DELTA_KEY_SEPARATOR = "";
2730
- var deltaKeyPartSchema = z20.string().min(1).refine((value) => !value.includes(THREAD_DELTA_KEY_SEPARATOR), {
2731
- message: "provider keys must not contain the internal key separator (\\u001f)"
2732
- });
2733
- var deltaItemKeySchema = z20.object({
2734
- providerItemId: deltaKeyPartSchema.optional(),
2735
- channel: deltaKeyPartSchema.optional(),
2736
- parentRef: deltaKeyPartSchema.optional()
2737
- });
2738
- var providerTurnIdSchema = deltaKeyPartSchema;
2739
- var deltaFileChangeSchema = z20.object({
2740
- path: z20.string(),
2741
- /** The bridge states the change kind; the assembler never derives it. */
2742
- kind: z20.enum(["add", "update", "delete"]),
2743
- movePath: z20.string().optional(),
2744
- /** Provider-supplied unified diff; preferred over old/new text building. */
2745
- diff: z20.string().optional(),
2746
- oldText: z20.string().optional(),
2747
- /** When present the assembler builds the unified diff from old/new text. */
2748
- newText: z20.string().optional()
2749
- });
2750
- var deltaBackgroundTaskShapeSchema = z20.object({
2751
- type: z20.literal("backgroundTask"),
2486
+ z14.object({
2487
+ kind: z14.literal("item.textClose"),
2488
+ key: deltaItemKeySchema,
2489
+ channel: deltaTextChannelSchema,
2490
+ text: z14.string().optional(),
2491
+ providerTurnId: providerTurnIdSchema.optional(),
2492
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2493
+ }),
2752
2494
  /**
2753
- * The provider's stable task id, shared by every generation (restart) of
2754
- * the same task. Rides through to the canonical item so consumers can
2755
- * correlate a restarted task with its earlier generations — the assembler
2756
- * mints fresh item ids per generation, so identity must travel as data,
2757
- * never as id text.
2495
+ * Item-keyed exact output append (codex command/fileChange output deltas).
2496
+ * Never synthesizes an open and never diffs — the text is already a delta.
2758
2497
  */
2759
- familyId: z20.string().min(1),
2760
- taskType: z20.string(),
2761
- description: z20.string(),
2762
- status: threadEventItemStatusSchema,
2763
- taskStatus: backgroundTaskStatusSchema,
2764
- skipTranscript: z20.boolean(),
2765
- workflowName: z20.string().optional(),
2766
- workflow: workflowProgressSnapshotSchema.optional(),
2767
- usage: backgroundTaskUsageSchema.optional(),
2768
- summary: z20.string().optional(),
2769
- error: z20.string().optional(),
2770
- outputFile: z20.string().optional()
2771
- });
2772
- var deltaFileReadShapeSchema = z20.object({
2773
- type: z20.literal("fileRead"),
2774
- path: z20.string(),
2775
- cmd: z20.string().optional()
2776
- });
2777
- var deltaSearchShapeSchema = z20.object({
2778
- type: z20.literal("search"),
2779
- mode: threadEventSearchModeSchema,
2780
- query: z20.string(),
2781
- path: z20.string().optional(),
2782
- cmd: z20.string().optional()
2783
- });
2784
- var deltaDelegationShapeSchema = z20.object({
2785
- type: z20.literal("delegation"),
2786
- childRef: deltaKeyPartSchema,
2787
- label: z20.string(),
2788
- background: z20.boolean(),
2789
- summary: z20.string().optional()
2790
- });
2791
- var deltaPlanStepsShapeSchema = z20.object({
2792
- type: z20.literal("planSteps"),
2793
- steps: z20.array(threadEventPlanStepSchema),
2794
- explanation: z20.string().optional()
2795
- });
2796
- var deltaExtensionShapeSchema = z20.object({
2797
- type: z20.literal("extension"),
2798
- kind: extensionKindSchema,
2799
- payload: jsonValueSchema
2800
- });
2801
- var deltaItemShapeSchema = z20.discriminatedUnion("type", [
2802
- z20.object({
2803
- type: z20.literal("command"),
2804
- command: z20.string(),
2805
- cwd: z20.string(),
2806
- aggregatedOutput: z20.string().optional(),
2807
- exitCode: z20.number().optional(),
2808
- durationMs: z20.number().optional()
2809
- }),
2810
- z20.object({
2811
- type: z20.literal("fileChange"),
2812
- /** Empty only on bare close-without-open fallbacks (path unknown). */
2813
- changes: z20.array(deltaFileChangeSchema)
2498
+ z14.object({
2499
+ kind: z14.literal("item.outputDelta"),
2500
+ key: deltaItemKeySchema,
2501
+ channel: deltaOutputChannelSchema,
2502
+ text: z14.string(),
2503
+ providerTurnId: providerTurnIdSchema.optional(),
2504
+ noTurnFallback: deltaNoTurnFallbackSchema.optional()
2814
2505
  }),
2815
2506
  /**
2816
- * The generic tool call: the escape hatch for tools with no core kind. In
2817
- * grammar v3 the bridge says how the row reads through the delta's
2818
- * `presentation` (label, icon, suppression) instead of core keeping a
2819
- * tool-name table; a `tool` item without presentation renders with the
2820
- * generic tool row.
2507
+ * Cumulative command output snapshot (pi bash). The assembler diffs
2508
+ * consecutive snapshots into `outputDelta`/`reset` events.
2821
2509
  */
2822
- z20.object({
2823
- type: z20.literal("tool"),
2824
- tool: z20.string(),
2825
- server: z20.string().optional(),
2826
- args: z20.unknown().optional(),
2827
- result: z20.unknown().optional(),
2828
- error: z20.string().optional(),
2829
- durationMs: z20.number().optional()
2830
- }),
2831
- z20.object({ type: z20.literal("compaction") }),
2832
- z20.object({ type: z20.literal("agentMessage"), text: z20.string() }),
2833
- z20.object({
2834
- type: z20.literal("reasoning"),
2835
- summary: z20.array(z20.string()),
2836
- content: z20.array(z20.string())
2837
- }),
2838
- z20.object({ type: z20.literal("plan"), text: z20.string() }),
2839
- z20.object({
2840
- type: z20.literal("webSearch"),
2841
- queries: z20.array(z20.string()).min(1)
2842
- }),
2843
- z20.object({
2844
- type: z20.literal("webFetch"),
2845
- url: z20.string(),
2846
- prompt: z20.string().nullable().optional(),
2847
- pattern: z20.string().nullable()
2848
- }),
2849
- z20.object({ type: z20.literal("imageView"), path: z20.string() }),
2850
- deltaBackgroundTaskShapeSchema,
2851
- // Grammar v3 shapes. Every existing shape above is kept unchanged.
2852
- deltaFileReadShapeSchema,
2853
- deltaSearchShapeSchema,
2854
- deltaDelegationShapeSchema,
2855
- deltaPlanStepsShapeSchema,
2856
- deltaExtensionShapeSchema
2857
- ]);
2858
- var deltaProgressSnapshotSchema = z20.discriminatedUnion("type", [
2859
- deltaBackgroundTaskShapeSchema,
2860
- deltaDelegationShapeSchema
2861
- ]);
2862
- var deltaTextChannelSchema = z20.enum([
2863
- "agentMessage",
2864
- "reasoningSummary",
2865
- "reasoningText",
2866
- "plan"
2867
- ]);
2868
- var deltaOutputChannelSchema = z20.enum(["command", "fileChange"]);
2869
- var deltaErrorSchema = z20.object({ message: z20.string() });
2870
- var deltaAttachSchema = z20.enum(["open", "currentOrLast"]);
2871
- var deltaNoTurnFallbackSchema = z20.object({
2872
- raw: providerRawEventSchema,
2873
- rawType: z20.string()
2874
- });
2875
- function requireExtensionPresentation(delta, ctx) {
2876
- if (delta.item.type === "extension" && delta.presentation === void 0) {
2877
- ctx.addIssue({
2878
- code: "custom",
2879
- message: "extension items require a presentation on item.open/item.close",
2880
- path: ["presentation"]
2881
- });
2882
- }
2883
- }
2884
- var threadDeltaSchema = z20.discriminatedUnion("kind", [
2885
- /**
2886
- * The provider consumed an input (immediate or steered). The assembler owns
2887
- * the queue-until-turn-opens behavior and the terminal-turn invariant.
2888
- * With `providerTurnId` the acceptance is emitted against that vouched turn
2889
- * directly (codex correlates acceptance to a named native turn).
2890
- */
2891
- z20.object({
2892
- kind: z20.literal("input.accepted"),
2893
- clientRequestId: clientTurnRequestIdSchema,
2894
- providerTurnId: providerTurnIdSchema.optional()
2895
- }),
2896
- /**
2897
- * Input the provider itself injected into the conversation, with no bb
2898
- * client request behind it (a pi extension's `sendMessage` custom message
2899
- * that triggered or steered a turn). The assembler records it as a
2900
- * `userMessage` item in the open turn so the transcript shows what the
2901
- * model was answering. Dropped silently when no turn is open: the provider
2902
- * appended it to its own context without running the agent, so there is no
2903
- * bb turn to attach it to.
2904
- */
2905
- z20.object({
2906
- kind: z20.literal("input.provider"),
2907
- text: z20.string().min(1),
2908
- parentRef: deltaKeyPartSchema.optional()
2909
- }),
2910
- /**
2911
- * An explicit provider signal opened work (pi `agent_start`, codex
2912
- * `turn/started`). With `providerTurnId` the turn lives in the keyed
2913
- * provider-turn space: several may be open at once (codex multiplexes
2914
- * subagent child turns onto one thread) and none of the current-turn
2915
- * machinery is touched.
2916
- */
2917
- z20.object({
2918
- kind: z20.literal("turn.open"),
2919
- providerTurnId: providerTurnIdSchema.optional(),
2920
- /** Provider-native parent tool-call id for delegated child turns. */
2921
- parentRef: deltaKeyPartSchema.optional()
2922
- }),
2923
- /**
2924
- * The bridge's conclusion that the turn settled. `claimIfIdle: true` marks
2925
- * fallback closers that own a turn only if accepted input is pending
2926
- * (the old bridge-kit terminal-turn rule, applied centrally); an open turn is
2927
- * always settled. A keyed boundary (`providerTurnId`) always emits — the
2928
- * provider named the turn — and settles only that turn.
2929
- */
2930
- z20.object({
2931
- kind: z20.literal("turn.boundary"),
2932
- status: threadEventTurnStatusSchema,
2933
- error: deltaErrorSchema.optional(),
2934
- providerCheckpointId: z20.string().min(1).optional(),
2935
- claimIfIdle: z20.boolean().optional(),
2936
- providerTurnId: providerTurnIdSchema.optional()
2937
- }),
2938
- /**
2939
- * A parsed item opened. `attach: "currentOrLast"` pins the item to the turn
2940
- * that is open or just closed without opening a new one (pi threshold
2941
- * compaction); the default attaches to the open turn only. A known
2942
- * `providerItemId` reuses its minted bb id (an explicit open reopens the
2943
- * same item, codex's settle/reopen rule).
2944
- */
2945
- z20.object({
2946
- kind: z20.literal("item.open"),
2947
- key: deltaItemKeySchema,
2948
- item: deltaItemShapeSchema,
2949
- /**
2950
- * Grammar v3: how the row reads, persisted with the opened item. The
2951
- * one place presentation travels. Optional for core shapes while v2
2952
- * deltas are accepted; REQUIRED for `extension` shapes.
2953
- */
2954
- presentation: deltaPresentationSchema.optional(),
2955
- attach: deltaAttachSchema.optional(),
2956
- providerTurnId: providerTurnIdSchema.optional(),
2957
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
2958
- }).superRefine(requireExtensionPresentation),
2959
- /**
2960
- * The item settled. `item` is REQUIRED and always carries the full terminal
2961
- * item shape (Michael's uniform close rule, 2026-08-18): the assembler
2962
- * builds the completed item from it. With a same-shaped item open under the
2963
- * key, the terminal shape wins and the opened item contributes only its
2964
- * minted id; with a different-shaped item open, the assembler closes the
2965
- * opened shape and then emits the terminal shape (ACP's dual-complete);
2966
- * with nothing open it builds the bare completed item.
2967
- *
2968
- * Provider-identified closes (`key.providerItemId`) dedup: a repeated close
2969
- * for a settled id is dropped and an explicit `item.open` reopens the id
2970
- * (codex retries the terminal notification after approvals).
2971
- */
2972
- z20.object({
2973
- kind: z20.literal("item.close"),
2974
- key: deltaItemKeySchema,
2975
- status: threadEventItemStatusSchema,
2976
- resultText: z20.string().optional(),
2977
- exitCode: z20.number().optional(),
2978
- aggregatedOutput: z20.string().optional(),
2979
- /** Terminal approval verdict (codex declined → denied). Default null. */
2980
- approvalStatus: z20.literal("denied").optional(),
2981
- item: deltaItemShapeSchema,
2982
- /**
2983
- * Grammar v3: the terminal presentation. Like `item`, the close carries
2984
- * the full terminal form; when absent the opened item's presentation
2985
- * survives onto the completed item (close-echo). REQUIRED for an
2986
- * `extension` shape, which has nothing to echo without it.
2987
- */
2988
- presentation: deltaPresentationSchema.optional(),
2989
- providerTurnId: providerTurnIdSchema.optional(),
2990
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
2991
- }).superRefine(requireExtensionPresentation),
2992
- /**
2993
- * Free-form progress on an open item (non-command tool updates), or — with
2994
- * `snapshot` — a re-embedded snapshot of work that outlives its turn: a
2995
- * background task (`item/backgroundTask/progress`) or, in grammar v3, a
2996
- * background delegation (`item/delegation/progress`); both thread-scoped,
2997
- * no turn required.
2998
- *
2999
- * Progress is throttled centrally by the assembler (one emission per item
3000
- * key per policy interval, 500ms default; the newest suppressed snapshot is
3001
- * flushed trailing-edge on the thread's next traffic once the window
3002
- * elapses, and an `item.close` supersedes it). `flush: true` bypasses the
3003
- * throttle and resets the window — status transitions must land immediately.
3004
- */
3005
- z20.object({
3006
- kind: z20.literal("item.progress"),
3007
- key: deltaItemKeySchema,
3008
- message: z20.string().optional(),
3009
- snapshot: deltaProgressSnapshotSchema.optional(),
3010
- flush: z20.boolean().optional(),
3011
- providerTurnId: providerTurnIdSchema.optional(),
3012
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
3013
- }),
3014
- /**
3015
- * Streamed text — the one streaming dialect. Every text stream is keyed
3016
- * like every other item: by the provider's own item id when the provider
3017
- * names its message items (codex), or by a bridge-chosen `key.channel`
3018
- * (`"assistant"`, `"thinking-2"`) plus `key.parentRef` for providers whose
3019
- * streams are anonymous (claude, pi, acp). The first delta for an unknown
3020
- * key synthesizes the channel's `item/started`; later deltas (and deltas
3021
- * for a provider id already opened or settled) reuse the mapped id. The
3022
- * assembler accumulates the stream text per open item so `item.textClose`
3023
- * can settle without a provider-final text.
3024
- */
3025
- z20.object({
3026
- kind: z20.literal("item.textDelta"),
3027
- key: deltaItemKeySchema,
3028
- channel: deltaTextChannelSchema,
3029
- text: z20.string(),
3030
- providerTurnId: providerTurnIdSchema.optional(),
3031
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
3032
- }),
3033
- /**
3034
- * Settle a text stream. `text` present: the provider's final text, preferred
3035
- * over the accumulated stream (and enough on its own — a close for a key
3036
- * nothing streamed under completes a fresh item). `text` absent: settle
3037
- * with the accumulated stream text, completing nothing when the stream only
3038
- * ever received whitespace. Either way the key is released, so later text
3039
- * mints a fresh item. `channel` says which item to mint for a bare close
3040
- * and where a provider-final `text` lands on a reasoning item. Providers
3041
- * that name their message items may instead settle through `item.close`
3042
- * with the full terminal shape (the uniform close rule) — that is the same
3043
- * item lifecycle, not a second streaming dialect.
3044
- */
3045
- z20.object({
3046
- kind: z20.literal("item.textClose"),
3047
- key: deltaItemKeySchema,
3048
- channel: deltaTextChannelSchema,
3049
- text: z20.string().optional(),
3050
- providerTurnId: providerTurnIdSchema.optional(),
3051
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
3052
- }),
3053
- /**
3054
- * Item-keyed exact output append (codex command/fileChange output deltas).
3055
- * Never synthesizes an open and never diffs — the text is already a delta.
3056
- */
3057
- z20.object({
3058
- kind: z20.literal("item.outputDelta"),
3059
- key: deltaItemKeySchema,
3060
- channel: deltaOutputChannelSchema,
3061
- text: z20.string(),
3062
- providerTurnId: providerTurnIdSchema.optional(),
3063
- noTurnFallback: deltaNoTurnFallbackSchema.optional()
3064
- }),
3065
- /**
3066
- * Cumulative command output snapshot (pi bash). The assembler diffs
3067
- * consecutive snapshots into `outputDelta`/`reset` events.
3068
- */
3069
- z20.object({
3070
- kind: z20.literal("command.outputSnapshot"),
2510
+ z14.object({
2511
+ kind: z14.literal("command.outputSnapshot"),
3071
2512
  key: deltaItemKeySchema,
3072
- text: z20.string(),
2513
+ text: z14.string(),
3073
2514
  noTurnFallback: deltaNoTurnFallbackSchema.optional()
3074
2515
  }),
3075
2516
  /**
@@ -3082,11 +2523,11 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3082
2523
  * `thread/tokenUsage/updated` only: a provider whose usage also measures
3083
2524
  * the context window sends the `contextWindow` delta beside it.
3084
2525
  */
3085
- z20.object({
3086
- kind: z20.literal("usage"),
2526
+ z14.object({
2527
+ kind: z14.literal("usage"),
3087
2528
  total: threadEventTokenUsageBreakdownSchema,
3088
2529
  last: threadEventTokenUsageBreakdownSchema,
3089
- modelContextWindow: z20.number().nullable(),
2530
+ modelContextWindow: z14.number().nullable(),
3090
2531
  providerTurnId: providerTurnIdSchema.optional()
3091
2532
  }),
3092
2533
  /**
@@ -3096,33 +2537,33 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3096
2537
  * (codex measures the window per native turn) and `attach` is then
3097
2538
  * irrelevant.
3098
2539
  */
3099
- z20.object({
3100
- kind: z20.literal("contextWindow"),
3101
- used: z20.number().nullable(),
3102
- size: z20.number().nullable().optional(),
3103
- estimated: z20.boolean(),
2540
+ z14.object({
2541
+ kind: z14.literal("contextWindow"),
2542
+ used: z14.number().nullable(),
2543
+ size: z14.number().nullable().optional(),
2544
+ estimated: z14.boolean(),
3104
2545
  attach: deltaAttachSchema,
3105
2546
  providerTurnId: providerTurnIdSchema.optional()
3106
2547
  }),
3107
- z20.object({
3108
- kind: z20.literal("context.compacted"),
2548
+ z14.object({
2549
+ kind: z14.literal("context.compacted"),
3109
2550
  providerTurnId: providerTurnIdSchema.optional(),
3110
2551
  noTurnFallback: deltaNoTurnFallbackSchema.optional()
3111
2552
  }),
3112
- z20.object({ kind: z20.literal("context.cleared") }),
2553
+ z14.object({ kind: z14.literal("context.cleared") }),
3113
2554
  /** The aggregate working-tree diff for a turn (codex turn/diff/updated). */
3114
- z20.object({
3115
- kind: z20.literal("turn.diff"),
3116
- diff: z20.string(),
2555
+ z14.object({
2556
+ kind: z14.literal("turn.diff"),
2557
+ diff: z14.string(),
3117
2558
  providerTurnId: providerTurnIdSchema.optional()
3118
2559
  }),
3119
2560
  // Thread metadata (codex thread lifecycle notifications).
3120
- z20.object({ kind: z20.literal("thread.started") }),
3121
- z20.object({
3122
- kind: z20.literal("thread.identity"),
3123
- providerThreadId: z20.string().min(1)
2561
+ z14.object({ kind: z14.literal("thread.started") }),
2562
+ z14.object({
2563
+ kind: z14.literal("thread.identity"),
2564
+ providerThreadId: z14.string().min(1)
3124
2565
  }),
3125
- z20.object({ kind: z20.literal("thread.name"), name: z20.string().min(1) }),
2566
+ z14.object({ kind: z14.literal("thread.name"), name: z14.string().min(1) }),
3126
2567
  /**
3127
2568
  * Plugin-declared thread state (grammar v3): `"<pluginId>/<name>"` kinds
3128
2569
  * beside the core thread-state family (usage, context window, rate limits,
@@ -3136,8 +2577,8 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3136
2577
  * this union's discriminator; the item shape and the persisted item call
3137
2578
  * the same value `kind`.
3138
2579
  */
3139
- z20.object({
3140
- kind: z20.literal("extension.state"),
2580
+ z14.object({
2581
+ kind: z14.literal("extension.state"),
3141
2582
  extensionKind: extensionKindSchema,
3142
2583
  payload: jsonValueSchema
3143
2584
  }),
@@ -3147,8 +2588,8 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3147
2588
  * bridge-side — it is seeded from a per-child post-initialize read the
3148
2589
  * assembler never sees.
3149
2590
  */
3150
- z20.object({
3151
- kind: z20.literal("provider.rateLimits"),
2591
+ z14.object({
2592
+ kind: z14.literal("provider.rateLimits"),
3152
2593
  rateLimits: providerRateLimitStateSchema
3153
2594
  }),
3154
2595
  /**
@@ -3158,16 +2599,16 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3158
2599
  * turn; `threadScoped: true` pins thread scope (codex errors without a
3159
2600
  * native turn id never attach to whatever turn happens to be open).
3160
2601
  */
3161
- z20.object({
3162
- kind: z20.literal("provider.error"),
3163
- message: z20.string(),
3164
- detail: z20.string().optional(),
3165
- willRetry: z20.boolean().optional(),
2602
+ z14.object({
2603
+ kind: z14.literal("provider.error"),
2604
+ message: z14.string(),
2605
+ detail: z14.string().optional(),
2606
+ willRetry: z14.boolean().optional(),
3166
2607
  category: providerErrorCategorySchema.optional(),
3167
2608
  errorInfo: providerErrorInfoSchema.optional(),
3168
- settlesTurn: z20.boolean().optional(),
2609
+ settlesTurn: z14.boolean().optional(),
3169
2610
  providerTurnId: providerTurnIdSchema.optional(),
3170
- threadScoped: z20.boolean().optional()
2611
+ threadScoped: z14.boolean().optional()
3171
2612
  }),
3172
2613
  /**
3173
2614
  * The provider switched models mid-flight (claude model fallback). Scoped to
@@ -3176,23 +2617,23 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3176
2617
  * assistant fallback block against the later system duplicate stays
3177
2618
  * bridge-side — it is keyed by the bridge's own segment tracking.
3178
2619
  */
3179
- z20.object({
3180
- kind: z20.literal("provider.modelFallback"),
3181
- originalModel: z20.string().min(1),
3182
- fallbackModel: z20.string().min(1),
3183
- reason: z20.enum(["refusal", "provider"]),
3184
- message: z20.string()
2620
+ z14.object({
2621
+ kind: z14.literal("provider.modelFallback"),
2622
+ originalModel: z14.string().min(1),
2623
+ fallbackModel: z14.string().min(1),
2624
+ reason: z14.enum(["refusal", "provider"]),
2625
+ message: z14.string()
3185
2626
  }),
3186
2627
  /**
3187
2628
  * `vouchedTurn: true` scopes the warning to the open turn when one exists
3188
2629
  * (ACP warnings are turn-scoped mid-turn); default is thread scope.
3189
2630
  */
3190
- z20.object({
3191
- kind: z20.literal("provider.warning"),
3192
- summary: z20.string().optional(),
3193
- details: z20.string().optional(),
2631
+ z14.object({
2632
+ kind: z14.literal("provider.warning"),
2633
+ summary: z14.string().optional(),
2634
+ details: z14.string().optional(),
3194
2635
  category: threadEventWarningCategorySchema.optional(),
3195
- vouchedTurn: z20.boolean().optional()
2636
+ vouchedTurn: z14.boolean().optional()
3196
2637
  }),
3197
2638
  /**
3198
2639
  * The bridge's visibility classification decided this raw event is unknown.
@@ -3203,12 +2644,12 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3203
2644
  * "known event, no active turn" visibility fallback for events that
3204
2645
  * otherwise translate to silence) and is dropped entirely mid-turn.
3205
2646
  */
3206
- z20.object({
3207
- kind: z20.literal("unhandled"),
2647
+ z14.object({
2648
+ kind: z14.literal("unhandled"),
3208
2649
  raw: providerRawEventSchema,
3209
- rawType: z20.string(),
3210
- vouchedTurn: z20.boolean(),
3211
- onlyIfNoTurn: z20.boolean().optional(),
2650
+ rawType: z14.string(),
2651
+ vouchedTurn: z14.boolean(),
2652
+ onlyIfNoTurn: z14.boolean().optional(),
3212
2653
  parentRef: deltaKeyPartSchema.optional(),
3213
2654
  providerTurnId: providerTurnIdSchema.optional()
3214
2655
  }),
@@ -3216,7 +2657,7 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3216
2657
  * Lifecycle settlement: the session was interrupted. The assembler closes
3217
2658
  * the open turn and open items as interrupted.
3218
2659
  */
3219
- z20.object({ kind: z20.literal("session.ended") }),
2660
+ z14.object({ kind: z14.literal("session.ended") }),
3220
2661
  /**
3221
2662
  * Provider-native id-space boundary: a new provider session was constructed
3222
2663
  * for this thread (start/resume/fork/rebuild), so its native turn/item ids
@@ -3224,28 +2665,32 @@ var threadDeltaSchema = z20.discriminatedUnion("kind", [
3224
2665
  * sets, open items and streams; the bridge settles any open work first
3225
2666
  * (nothing is in flight at any construction site).
3226
2667
  */
3227
- z20.object({ kind: z20.literal("session.reset") })
2668
+ z14.object({ kind: z14.literal("session.reset") })
3228
2669
  ]);
3229
- var threadDeltaNotificationParamsSchema = z20.object({
3230
- threadId: z20.string().min(1),
3231
- deltas: z20.array(threadDeltaSchema)
2670
+ var threadDeltaNotificationParamsSchema = z14.object({
2671
+ threadId: z14.string().min(1),
2672
+ deltas: z14.array(threadDeltaSchema)
3232
2673
  }).passthrough();
3233
2674
 
3234
2675
  // ../provider-bridge-protocol/src/assembler/delta-assembler.ts
3235
2676
  import { randomUUID } from "node:crypto";
3236
2677
 
2678
+ // ../provider-bridge-protocol/src/version.ts
2679
+ var PROVIDER_BRIDGE_PROTOCOL_VERSION = 2;
2680
+ var THREAD_DELTA_GRAMMAR_V3 = 3;
2681
+
3237
2682
  // ../provider-bridge-protocol/src/bridge-kit/adapter-utils.ts
3238
- import { z as z22 } from "zod";
2683
+ import { z as z16 } from "zod";
3239
2684
 
3240
2685
  // ../provider-bridge-protocol/src/bridge-kit/tool-arg-schemas.ts
3241
- import { z as z21 } from "zod";
3242
- var bashArgsSchema = z21.object({
3243
- command: z21.string().optional(),
3244
- cwd: z21.string().optional()
2686
+ import { z as z15 } from "zod";
2687
+ var bashArgsSchema = z15.object({
2688
+ command: z15.string().optional(),
2689
+ cwd: z15.string().optional()
3245
2690
  }).passthrough();
3246
- var textBlockSchema = z21.object({
3247
- type: z21.literal("text"),
3248
- text: z21.string()
2691
+ var textBlockSchema = z15.object({
2692
+ type: z15.literal("text"),
2693
+ text: z15.string()
3249
2694
  });
3250
2695
 
3251
2696
  // ../provider-bridge-protocol/src/bridge-kit/provider-visibility-helpers.ts
@@ -3254,10 +2699,10 @@ function isRecord(value) {
3254
2699
  }
3255
2700
 
3256
2701
  // ../provider-bridge-protocol/src/bridge-kit/adapter-utils.ts
3257
- var contentWrapperSchema = z22.object({
3258
- content: z22.array(z22.unknown())
2702
+ var contentWrapperSchema = z16.object({
2703
+ content: z16.array(z16.unknown())
3259
2704
  }).passthrough();
3260
- var shellEnvironmentVariableKeySchema = z22.string().regex(/^[A-Z_][A-Z0-9_]*$/i);
2705
+ var shellEnvironmentVariableKeySchema = z16.string().regex(/^[A-Z_][A-Z0-9_]*$/i);
3261
2706
  var MAX_EXACT_LINE_DIFF_CELLS = 1e6;
3262
2707
  function splitComparableLines(text) {
3263
2708
  const normalized = text.replace(/\r\n?/gu, "\n");
@@ -4087,7 +3532,7 @@ function createDeltaAssembler(options) {
4087
3532
  );
4088
3533
  case "tool": {
4089
3534
  const toolArguments = toOptionalRecord(shape.args);
4090
- const result = shape.result ?? close.resultText;
3535
+ const result2 = shape.result ?? close.resultText;
4091
3536
  return withParentToolCallId(
4092
3537
  {
4093
3538
  type: "toolCall",
@@ -4096,7 +3541,7 @@ function createDeltaAssembler(options) {
4096
3541
  tool: shape.tool,
4097
3542
  ...toolArguments ? { arguments: toolArguments } : {},
4098
3543
  status: close.status,
4099
- ...result === void 0 ? {} : { result },
3544
+ ...result2 === void 0 ? {} : { result: result2 },
4100
3545
  ...shape.error === void 0 ? {} : { error: shape.error },
4101
3546
  ...shape.durationMs === void 0 ? {} : { durationMs: shape.durationMs }
4102
3547
  },
@@ -4864,178 +4309,942 @@ function createDeltaAssembler(options) {
4864
4309
  case "turn.diff": {
4865
4310
  const turnId = delta.providerTurnId !== void 0 ? resolveVouchedTurnId(state, delta.providerTurnId) : state.currentTurnId;
4866
4311
  if (turnId === void 0) {
4867
- return;
4312
+ return;
4313
+ }
4314
+ events.push({
4315
+ type: "turn/diff/updated",
4316
+ threadId: UNSTAMPED_THREAD_ID,
4317
+ providerThreadId: "",
4318
+ scope: turnScope(turnId),
4319
+ diff: delta.diff
4320
+ });
4321
+ return;
4322
+ }
4323
+ case "thread.started": {
4324
+ events.push({
4325
+ type: "thread/started",
4326
+ threadId: UNSTAMPED_THREAD_ID,
4327
+ scope: threadScope()
4328
+ });
4329
+ return;
4330
+ }
4331
+ case "thread.identity": {
4332
+ events.push({
4333
+ type: "thread/identity",
4334
+ threadId: UNSTAMPED_THREAD_ID,
4335
+ providerThreadId: delta.providerThreadId,
4336
+ scope: threadScope()
4337
+ });
4338
+ return;
4339
+ }
4340
+ case "thread.name": {
4341
+ events.push({
4342
+ type: "thread/name/updated",
4343
+ threadId: UNSTAMPED_THREAD_ID,
4344
+ providerThreadId: "",
4345
+ scope: threadScope(),
4346
+ threadName: delta.name
4347
+ });
4348
+ return;
4349
+ }
4350
+ case "provider.rateLimits": {
4351
+ events.push({
4352
+ type: "provider/rateLimits/updated",
4353
+ threadId: UNSTAMPED_THREAD_ID,
4354
+ providerThreadId: "",
4355
+ scope: threadScope(),
4356
+ rateLimits: delta.rateLimits
4357
+ });
4358
+ return;
4359
+ }
4360
+ case "extension.state": {
4361
+ events.push({
4362
+ type: "thread/extensionState/updated",
4363
+ threadId: UNSTAMPED_THREAD_ID,
4364
+ providerThreadId: "",
4365
+ scope: threadScope(),
4366
+ kind: delta.extensionKind,
4367
+ payload: delta.payload
4368
+ });
4369
+ return;
4370
+ }
4371
+ case "session.reset": {
4372
+ return;
4373
+ }
4374
+ case "session.ended": {
4375
+ const turnId = state.currentTurnId ?? (state.pendingAccepted.length > 0 ? ensureTurnOpen(state, events) : void 0);
4376
+ if (turnId === void 0) {
4377
+ return;
4378
+ }
4379
+ for (const open of state.openItemsByKey.values()) {
4380
+ if (open.threadAttached) {
4381
+ continue;
4382
+ }
4383
+ const streamed = open.text.length > 0 || open.summaryText.length > 0;
4384
+ const item = (streamed ? settleTextItem(open, void 0, void 0) : void 0) ?? completeStartedItem(
4385
+ open.item,
4386
+ { status: "interrupted" },
4387
+ void 0
4388
+ );
4389
+ events.push({
4390
+ type: "item/completed",
4391
+ threadId: UNSTAMPED_THREAD_ID,
4392
+ providerThreadId: "",
4393
+ scope: turnScope(turnId),
4394
+ item
4395
+ });
4396
+ }
4397
+ events.push({
4398
+ type: "turn/completed",
4399
+ threadId: UNSTAMPED_THREAD_ID,
4400
+ providerThreadId: "",
4401
+ scope: turnScope(turnId),
4402
+ status: "interrupted"
4403
+ });
4404
+ finishTurn(state);
4405
+ return;
4406
+ }
4407
+ }
4408
+ }
4409
+ return {
4410
+ assemble(args) {
4411
+ const events = [];
4412
+ const sink = {
4413
+ push: (...newEvents) => {
4414
+ for (const event of newEvents) {
4415
+ const textDelta = textDeltaFlushMs > 0 ? asTextDeltaEvent(event) : void 0;
4416
+ const state = states.get(args.threadId);
4417
+ if (textDelta !== void 0 && state !== void 0) {
4418
+ bufferTextDelta(state, textDelta, events);
4419
+ continue;
4420
+ }
4421
+ if (state !== void 0) {
4422
+ flushPendingText(state, events);
4423
+ }
4424
+ events.push(event);
4425
+ }
4426
+ }
4427
+ };
4428
+ const existing = args.deltas[0]?.kind === "session.reset" ? void 0 : states.get(args.threadId);
4429
+ if (existing !== void 0) {
4430
+ flushElapsedPendingText(existing, events);
4431
+ const progressKeysInBatch = /* @__PURE__ */ new Set();
4432
+ for (const delta of args.deltas) {
4433
+ if (delta.kind === "item.progress") {
4434
+ progressKeysInBatch.add(itemKeyString(delta.key));
4435
+ }
4436
+ }
4437
+ flushElapsedPendingProgress(existing, sink, progressKeysInBatch);
4438
+ }
4439
+ for (const delta of args.deltas) {
4440
+ if (delta.kind === "session.reset") {
4441
+ const state = states.get(args.threadId);
4442
+ if (state !== void 0) {
4443
+ flushPendingText(state, events);
4444
+ }
4445
+ states.delete(args.threadId);
4446
+ continue;
4447
+ }
4448
+ if (delta.kind === "item.textClose" || delta.kind === "item.close" || delta.kind === "session.ended") {
4449
+ const state = states.get(args.threadId);
4450
+ if (state !== void 0) {
4451
+ flushPendingText(state, events);
4452
+ }
4453
+ }
4454
+ handleDelta(stateFor(args.threadId), delta, sink);
4455
+ }
4456
+ return events;
4457
+ },
4458
+ getBbItemId(threadId, providerItemId) {
4459
+ return states.get(threadId)?.bbItemIdByProviderItemId.get(providerItemId);
4460
+ },
4461
+ getProviderItemId(threadId, bbItemId) {
4462
+ return states.get(threadId)?.providerItemIdByBbItemId.get(bbItemId);
4463
+ },
4464
+ getBbTurnId(threadId, providerTurnId) {
4465
+ return states.get(threadId)?.bbTurnIdByProviderTurnId.get(providerTurnId);
4466
+ },
4467
+ getProviderTurnId(threadId, bbTurnId) {
4468
+ return states.get(threadId)?.providerTurnIdByBbTurnId.get(bbTurnId);
4469
+ },
4470
+ getOpenTurnId(threadId) {
4471
+ return states.get(threadId)?.currentTurnId;
4472
+ }
4473
+ };
4474
+ }
4475
+
4476
+ // ../provider-bridge-protocol/src/testing/bridge-delta-assembly.ts
4477
+ function createBridgeDeltaEventCollector(providerId = "pi") {
4478
+ const assembler = createDeltaAssembler({ providerId, textDeltaFlushMs: 0 });
4479
+ return {
4480
+ assembler,
4481
+ assembleMessage(message) {
4482
+ if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
4483
+ return [];
4484
+ }
4485
+ const parsed = threadDeltaNotificationParamsSchema.safeParse(
4486
+ message.params
4487
+ );
4488
+ if (!parsed.success) {
4489
+ throw new Error(
4490
+ `Invalid thread/delta notification: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
4491
+ "; "
4492
+ )} (params: ${JSON.stringify(message.params)?.slice(0, 400)})`
4493
+ );
4494
+ }
4495
+ return assembler.assemble({
4496
+ threadId: parsed.data.threadId,
4497
+ deltas: parsed.data.deltas
4498
+ });
4499
+ }
4500
+ };
4501
+ }
4502
+ function assembleCapturedThreadEvents(messages, providerId = "pi") {
4503
+ const collector = createBridgeDeltaEventCollector(providerId);
4504
+ return messages.flatMap((message) => collector.assembleMessage(message));
4505
+ }
4506
+ function toConformanceMessages() {
4507
+ throw new Error(
4508
+ "experimental_toConformanceMessages was removed: experimental_runBridgeConformance assembles thread/delta itself. Hand it a transport whose takeMessages returns the raw captured messages (CapturedBridgeJsonRpcOutput.takeMessages) and pass the bridge's providerId."
4509
+ );
4510
+ }
4511
+
4512
+ // ../provider-bridge-protocol/src/conformance/client.ts
4513
+ import { z as z17 } from "zod";
4514
+ function isWireMessage(value) {
4515
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4516
+ }
4517
+ var threadDeltaAddressSchema = z17.object({ threadId: z17.string() }).passthrough();
4518
+ var ConformanceClient = class {
4519
+ constructor(transport, timeoutMs, collector) {
4520
+ this.transport = transport;
4521
+ this.timeoutMs = timeoutMs;
4522
+ this.collector = collector;
4523
+ }
4524
+ transport;
4525
+ timeoutMs;
4526
+ collector;
4527
+ nextId = 1;
4528
+ log = [];
4529
+ /** Every assembled event, in wire order. */
4530
+ events = [];
4531
+ drainIntoLog() {
4532
+ for (const raw of this.transport.takeMessages()) {
4533
+ if (!isWireMessage(raw)) {
4534
+ continue;
4535
+ }
4536
+ const logIndex = this.log.length;
4537
+ this.log.push(raw);
4538
+ if (raw.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
4539
+ continue;
4540
+ }
4541
+ const events = this.collector.assembleMessage(raw);
4542
+ const address = threadDeltaAddressSchema.parse(raw.params);
4543
+ for (const event of events) {
4544
+ this.events.push({ threadId: address.threadId, event, logIndex });
4545
+ }
4546
+ }
4547
+ }
4548
+ sendRaw(line) {
4549
+ this.transport.send(line);
4550
+ }
4551
+ notify(method, params) {
4552
+ this.transport.send(
4553
+ JSON.stringify({
4554
+ jsonrpc: "2.0",
4555
+ method,
4556
+ ...params !== void 0 ? { params } : {}
4557
+ })
4558
+ );
4559
+ }
4560
+ request(method, params) {
4561
+ const id = this.nextId;
4562
+ this.nextId += 1;
4563
+ this.transport.send(
4564
+ JSON.stringify({
4565
+ jsonrpc: "2.0",
4566
+ id,
4567
+ method,
4568
+ ...params !== void 0 ? { params } : {}
4569
+ })
4570
+ );
4571
+ return id;
4572
+ }
4573
+ /** Poll until `resolve` yields a value or the deadline passes (→ null). */
4574
+ async waitFor(resolve3) {
4575
+ const deadline = Date.now() + this.timeoutMs;
4576
+ for (; ; ) {
4577
+ this.drainIntoLog();
4578
+ const value = resolve3();
4579
+ if (value !== void 0) {
4580
+ return value;
4581
+ }
4582
+ if (Date.now() > deadline) {
4583
+ return null;
4584
+ }
4585
+ await new Promise((r) => setTimeout(r, 15));
4586
+ }
4587
+ }
4588
+ async waitForResponse(id) {
4589
+ return this.waitFor(
4590
+ () => this.log.find(
4591
+ (message) => message.id === id && message.method === void 0
4592
+ )
4593
+ );
4594
+ }
4595
+ /** A settle window: drain for the given quiet period without expectations. */
4596
+ async settle(quietMs) {
4597
+ const deadline = Date.now() + quietMs;
4598
+ while (Date.now() < deadline) {
4599
+ this.drainIntoLog();
4600
+ await new Promise((r) => setTimeout(r, 15));
4601
+ }
4602
+ this.drainIntoLog();
4603
+ }
4604
+ responsesFor(id) {
4605
+ return this.log.filter(
4606
+ (message) => message.id === id && message.method === void 0
4607
+ );
4608
+ }
4609
+ notifications(method) {
4610
+ return this.log.filter(
4611
+ (message) => message.id === void 0 && typeof message.method === "string" && (method === void 0 || message.method === method)
4612
+ );
4613
+ }
4614
+ };
4615
+ var clientRequestCounter = 0;
4616
+ function nextConformanceClientRequestId() {
4617
+ const alphabet = "23456789abcdefghijkmnpqrstuvwxyz";
4618
+ clientRequestCounter += 1;
4619
+ let remaining = clientRequestCounter;
4620
+ let suffix = "";
4621
+ while (suffix.length < 10) {
4622
+ suffix = alphabet[remaining % alphabet.length] + suffix;
4623
+ remaining = Math.floor(remaining / alphabet.length);
4624
+ }
4625
+ return `creq_${suffix}`;
4626
+ }
4627
+
4628
+ // ../provider-bridge-protocol/src/conformance/scenarios.ts
4629
+ import { z as z25 } from "zod";
4630
+
4631
+ // ../provider-bridge-protocol/src/handshake.ts
4632
+ import { z as z18 } from "zod";
4633
+ var bridgeGrammarVersionsSchema = z18.tuple([z18.number().int().positive(), z18.number().int().positive()]).refine(([min, max]) => min <= max, {
4634
+ message: "grammarVersions must be an ascending [min, max] range"
4635
+ });
4636
+ function negotiateGrammarVersion(runtime, bridge) {
4637
+ const min = Math.max(runtime[0], bridge[0]);
4638
+ const max = Math.min(runtime[1], bridge[1]);
4639
+ return min <= max ? max : null;
4640
+ }
4641
+ var bridgeSteerModeSchema = z18.enum(["inject", "queue"]);
4642
+ var bridgeCapabilitiesSchema = z18.object({
4643
+ /**
4644
+ * A released session can be re-attached later from its persisted
4645
+ * providerThreadId. The per-session `sessionRestorable` flag on
4646
+ * thread-identity results refines this (an agent update can drop restore
4647
+ * support mid-flight); this handshake value is the default for sessions
4648
+ * that do not say.
4649
+ */
4650
+ sessionRestore: z18.boolean().default(false),
4651
+ /**
4652
+ * The bridge mirrors bb archive state into the provider's own session
4653
+ * list. When false the runtime never sends thread/archive or
4654
+ * thread/unarchive.
4655
+ */
4656
+ threadArchive: z18.boolean().default(false),
4657
+ /**
4658
+ * The bridge pushes bb thread titles to the provider. When false the
4659
+ * runtime never sends thread/name/set.
4660
+ */
4661
+ threadRename: z18.boolean().default(false),
4662
+ /** The bridge supports thread/goal/clear. */
4663
+ threadGoalClear: z18.boolean().default(false),
4664
+ /**
4665
+ * Session cloning support ({@link providerForkSchema} — the same
4666
+ * vocabulary the provider declaration uses). The declaration is a ceiling
4667
+ * for UI affordances; this is the operative truth, and it may only narrow
4668
+ * the declaration, never widen it.
4669
+ */
4670
+ fork: providerForkSchema.default("none"),
4671
+ /**
4672
+ * Where the thread's approval policy is enforced. "runtime" bridges
4673
+ * forward every approval request and the runtime applies the thread
4674
+ * policy (including auto-deny). "provider" bridges enforce policy before
4675
+ * forwarding, so every forwarded request is already known to need user
4676
+ * input and the runtime must not reclassify it against mutable thread
4677
+ * settings.
4678
+ */
4679
+ approvalEnforcedBy: z18.enum(["runtime", "provider"]).default("runtime"),
4680
+ /**
4681
+ * The `thread/delta` grammar range this bridge speaks. A bridge that says
4682
+ * nothing is read as speaking exactly the protocol version it negotiated,
4683
+ * so the default is `[2, 2]` — never a wider range it never claimed.
4684
+ * Every bridge in this repo emits v3 and reports `[3, 3]`, and the
4685
+ * runtime's assembler speaks `[3, 3]` only (`ASSEMBLER_GRAMMAR_VERSIONS`),
4686
+ * so a bridge that takes the default is refused at startup: the two
4687
+ * ranges must intersect ({@link negotiateGrammarVersion}) and both sides
4688
+ * emit the highest common version.
4689
+ */
4690
+ grammarVersions: bridgeGrammarVersionsSchema.default([
4691
+ PROVIDER_BRIDGE_PROTOCOL_VERSION,
4692
+ PROVIDER_BRIDGE_PROTOCOL_VERSION
4693
+ ]),
4694
+ /**
4695
+ * Mid-turn steer delivery ({@link bridgeSteerModeSchema}). Defaults to
4696
+ * `queue`, the conservative reading: absence is the definite "no" the
4697
+ * rest of this handshake uses, and `inject` is the stronger promise (the
4698
+ * steer reaches the model before the turn ends) a bridge must make
4699
+ * explicitly. Nothing in the runtime, server, or clients reads it today:
4700
+ * `turn/steer` is sent either way, and a steer whose turn is gone is
4701
+ * dropped on the bridge's `staleTurn` recovery hint or `NO_ACTIVE_TURN`
4702
+ * error (`steerTurn` in @bb/agent-runtime), whatever the mode. claude,
4703
+ * codex, and pi declare `inject`; ACP and the echo example declare
4704
+ * `queue`.
4705
+ */
4706
+ steerMode: bridgeSteerModeSchema.default("queue"),
4707
+ /**
4708
+ * Which optional requests the bridge handles. `skills.configure`: the
4709
+ * bridge accepts `skills/configure` (bb's injected skill roots). When
4710
+ * false the runtime never sends it, so a bridge that answers unknown
4711
+ * methods with METHOD_NOT_FOUND — as the protocol instructs — still
4712
+ * starts threads; it simply runs without injected skills. A bridge that
4713
+ * handles the request declares it; the runtime never probes.
4714
+ */
4715
+ skills: z18.object({ configure: z18.boolean().default(false) }).default({ configure: false })
4716
+ }).passthrough();
4717
+ var initializeParamsSchema = z18.object({
4718
+ protocolVersion: z18.number().int().positive(),
4719
+ client: z18.object({ name: z18.string().min(1), version: z18.string().min(1) }),
4720
+ /**
4721
+ * The `thread/delta` grammar range the runtime's assembler accepts (see
4722
+ * {@link negotiateGrammarVersion}). A runtime that predates the field
4723
+ * reads as speaking exactly its protocol version.
4724
+ */
4725
+ grammarVersions: bridgeGrammarVersionsSchema.default([
4726
+ PROVIDER_BRIDGE_PROTOCOL_VERSION,
4727
+ PROVIDER_BRIDGE_PROTOCOL_VERSION
4728
+ ])
4729
+ }).passthrough();
4730
+ var initializeResultSchema = z18.object({
4731
+ protocolVersion: z18.number().int().positive(),
4732
+ // An absent capabilities block reads as "no capabilities" via the inner
4733
+ // per-field defaults, so older bridges parse to explicit values.
4734
+ capabilities: z18.preprocess(
4735
+ (value) => value ?? {},
4736
+ bridgeCapabilitiesSchema
4737
+ )
4738
+ }).passthrough();
4739
+
4740
+ // ../provider-bridge-protocol/src/execution-options.ts
4741
+ import { z as z19 } from "zod";
4742
+ var bridgeExecutionOptionsSchema = z19.object({
4743
+ model: z19.string().min(1).optional(),
4744
+ serviceTier: serviceTierSchema.optional(),
4745
+ reasoningLevel: reasoningLevelSchema.optional(),
4746
+ /**
4747
+ * BB prompt mode (`"plan"`), present only when the prompt entered one
4748
+ * through the provider's declared composer action. Each bridge maps it
4749
+ * onto the agent's native equivalent.
4750
+ */
4751
+ promptMode: promptModeSchema.optional(),
4752
+ /** Frozen for the life of a provider session; applied at construction. */
4753
+ instructions: z19.string().optional(),
4754
+ envVars: z19.record(z19.string(), z19.string()).optional(),
4755
+ /** Provider-scoped session options. Opaque outside the owning bridge. */
4756
+ providerOptions: z19.record(z19.string(), z19.unknown()).optional()
4757
+ }).and(runtimePermissionPolicySchema);
4758
+
4759
+ // ../provider-bridge-protocol/src/provider-maintenance.ts
4760
+ import { z as z20 } from "zod";
4761
+ var providerMaintenanceParamsSchema = z20.object({
4762
+ providerId: z20.string().min(1),
4763
+ cwd: z20.string().min(1).optional(),
4764
+ providerOptions: z20.record(z20.string(), z20.unknown()).optional()
4765
+ }).passthrough();
4766
+ var providerInstallationRequirementSchema = z20.enum([
4767
+ "thread_rewind"
4768
+ ]);
4769
+ var providerInstallationStatusParamsSchema = providerMaintenanceParamsSchema.extend({
4770
+ requirement: providerInstallationRequirementSchema.optional()
4771
+ });
4772
+ var providerHealthSchema = z20.object({
4773
+ status: z20.enum([
4774
+ "ready",
4775
+ "not_installed",
4776
+ "unauthenticated",
4777
+ "expired",
4778
+ "unsupported_version",
4779
+ "unknown"
4780
+ ]),
4781
+ statusMessage: z20.string().min(1).nullable(),
4782
+ accountEmail: z20.string().nullable(),
4783
+ planLabel: z20.string().min(1).nullable(),
4784
+ installedVersion: z20.string().min(1).nullable(),
4785
+ minimumSupportedVersion: z20.string().min(1).nullable(),
4786
+ canInstall: z20.boolean(),
4787
+ canUpdate: z20.boolean(),
4788
+ loginCommand: z20.string().min(1).nullable()
4789
+ }).passthrough();
4790
+ var providerUsageWindowSchema = z20.object({
4791
+ label: z20.string().min(1),
4792
+ usedPercent: z20.number().min(0).max(100),
4793
+ resetsAt: z20.string().min(1).nullable(),
4794
+ cost: z20.object({
4795
+ usedUsdCents: z20.number().int().nonnegative(),
4796
+ limitUsdCents: z20.number().int().positive()
4797
+ }).optional()
4798
+ }).passthrough();
4799
+ var providerUsageSchema = z20.discriminatedUnion("status", [
4800
+ z20.object({
4801
+ status: z20.literal("ok"),
4802
+ accountEmail: z20.string().email().nullable(),
4803
+ planLabel: z20.string().min(1).nullable(),
4804
+ windows: z20.array(providerUsageWindowSchema)
4805
+ }).passthrough(),
4806
+ z20.object({ status: z20.literal("not_installed") }).passthrough(),
4807
+ z20.object({ status: z20.literal("unauthenticated") }).passthrough(),
4808
+ z20.object({ status: z20.literal("expired") }).passthrough(),
4809
+ z20.object({
4810
+ status: z20.literal("error"),
4811
+ message: z20.string().min(1),
4812
+ planLabel: z20.string().min(1).nullable().default(null),
4813
+ accountEmail: z20.string().nullable().default(null)
4814
+ }).passthrough()
4815
+ ]);
4816
+ var providerHealthResultSchema = z20.discriminatedUnion(
4817
+ "supported",
4818
+ [
4819
+ z20.object({ supported: z20.literal(false) }).passthrough(),
4820
+ z20.object({
4821
+ supported: z20.literal(true),
4822
+ health: providerHealthSchema
4823
+ }).passthrough()
4824
+ ]
4825
+ );
4826
+ var providerUsageResultSchema = z20.discriminatedUnion(
4827
+ "supported",
4828
+ [
4829
+ z20.object({ supported: z20.literal(false) }).passthrough(),
4830
+ z20.object({
4831
+ supported: z20.literal(true),
4832
+ usage: providerUsageSchema
4833
+ }).passthrough()
4834
+ ]
4835
+ );
4836
+ var providerInstallationActionKindSchema = z20.enum([
4837
+ "install",
4838
+ "update"
4839
+ ]);
4840
+ var providerInstallationActionSchema = z20.object({
4841
+ kind: providerInstallationActionKindSchema,
4842
+ label: z20.enum(["Install", "Update"]),
4843
+ command: z20.string().min(1)
4844
+ }).passthrough();
4845
+ var providerInstallationSourceSchema = z20.enum([
4846
+ "notInstalled",
4847
+ "npmGlobal",
4848
+ "external"
4849
+ ]);
4850
+ var providerInstallationStatusSchema = z20.object({
4851
+ executableName: z20.string().min(1),
4852
+ executablePath: z20.string().min(1).nullable(),
4853
+ installed: z20.boolean(),
4854
+ installSource: providerInstallationSourceSchema,
4855
+ currentVersion: z20.string().min(1).nullable(),
4856
+ latestVersion: z20.string().min(1).nullable(),
4857
+ minimumSupportedVersion: z20.string().min(1).nullable(),
4858
+ npmPackageName: z20.string().min(1).nullable(),
4859
+ npmGlobalPackageVersion: z20.string().min(1).nullable(),
4860
+ installAction: providerInstallationActionSchema.nullable(),
4861
+ needsUpdate: z20.boolean(),
4862
+ versionUnsupported: z20.boolean()
4863
+ }).passthrough();
4864
+ var providerInstallationRunParamsSchema = providerMaintenanceParamsSchema.extend({
4865
+ action: providerInstallationActionKindSchema
4866
+ });
4867
+ var providerInstallationCommandSchema = z20.object({
4868
+ command: z20.string().min(1),
4869
+ args: z20.array(z20.string()).max(64),
4870
+ displayCommand: z20.string().min(1)
4871
+ }).passthrough();
4872
+ var providerInstallationVerificationSchema = z20.discriminatedUnion("kind", [
4873
+ z20.object({ kind: z20.literal("installed") }).passthrough(),
4874
+ z20.object({
4875
+ kind: z20.literal("version_changed"),
4876
+ previousVersion: z20.string().min(1)
4877
+ }).passthrough(),
4878
+ z20.object({
4879
+ kind: z20.literal("version_at_least"),
4880
+ version: z20.string().min(1)
4881
+ }).passthrough()
4882
+ ]);
4883
+ var providerInstallationRunResultSchema = z20.discriminatedUnion("available", [
4884
+ z20.object({
4885
+ available: z20.literal(false),
4886
+ message: z20.string().min(1)
4887
+ }).passthrough(),
4888
+ z20.object({
4889
+ available: z20.literal(true),
4890
+ command: providerInstallationCommandSchema,
4891
+ verification: providerInstallationVerificationSchema
4892
+ }).passthrough()
4893
+ ]);
4894
+
4895
+ // ../provider-bridge-protocol/src/requests.ts
4896
+ import { z as z21 } from "zod";
4897
+ var BRIDGE_REQUEST_METHODS = {
4898
+ initialize: "initialize",
4899
+ modelList: "model/list",
4900
+ providerHealth: "provider/health",
4901
+ providerUsage: "provider/usage",
4902
+ providerInstallationStatus: "provider/installation/status",
4903
+ providerInstallationRun: "provider/installation/run",
4904
+ threadStart: "thread/start",
4905
+ threadResume: "thread/resume",
4906
+ threadFork: "thread/fork",
4907
+ threadStop: "thread/stop",
4908
+ threadDiscard: "thread/discard",
4909
+ threadNameSet: "thread/name/set",
4910
+ threadArchive: "thread/archive",
4911
+ threadUnarchive: "thread/unarchive",
4912
+ threadGoalClear: "thread/goal/clear",
4913
+ turnStart: "turn/start",
4914
+ turnSteer: "turn/steer",
4915
+ skillsConfigure: "skills/configure"
4916
+ };
4917
+ var sessionConstructionFields = {
4918
+ threadId: z21.string().min(1),
4919
+ cwd: z21.string().min(1),
4920
+ options: bridgeExecutionOptionsSchema,
4921
+ dynamicTools: z21.array(dynamicToolSchema).optional(),
4922
+ disallowedTools: z21.array(z21.string().min(1)).optional(),
4923
+ instructionMode: instructionModeSchema
4924
+ };
4925
+ var modelListParamsSchema = z21.object({ cwd: z21.string().min(1).optional() }).passthrough();
4926
+ var threadStartParamsSchema = z21.object({
4927
+ ...sessionConstructionFields,
4928
+ input: z21.array(promptInputSchema).optional()
4929
+ }).passthrough();
4930
+ var threadResumeParamsSchema = z21.object({
4931
+ ...sessionConstructionFields,
4932
+ providerThreadId: z21.string().min(1)
4933
+ }).passthrough();
4934
+ var threadForkParamsSchema = z21.object({
4935
+ ...sessionConstructionFields,
4936
+ sourceProviderThreadId: z21.string().min(1),
4937
+ /**
4938
+ * Absent means fork at the tip. Bridges whose handshake advertises
4939
+ * `fork: "tip"` reject a request carrying a checkpoint instead of
4940
+ * silently cloning more history than the bb timeline shows.
4941
+ */
4942
+ sourceProviderCheckpointId: z21.string().min(1).optional()
4943
+ }).passthrough();
4944
+ var threadStopParamsSchema = z21.object({
4945
+ threadId: z21.string().min(1),
4946
+ providerThreadId: z21.string().min(1),
4947
+ /**
4948
+ * "interrupt" stops an active turn and settles it as interrupted.
4949
+ * "release" detaches an idle session so its resources can be reclaimed;
4950
+ * it must never fabricate an interruption. One verb serving both intents
4951
+ * is the #1584 incident — the field is required.
4952
+ */
4953
+ intent: z21.enum(["interrupt", "release"]),
4954
+ /** Non-null when the stop interrupts an active provider turn. */
4955
+ activeTurnId: z21.string().min(1).nullable()
4956
+ }).passthrough();
4957
+ var threadRefParams = z21.object({
4958
+ threadId: z21.string().min(1),
4959
+ providerThreadId: z21.string().min(1)
4960
+ }).passthrough();
4961
+ var threadNameSetParamsSchema = z21.object({
4962
+ threadId: z21.string().min(1),
4963
+ providerThreadId: z21.string().min(1),
4964
+ title: z21.string().min(1)
4965
+ }).passthrough();
4966
+ var turnInputFields = {
4967
+ threadId: z21.string().min(1),
4968
+ providerThreadId: z21.string().min(1),
4969
+ input: z21.array(promptInputSchema),
4970
+ clientRequestId: clientTurnRequestIdSchema,
4971
+ options: bridgeExecutionOptionsSchema
4972
+ };
4973
+ var turnStartParamsSchema = z21.object(turnInputFields).passthrough();
4974
+ var turnSteerParamsSchema = z21.object({
4975
+ ...turnInputFields,
4976
+ expectedTurnId: z21.string().min(1)
4977
+ }).passthrough();
4978
+ var skillsConfigureRootSchema = z21.object({
4979
+ id: z21.string().min(1),
4980
+ path: z21.string().min(1),
4981
+ skills: z21.array(
4982
+ z21.object({
4983
+ name: z21.string().min(1),
4984
+ description: z21.string()
4985
+ }).passthrough()
4986
+ )
4987
+ }).passthrough();
4988
+ var skillsConfigureParamsSchema = z21.object({
4989
+ roots: z21.array(skillsConfigureRootSchema)
4990
+ }).passthrough();
4991
+ var threadIdentityResultSchema = z21.object({
4992
+ providerThreadId: z21.string().min(1),
4993
+ /** Refines the handshake's `sessionRestore` for this session. */
4994
+ sessionRestorable: z21.boolean().optional()
4995
+ }).passthrough();
4996
+ var modelListResultSchema = z21.object({
4997
+ models: z21.array(availableModelSchema),
4998
+ selectedOnlyModels: z21.array(availableModelSchema).default([])
4999
+ }).passthrough();
5000
+
5001
+ // ../provider-bridge-protocol/src/notifications.ts
5002
+ import { z as z23 } from "zod";
5003
+
5004
+ // ../provider-bridge-protocol/src/errors.ts
5005
+ import { z as z22 } from "zod";
5006
+ var BRIDGE_JSON_RPC_ERRORS = {
5007
+ /** Standard JSON-RPC: params failed schema validation. */
5008
+ INVALID_PARAMS: -32602,
5009
+ /** Standard JSON-RPC: method not implemented by this bridge. */
5010
+ METHOD_NOT_FOUND: -32601,
5011
+ /** Generic bridge failure. */
5012
+ BRIDGE_ERROR: -32e3,
5013
+ /** A turn/steer arrived but the session has no active turn. */
5014
+ NO_ACTIVE_TURN: -32001,
5015
+ /** thread/resume for a session the provider can no longer restore. */
5016
+ SESSION_NOT_RESTORABLE: -32002,
5017
+ /** thread/fork with a checkpoint on a bridge that only forks at the tip. */
5018
+ FORK_CHECKPOINT_UNSUPPORTED: -32003
5019
+ };
5020
+ var providerRecoveryHintSchema = z22.object({
5021
+ kind: providerRecoveryKindSchema,
5022
+ message: z22.string().min(1),
5023
+ retryable: z22.boolean()
5024
+ });
5025
+ var bridgeErrorDataSchema = z22.object({ recovery: providerRecoveryHintSchema.optional() }).passthrough();
5026
+
5027
+ // ../provider-bridge-protocol/src/notifications.ts
5028
+ var threadIdentityNotificationSchema = z23.object({
5029
+ threadId: z23.string().min(1),
5030
+ providerThreadId: z23.string().min(1),
5031
+ /** Refines the handshake's `sessionRestore` for this session. */
5032
+ sessionRestorable: z23.boolean().optional()
5033
+ }).passthrough();
5034
+ var sessionReplacedNotificationSchema = z23.object({
5035
+ threadId: z23.string().min(1),
5036
+ /** Identity of the replacement session (may equal the old identity). */
5037
+ providerThreadId: z23.string().min(1).nullable(),
5038
+ /** Human-readable cause, shown in the timeline. */
5039
+ reason: z23.string().min(1),
5040
+ /** True when provider-side context did not survive the replacement. */
5041
+ contextLost: z23.boolean().default(false)
5042
+ }).passthrough();
5043
+ var providerRawNotificationSchema = z23.object({
5044
+ threadId: z23.string().min(1).optional(),
5045
+ coverage: z23.enum(["noise", "unknown"]),
5046
+ payload: z23.unknown()
5047
+ }).passthrough();
5048
+ var providerRecoveryNotificationSchema = z23.object({
5049
+ threadId: z23.string().min(1).optional(),
5050
+ ...providerRecoveryHintSchema.shape
5051
+ }).passthrough();
5052
+ var errorNotificationSchema = z23.object({
5053
+ threadId: z23.string().min(1).optional(),
5054
+ message: z23.string().min(1)
5055
+ }).passthrough();
5056
+
5057
+ // ../provider-bridge-protocol/src/bridge-requests.ts
5058
+ import { z as z24 } from "zod";
5059
+ var toolCallRequestParamsSchema = z24.object({
5060
+ providerThreadId: z24.string().min(1),
5061
+ threadId: z24.string().min(1).optional(),
5062
+ turnId: z24.union([z24.string().min(1), z24.null()]),
5063
+ callId: z24.string().min(1),
5064
+ tool: z24.string().min(1),
5065
+ arguments: z24.unknown()
5066
+ }).passthrough();
5067
+ var toolCallResultSchema = z24.object({
5068
+ success: z24.boolean(),
5069
+ contentItems: z24.array(
5070
+ z24.discriminatedUnion("type", [
5071
+ z24.object({ type: z24.literal("inputText"), text: z24.string() }),
5072
+ z24.object({
5073
+ type: z24.literal("inputImage"),
5074
+ imageUrl: z24.string().min(1)
5075
+ })
5076
+ ])
5077
+ )
5078
+ }).passthrough();
5079
+ var interactionRequestParamsSchema = z24.object({
5080
+ providerThreadId: z24.string().min(1),
5081
+ threadId: z24.string().min(1).optional(),
5082
+ turnId: z24.union([z24.string().min(1), z24.null()]),
5083
+ payload: pendingInteractionPayloadSchema,
5084
+ /**
5085
+ * The request's turn id and approval-subject item ids are in the
5086
+ * provider's native id space (a `thread/delta` bridge holds no bb ids):
5087
+ * the runtime adapter translates them through the delta assembler's maps
5088
+ * before the interaction reaches the app. Omission means the ids are
5089
+ * already app-visible (bridges whose approval subjects never referenced
5090
+ * timeline ids — ACP's approval ids never matched timeline ids).
5091
+ */
5092
+ providerNativeIds: z24.boolean().optional()
5093
+ }).passthrough();
5094
+
5095
+ // ../provider-bridge-protocol/src/thread-event-grammar.ts
5096
+ var ITEM_STREAMING_EVENT_TYPES = /* @__PURE__ */ new Set([
5097
+ "item/agentMessage/delta",
5098
+ "item/plan/delta",
5099
+ "item/commandExecution/outputDelta",
5100
+ "item/fileChange/outputDelta",
5101
+ "item/reasoning/summaryTextDelta",
5102
+ "item/reasoning/textDelta",
5103
+ "item/mcpToolCall/progress",
5104
+ "item/toolCall/progress"
5105
+ ]);
5106
+ var MAX_ITEM_IDS_PER_THREAD = 512;
5107
+ var THREAD_EVENT_GRAMMAR_RULES = {
5108
+ itemOpensBeforeDelta: "item/opens-before-delta",
5109
+ itemSettlesOnce: "item/settles-once",
5110
+ turnStartsOnce: "turn/starts-once",
5111
+ turnSettlesOnce: "turn/settles-once",
5112
+ turnKnown: "turn/known"
5113
+ };
5114
+ var OK = { kind: "ok" };
5115
+ var ThreadEventGrammar = class {
5116
+ #byThreadId = /* @__PURE__ */ new Map();
5117
+ clear() {
5118
+ this.#byThreadId.clear();
5119
+ }
5120
+ clearThread(threadId) {
5121
+ this.#byThreadId.delete(threadId);
5122
+ }
5123
+ observe(event) {
5124
+ const state = this.#stateFor(event.threadId);
5125
+ switch (event.type) {
5126
+ case "turn/started": {
5127
+ const turnId = turnIdOf(event);
5128
+ if (turnId === void 0) {
5129
+ return OK;
4868
5130
  }
4869
- events.push({
4870
- type: "turn/diff/updated",
4871
- threadId: UNSTAMPED_THREAD_ID,
4872
- providerThreadId: "",
4873
- scope: turnScope(turnId),
4874
- diff: delta.diff
4875
- });
4876
- return;
4877
- }
4878
- case "thread.started": {
4879
- events.push({
4880
- type: "thread/started",
4881
- threadId: UNSTAMPED_THREAD_ID,
4882
- scope: threadScope()
4883
- });
4884
- return;
4885
- }
4886
- case "thread.identity": {
4887
- events.push({
4888
- type: "thread/identity",
4889
- threadId: UNSTAMPED_THREAD_ID,
4890
- providerThreadId: delta.providerThreadId,
4891
- scope: threadScope()
4892
- });
4893
- return;
4894
- }
4895
- case "thread.name": {
4896
- events.push({
4897
- type: "thread/name/updated",
4898
- threadId: UNSTAMPED_THREAD_ID,
4899
- providerThreadId: "",
4900
- scope: threadScope(),
4901
- threadName: delta.name
4902
- });
4903
- return;
4904
- }
4905
- case "provider.rateLimits": {
4906
- events.push({
4907
- type: "provider/rateLimits/updated",
4908
- threadId: UNSTAMPED_THREAD_ID,
4909
- providerThreadId: "",
4910
- scope: threadScope(),
4911
- rateLimits: delta.rateLimits
4912
- });
4913
- return;
4914
- }
4915
- case "extension.state": {
4916
- events.push({
4917
- type: "thread/extensionState/updated",
4918
- threadId: UNSTAMPED_THREAD_ID,
4919
- providerThreadId: "",
4920
- scope: threadScope(),
4921
- kind: delta.extensionKind,
4922
- payload: delta.payload
4923
- });
4924
- return;
4925
- }
4926
- case "session.reset": {
4927
- return;
5131
+ if (state.completedTurnIds.has(turnId)) {
5132
+ return violation(
5133
+ THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
5134
+ `turn/started for turn "${turnId}", which already completed`
5135
+ );
5136
+ }
5137
+ if (state.startedTurnIds.has(turnId)) {
5138
+ return violation(
5139
+ THREAD_EVENT_GRAMMAR_RULES.turnStartsOnce,
5140
+ `turn/started for turn "${turnId}", which is already open`
5141
+ );
5142
+ }
5143
+ state.startedTurnIds.add(turnId);
5144
+ return OK;
4928
5145
  }
4929
- case "session.ended": {
4930
- const turnId = state.currentTurnId ?? (state.pendingAccepted.length > 0 ? ensureTurnOpen(state, events) : void 0);
5146
+ case "turn/completed": {
5147
+ const turnId = turnIdOf(event);
4931
5148
  if (turnId === void 0) {
4932
- return;
5149
+ return OK;
4933
5150
  }
4934
- for (const open of state.openItemsByKey.values()) {
4935
- if (open.threadAttached) {
4936
- continue;
4937
- }
4938
- const streamed = open.text.length > 0 || open.summaryText.length > 0;
4939
- const item = (streamed ? settleTextItem(open, void 0, void 0) : void 0) ?? completeStartedItem(
4940
- open.item,
4941
- { status: "interrupted" },
4942
- void 0
5151
+ if (state.completedTurnIds.has(turnId)) {
5152
+ return violation(
5153
+ THREAD_EVENT_GRAMMAR_RULES.turnSettlesOnce,
5154
+ `turn/completed for turn "${turnId}", which already completed`
4943
5155
  );
4944
- events.push({
4945
- type: "item/completed",
4946
- threadId: UNSTAMPED_THREAD_ID,
4947
- providerThreadId: "",
4948
- scope: turnScope(turnId),
4949
- item
4950
- });
4951
5156
  }
4952
- events.push({
4953
- type: "turn/completed",
4954
- threadId: UNSTAMPED_THREAD_ID,
4955
- providerThreadId: "",
4956
- scope: turnScope(turnId),
4957
- status: "interrupted"
4958
- });
4959
- finishTurn(state);
4960
- return;
4961
- }
4962
- }
4963
- }
4964
- return {
4965
- assemble(args) {
4966
- const events = [];
4967
- const sink = {
4968
- push: (...newEvents) => {
4969
- for (const event of newEvents) {
4970
- const textDelta = textDeltaFlushMs > 0 ? asTextDeltaEvent(event) : void 0;
4971
- const state = states.get(args.threadId);
4972
- if (textDelta !== void 0 && state !== void 0) {
4973
- bufferTextDelta(state, textDelta, events);
4974
- continue;
4975
- }
4976
- if (state !== void 0) {
4977
- flushPendingText(state, events);
4978
- }
4979
- events.push(event);
4980
- }
5157
+ if (!state.startedTurnIds.has(turnId)) {
5158
+ return violation(
5159
+ THREAD_EVENT_GRAMMAR_RULES.turnKnown,
5160
+ `turn/completed for turn "${turnId}", which never started`
5161
+ );
4981
5162
  }
4982
- };
4983
- const existing = args.deltas[0]?.kind === "session.reset" ? void 0 : states.get(args.threadId);
4984
- if (existing !== void 0) {
4985
- flushElapsedPendingText(existing, events);
4986
- const progressKeysInBatch = /* @__PURE__ */ new Set();
4987
- for (const delta of args.deltas) {
4988
- if (delta.kind === "item.progress") {
4989
- progressKeysInBatch.add(itemKeyString(delta.key));
4990
- }
5163
+ state.startedTurnIds.delete(turnId);
5164
+ state.completedTurnIds.add(turnId);
5165
+ return OK;
5166
+ }
5167
+ case "item/started": {
5168
+ state.openItemIds.add(event.item.id);
5169
+ state.settledItemIds.delete(event.item.id);
5170
+ trim(state.openItemIds);
5171
+ return OK;
5172
+ }
5173
+ case "item/completed":
5174
+ case "item/backgroundTask/completed":
5175
+ case "item/delegation/completed": {
5176
+ const itemId = event.item.id;
5177
+ if (state.settledItemIds.has(itemId)) {
5178
+ return violation(
5179
+ THREAD_EVENT_GRAMMAR_RULES.itemSettlesOnce,
5180
+ `${event.type} for item "${itemId}", which already settled`
5181
+ );
4991
5182
  }
4992
- flushElapsedPendingProgress(existing, sink, progressKeysInBatch);
5183
+ state.openItemIds.delete(itemId);
5184
+ state.settledItemIds.add(itemId);
5185
+ trim(state.settledItemIds);
5186
+ return OK;
4993
5187
  }
4994
- for (const delta of args.deltas) {
4995
- if (delta.kind === "session.reset") {
4996
- const state = states.get(args.threadId);
4997
- if (state !== void 0) {
4998
- flushPendingText(state, events);
4999
- }
5000
- states.delete(args.threadId);
5001
- continue;
5188
+ case "item/backgroundTask/progress":
5189
+ case "item/delegation/progress": {
5190
+ return this.#checkOpenItem(state, event.type, event.item.id);
5191
+ }
5192
+ default: {
5193
+ if (!ITEM_STREAMING_EVENT_TYPES.has(event.type)) {
5194
+ return OK;
5002
5195
  }
5003
- if (delta.kind === "item.textClose" || delta.kind === "item.close" || delta.kind === "session.ended") {
5004
- const state = states.get(args.threadId);
5005
- if (state !== void 0) {
5006
- flushPendingText(state, events);
5007
- }
5196
+ if (!("itemId" in event) || typeof event.itemId !== "string") {
5197
+ return OK;
5008
5198
  }
5009
- handleDelta(stateFor(args.threadId), delta, sink);
5199
+ return this.#checkOpenItem(state, event.type, event.itemId);
5010
5200
  }
5011
- return events;
5012
- },
5013
- getBbItemId(threadId, providerItemId) {
5014
- return states.get(threadId)?.bbItemIdByProviderItemId.get(providerItemId);
5015
- },
5016
- getProviderItemId(threadId, bbItemId) {
5017
- return states.get(threadId)?.providerItemIdByBbItemId.get(bbItemId);
5018
- },
5019
- getBbTurnId(threadId, providerTurnId) {
5020
- return states.get(threadId)?.bbTurnIdByProviderTurnId.get(providerTurnId);
5021
- },
5022
- getProviderTurnId(threadId, bbTurnId) {
5023
- return states.get(threadId)?.providerTurnIdByBbTurnId.get(bbTurnId);
5024
- },
5025
- getOpenTurnId(threadId) {
5026
- return states.get(threadId)?.currentTurnId;
5027
5201
  }
5028
- };
5202
+ }
5203
+ #checkOpenItem(state, eventType, itemId) {
5204
+ if (state.openItemIds.has(itemId)) {
5205
+ return OK;
5206
+ }
5207
+ return violation(
5208
+ THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
5209
+ `${eventType} for item "${itemId}" arrived before item/started`
5210
+ );
5211
+ }
5212
+ #stateFor(threadId) {
5213
+ const existing = this.#byThreadId.get(threadId);
5214
+ if (existing !== void 0) {
5215
+ return existing;
5216
+ }
5217
+ const created = {
5218
+ openItemIds: /* @__PURE__ */ new Set(),
5219
+ settledItemIds: /* @__PURE__ */ new Set(),
5220
+ startedTurnIds: /* @__PURE__ */ new Set(),
5221
+ completedTurnIds: /* @__PURE__ */ new Set()
5222
+ };
5223
+ this.#byThreadId.set(threadId, created);
5224
+ return created;
5225
+ }
5226
+ };
5227
+ function violation(rule, reason) {
5228
+ return { kind: "violation", rule, reason };
5029
5229
  }
5030
-
5031
- // ../provider-bridge-protocol/src/conformance/types.ts
5032
- var CONFORMANCE_ASSEMBLED_EVENT_METHOD = "conformance/assembledEvent";
5033
- function reportPassed(results) {
5034
- return results.every((result) => result.status === "pass");
5230
+ function turnIdOf(event) {
5231
+ return "scope" in event ? getThreadEventScopeTurnId(event.scope) : void 0;
5232
+ }
5233
+ function trim(itemIds) {
5234
+ while (itemIds.size > MAX_ITEM_IDS_PER_THREAD) {
5235
+ const oldest = itemIds.values().next();
5236
+ if (oldest.done === true) {
5237
+ return;
5238
+ }
5239
+ itemIds.delete(oldest.value);
5240
+ }
5035
5241
  }
5036
5242
 
5037
5243
  // ../provider-bridge-protocol/src/conformance/scenarios.ts
5038
- var assembledEventNotificationSchema = z23.object({ threadId: z23.string().min(1), event: threadEventSchema }).passthrough();
5244
+ var IDENTITY_RESULT_SHAPE = "{ providerThreadId, sessionRestorable? }";
5245
+ function identityProblem(parsed, result2) {
5246
+ return `the result must be ${IDENTITY_RESULT_SHAPE} \u2014 the runtime adopts no session without providerThreadId on the result (a thread/identity notification does not substitute for it); issues: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")} (got ${JSON.stringify(result2)})`;
5247
+ }
5039
5248
  function pass(id, title) {
5040
5249
  return { id, title, status: "pass", detail: "" };
5041
5250
  }
@@ -5055,17 +5264,12 @@ function defaultOptions(fixture) {
5055
5264
  }
5056
5265
  function threadEvents(context, threadId) {
5057
5266
  context.client.drainIntoLog();
5058
- const events = [];
5059
- for (const message of context.client.notifications(
5060
- CONFORMANCE_ASSEMBLED_EVENT_METHOD
5061
- )) {
5062
- const parsed = assembledEventNotificationSchema.safeParse(message.params);
5063
- if (parsed.success && parsed.data.threadId === threadId) {
5064
- events.push(parsed.data.event);
5065
- }
5066
- }
5067
- return events;
5267
+ return context.client.events.filter((entry) => entry.threadId === threadId).map((entry) => entry.event);
5068
5268
  }
5269
+ var persistedEventSchema = z25.preprocess(
5270
+ (event) => JSON.parse(JSON.stringify(event)),
5271
+ threadEventSchema
5272
+ );
5069
5273
  function errorCode(message) {
5070
5274
  const code = message?.error?.code;
5071
5275
  return typeof code === "number" ? code : void 0;
@@ -5074,12 +5278,12 @@ var ITEM_OPENS_BEFORE_DELTA_TITLE = "every item's first event is item/started";
5074
5278
  function checkItemOpensBeforeDelta(events) {
5075
5279
  const grammar = new ThreadEventGrammar();
5076
5280
  for (const event of events) {
5077
- const result = grammar.observe(event);
5078
- if (result.kind === "violation" && result.rule === THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta) {
5281
+ const result2 = grammar.observe(event);
5282
+ if (result2.kind === "violation" && result2.rule === THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta) {
5079
5283
  return fail(
5080
5284
  THREAD_EVENT_GRAMMAR_RULES.itemOpensBeforeDelta,
5081
5285
  ITEM_OPENS_BEFORE_DELTA_TITLE,
5082
- result.reason
5286
+ result2.reason
5083
5287
  );
5084
5288
  }
5085
5289
  }
@@ -5092,6 +5296,44 @@ function checkItemOpensBeforeDelta(events) {
5092
5296
  }
5093
5297
  return pass("item/opens-before-delta", ITEM_OPENS_BEFORE_DELTA_TITLE);
5094
5298
  }
5299
+ var PRESENTATION_ICONS_DECLARED_ID = "presentation/icon-namespaced-declared";
5300
+ var PRESENTATION_ICONS_DECLARED_TITLE = "every namespaced presentation glyph names one of the plugin's declared icons";
5301
+ function checkPresentationIconsDeclared(events, icons) {
5302
+ const declared = new Set(icons.names);
5303
+ let inspected = 0;
5304
+ for (const event of events) {
5305
+ if (!isThreadEventWithItem(event)) {
5306
+ continue;
5307
+ }
5308
+ if (event.item.type === "toolCall" && event.item.server === "bb") {
5309
+ continue;
5310
+ }
5311
+ const glyph = "presentation" in event.item ? event.item.presentation?.icon.glyph : void 0;
5312
+ if (glyph === void 0) {
5313
+ continue;
5314
+ }
5315
+ inspected += 1;
5316
+ const parsed = parseNamespacedGlyph(glyph);
5317
+ if (parsed === null) {
5318
+ continue;
5319
+ }
5320
+ if (parsed.pluginId !== icons.pluginId || !declared.has(parsed.name)) {
5321
+ return fail(
5322
+ PRESENTATION_ICONS_DECLARED_ID,
5323
+ PRESENTATION_ICONS_DECLARED_TITLE,
5324
+ `${event.type} ${event.item.type} "${event.item.id}" names presentation.icon "${glyph}", which is not an icon declared by plugin "${icons.pluginId}" (declared: ${icons.names.length === 0 ? "none" : icons.names.join(", ")}); the server would persist it as provider/unhandled`
5325
+ );
5326
+ }
5327
+ }
5328
+ if (inspected === 0) {
5329
+ return skipped(
5330
+ PRESENTATION_ICONS_DECLARED_ID,
5331
+ PRESENTATION_ICONS_DECLARED_TITLE,
5332
+ "no item carried a presentation to inspect"
5333
+ );
5334
+ }
5335
+ return pass(PRESENTATION_ICONS_DECLARED_ID, PRESENTATION_ICONS_DECLARED_TITLE);
5336
+ }
5095
5337
  async function runRpcHygieneScenarios(client) {
5096
5338
  const results = [];
5097
5339
  let unknownMethodsAnswered = false;
@@ -5200,29 +5442,23 @@ async function runHandshakeScenario(client) {
5200
5442
  });
5201
5443
  const response = await client.waitForResponse(id);
5202
5444
  const title = "initialize answers a versioned handshake with capabilities";
5445
+ const failed = (detail) => ({
5446
+ results: [fail("handshake/initialize", title, detail)],
5447
+ capabilities: null
5448
+ });
5203
5449
  if (response === null) {
5204
- return [fail("handshake/initialize", title, "no response")];
5450
+ return failed("no response");
5205
5451
  }
5206
5452
  const parsed = initializeResultSchema.safeParse(response.result);
5207
5453
  if (!parsed.success) {
5208
- return [
5209
- fail(
5210
- "handshake/initialize",
5211
- title,
5212
- `result did not parse: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
5213
- "; "
5214
- )} (got ${JSON.stringify(response.result ?? response.error)})`
5215
- )
5216
- ];
5454
+ return failed(
5455
+ `result did not parse: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")} (got ${JSON.stringify(response.result ?? response.error)})`
5456
+ );
5217
5457
  }
5218
5458
  if (parsed.data.protocolVersion !== PROVIDER_BRIDGE_PROTOCOL_VERSION) {
5219
- return [
5220
- fail(
5221
- "handshake/initialize",
5222
- title,
5223
- `bridge answered protocol version ${parsed.data.protocolVersion}; this kit (and the runtime) require ${PROVIDER_BRIDGE_PROTOCOL_VERSION}`
5224
- )
5225
- ];
5459
+ return failed(
5460
+ `bridge answered protocol version ${parsed.data.protocolVersion}; this kit (and the runtime) require ${PROVIDER_BRIDGE_PROTOCOL_VERSION}`
5461
+ );
5226
5462
  }
5227
5463
  const [bridgeMin, bridgeMax] = parsed.data.capabilities.grammarVersions;
5228
5464
  if (negotiateGrammarVersion(
@@ -5230,15 +5466,53 @@ async function runHandshakeScenario(client) {
5230
5466
  parsed.data.capabilities.grammarVersions
5231
5467
  ) === null) {
5232
5468
  const [runtimeMin, runtimeMax] = ASSEMBLER_GRAMMAR_VERSIONS;
5469
+ return failed(
5470
+ `bridge reported grammarVersions [${bridgeMin}, ${bridgeMax}]; the runtime's assembler speaks [${runtimeMin}, ${runtimeMax}], so the handshake would be refused`
5471
+ );
5472
+ }
5473
+ return {
5474
+ results: [
5475
+ pass("handshake/initialize", title),
5476
+ ...await runSkillsConfigureDeclaredScenario(
5477
+ client,
5478
+ parsed.data.capabilities.skills.configure
5479
+ )
5480
+ ],
5481
+ capabilities: parsed.data.capabilities
5482
+ };
5483
+ }
5484
+ var SKILLS_CONFIGURE_DECLARED_ID = "skills/configure-declared";
5485
+ var SKILLS_CONFIGURE_DECLARED_TITLE = "skills/configure is handled iff the handshake declares skills.configure";
5486
+ async function runSkillsConfigureDeclaredScenario(client, declared) {
5487
+ const id = client.request(BRIDGE_REQUEST_METHODS.skillsConfigure, {
5488
+ roots: []
5489
+ });
5490
+ const response = await client.waitForResponse(id);
5491
+ if (response === null) {
5233
5492
  return [
5234
5493
  fail(
5235
- "handshake/initialize",
5236
- title,
5237
- `bridge reported grammarVersions [${bridgeMin}, ${bridgeMax}]; the runtime's assembler speaks [${runtimeMin}, ${runtimeMax}], so the handshake would be refused`
5494
+ SKILLS_CONFIGURE_DECLARED_ID,
5495
+ SKILLS_CONFIGURE_DECLARED_TITLE,
5496
+ "skills/configure was not answered"
5497
+ )
5498
+ ];
5499
+ }
5500
+ if (declared) {
5501
+ return response.error === void 0 ? [pass(SKILLS_CONFIGURE_DECLARED_ID, SKILLS_CONFIGURE_DECLARED_TITLE)] : [
5502
+ fail(
5503
+ SKILLS_CONFIGURE_DECLARED_ID,
5504
+ SKILLS_CONFIGURE_DECLARED_TITLE,
5505
+ `the handshake declares skills.configure but the request failed: ${JSON.stringify(response.error)}`
5238
5506
  )
5239
5507
  ];
5240
5508
  }
5241
- return [pass("handshake/initialize", title)];
5509
+ return errorCode(response) === BRIDGE_JSON_RPC_ERRORS.METHOD_NOT_FOUND ? [pass(SKILLS_CONFIGURE_DECLARED_ID, SKILLS_CONFIGURE_DECLARED_TITLE)] : [
5510
+ fail(
5511
+ SKILLS_CONFIGURE_DECLARED_ID,
5512
+ SKILLS_CONFIGURE_DECLARED_TITLE,
5513
+ `the handshake does not declare skills.configure, yet the bridge answered the request with ${JSON.stringify(response.error ?? response.result)}; declare it (the runtime never sends an undeclared request)`
5514
+ )
5515
+ ];
5242
5516
  }
5243
5517
  async function runSessionLifecycleScenarios(context) {
5244
5518
  const { client, fixture } = context;
@@ -5270,7 +5544,7 @@ async function runSessionLifecycleScenarios(context) {
5270
5544
  fail(
5271
5545
  "session/start-identity",
5272
5546
  title,
5273
- `result did not parse: ${JSON.stringify(response.result)}`
5547
+ identityProblem(parsed, response.result)
5274
5548
  )
5275
5549
  );
5276
5550
  } else {
@@ -5331,20 +5605,27 @@ async function runSessionLifecycleScenarios(context) {
5331
5605
  }
5332
5606
  {
5333
5607
  client.drainIntoLog();
5334
- const raw = client.notifications(CONFORMANCE_ASSEMBLED_EVENT_METHOD);
5335
- const invalid = raw.filter(
5336
- (message) => !assembledEventNotificationSchema.safeParse(message.params).success
5608
+ const invalid = client.events.filter(
5609
+ (entry) => !persistedEventSchema.safeParse(entry.event).success
5337
5610
  );
5338
5611
  const title2 = "every assembled event is a valid ThreadEvent";
5339
5612
  results.push(
5340
5613
  invalid.length === 0 ? pass("events/schema-valid", title2) : fail(
5341
5614
  "events/schema-valid",
5342
5615
  title2,
5343
- `${invalid.length} assembled event notification(s) failed validation; first: ${JSON.stringify(invalid[0]?.params).slice(0, 400)}`
5616
+ `${invalid.length} assembled event(s) failed validation; first: ${JSON.stringify(invalid[0]?.event).slice(0, 400)}`
5344
5617
  )
5345
5618
  );
5346
5619
  }
5347
5620
  results.push(checkItemOpensBeforeDelta(threadEvents(context, threadId)));
5621
+ if (fixture.icons !== void 0) {
5622
+ results.push(
5623
+ checkPresentationIconsDeclared(
5624
+ threadEvents(context, threadId),
5625
+ fixture.icons
5626
+ )
5627
+ );
5628
+ }
5348
5629
  }
5349
5630
  if (context.providerThreadId === void 0) {
5350
5631
  results.push(
@@ -5397,13 +5678,11 @@ async function runSessionLifecycleScenarios(context) {
5397
5678
  results.push(pass("stop/release-not-interrupted", title));
5398
5679
  }
5399
5680
  }
5681
+ const uniquenessTitle = "turn and item ids never repeat across a resume";
5400
5682
  if (context.providerThreadId === void 0) {
5401
5683
  results.push(
5402
- skipped(
5403
- "session/resume-id-uniqueness",
5404
- "turn and item ids never repeat across a resume",
5405
- startSkipDetail
5406
- )
5684
+ skipped(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE, startSkipDetail),
5685
+ skipped("session/resume-id-uniqueness", uniquenessTitle, startSkipDetail)
5407
5686
  );
5408
5687
  } else {
5409
5688
  const resumeId = client.request(BRIDGE_REQUEST_METHODS.threadResume, {
@@ -5414,16 +5693,30 @@ async function runSessionLifecycleScenarios(context) {
5414
5693
  instructionMode: "append"
5415
5694
  });
5416
5695
  const resumeResponse = await client.waitForResponse(resumeId);
5417
- const title = "turn and item ids never repeat across a resume";
5418
- if (resumeResponse === null || resumeResponse.error !== void 0) {
5696
+ const title = uniquenessTitle;
5697
+ const resumed = resumeResponse === null || resumeResponse.error !== void 0 ? null : threadIdentityResultSchema.safeParse(resumeResponse.result);
5698
+ if (resumed === null) {
5699
+ const detail = resumeResponse === null ? "thread/resume was not answered" : `thread/resume failed: ${JSON.stringify(resumeResponse.error)}`;
5419
5700
  results.push(
5701
+ skipped(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE, detail),
5702
+ skipped("session/resume-id-uniqueness", title, detail)
5703
+ );
5704
+ } else if (!resumed.success) {
5705
+ results.push(
5706
+ fail(
5707
+ RESUME_IDENTITY_ID,
5708
+ RESUME_IDENTITY_TITLE,
5709
+ identityProblem(resumed, resumeResponse?.result)
5710
+ ),
5420
5711
  skipped(
5421
5712
  "session/resume-id-uniqueness",
5422
5713
  title,
5423
- resumeResponse === null ? "thread/resume was not answered" : `thread/resume failed: ${JSON.stringify(resumeResponse.error)}`
5714
+ "prerequisite session/resume-identity failed"
5424
5715
  )
5425
5716
  );
5426
5717
  } else {
5718
+ context.providerThreadId = resumed.data.providerThreadId;
5719
+ results.push(pass(RESUME_IDENTITY_ID, RESUME_IDENTITY_TITLE));
5427
5720
  const turnId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
5428
5721
  threadId,
5429
5722
  providerThreadId: context.providerThreadId,
@@ -5486,9 +5779,392 @@ async function runSessionLifecycleScenarios(context) {
5486
5779
  }
5487
5780
  }
5488
5781
  }
5782
+ results.push(...await runForkIdentityScenario(context, threadId));
5489
5783
  results.push(...await runZeroWorkTurnScenario(context, threadId));
5784
+ results.push(...await runArchivedResumeRecoveryScenario(context, threadId));
5785
+ results.push(...await runThreadsIndependentScenario(context, threadId));
5786
+ results.push(...await runInterruptStopScenario(context, threadId));
5787
+ if (context.providerThreadId !== void 0) {
5788
+ const releaseId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
5789
+ threadId,
5790
+ providerThreadId: context.providerThreadId,
5791
+ intent: "release",
5792
+ activeTurnId: null
5793
+ });
5794
+ await client.waitForResponse(releaseId);
5795
+ }
5490
5796
  return results;
5491
5797
  }
5798
+ var RESUME_IDENTITY_ID = "session/resume-identity";
5799
+ var RESUME_IDENTITY_TITLE = "thread/resume returns a provider thread identity";
5800
+ var FORK_IDENTITY_ID = "session/fork-identity";
5801
+ var FORK_IDENTITY_TITLE = "thread/fork returns a provider thread identity for the forked session";
5802
+ async function runForkIdentityScenario(context, threadId) {
5803
+ const { client, fixture } = context;
5804
+ if (context.fork === "none") {
5805
+ return [];
5806
+ }
5807
+ if (context.providerThreadId === void 0) {
5808
+ return [
5809
+ skipped(
5810
+ FORK_IDENTITY_ID,
5811
+ FORK_IDENTITY_TITLE,
5812
+ "prerequisite session/start-identity failed"
5813
+ )
5814
+ ];
5815
+ }
5816
+ const forkThreadId = `${threadId}_fork`;
5817
+ const forkId = client.request(BRIDGE_REQUEST_METHODS.threadFork, {
5818
+ threadId: forkThreadId,
5819
+ cwd: fixture.cwd,
5820
+ sourceProviderThreadId: context.providerThreadId,
5821
+ options: defaultOptions(fixture),
5822
+ instructionMode: "append"
5823
+ });
5824
+ const response = await client.waitForResponse(forkId);
5825
+ if (response === null) {
5826
+ return [
5827
+ fail(FORK_IDENTITY_ID, FORK_IDENTITY_TITLE, "thread/fork was not answered")
5828
+ ];
5829
+ }
5830
+ if (response.error !== void 0) {
5831
+ return [
5832
+ fail(
5833
+ FORK_IDENTITY_ID,
5834
+ FORK_IDENTITY_TITLE,
5835
+ `the handshake declares fork "${context.fork}", yet forking the lifecycle session at its tip failed: ${JSON.stringify(response.error)}`
5836
+ )
5837
+ ];
5838
+ }
5839
+ const parsed = threadIdentityResultSchema.safeParse(response.result);
5840
+ if (!parsed.success) {
5841
+ return [
5842
+ fail(
5843
+ FORK_IDENTITY_ID,
5844
+ FORK_IDENTITY_TITLE,
5845
+ identityProblem(parsed, response.result)
5846
+ )
5847
+ ];
5848
+ }
5849
+ const releaseId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
5850
+ threadId: forkThreadId,
5851
+ providerThreadId: parsed.data.providerThreadId,
5852
+ intent: "release",
5853
+ activeTurnId: null
5854
+ });
5855
+ await client.waitForResponse(releaseId);
5856
+ return [pass(FORK_IDENTITY_ID, FORK_IDENTITY_TITLE)];
5857
+ }
5858
+ var THREADS_INDEPENDENT_ID = "session/threads-independent";
5859
+ var THREADS_INDEPENDENT_TITLE = "requests on different threads are independent";
5860
+ async function runThreadsIndependentScenario(context, threadId) {
5861
+ const { client, fixture } = context;
5862
+ const interruptiblePromptInput = fixture.interruptiblePromptInput;
5863
+ if (interruptiblePromptInput === void 0) {
5864
+ return [];
5865
+ }
5866
+ if (context.providerThreadId === void 0) {
5867
+ return [
5868
+ skipped(
5869
+ THREADS_INDEPENDENT_ID,
5870
+ THREADS_INDEPENDENT_TITLE,
5871
+ "prerequisite session/start-identity failed"
5872
+ )
5873
+ ];
5874
+ }
5875
+ const startedBefore = threadEvents(context, threadId).filter(
5876
+ (event) => event.type === "turn/started"
5877
+ ).length;
5878
+ const holdId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
5879
+ threadId,
5880
+ providerThreadId: context.providerThreadId,
5881
+ input: interruptiblePromptInput,
5882
+ clientRequestId: nextConformanceClientRequestId(),
5883
+ options: defaultOptions(fixture)
5884
+ });
5885
+ const held = await client.waitFor(() => {
5886
+ const starts = threadEvents(context, threadId).filter(
5887
+ (event) => event.type === "turn/started"
5888
+ );
5889
+ return starts.length > startedBefore ? starts[startedBefore] : void 0;
5890
+ });
5891
+ await client.waitForResponse(holdId);
5892
+ if (held === null) {
5893
+ return [
5894
+ fail(
5895
+ THREADS_INDEPENDENT_ID,
5896
+ THREADS_INDEPENDENT_TITLE,
5897
+ "the interruptible prompt never opened a turn"
5898
+ )
5899
+ ];
5900
+ }
5901
+ const otherThreadId = `${threadId}_other`;
5902
+ const startId = client.request(BRIDGE_REQUEST_METHODS.threadStart, {
5903
+ threadId: otherThreadId,
5904
+ cwd: fixture.cwd,
5905
+ options: defaultOptions(fixture),
5906
+ instructionMode: "append"
5907
+ });
5908
+ const startResponse = await client.waitForResponse(startId);
5909
+ const startResult = threadIdentityResultSchema.safeParse(
5910
+ startResponse?.result
5911
+ );
5912
+ if (startResponse === null || !startResult.success) {
5913
+ return [
5914
+ fail(
5915
+ THREADS_INDEPENDENT_ID,
5916
+ THREADS_INDEPENDENT_TITLE,
5917
+ `the second thread did not start while the first held a turn: ${JSON.stringify(startResponse?.error ?? startResponse?.result)}`
5918
+ )
5919
+ ];
5920
+ }
5921
+ const turnId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
5922
+ threadId: otherThreadId,
5923
+ providerThreadId: startResult.data.providerThreadId,
5924
+ input: fixture.promptInput,
5925
+ clientRequestId: nextConformanceClientRequestId(),
5926
+ options: defaultOptions(fixture)
5927
+ });
5928
+ const completed = await client.waitFor(
5929
+ () => threadEvents(context, otherThreadId).find(
5930
+ (event) => event.type === "turn/completed"
5931
+ )
5932
+ );
5933
+ await client.waitForResponse(turnId);
5934
+ const stopId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
5935
+ threadId: otherThreadId,
5936
+ providerThreadId: startResult.data.providerThreadId,
5937
+ intent: "release",
5938
+ activeTurnId: null
5939
+ });
5940
+ await client.waitForResponse(stopId);
5941
+ if (completed === null) {
5942
+ return [
5943
+ fail(
5944
+ THREADS_INDEPENDENT_ID,
5945
+ THREADS_INDEPENDENT_TITLE,
5946
+ "the second thread's turn never completed while the first thread held a turn"
5947
+ )
5948
+ ];
5949
+ }
5950
+ const firstStillOpen = !threadEvents(context, threadId).some(
5951
+ (event) => event.type === "turn/completed" && getThreadEventScopeTurnId(event.scope) === getThreadEventScopeTurnId(held.scope)
5952
+ );
5953
+ if (!firstStillOpen) {
5954
+ return [
5955
+ fail(
5956
+ THREADS_INDEPENDENT_ID,
5957
+ THREADS_INDEPENDENT_TITLE,
5958
+ "the held turn on the first thread settled while the second thread ran"
5959
+ )
5960
+ ];
5961
+ }
5962
+ return [pass(THREADS_INDEPENDENT_ID, THREADS_INDEPENDENT_TITLE)];
5963
+ }
5964
+ var INTERRUPT_SETTLES_ID = "stop/interrupt-settles-before-result";
5965
+ var INTERRUPT_SETTLES_TITLE = "thread/stop {interrupt} settles the turn before it is answered";
5966
+ async function runInterruptStopScenario(context, threadId) {
5967
+ const { client, fixture } = context;
5968
+ const interruptiblePromptInput = fixture.interruptiblePromptInput;
5969
+ if (interruptiblePromptInput === void 0) {
5970
+ return [];
5971
+ }
5972
+ if (context.providerThreadId === void 0) {
5973
+ return [
5974
+ skipped(
5975
+ INTERRUPT_SETTLES_ID,
5976
+ INTERRUPT_SETTLES_TITLE,
5977
+ "prerequisite session/start-identity failed"
5978
+ )
5979
+ ];
5980
+ }
5981
+ let started = openTurnStart(context, threadId);
5982
+ if (started === void 0) {
5983
+ const startedBefore = threadEvents(context, threadId).filter(
5984
+ (event) => event.type === "turn/started"
5985
+ ).length;
5986
+ const turnRequestId = client.request(BRIDGE_REQUEST_METHODS.turnStart, {
5987
+ threadId,
5988
+ providerThreadId: context.providerThreadId,
5989
+ input: interruptiblePromptInput,
5990
+ clientRequestId: nextConformanceClientRequestId(),
5991
+ options: defaultOptions(fixture)
5992
+ });
5993
+ started = await client.waitFor(() => {
5994
+ const starts = threadEvents(context, threadId).filter(
5995
+ (event) => event.type === "turn/started"
5996
+ );
5997
+ return starts.length > startedBefore ? starts[startedBefore] : void 0;
5998
+ }) ?? void 0;
5999
+ await client.waitForResponse(turnRequestId);
6000
+ }
6001
+ if (started === void 0 || started.scope.kind !== "turn") {
6002
+ return [
6003
+ fail(
6004
+ INTERRUPT_SETTLES_ID,
6005
+ INTERRUPT_SETTLES_TITLE,
6006
+ "the interruptible prompt never opened a turn"
6007
+ )
6008
+ ];
6009
+ }
6010
+ const bbTurnId = started.scope.turnId;
6011
+ const providerTurnId = context.resolveProviderTurnId(threadId, bbTurnId) ?? bbTurnId;
6012
+ const stopId = client.request(BRIDGE_REQUEST_METHODS.threadStop, {
6013
+ threadId,
6014
+ providerThreadId: context.providerThreadId,
6015
+ intent: "interrupt",
6016
+ activeTurnId: providerTurnId
6017
+ });
6018
+ const stopResponse = await client.waitForResponse(stopId);
6019
+ if (stopResponse === null) {
6020
+ return [
6021
+ fail(INTERRUPT_SETTLES_ID, INTERRUPT_SETTLES_TITLE, "thread/stop was not answered")
6022
+ ];
6023
+ }
6024
+ if (stopResponse.error !== void 0) {
6025
+ return [
6026
+ fail(
6027
+ INTERRUPT_SETTLES_ID,
6028
+ INTERRUPT_SETTLES_TITLE,
6029
+ `thread/stop failed: ${JSON.stringify(stopResponse.error)}`
6030
+ )
6031
+ ];
6032
+ }
6033
+ const responseIndex = client.log.indexOf(stopResponse);
6034
+ const completedIndex = client.events.find(
6035
+ (entry) => entry.threadId === threadId && entry.event.type === "turn/completed" && getThreadEventScopeTurnId(entry.event.scope) === bbTurnId
6036
+ )?.logIndex ?? -1;
6037
+ if (completedIndex === -1) {
6038
+ return [
6039
+ fail(
6040
+ INTERRUPT_SETTLES_ID,
6041
+ INTERRUPT_SETTLES_TITLE,
6042
+ "the interrupted turn never reached turn/completed"
6043
+ )
6044
+ ];
6045
+ }
6046
+ if (completedIndex > responseIndex) {
6047
+ return [
6048
+ fail(
6049
+ INTERRUPT_SETTLES_ID,
6050
+ INTERRUPT_SETTLES_TITLE,
6051
+ "turn/completed arrived after the thread/stop response; the runtime had already detached the thread"
6052
+ )
6053
+ ];
6054
+ }
6055
+ return [pass(INTERRUPT_SETTLES_ID, INTERRUPT_SETTLES_TITLE)];
6056
+ }
6057
+ var SESSION_ARCHIVED_RECOVERY_ID = "recovery/session-archived";
6058
+ var SESSION_ARCHIVED_RECOVERY_TITLE = "resuming an archived session is rejected with a sessionArchived hint";
6059
+ async function runArchivedResumeRecoveryScenario(context, threadId) {
6060
+ const { client, fixture } = context;
6061
+ const providerThreadId = context.providerThreadId;
6062
+ if (providerThreadId === void 0) {
6063
+ return [
6064
+ skipped(
6065
+ SESSION_ARCHIVED_RECOVERY_ID,
6066
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6067
+ "prerequisite session/start-identity failed"
6068
+ )
6069
+ ];
6070
+ }
6071
+ const archiveId = client.request(BRIDGE_REQUEST_METHODS.threadArchive, {
6072
+ threadId,
6073
+ providerThreadId
6074
+ });
6075
+ const archiveResponse = await client.waitForResponse(archiveId);
6076
+ if (archiveResponse === null) {
6077
+ return [
6078
+ fail(
6079
+ SESSION_ARCHIVED_RECOVERY_ID,
6080
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6081
+ "thread/archive was not answered"
6082
+ )
6083
+ ];
6084
+ }
6085
+ if (archiveResponse.error !== void 0) {
6086
+ return [];
6087
+ }
6088
+ const resumeParams = {
6089
+ threadId,
6090
+ cwd: fixture.cwd,
6091
+ providerThreadId,
6092
+ options: defaultOptions(fixture),
6093
+ instructionMode: "append"
6094
+ };
6095
+ const resumeId = client.request(
6096
+ BRIDGE_REQUEST_METHODS.threadResume,
6097
+ resumeParams
6098
+ );
6099
+ const resumeResponse = await client.waitForResponse(resumeId);
6100
+ const unarchiveId = client.request(BRIDGE_REQUEST_METHODS.threadUnarchive, {
6101
+ threadId,
6102
+ providerThreadId
6103
+ });
6104
+ await client.waitForResponse(unarchiveId);
6105
+ if (resumeResponse === null) {
6106
+ return [
6107
+ fail(
6108
+ SESSION_ARCHIVED_RECOVERY_ID,
6109
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6110
+ "thread/resume of the archived session was not answered"
6111
+ )
6112
+ ];
6113
+ }
6114
+ if (resumeResponse.error === void 0) {
6115
+ const resumed = threadIdentityResultSchema.safeParse(resumeResponse.result);
6116
+ if (!resumed.success) {
6117
+ return [
6118
+ fail(
6119
+ SESSION_ARCHIVED_RECOVERY_ID,
6120
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6121
+ `the archived session was resumed with a result the runtime cannot adopt: ${identityProblem(resumed, resumeResponse.result)}`
6122
+ )
6123
+ ];
6124
+ }
6125
+ context.providerThreadId = resumed.data.providerThreadId;
6126
+ return [];
6127
+ }
6128
+ const reResumeId = client.request(
6129
+ BRIDGE_REQUEST_METHODS.threadResume,
6130
+ resumeParams
6131
+ );
6132
+ const reResumeResponse = await client.waitForResponse(reResumeId);
6133
+ const data = bridgeErrorDataSchema.safeParse(resumeResponse.error.data);
6134
+ const kind = data.success ? data.data.recovery?.kind : void 0;
6135
+ if (kind !== "sessionArchived") {
6136
+ return [
6137
+ fail(
6138
+ SESSION_ARCHIVED_RECOVERY_ID,
6139
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6140
+ `the rejection carried ${kind === void 0 ? "no recovery hint" : `kind "${kind}"`}: ${JSON.stringify(resumeResponse.error)}`
6141
+ )
6142
+ ];
6143
+ }
6144
+ const notBack = (detail) => [
6145
+ fail(
6146
+ SESSION_ARCHIVED_RECOVERY_ID,
6147
+ SESSION_ARCHIVED_RECOVERY_TITLE,
6148
+ `the session did not come back: ${detail}`
6149
+ )
6150
+ ];
6151
+ if (reResumeResponse === null) {
6152
+ return notBack("thread/resume after thread/unarchive was not answered");
6153
+ }
6154
+ if (reResumeResponse.error !== void 0) {
6155
+ return notBack(
6156
+ `thread/resume after thread/unarchive failed: ${JSON.stringify(reResumeResponse.error)}`
6157
+ );
6158
+ }
6159
+ const reResumed = threadIdentityResultSchema.safeParse(
6160
+ reResumeResponse.result
6161
+ );
6162
+ if (!reResumed.success) {
6163
+ return notBack(identityProblem(reResumed, reResumeResponse.result));
6164
+ }
6165
+ context.providerThreadId = reResumed.data.providerThreadId;
6166
+ return [pass(SESSION_ARCHIVED_RECOVERY_ID, SESSION_ARCHIVED_RECOVERY_TITLE)];
6167
+ }
5492
6168
  var SETTLES_WITHOUT_ACTIVITY_ID = "turn/settles-without-activity";
5493
6169
  var SETTLES_WITHOUT_ACTIVITY_TITLE = "a turn the provider completes without activity still settles";
5494
6170
  async function runZeroWorkTurnScenario(context, threadId) {
@@ -5543,20 +6219,127 @@ async function runZeroWorkTurnScenario(context, threadId) {
5543
6219
  }
5544
6220
  return [pass(SETTLES_WITHOUT_ACTIVITY_ID, SETTLES_WITHOUT_ACTIVITY_TITLE)];
5545
6221
  }
6222
+ function openTurnStart(context, threadId) {
6223
+ const events = threadEvents(context, threadId);
6224
+ const completedTurnIds = new Set(
6225
+ events.filter((event) => event.type === "turn/completed").map((event) => getThreadEventScopeTurnId(event.scope))
6226
+ );
6227
+ return [...events].reverse().find(
6228
+ (event) => event.type === "turn/started" && !completedTurnIds.has(getThreadEventScopeTurnId(event.scope))
6229
+ );
6230
+ }
6231
+
6232
+ // ../provider-bridge-protocol/src/conformance/recorded.ts
6233
+ var RECORDED_CONFORMANCE_CELLS = [
6234
+ "turn-tools",
6235
+ "steer",
6236
+ "stop-interrupt",
6237
+ "approval-allow",
6238
+ "approval-deny",
6239
+ "user-question",
6240
+ "resume",
6241
+ "fork"
6242
+ ];
6243
+ function result(id, title, detail) {
6244
+ return detail === null ? { id, title, status: "pass", detail: "" } : { id, title, status: "fail", detail };
6245
+ }
6246
+ function countTurns(events) {
6247
+ let started = 0;
6248
+ let completed = 0;
6249
+ for (const event of events) {
6250
+ if (event.type === "turn/started") started += 1;
6251
+ if (event.type === "turn/completed") completed += 1;
6252
+ }
6253
+ return { started, completed };
6254
+ }
6255
+ function checkRecordedCellReplay(replay) {
6256
+ const prefix = `recorded/${replay.cell}`;
6257
+ const results = [];
6258
+ const recordedTurns = countTurns(replay.recordedEvents);
6259
+ const liveTurns = countTurns(replay.events);
6260
+ results.push(
6261
+ result(
6262
+ `${prefix}/replays`,
6263
+ "the bridge answers every recorded runtime request and the replay needs no help",
6264
+ replay.stalls.length === 0 ? null : replay.stalls.join("; ")
6265
+ )
6266
+ );
6267
+ results.push(
6268
+ result(
6269
+ `${prefix}/events-schema-valid`,
6270
+ "every assembled event is a valid ThreadEvent",
6271
+ (() => {
6272
+ for (const [index, event] of replay.events.entries()) {
6273
+ const parsed = threadEventSchema.safeParse(event);
6274
+ if (!parsed.success) {
6275
+ return `event ${index} (${event.type}) failed: ${parsed.error.issues[0]?.message ?? "invalid"}`;
6276
+ }
6277
+ }
6278
+ return null;
6279
+ })()
6280
+ )
6281
+ );
6282
+ results.push(
6283
+ result(
6284
+ `${prefix}/grammar`,
6285
+ "the event stream breaks no thread-event grammar rule",
6286
+ (() => {
6287
+ const grammar = new ThreadEventGrammar();
6288
+ for (const event of replay.events) {
6289
+ const verdict = grammar.observe(event);
6290
+ if (verdict.kind === "violation") {
6291
+ return `${verdict.rule} on ${event.type}: ${verdict.reason}`;
6292
+ }
6293
+ }
6294
+ return null;
6295
+ })()
6296
+ )
6297
+ );
6298
+ results.push(
6299
+ result(
6300
+ `${prefix}/turn-lifecycle`,
6301
+ "the replay opens and settles as many turns as the recording did",
6302
+ liveTurns.started === recordedTurns.started && liveTurns.completed === recordedTurns.completed && liveTurns.started === liveTurns.completed ? null : `replay ${liveTurns.started} started/${liveTurns.completed} completed, recording ${recordedTurns.started}/${recordedTurns.completed}`
6303
+ )
6304
+ );
6305
+ results.push(
6306
+ result(
6307
+ `${prefix}/not-empty`,
6308
+ "a recorded session that produced events still does",
6309
+ replay.recordedEvents.length === 0 || replay.events.length > 0 ? null : `the recording assembled ${replay.recordedEvents.length} events, the replay none`
6310
+ )
6311
+ );
6312
+ return results;
6313
+ }
6314
+
6315
+ // ../provider-bridge-protocol/src/conformance/types.ts
6316
+ var CONFORMANCE_ASSEMBLED_EVENT_METHOD = "conformance/assembledEvent";
6317
+ function reportPassed(results) {
6318
+ return results.every((result2) => result2.status === "pass");
6319
+ }
5546
6320
 
5547
6321
  // ../provider-bridge-protocol/src/conformance/index.ts
5548
6322
  async function runBridgeConformance(options) {
6323
+ const collector = createBridgeDeltaEventCollector(options.providerId);
5549
6324
  const client = new ConformanceClient(
5550
6325
  options.transport,
5551
- options.timeoutMs ?? 5e3
6326
+ options.timeoutMs ?? 5e3,
6327
+ collector
5552
6328
  );
5553
6329
  const results = [];
5554
6330
  results.push(...await runRpcHygieneScenarios(client));
5555
- results.push(...await runHandshakeScenario(client));
6331
+ const handshake = await runHandshakeScenario(client);
6332
+ results.push(...handshake.results);
5556
6333
  results.push(
5557
6334
  ...await runSessionLifecycleScenarios({
5558
6335
  client,
5559
- fixture: options.session
6336
+ fixture: options.session,
6337
+ // The same reverse mapping the runtime applies when it names a turn
6338
+ // to the bridge: the assembler's provider↔bb turn-id map.
6339
+ resolveProviderTurnId: (threadId, bbTurnId) => collector.assembler.getProviderTurnId(threadId, bbTurnId),
6340
+ // A failed handshake reads as the definite absences the schema
6341
+ // defaults to: no fork, so the fork rule is not attempted.
6342
+ fork: handshake.capabilities?.fork ?? "none"
5560
6343
  })
5561
6344
  );
5562
6345
  await options.transport.close?.();
@@ -5564,36 +6347,36 @@ async function runBridgeConformance(options) {
5564
6347
  }
5565
6348
  function formatConformanceReport(report) {
5566
6349
  return report.results.map(
5567
- (result) => `${result.status.padEnd(7)} ${result.id}${result.detail === "" ? "" : ` \u2014 ${result.detail}`}`
6350
+ (result2) => `${result2.status.padEnd(7)} ${result2.id}${result2.detail === "" ? "" : ` \u2014 ${result2.detail}`}`
5568
6351
  ).join("\n");
5569
6352
  }
5570
6353
 
5571
6354
  // ../provider-bridge-protocol/src/testing/bridge-json-rpc-test-helpers.ts
5572
- import { z as z24 } from "zod";
5573
- var bridgeJsonRpcValueSchema = z24.lazy(
5574
- () => z24.union([
5575
- z24.string(),
5576
- z24.number(),
5577
- z24.boolean(),
5578
- z24.null(),
5579
- z24.array(bridgeJsonRpcValueSchema),
5580
- z24.record(z24.string(), bridgeJsonRpcValueSchema)
6355
+ import { z as z26 } from "zod";
6356
+ var bridgeJsonRpcValueSchema = z26.lazy(
6357
+ () => z26.union([
6358
+ z26.string(),
6359
+ z26.number(),
6360
+ z26.boolean(),
6361
+ z26.null(),
6362
+ z26.array(bridgeJsonRpcValueSchema),
6363
+ z26.record(z26.string(), bridgeJsonRpcValueSchema)
5581
6364
  ])
5582
6365
  );
5583
- var bridgeJsonRpcOutputSchema = z24.object({
5584
- jsonrpc: z24.literal("2.0"),
5585
- id: z24.union([z24.string(), z24.number()]).optional(),
5586
- method: z24.string().optional(),
6366
+ var bridgeJsonRpcOutputSchema = z26.object({
6367
+ jsonrpc: z26.literal("2.0"),
6368
+ id: z26.union([z26.string(), z26.number()]).optional(),
6369
+ method: z26.string().optional(),
5587
6370
  params: bridgeJsonRpcValueSchema.optional(),
5588
6371
  result: bridgeJsonRpcValueSchema.optional(),
5589
- error: z24.object({
5590
- code: z24.number(),
5591
- message: z24.string(),
6372
+ error: z26.object({
6373
+ code: z26.number(),
6374
+ message: z26.string(),
5592
6375
  data: bridgeJsonRpcValueSchema.optional()
5593
6376
  }).optional()
5594
6377
  });
5595
6378
  function waitForNextBridgeTick() {
5596
- return new Promise((resolve2) => setTimeout(resolve2, 0));
6379
+ return new Promise((resolve3) => setTimeout(resolve3, 0));
5597
6380
  }
5598
6381
  function captureBridgeJsonRpcOutput() {
5599
6382
  const messages = [];
@@ -5608,8 +6391,14 @@ function captureBridgeJsonRpcOutput() {
5608
6391
  return true;
5609
6392
  };
5610
6393
  process.stdout.write = capturingWrite;
6394
+ let drained = 0;
5611
6395
  return {
5612
6396
  messages,
6397
+ takeMessages() {
6398
+ const fresh = messages.slice(drained);
6399
+ drained = messages.length;
6400
+ return fresh;
6401
+ },
5613
6402
  restore() {
5614
6403
  if (process.stdout.write === capturingWrite) {
5615
6404
  process.stdout.write = originalWrite;
@@ -5647,6 +6436,7 @@ function createBridgeJsonRpcTestHarness(handleLine) {
5647
6436
  const output = captureBridgeJsonRpcOutput();
5648
6437
  return {
5649
6438
  messages: output.messages,
6439
+ takeMessages: output.takeMessages,
5650
6440
  flushWork: waitForNextBridgeTick,
5651
6441
  hasResponse(id) {
5652
6442
  return bridgeJsonRpcResponseExists({ id, output });
@@ -5663,49 +6453,6 @@ function createBridgeJsonRpcTestHarness(handleLine) {
5663
6453
  };
5664
6454
  }
5665
6455
 
5666
- // ../provider-bridge-protocol/src/testing/bridge-delta-assembly.ts
5667
- function createBridgeDeltaEventCollector(providerId = "pi") {
5668
- const assembler = createDeltaAssembler({ providerId, textDeltaFlushMs: 0 });
5669
- return {
5670
- assembler,
5671
- assembleMessage(message) {
5672
- if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
5673
- return [];
5674
- }
5675
- const parsed = threadDeltaNotificationParamsSchema.safeParse(
5676
- message.params
5677
- );
5678
- if (!parsed.success) {
5679
- throw new Error(
5680
- `Invalid thread/delta notification: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join(
5681
- "; "
5682
- )} (params: ${JSON.stringify(message.params)?.slice(0, 400)})`
5683
- );
5684
- }
5685
- return assembler.assemble({
5686
- threadId: parsed.data.threadId,
5687
- deltas: parsed.data.deltas
5688
- });
5689
- }
5690
- };
5691
- }
5692
- function assembleCapturedThreadEvents(messages, providerId = "pi") {
5693
- const collector = createBridgeDeltaEventCollector(providerId);
5694
- return messages.flatMap((message) => collector.assembleMessage(message));
5695
- }
5696
- function toConformanceMessages(message, collector) {
5697
- if (message.method !== THREAD_DELTA_NOTIFICATION_METHOD) {
5698
- return [message];
5699
- }
5700
- const threadId = typeof message.params?.threadId === "string" ? message.params.threadId : "";
5701
- return collector.assembleMessage(message).map((event) => ({
5702
- jsonrpc: "2.0",
5703
- method: CONFORMANCE_ASSEMBLED_EVENT_METHOD,
5704
- // ThreadEvents are JSON data; the capture type demands JsonValue.
5705
- params: JSON.parse(JSON.stringify({ threadId, event }))
5706
- }));
5707
- }
5708
-
5709
6456
  // ../provider-bridge-protocol/src/testing/calibration-diff.ts
5710
6457
  var DEFAULT_INTERNED_ID_FIELDS = [
5711
6458
  "turnId",
@@ -5760,6 +6507,38 @@ function normalizeCalibrationEvents(events, options = {}) {
5760
6507
  const list = Array.isArray(wireShaped) ? wireShaped : [];
5761
6508
  return list.map((event) => normalizeValue(event, interner, idFields));
5762
6509
  }
6510
+ function diffCalibrationStreams(legacy, bridge) {
6511
+ const left = legacy.map((event) => JSON.stringify(event));
6512
+ const right = bridge.map((event) => JSON.stringify(event));
6513
+ const lengths = Array.from(
6514
+ { length: left.length + 1 },
6515
+ () => new Array(right.length + 1).fill(0)
6516
+ );
6517
+ for (let i2 = left.length - 1; i2 >= 0; i2 -= 1) {
6518
+ for (let j2 = right.length - 1; j2 >= 0; j2 -= 1) {
6519
+ lengths[i2][j2] = left[i2] === right[j2] ? lengths[i2 + 1][j2 + 1] + 1 : Math.max(lengths[i2 + 1][j2], lengths[i2][j2 + 1]);
6520
+ }
6521
+ }
6522
+ const onlyInLegacy = [];
6523
+ const onlyInBridge = [];
6524
+ let i = 0;
6525
+ let j = 0;
6526
+ while (i < left.length && j < right.length) {
6527
+ if (left[i] === right[j]) {
6528
+ i += 1;
6529
+ j += 1;
6530
+ } else if (lengths[i + 1][j] >= lengths[i][j + 1]) {
6531
+ onlyInLegacy.push(legacy[i]);
6532
+ i += 1;
6533
+ } else {
6534
+ onlyInBridge.push(bridge[j]);
6535
+ j += 1;
6536
+ }
6537
+ }
6538
+ onlyInLegacy.push(...legacy.slice(i));
6539
+ onlyInBridge.push(...bridge.slice(j));
6540
+ return { onlyInLegacy, onlyInBridge };
6541
+ }
5763
6542
  function describeCalibrationEvents(events) {
5764
6543
  return events.map((event) => {
5765
6544
  if (event === null || typeof event !== "object") {
@@ -5776,35 +6555,840 @@ function describeCalibrationEvents(events) {
5776
6555
  }
5777
6556
 
5778
6557
  // ../provider-bridge-protocol/src/testing/parity.ts
6558
+ import { spawn } from "node:child_process";
6559
+ import { existsSync as existsSync2, mkdtempSync, rmSync, writeFileSync } from "node:fs";
6560
+ import { tmpdir } from "node:os";
6561
+ import { isAbsolute, join as join2, resolve as resolve2 } from "node:path";
5779
6562
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5780
6563
 
5781
6564
  // ../provider-bridge-protocol/src/bridge-kit/bounded-line-reader.ts
6565
+ import { StringDecoder } from "node:string_decoder";
5782
6566
  var MAX_JSON_RPC_LINE_BYTES = 64 * 1024 * 1024;
6567
+ function readBoundedLines(args) {
6568
+ const maxLineBytes = args.maxLineBytes ?? MAX_JSON_RPC_LINE_BYTES;
6569
+ const decoder = new StringDecoder("utf8");
6570
+ let pending = "";
6571
+ let discarding = false;
6572
+ let discardedBytes = 0;
6573
+ args.input.on("data", (chunk) => {
6574
+ const text = typeof chunk === "string" ? chunk : decoder.write(chunk);
6575
+ let start = 0;
6576
+ for (; ; ) {
6577
+ const newlineIndex = text.indexOf("\n", start);
6578
+ if (newlineIndex === -1) {
6579
+ break;
6580
+ }
6581
+ if (discarding) {
6582
+ discarding = false;
6583
+ args.onOverflow(discardedBytes);
6584
+ discardedBytes = 0;
6585
+ } else {
6586
+ emit(pending + text.slice(start, newlineIndex));
6587
+ }
6588
+ pending = "";
6589
+ start = newlineIndex + 1;
6590
+ }
6591
+ const tail = text.slice(start);
6592
+ if (discarding) {
6593
+ discardedBytes += Buffer.byteLength(tail);
6594
+ return;
6595
+ }
6596
+ pending += tail;
6597
+ if (Buffer.byteLength(pending) > maxLineBytes) {
6598
+ discarding = true;
6599
+ discardedBytes = Buffer.byteLength(pending);
6600
+ pending = "";
6601
+ }
6602
+ });
6603
+ args.input.on("end", () => {
6604
+ if (!discarding && pending.length > 0) {
6605
+ emit(pending);
6606
+ }
6607
+ pending = "";
6608
+ args.onClose?.();
6609
+ });
6610
+ function emit(line) {
6611
+ args.onLine(line.endsWith("\r") ? line.slice(0, -1) : line);
6612
+ }
6613
+ }
5783
6614
 
5784
6615
  // ../provider-bridge-protocol/src/testing/recording.ts
6616
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
5785
6617
  import { join, resolve } from "node:path";
5786
6618
  import { fileURLToPath } from "node:url";
6619
+
6620
+ // ../provider-bridge-protocol/src/bridge-kit/bridge-recorder.ts
6621
+ var BRIDGE_RECORDING_DIRECTIONS = [
6622
+ "runtime\u2192bridge",
6623
+ "bridge\u2192runtime",
6624
+ "provider\u2192bridge",
6625
+ "bridge\u2192provider"
6626
+ ];
6627
+ function bridgeRecordingFileName(direction) {
6628
+ return `${direction}.ndjson`;
6629
+ }
6630
+
6631
+ // ../provider-bridge-protocol/src/testing/recording.ts
5787
6632
  var COMMITTED_RECORDINGS_ROOT = fileURLToPath(
5788
6633
  new URL("../../recordings", import.meta.url)
5789
6634
  );
5790
6635
  var RECORDINGS_CHECKOUT_ROOT = resolve(COMMITTED_RECORDINGS_ROOT, "../../..");
6636
+ function compareRecordingEntries(left, right) {
6637
+ return left.run - right.run || left.seq - right.seq;
6638
+ }
6639
+ function parseEntry(raw, file, lineNumber) {
6640
+ let parsed;
6641
+ try {
6642
+ parsed = JSON.parse(raw);
6643
+ } catch (error) {
6644
+ throw new Error(
6645
+ `${file}:${lineNumber}: not JSON (${error instanceof Error ? error.message : String(error)})`
6646
+ );
6647
+ }
6648
+ if (typeof parsed !== "object" || parsed === null || typeof parsed.line !== "string" || typeof parsed.seq !== "number" || typeof parsed.dir !== "string") {
6649
+ throw new Error(`${file}:${lineNumber}: not a recording entry`);
6650
+ }
6651
+ const entry = parsed;
6652
+ return { ...entry, run: typeof entry.run === "number" ? entry.run : 0 };
6653
+ }
6654
+ function readBridgeRecordingLane(dir, direction) {
6655
+ const file = join(dir, bridgeRecordingFileName(direction));
6656
+ if (!existsSync(file)) {
6657
+ return [];
6658
+ }
6659
+ const entries = [];
6660
+ const lines = readFileSync(file, "utf8").split("\n");
6661
+ for (const [index, raw] of lines.entries()) {
6662
+ if (raw.length === 0) continue;
6663
+ const entry = parseEntry(raw, file, index + 1);
6664
+ if (entry.dir !== direction) {
6665
+ throw new Error(`${file}:${index + 1}: entry direction ${entry.dir} in the ${direction} lane`);
6666
+ }
6667
+ entries.push(entry);
6668
+ }
6669
+ return entries;
6670
+ }
6671
+ var CURRENT_BRIDGE_LANE_FILE = "bridge\u2192runtime.current.ndjson";
6672
+ function readCurrentBridgeLane(dir) {
6673
+ const file = join(dir, CURRENT_BRIDGE_LANE_FILE);
6674
+ if (!existsSync(file)) {
6675
+ return null;
6676
+ }
6677
+ const entries = [];
6678
+ const lines = readFileSync(file, "utf8").split("\n");
6679
+ for (const [index, raw] of lines.entries()) {
6680
+ if (raw.length === 0) continue;
6681
+ const entry = parseEntry(raw, file, index + 1);
6682
+ if (entry.dir !== "bridge\u2192runtime") {
6683
+ throw new Error(`${file}:${index + 1}: entry direction ${entry.dir} in the current bridge lane`);
6684
+ }
6685
+ entries.push(entry);
6686
+ }
6687
+ return entries;
6688
+ }
6689
+ function withCurrentBridgeLane(recording) {
6690
+ const current = readCurrentBridgeLane(recording.dir);
6691
+ if (current === null) {
6692
+ return recording;
6693
+ }
6694
+ const entries = [
6695
+ ...recording.entries.filter((entry) => entry.dir !== "bridge\u2192runtime"),
6696
+ ...current
6697
+ ];
6698
+ entries.sort(compareRecordingEntries);
6699
+ return { ...recording, entries };
6700
+ }
6701
+ function readBridgeRecording(dir) {
6702
+ const manifestPath = join(dir, "manifest.json");
6703
+ const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf8")) : null;
6704
+ const entries = [];
6705
+ for (const direction of BRIDGE_RECORDING_DIRECTIONS) {
6706
+ entries.push(...readBridgeRecordingLane(dir, direction));
6707
+ }
6708
+ entries.sort(compareRecordingEntries);
6709
+ return { dir, manifest, entries };
6710
+ }
6711
+ function listRecordedCells(root) {
6712
+ const cells = [];
6713
+ if (!existsSync(root)) {
6714
+ return cells;
6715
+ }
6716
+ for (const provider of readdirSync(root).sort()) {
6717
+ const providerDir = join(root, provider);
6718
+ if (!statSync(providerDir).isDirectory()) continue;
6719
+ for (const cell of readdirSync(providerDir).sort()) {
6720
+ const dir = join(providerDir, cell);
6721
+ if (!statSync(dir).isDirectory()) continue;
6722
+ const hasLane = BRIDGE_RECORDING_DIRECTIONS.some(
6723
+ (direction) => existsSync(join(dir, bridgeRecordingFileName(direction)))
6724
+ );
6725
+ if (hasLane) {
6726
+ cells.push({ provider, cell, dir });
6727
+ }
6728
+ }
6729
+ }
6730
+ return cells;
6731
+ }
5791
6732
 
5792
6733
  // ../provider-bridge-protocol/src/testing/parity.ts
6734
+ var SOURCE_BOOTSTRAP = fileURLToPath2(new URL("../bridge-worker-entry.ts", import.meta.url));
6735
+ var BUNDLED_BOOTSTRAP = fileURLToPath2(new URL("./provider-bridge-worker-entry.mjs", import.meta.url));
6736
+ function resolveProviderBridgeBootstrapPath() {
6737
+ if (existsSync2(SOURCE_BOOTSTRAP)) return SOURCE_BOOTSTRAP;
6738
+ if (existsSync2(BUNDLED_BOOTSTRAP)) return BUNDLED_BOOTSTRAP;
6739
+ throw new Error(
6740
+ `provider-bridge bootstrap not found at ${SOURCE_BOOTSTRAP} or ${BUNDLED_BOOTSTRAP}`
6741
+ );
6742
+ }
6743
+ function isTypeScriptPath(path) {
6744
+ return /\.[cm]?tsx?$/u.test(path);
6745
+ }
6746
+ function tsxSpecifier() {
6747
+ return import.meta.resolve("tsx");
6748
+ }
6749
+ function defaultNodeArgs(bootstrapPath, modulePath) {
6750
+ if (isTypeScriptPath(bootstrapPath)) {
6751
+ return ["--conditions=source", "--import", tsxSpecifier()];
6752
+ }
6753
+ return isTypeScriptPath(modulePath) ? ["--import", tsxSpecifier()] : [];
6754
+ }
6755
+ function resolveProviderBridgeLaunch(options) {
6756
+ if (!isAbsolute(options.modulePath)) {
6757
+ throw new Error(`bridge module path must be absolute: ${options.modulePath}`);
6758
+ }
6759
+ const bootstrapPath = options.bootstrapPath ?? resolveProviderBridgeBootstrapPath();
6760
+ const dataDir = options.dataDir ?? mkdtempSync(join2(tmpdir(), "bb-parity-data-"));
6761
+ return {
6762
+ command: process.execPath,
6763
+ args: [
6764
+ ...options.nodeArgs ?? defaultNodeArgs(bootstrapPath, options.modulePath),
6765
+ bootstrapPath,
6766
+ options.modulePath,
6767
+ options.pluginId,
6768
+ dataDir
6769
+ ],
6770
+ cwd: options.cwd ?? process.cwd(),
6771
+ env: {}
6772
+ };
6773
+ }
6774
+ var DEFAULT_REPLAY_PROFILE = {
6775
+ dialect: "json-rpc",
6776
+ env: () => ({})
6777
+ };
6778
+ function rewriteRecordedMachineFacts(line, workspaceDir) {
6779
+ if (!line.includes('"PATH"') && !line.includes('"cwd"')) {
6780
+ return line;
6781
+ }
6782
+ let parsed;
6783
+ try {
6784
+ parsed = JSON.parse(line);
6785
+ } catch {
6786
+ return line;
6787
+ }
6788
+ const params = parsed.params;
6789
+ if (params === void 0) {
6790
+ return line;
6791
+ }
6792
+ let changed = false;
6793
+ const envVars = params.options?.envVars;
6794
+ if (envVars !== void 0 && typeof envVars.PATH === "string") {
6795
+ envVars.PATH = process.env.PATH ?? envVars.PATH;
6796
+ changed = true;
6797
+ }
6798
+ if (typeof params.cwd === "string") {
6799
+ params.cwd = workspaceDir;
6800
+ changed = true;
6801
+ }
6802
+ return changed ? JSON.stringify(parsed) : line;
6803
+ }
6804
+ function recordedWorkspaceDir(recording) {
6805
+ for (const entry of recording.entries) {
6806
+ if (entry.dir !== "runtime\u2192bridge") continue;
6807
+ const message = parseWire(entry.line);
6808
+ const cwd = message?.params?.cwd;
6809
+ if (typeof cwd === "string" && cwd.length > 0) return cwd;
6810
+ }
6811
+ return null;
6812
+ }
6813
+ function parseWire(line) {
6814
+ try {
6815
+ const parsed = JSON.parse(line);
6816
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
6817
+ } catch {
6818
+ return null;
6819
+ }
6820
+ }
6821
+ function isRequest(message) {
6822
+ return message.id !== void 0 && typeof message.method === "string";
6823
+ }
6824
+ function isResponse(message) {
6825
+ return message.id !== void 0 && message.method === void 0;
6826
+ }
6827
+ function countTurnBoundaries(events) {
6828
+ let started = 0;
6829
+ let completed = 0;
6830
+ for (const event of events) {
6831
+ if (event.type === "turn/started") started += 1;
6832
+ if (event.type === "turn/completed") completed += 1;
6833
+ }
6834
+ return { started, completed };
6835
+ }
6836
+ function planRuntimeSteps(recording, assembler) {
6837
+ const steps = [];
6838
+ const assembled = [];
6839
+ for (const entry of recording.entries) {
6840
+ if (entry.dir === "bridge\u2192runtime") {
6841
+ const message = parseWire(entry.line);
6842
+ if (message !== null && message.method === THREAD_DELTA_NOTIFICATION_METHOD) {
6843
+ try {
6844
+ assembled.push(...assembler.assembleMessage(message));
6845
+ } catch {
6846
+ }
6847
+ }
6848
+ continue;
6849
+ }
6850
+ if (entry.dir !== "runtime\u2192bridge") {
6851
+ continue;
6852
+ }
6853
+ steps.push({
6854
+ entry,
6855
+ message: parseWire(entry.line),
6856
+ gate: countTurnBoundaries(assembled),
6857
+ eventsBefore: assembled.length
6858
+ });
6859
+ }
6860
+ return steps;
6861
+ }
6862
+ function methodOfRecordedBridgeRequest(recording, response, id) {
6863
+ for (const entry of recording.entries) {
6864
+ if (entry.dir !== "bridge\u2192runtime" || entry.run !== response.run) continue;
6865
+ const message = parseWire(entry.line);
6866
+ if (message !== null && isRequest(message) && String(message.id) === String(id)) {
6867
+ return message.method;
6868
+ }
6869
+ }
6870
+ return void 0;
6871
+ }
5793
6872
  var REPLAY_CHILD_PATH = fileURLToPath2(new URL("./replay-provider-child.mjs", import.meta.url));
6873
+ var PARITY_INITIALIZE_ID = "parity-initialize";
6874
+ function sleep(ms) {
6875
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
6876
+ }
6877
+ async function replayRecording(options) {
6878
+ const timeoutMs = options.timeoutMs ?? 15e3;
6879
+ const orderTimeoutMs = options.orderTimeoutMs ?? 5e3;
6880
+ const settleMs = options.settleMs ?? 750;
6881
+ const drainMs = options.drainMs ?? 300;
6882
+ const providerId = options.providerId;
6883
+ const profile = options.profile ?? DEFAULT_REPLAY_PROFILE;
6884
+ const recording = readBridgeRecording(options.recordingDir);
6885
+ const stateDir = mkdtempSync(join2(tmpdir(), "bb-parity-replay-"));
6886
+ const workspaceDir = mkdtempSync(join2(tmpdir(), "bb-parity-ws-"));
6887
+ const replayCommand = [
6888
+ process.execPath,
6889
+ REPLAY_CHILD_PATH,
6890
+ "--recording",
6891
+ resolve2(options.recordingDir),
6892
+ "--dialect",
6893
+ profile.dialect,
6894
+ "--state",
6895
+ stateDir
6896
+ ];
6897
+ const cursorPath = join2(stateDir, "cursor");
6898
+ const setCursor = (position) => {
6899
+ writeFileSync(
6900
+ cursorPath,
6901
+ position === "end" ? "end" : `${position.run} ${position.seq}`
6902
+ );
6903
+ };
6904
+ const wrapperPath = join2(stateDir, "replay-provider.mjs");
6905
+ writeFileSync(
6906
+ wrapperPath,
6907
+ [
6908
+ "#!/usr/bin/env node",
6909
+ `process.argv.splice(2, 0, ${JSON.stringify(replayCommand.slice(2)).slice(1, -1)});`,
6910
+ `await import(${JSON.stringify(REPLAY_CHILD_PATH)});`,
6911
+ ""
6912
+ ].join("\n"),
6913
+ { mode: 493 }
6914
+ );
6915
+ profile.prepareState?.({ recording, stateDir, workspaceDir });
6916
+ const launch = options.bridge;
6917
+ const child = spawn(launch.command, launch.args, {
6918
+ cwd: launch.cwd,
6919
+ env: {
6920
+ ...process.env,
6921
+ ...launch.env,
6922
+ ...profile.env({ replayCommand, wrapperPath, stateDir })
6923
+ },
6924
+ stdio: ["pipe", "pipe", "pipe"]
6925
+ });
6926
+ const recordedCwd = recordedWorkspaceDir(recording);
6927
+ const restoreRecordedWorkspace = (line) => recordedCwd === null || recordedCwd === workspaceDir ? line : line.split(workspaceDir).join(recordedCwd);
6928
+ const initializeId = PARITY_INITIALIZE_ID;
6929
+ const startedAt = Date.now();
6930
+ const lines = [];
6931
+ const lineTimes = [];
6932
+ const lineAfter = [];
6933
+ let lastSentRuntimeEntry = null;
6934
+ const events = [];
6935
+ const grammarViolations = [];
6936
+ const stalls = [];
6937
+ let stderr = "";
6938
+ const grammar = new ThreadEventGrammar();
6939
+ const liveAssembler = options.createAssembler(providerId);
6940
+ const planAssembler = (options.createPlanAssembler ?? options.createAssembler)(providerId);
6941
+ const exactPlan = options.planFromCurrentLane === true;
6942
+ const steps = planRuntimeSteps(
6943
+ exactPlan ? withCurrentBridgeLane(recording) : recording,
6944
+ planAssembler
6945
+ );
6946
+ const answeredIds = /* @__PURE__ */ new Set();
6947
+ const pendingBridgeRequests = [];
6948
+ const recordedAnswers = /* @__PURE__ */ new Map();
6949
+ for (const step of steps) {
6950
+ if (step.message !== null && isResponse(step.message)) {
6951
+ const method = methodOfRecordedBridgeRequest(recording, step.entry, step.message.id) ?? "?";
6952
+ const queue = recordedAnswers.get(method) ?? [];
6953
+ queue.push(step.message);
6954
+ recordedAnswers.set(method, queue);
6955
+ }
6956
+ }
6957
+ let lastOutputAt = Date.now();
6958
+ const exited = new Promise((resolveExit) => {
6959
+ child.on("exit", (code) => resolveExit(code));
6960
+ });
6961
+ child.stderr?.setEncoding("utf8").on("data", (chunk) => {
6962
+ stderr += chunk;
6963
+ options.onStderr?.(chunk);
6964
+ });
6965
+ function write(line) {
6966
+ if (child.stdin?.writable) {
6967
+ child.stdin.write(`${line}
6968
+ `);
6969
+ }
6970
+ }
6971
+ function answerBridgeRequest(message) {
6972
+ const method = message.method ?? "?";
6973
+ const queue = recordedAnswers.get(method);
6974
+ const recorded = queue?.shift();
6975
+ if (recorded === void 0) {
6976
+ stalls.push(`no recorded answer for bridge request ${method} (${String(message.id)})`);
6977
+ write(
6978
+ JSON.stringify({
6979
+ jsonrpc: "2.0",
6980
+ id: message.id,
6981
+ error: { code: -32e3, message: "parity replay: no recorded answer" }
6982
+ })
6983
+ );
6984
+ return;
6985
+ }
6986
+ write(JSON.stringify({ ...recorded, id: message.id }));
6987
+ }
6988
+ readBoundedLines({
6989
+ input: child.stdout,
6990
+ onLine: (rawLine) => {
6991
+ const line = restoreRecordedWorkspace(rawLine);
6992
+ lastOutputAt = Date.now();
6993
+ lines.push(line);
6994
+ lineTimes.push(lastOutputAt - startedAt);
6995
+ lineAfter.push(lastSentRuntimeEntry);
6996
+ const message = parseWire(line);
6997
+ if (message === null) return;
6998
+ if (isResponse(message)) {
6999
+ answeredIds.add(String(message.id));
7000
+ return;
7001
+ }
7002
+ if (isRequest(message)) {
7003
+ pendingBridgeRequests.push({ id: message.id, method: message.method });
7004
+ answerBridgeRequest(message);
7005
+ return;
7006
+ }
7007
+ if (message.method === THREAD_DELTA_NOTIFICATION_METHOD) {
7008
+ let assembled;
7009
+ try {
7010
+ assembled = liveAssembler.assembleMessage(message);
7011
+ } catch (error) {
7012
+ stalls.push(`invalid thread/delta: ${error instanceof Error ? error.message : String(error)}`);
7013
+ return;
7014
+ }
7015
+ for (const event of assembled) {
7016
+ const result2 = grammar.observe(event);
7017
+ if (result2.kind === "violation") {
7018
+ grammarViolations.push({ rule: result2.rule, reason: result2.reason, eventType: event.type });
7019
+ continue;
7020
+ }
7021
+ events.push(event);
7022
+ }
7023
+ }
7024
+ },
7025
+ onOverflow: (bytes) => {
7026
+ stalls.push(`oversized bridge line (${bytes} bytes)`);
7027
+ }
7028
+ });
7029
+ async function waitFor(label, predicate, limitMs = timeoutMs, reportStall = true) {
7030
+ const deadline = Date.now() + limitMs;
7031
+ while (!predicate()) {
7032
+ if (child.exitCode !== null) {
7033
+ stalls.push(`bridge exited while waiting for ${label}`);
7034
+ return;
7035
+ }
7036
+ if (Date.now() > deadline) {
7037
+ if (reportStall) stalls.push(`timed out waiting for ${label}`);
7038
+ return;
7039
+ }
7040
+ await sleep(10);
7041
+ }
7042
+ }
7043
+ const firstStep = steps.find((step) => step.message !== null && isRequest(step.message));
7044
+ setCursor(firstStep === void 0 ? "end" : { run: firstStep.entry.run, seq: firstStep.entry.seq });
7045
+ write(
7046
+ JSON.stringify({
7047
+ jsonrpc: "2.0",
7048
+ id: initializeId,
7049
+ method: "initialize",
7050
+ params: {
7051
+ protocolVersion: PROVIDER_BRIDGE_PROTOCOL_VERSION,
7052
+ client: { name: "bb-parity", version: "0" }
7053
+ }
7054
+ })
7055
+ );
7056
+ await waitFor("initialize response", () => answeredIds.has(initializeId));
7057
+ const sentRequestIds = [];
7058
+ for (const step of steps) {
7059
+ if (step.message === null || !isRequest(step.message)) {
7060
+ if (step.message !== null && !isResponse(step.message)) {
7061
+ lastSentRuntimeEntry = { run: step.entry.run, seq: step.entry.seq, ts: step.entry.ts };
7062
+ write(step.entry.line);
7063
+ }
7064
+ continue;
7065
+ }
7066
+ const request = step.message;
7067
+ const method = request.method;
7068
+ await waitFor(
7069
+ `earlier requests before ${method}`,
7070
+ () => sentRequestIds.every((id) => answeredIds.has(id))
7071
+ );
7072
+ await waitFor(
7073
+ `${step.gate.started} turn/started and ${step.gate.completed} turn/completed before ${method}`,
7074
+ () => {
7075
+ const live = countTurnBoundaries(events);
7076
+ return live.started >= step.gate.started && live.completed >= step.gate.completed;
7077
+ }
7078
+ );
7079
+ await waitFor(
7080
+ `${step.eventsBefore} events before ${method}`,
7081
+ () => events.length >= step.eventsBefore || !exactPlan && Date.now() - lastOutputAt >= orderTimeoutMs,
7082
+ timeoutMs,
7083
+ exactPlan
7084
+ );
7085
+ await waitFor(
7086
+ `the stream to drain before ${method}`,
7087
+ () => Date.now() - lastOutputAt >= drainMs,
7088
+ timeoutMs,
7089
+ false
7090
+ );
7091
+ if (child.exitCode !== null) break;
7092
+ if (method === "thread/stop" && typeof request.params === "object" && request.params !== null && request.params.intent === "release") {
7093
+ const threadId = request.params.threadId;
7094
+ if (typeof threadId === "string") grammar.clearThread(threadId);
7095
+ }
7096
+ const rewritten = rewriteRecordedMachineFacts(step.entry.line, workspaceDir);
7097
+ const line = profile.rewriteRuntimeLine === void 0 ? rewritten : profile.rewriteRuntimeLine(rewritten, { replayCommand });
7098
+ lastSentRuntimeEntry = { run: step.entry.run, seq: step.entry.seq, ts: step.entry.ts };
7099
+ write(line);
7100
+ sentRequestIds.push(String(request.id));
7101
+ const nextStep = steps.slice(steps.indexOf(step) + 1).find((candidate) => candidate.message !== null && isRequest(candidate.message));
7102
+ setCursor(nextStep === void 0 ? "end" : { run: nextStep.entry.run, seq: nextStep.entry.seq });
7103
+ }
7104
+ setCursor("end");
7105
+ await waitFor("the last responses", () => sentRequestIds.every((id) => answeredIds.has(id)));
7106
+ await waitFor("the stream to settle", () => Date.now() - lastOutputAt >= settleMs);
7107
+ child.stdin?.end();
7108
+ const exitCode = await Promise.race([
7109
+ exited,
7110
+ sleep(timeoutMs).then(() => {
7111
+ stalls.push("bridge did not exit after stdin closed; killed");
7112
+ child.kill("SIGKILL");
7113
+ return null;
7114
+ })
7115
+ ]);
7116
+ rmSync(stateDir, { recursive: true, force: true });
7117
+ rmSync(workspaceDir, { recursive: true, force: true });
7118
+ return {
7119
+ providerId,
7120
+ recordingDir: options.recordingDir,
7121
+ lines,
7122
+ lineTimes,
7123
+ lineAfter,
7124
+ events,
7125
+ grammarViolations,
7126
+ stalls,
7127
+ stderr,
7128
+ exitCode
7129
+ };
7130
+ }
7131
+ function assembleRecordedEvents(recording, createAssembler, providerId) {
7132
+ const assembler = createAssembler(providerId);
7133
+ const grammar = new ThreadEventGrammar();
7134
+ const events = [];
7135
+ const grammarViolations = [];
7136
+ const invalidDeltas = [];
7137
+ for (const entry of recording.entries) {
7138
+ if (entry.dir === "runtime\u2192bridge") {
7139
+ const message2 = parseWire(entry.line);
7140
+ if (message2 !== null && message2.method === "thread/stop" && typeof message2.params === "object" && message2.params !== null && message2.params.intent === "release") {
7141
+ const threadId = message2.params.threadId;
7142
+ if (typeof threadId === "string") grammar.clearThread(threadId);
7143
+ }
7144
+ continue;
7145
+ }
7146
+ if (entry.dir !== "bridge\u2192runtime") continue;
7147
+ const message = parseWire(entry.line);
7148
+ if (message === null || message.method !== THREAD_DELTA_NOTIFICATION_METHOD) continue;
7149
+ let assembled;
7150
+ try {
7151
+ assembled = assembler.assembleMessage(message);
7152
+ } catch (error) {
7153
+ invalidDeltas.push(error instanceof Error ? error.message : String(error));
7154
+ continue;
7155
+ }
7156
+ for (const event of assembled) {
7157
+ const result2 = grammar.observe(event);
7158
+ if (result2.kind === "violation") {
7159
+ grammarViolations.push({ rule: result2.rule, reason: result2.reason, eventType: event.type });
7160
+ continue;
7161
+ }
7162
+ events.push(event);
7163
+ }
7164
+ }
7165
+ return { events, grammarViolations, invalidDeltas };
7166
+ }
7167
+ var TIME_FIELDS = /* @__PURE__ */ new Set([
7168
+ "createdAt",
7169
+ "updatedAt",
7170
+ "startedAt",
7171
+ "completedAt",
7172
+ "startedAtMs",
7173
+ "completedAtMs",
7174
+ "timestamp",
7175
+ "ts",
7176
+ "resetsAtMs",
7177
+ "resetsAt",
7178
+ "expiresAt"
7179
+ ]);
7180
+ function blankTimeFields(value) {
7181
+ if (Array.isArray(value)) {
7182
+ return value.map(blankTimeFields);
7183
+ }
7184
+ if (value !== null && typeof value === "object") {
7185
+ const out = {};
7186
+ for (const [key, entry] of Object.entries(value)) {
7187
+ out[key] = TIME_FIELDS.has(key) && (typeof entry === "number" || typeof entry === "string") ? 0 : blankTimeFields(entry);
7188
+ }
7189
+ return out;
7190
+ }
7191
+ return value;
7192
+ }
7193
+ var ROW_ID_FIELDS = [
7194
+ "turnId",
7195
+ "itemId",
7196
+ "id",
7197
+ "parentToolCallId",
7198
+ "toolCallId",
7199
+ "callId",
7200
+ "requestId",
7201
+ "messageId",
7202
+ "rowId",
7203
+ "agentId",
7204
+ "taskId",
7205
+ "backgroundTaskId",
7206
+ "sourceItemId",
7207
+ "interactionId"
7208
+ ];
7209
+ function normalizeParityEvents(events) {
7210
+ return blankTimeFields(normalizeCalibrationEvents(events));
7211
+ }
7212
+ function normalizeParityRows(rows) {
7213
+ return blankTimeFields(
7214
+ normalizeCalibrationEvents(rows, {
7215
+ internedIdFields: ROW_ID_FIELDS
7216
+ })
7217
+ );
7218
+ }
7219
+ function pointerSegments(path) {
7220
+ return path.split("/").filter((segment) => segment.length > 0);
7221
+ }
7222
+ function maskPath(value, path) {
7223
+ const segments = pointerSegments(path);
7224
+ if (segments.length === 0) {
7225
+ if (!Array.isArray(value)) return 0;
7226
+ const removed2 = value.length;
7227
+ value.length = 0;
7228
+ return removed2;
7229
+ }
7230
+ let removed = 0;
7231
+ const visit = (node, index) => {
7232
+ if (index >= segments.length || node === null || typeof node !== "object") {
7233
+ return;
7234
+ }
7235
+ const segment = segments[index];
7236
+ const last = index === segments.length - 1;
7237
+ if (segment === "**") {
7238
+ visit(node, index + 1);
7239
+ for (const child of Object.values(node)) {
7240
+ visit(child, index);
7241
+ }
7242
+ return;
7243
+ }
7244
+ const keys = segment === "*" ? Object.keys(node) : Object.hasOwn(node, segment) ? [segment] : [];
7245
+ for (const key of keys) {
7246
+ if (last) {
7247
+ if (Array.isArray(node)) {
7248
+ node[Number(key)] = null;
7249
+ } else {
7250
+ delete node[key];
7251
+ }
7252
+ removed += 1;
7253
+ } else {
7254
+ visit(node[key], index + 1);
7255
+ }
7256
+ }
7257
+ };
7258
+ visit(value, 0);
7259
+ return removed;
7260
+ }
7261
+ function entryApplies(entry, provider, cell) {
7262
+ return (entry.provider === "*" || entry.provider === provider) && (entry.cell === "*" || entry.cell === cell);
7263
+ }
7264
+ function compareParity(oldRun, newRun, allowlist, scope) {
7265
+ const layers = {
7266
+ events: [normalizeParityEvents(oldRun.events), normalizeParityEvents(newRun.events)],
7267
+ rows: [normalizeParityRows(oldRun.rows), normalizeParityRows(newRun.rows)]
7268
+ };
7269
+ const staleAllowlist = [];
7270
+ for (const entry of allowlist) {
7271
+ if (!entryApplies(entry, scope.provider, scope.cell)) continue;
7272
+ const [oldSide, newSide] = layers[entry.layer];
7273
+ const removed = maskPath(oldSide, entry.path) + maskPath(newSide, entry.path);
7274
+ if (removed === 0) {
7275
+ staleAllowlist.push(entry);
7276
+ }
7277
+ }
7278
+ const events = diffLayer(layers.events[0], layers.events[1]);
7279
+ const rows = diffLayer(layers.rows[0], layers.rows[1]);
7280
+ const grammar = diffLayer(
7281
+ (oldRun.grammarViolations ?? []).map((violation2) => `${violation2.rule}:${violation2.eventType}`),
7282
+ (newRun.grammarViolations ?? []).map((violation2) => `${violation2.rule}:${violation2.eventType}`)
7283
+ );
7284
+ const clean = (diff) => diff.onlyInOld.length === 0 && diff.onlyInNew.length === 0;
7285
+ return {
7286
+ provider: scope.provider,
7287
+ cell: scope.cell,
7288
+ events,
7289
+ rows,
7290
+ grammar,
7291
+ staleAllowlist,
7292
+ passed: clean(events) && clean(rows) && clean(grammar) && staleAllowlist.length === 0
7293
+ };
7294
+ }
7295
+ function diffLayer(oldSide, newSide) {
7296
+ const diff = diffCalibrationStreams(oldSide, newSide);
7297
+ return { onlyInOld: diff.onlyInLegacy, onlyInNew: diff.onlyInBridge };
7298
+ }
7299
+
7300
+ // ../provider-bridge-protocol/src/testing/rerecord.ts
7301
+ import { writeFileSync as writeFileSync2 } from "node:fs";
7302
+ import { join as join3 } from "node:path";
7303
+ var BRIDGE_TO_RUNTIME = "bridge\u2192runtime";
7304
+ function parseWireLine(line) {
7305
+ try {
7306
+ const parsed = JSON.parse(line);
7307
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
7308
+ } catch {
7309
+ return null;
7310
+ }
7311
+ }
7312
+ async function rerecordCurrentBridgeLane(options) {
7313
+ const recording = readBridgeRecording(options.recordingDir);
7314
+ const run = await replayRecording({ ...options, planFromCurrentLane: false });
7315
+ if (run.stalls.length > 0) {
7316
+ return { file: null, lines: 0, events: run.events.length, stalls: run.stalls };
7317
+ }
7318
+ const firstRuntime = recording.entries.find(
7319
+ (entry) => entry.dir === "runtime\u2192bridge"
7320
+ );
7321
+ const recordedRequestIds = /* @__PURE__ */ new Map();
7322
+ for (const entry of recording.entries) {
7323
+ if (entry.dir !== BRIDGE_TO_RUNTIME) continue;
7324
+ const message = parseWireLine(entry.line);
7325
+ if (message?.method === void 0 || message.id === void 0) continue;
7326
+ const queue = recordedRequestIds.get(message.method) ?? [];
7327
+ queue.push(message.id);
7328
+ recordedRequestIds.set(message.method, queue);
7329
+ }
7330
+ const entries = [];
7331
+ const perAnchor = /* @__PURE__ */ new Map();
7332
+ run.lines.forEach((rawLine, index) => {
7333
+ let line = rawLine;
7334
+ const message = parseWireLine(rawLine);
7335
+ if (message?.id === PARITY_INITIALIZE_ID) {
7336
+ return;
7337
+ }
7338
+ if (message?.method !== void 0 && message.id !== void 0) {
7339
+ const recordedId = recordedRequestIds.get(message.method)?.shift();
7340
+ if (recordedId !== void 0 && recordedId !== message.id) {
7341
+ line = JSON.stringify({ ...message, id: recordedId });
7342
+ }
7343
+ }
7344
+ const anchor = run.lineAfter[index] ?? (firstRuntime ? {
7345
+ run: firstRuntime.run,
7346
+ seq: firstRuntime.seq - 1,
7347
+ ts: firstRuntime.ts
7348
+ } : { run: 0, seq: 0, ts: 0 });
7349
+ const anchorKey = `${anchor.run}:${anchor.seq}`;
7350
+ const ordinal = (perAnchor.get(anchorKey) ?? 0) + 1;
7351
+ perAnchor.set(anchorKey, ordinal);
7352
+ entries.push({
7353
+ ts: anchor.ts + ordinal,
7354
+ run: anchor.run,
7355
+ // Fractional: after the anchoring runtime entry, before the next one.
7356
+ seq: anchor.seq + ordinal / (run.lines.length + 1),
7357
+ dir: BRIDGE_TO_RUNTIME,
7358
+ line
7359
+ });
7360
+ });
7361
+ const file = join3(options.recordingDir, CURRENT_BRIDGE_LANE_FILE);
7362
+ writeFileSync2(
7363
+ file,
7364
+ entries.map((entry) => JSON.stringify(entry)).join("\n") + (entries.length > 0 ? "\n" : "")
7365
+ );
7366
+ return { file, lines: entries.length, events: run.events.length, stalls: [] };
7367
+ }
5794
7368
  export {
5795
7369
  ASSEMBLER_GRAMMAR_VERSIONS,
5796
7370
  CONFORMANCE_ASSEMBLED_EVENT_METHOD,
5797
- ConformanceClient as experimental_ConformanceClient,
7371
+ CURRENT_BRIDGE_LANE_FILE,
7372
+ DEFAULT_REPLAY_PROFILE,
7373
+ PARITY_INITIALIZE_ID,
7374
+ RECORDED_CONFORMANCE_CELLS,
5798
7375
  assembleCapturedThreadEvents as experimental_assembleCapturedThreadEvents,
7376
+ assembleRecordedEvents as experimental_assembleRecordedEvents,
5799
7377
  captureBridgeJsonRpcOutput as experimental_captureBridgeJsonRpcOutput,
5800
- checkItemOpensBeforeDelta as experimental_checkItemOpensBeforeDelta,
7378
+ checkRecordedCellReplay as experimental_checkRecordedCellReplay,
7379
+ compareParity as experimental_compareParity,
5801
7380
  createBridgeDeltaEventCollector as experimental_createBridgeDeltaEventCollector,
5802
7381
  createBridgeJsonRpcTestHarness as experimental_createBridgeJsonRpcTestHarness,
5803
7382
  createDeltaAssembler as experimental_createDeltaAssembler,
5804
7383
  describeCalibrationEvents as experimental_describeCalibrationEvents,
5805
- diffCumulativeText as experimental_diffCumulativeText,
5806
7384
  formatConformanceReport as experimental_formatConformanceReport,
7385
+ listRecordedCells as experimental_listRecordedCells,
5807
7386
  normalizeCalibrationEvents as experimental_normalizeCalibrationEvents,
7387
+ readBridgeRecording as experimental_readBridgeRecording,
7388
+ replayRecording as experimental_replayRecording,
7389
+ rerecordCurrentBridgeLane as experimental_rerecordCurrentBridgeLane,
7390
+ resolveProviderBridgeLaunch as experimental_resolveProviderBridgeLaunch,
5808
7391
  runBridgeConformance as experimental_runBridgeConformance,
5809
- toConformanceMessages as experimental_toConformanceMessages
7392
+ toConformanceMessages as experimental_toConformanceMessages,
7393
+ withCurrentBridgeLane as experimental_withCurrentBridgeLane
5810
7394
  };