@xemahq/kernel-contracts 0.50.0 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/biome/lib/biome-manifest.d.ts +9 -0
  2. package/dist/biome/lib/biome-manifest.d.ts.map +1 -1
  3. package/dist/biome/lib/biome-manifest.js +2 -0
  4. package/dist/biome/lib/biome-manifest.js.map +1 -1
  5. package/dist/capability/index.d.ts +3 -1
  6. package/dist/capability/index.d.ts.map +1 -1
  7. package/dist/capability/index.js +3 -1
  8. package/dist/capability/index.js.map +1 -1
  9. package/dist/capability/lib/authority-effect.d.ts +8 -0
  10. package/dist/capability/lib/authority-effect.d.ts.map +1 -0
  11. package/dist/capability/lib/authority-effect.js +17 -0
  12. package/dist/capability/lib/authority-effect.js.map +1 -0
  13. package/dist/capability/lib/capability-contribution.d.ts +4 -0
  14. package/dist/capability/lib/capability-contribution.d.ts.map +1 -1
  15. package/dist/capability/lib/capability-contribution.js +4 -0
  16. package/dist/capability/lib/capability-contribution.js.map +1 -1
  17. package/dist/capability/lib/capability-domain.d.ts +8 -0
  18. package/dist/capability/lib/capability-domain.d.ts.map +1 -0
  19. package/dist/capability/lib/capability-domain.js +24 -0
  20. package/dist/capability/lib/capability-domain.js.map +1 -0
  21. package/dist/capability/lib/capability-reach.d.ts +13 -0
  22. package/dist/capability/lib/capability-reach.d.ts.map +1 -0
  23. package/dist/capability/lib/capability-reach.js +34 -0
  24. package/dist/capability/lib/capability-reach.js.map +1 -0
  25. package/dist/capability/lib/meta-tool.d.ts +51 -14
  26. package/dist/capability/lib/meta-tool.d.ts.map +1 -1
  27. package/dist/capability/lib/meta-tool.js +66 -12
  28. package/dist/capability/lib/meta-tool.js.map +1 -1
  29. package/dist/capability-projection/index.d.ts +4 -0
  30. package/dist/capability-projection/index.d.ts.map +1 -0
  31. package/dist/capability-projection/index.js +20 -0
  32. package/dist/capability-projection/index.js.map +1 -0
  33. package/dist/capability-projection/lib/capability-invocation-binding.d.ts +72 -0
  34. package/dist/capability-projection/lib/capability-invocation-binding.d.ts.map +1 -0
  35. package/dist/capability-projection/lib/capability-invocation-binding.js +101 -0
  36. package/dist/capability-projection/lib/capability-invocation-binding.js.map +1 -0
  37. package/dist/capability-projection/lib/capability-sync-manifest.d.ts +8 -0
  38. package/dist/capability-projection/lib/capability-sync-manifest.d.ts.map +1 -0
  39. package/dist/capability-projection/lib/capability-sync-manifest.js +8 -0
  40. package/dist/capability-projection/lib/capability-sync-manifest.js.map +1 -0
  41. package/dist/capability-projection/lib/projected-capability.d.ts +15 -0
  42. package/dist/capability-projection/lib/projected-capability.d.ts.map +1 -0
  43. package/dist/capability-projection/lib/projected-capability.js +17 -0
  44. package/dist/capability-projection/lib/projected-capability.js.map +1 -0
  45. package/dist/connector/lib/connector-adapter-contribution.d.ts +2 -0
  46. package/dist/connector/lib/connector-adapter-contribution.d.ts.map +1 -1
  47. package/dist/connector/lib/onboarding-manifest.d.ts +2 -0
  48. package/dist/connector/lib/onboarding-manifest.d.ts.map +1 -1
  49. package/dist/connector/lib/onboarding-manifest.js +1 -0
  50. package/dist/connector/lib/onboarding-manifest.js.map +1 -1
  51. package/dist/connector/lib/provider-descriptor.d.ts +2 -0
  52. package/dist/connector/lib/provider-descriptor.d.ts.map +1 -1
  53. package/dist/contribution/lib/contribution-kind.d.ts +1 -6
  54. package/dist/contribution/lib/contribution-kind.d.ts.map +1 -1
  55. package/dist/contribution/lib/contribution-kind.js +1 -6
  56. package/dist/contribution/lib/contribution-kind.js.map +1 -1
  57. package/dist/distribution/index.d.ts +1 -0
  58. package/dist/distribution/index.d.ts.map +1 -1
  59. package/dist/distribution/index.js +1 -0
  60. package/dist/distribution/index.js.map +1 -1
  61. package/dist/distribution/lib/distribution-lock.d.ts +6 -0
  62. package/dist/distribution/lib/distribution-lock.d.ts.map +1 -1
  63. package/dist/distribution/lib/distribution-lock.js +9 -1
  64. package/dist/distribution/lib/distribution-lock.js.map +1 -1
  65. package/dist/distribution/lib/first-party-capability-domains.d.ts +15 -0
  66. package/dist/distribution/lib/first-party-capability-domains.d.ts.map +1 -0
  67. package/dist/distribution/lib/first-party-capability-domains.js +52 -0
  68. package/dist/distribution/lib/first-party-capability-domains.js.map +1 -0
  69. package/dist/object/lib/xema-object-ref.d.ts +0 -5
  70. package/dist/object/lib/xema-object-ref.d.ts.map +1 -1
  71. package/dist/object/lib/xema-object-ref.js +12 -62
  72. package/dist/object/lib/xema-object-ref.js.map +1 -1
  73. package/dist/output-surface-inspect/index.d.ts +1 -0
  74. package/dist/output-surface-inspect/index.d.ts.map +1 -1
  75. package/dist/output-surface-inspect/index.js +1 -0
  76. package/dist/output-surface-inspect/index.js.map +1 -1
  77. package/dist/output-surface-inspect/lib/buffers.d.ts +52 -0
  78. package/dist/output-surface-inspect/lib/buffers.d.ts.map +1 -0
  79. package/dist/output-surface-inspect/lib/buffers.js +132 -0
  80. package/dist/output-surface-inspect/lib/buffers.js.map +1 -0
  81. package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts +6 -1
  82. package/dist/output-surface-inspect/lib/output-surface-inspect.d.ts.map +1 -1
  83. package/dist/output-surface-inspect/lib/output-surface-inspect.js +12 -1
  84. package/dist/output-surface-inspect/lib/output-surface-inspect.js.map +1 -1
  85. package/dist/output-surface-inspect/lib/step-schema.d.ts +2 -1
  86. package/dist/output-surface-inspect/lib/step-schema.d.ts.map +1 -1
  87. package/dist/output-surface-inspect/lib/step-schema.js +9 -2
  88. package/dist/output-surface-inspect/lib/step-schema.js.map +1 -1
  89. package/dist/output-surface-inspect/runtime/index.d.ts +1 -0
  90. package/dist/output-surface-inspect/runtime/index.d.ts.map +1 -1
  91. package/dist/output-surface-inspect/runtime/index.js +1 -0
  92. package/dist/output-surface-inspect/runtime/index.js.map +1 -1
  93. package/dist/search/index.d.ts +1 -0
  94. package/dist/search/index.d.ts.map +1 -1
  95. package/dist/search/index.js +1 -0
  96. package/dist/search/index.js.map +1 -1
  97. package/dist/search/lib/route-template.d.ts +31 -0
  98. package/dist/search/lib/route-template.d.ts.map +1 -0
  99. package/dist/search/lib/route-template.js +142 -0
  100. package/dist/search/lib/route-template.js.map +1 -0
  101. package/dist/search/lib/search-contribution.d.ts +4 -2
  102. package/dist/search/lib/search-contribution.d.ts.map +1 -1
  103. package/dist/search/lib/search-contribution.js +46 -4
  104. package/dist/search/lib/search-contribution.js.map +1 -1
  105. package/dist/search-source/lib/indexable-document.d.ts +22 -0
  106. package/dist/search-source/lib/indexable-document.d.ts.map +1 -1
  107. package/dist/search-source/lib/indexable-document.js +10 -1
  108. package/dist/search-source/lib/indexable-document.js.map +1 -1
  109. package/dist/search-source/lib/search-index-event.d.ts +10 -0
  110. package/dist/search-source/lib/search-index-event.d.ts.map +1 -1
  111. package/dist/search-source/lib/search-ref.d.ts.map +1 -1
  112. package/dist/search-source/lib/search-ref.js +11 -2
  113. package/dist/search-source/lib/search-ref.js.map +1 -1
  114. package/dist/search-source/lib/search-replay.d.ts +10 -0
  115. package/dist/search-source/lib/search-replay.d.ts.map +1 -1
  116. package/dist/service-registry/lib/service-descriptor.d.ts +1 -0
  117. package/dist/service-registry/lib/service-descriptor.d.ts.map +1 -1
  118. package/dist/space/lib/space-ref-parser.d.ts +6 -4
  119. package/dist/space/lib/space-ref-parser.d.ts.map +1 -1
  120. package/dist/space/lib/space-ref-parser.js +90 -126
  121. package/dist/space/lib/space-ref-parser.js.map +1 -1
  122. package/dist/space/lib/space-traversal.d.ts.map +1 -1
  123. package/dist/space/lib/space-traversal.js +9 -13
  124. package/dist/space/lib/space-traversal.js.map +1 -1
  125. package/dist/space/lib/space.d.ts +7 -0
  126. package/dist/space/lib/space.d.ts.map +1 -1
  127. package/dist/space/lib/space.js +48 -29
  128. package/dist/space/lib/space.js.map +1 -1
  129. package/dist/subject/lib/subject.d.ts +1 -0
  130. package/dist/subject/lib/subject.d.ts.map +1 -1
  131. package/dist/subject/lib/subject.js +1 -0
  132. package/dist/subject/lib/subject.js.map +1 -1
  133. package/dist/surface/index.d.ts +4 -0
  134. package/dist/surface/index.d.ts.map +1 -0
  135. package/dist/surface/index.js +20 -0
  136. package/dist/surface/index.js.map +1 -0
  137. package/dist/surface/lib/surface-contribution.d.ts +24 -0
  138. package/dist/surface/lib/surface-contribution.d.ts.map +1 -0
  139. package/dist/surface/lib/surface-contribution.js +23 -0
  140. package/dist/surface/lib/surface-contribution.js.map +1 -0
  141. package/dist/surface/lib/surface-kind.d.ts +23 -0
  142. package/dist/surface/lib/surface-kind.d.ts.map +1 -0
  143. package/dist/surface/lib/surface-kind.js +27 -0
  144. package/dist/surface/lib/surface-kind.js.map +1 -0
  145. package/dist/surface/lib/vista-surface.d.ts +11 -0
  146. package/dist/surface/lib/vista-surface.d.ts.map +1 -0
  147. package/dist/surface/lib/vista-surface.js +13 -0
  148. package/dist/surface/lib/vista-surface.js.map +1 -0
  149. package/dist/widget/lib/chat-widget-envelope.d.ts.map +1 -1
  150. package/dist/widget/lib/chat-widget-envelope.js.map +1 -1
  151. package/dist/workflow/lib/enums.d.ts.map +1 -1
  152. package/dist/workflow/lib/enums.js.map +1 -1
  153. package/package.json +1 -1
  154. package/src/biome/lib/biome-manifest.ts +41 -1
  155. package/src/capability/index.ts +3 -1
  156. package/src/capability/lib/authority-effect.ts +88 -0
  157. package/src/capability/lib/capability-contribution.ts +44 -0
  158. package/src/capability/lib/capability-domain.ts +51 -0
  159. package/src/capability/lib/capability-grant.ts +1 -1
  160. package/src/capability/lib/capability-reach.ts +119 -0
  161. package/src/capability/lib/meta-tool.ts +172 -39
  162. package/src/capability-projection/index.ts +3 -0
  163. package/src/capability-projection/lib/capability-invocation-binding.ts +223 -0
  164. package/src/capability-projection/lib/capability-sync-manifest.ts +52 -0
  165. package/src/capability-projection/lib/projected-capability.ts +63 -0
  166. package/src/connector/lib/onboarding-manifest.ts +16 -0
  167. package/src/contribution/lib/contribution-kind.ts +165 -61
  168. package/src/distribution/index.ts +1 -0
  169. package/src/distribution/lib/distribution-lock.ts +36 -0
  170. package/src/distribution/lib/first-party-capability-domains.ts +166 -0
  171. package/src/object/lib/xema-object-ref.ts +27 -82
  172. package/src/output-surface-inspect/index.ts +2 -0
  173. package/src/output-surface-inspect/lib/buffers.ts +252 -0
  174. package/src/output-surface-inspect/lib/output-surface-inspect.ts +65 -6
  175. package/src/output-surface-inspect/lib/step-schema.ts +36 -3
  176. package/src/output-surface-inspect/runtime/index.ts +1 -0
  177. package/src/search/index.ts +1 -0
  178. package/src/search/lib/route-template.ts +381 -0
  179. package/src/search/lib/search-contribution.ts +138 -31
  180. package/src/search-source/lib/indexable-document.ts +44 -0
  181. package/src/search-source/lib/search-ref.ts +22 -9
  182. package/src/service-registry/lib/service-descriptor.ts +16 -0
  183. package/src/space/lib/skill-mirror-path.ts +1 -1
  184. package/src/space/lib/space-ref-parser.ts +190 -165
  185. package/src/space/lib/space-traversal.ts +34 -17
  186. package/src/space/lib/space.ts +110 -35
  187. package/src/subject/lib/subject.ts +16 -0
  188. package/src/surface/index.ts +3 -0
  189. package/src/surface/lib/surface-contribution.ts +101 -0
  190. package/src/surface/lib/surface-kind.ts +65 -0
  191. package/src/surface/lib/vista-surface.ts +44 -0
  192. package/src/widget/lib/chat-widget-envelope.ts +6 -2
  193. package/src/workflow/lib/enums.ts +6 -7
  194. package/src/capability/lib/first-party-capability-domains.ts +0 -77
@@ -1,5 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { SpaceRef, SpaceKind, spaceRefToPath } from '../../space';
2
+ import {
3
+ parseSpaceRefScopeSegments,
4
+ SpaceRef,
5
+ SpaceRefParseError,
6
+ spaceRefToPath,
7
+ } from '../../space';
3
8
  import { XemaObjectKind, XemaObjectKindSchema } from './xema-object-kind';
4
9
 
5
10
  /**
@@ -69,9 +74,26 @@ const SLUG_SEGMENT = '[a-zA-Z0-9][a-zA-Z0-9_.-]*';
69
74
  const SLUG_PATTERN = new RegExp(`^${SLUG_SEGMENT}(?:/${SLUG_SEGMENT})*$`);
70
75
  const VERSION_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.+-]*$/;
71
76
 
72
- export interface ScopeParse {
73
- scope: SpaceRef;
74
- consumed: number;
77
+ /**
78
+ * Consume the leading scope segments of a `xema://` object ref, re-raising any
79
+ * scope-grammar failure as a {@link XemaObjectRefParseError} so an object-ref
80
+ * caller only ever has to catch ONE error type. The scope grammar itself lives
81
+ * in `@xemahq/kernel-contracts/space` (`parseSpaceRefScopeSegments`) — it is a
82
+ * SPACE grammar, and object refs, search refs and Space refs all read it from
83
+ * there rather than restating it.
84
+ */
85
+ function parseScope(
86
+ segments: string[],
87
+ ref: string,
88
+ ): { scope: SpaceRef; consumed: number } {
89
+ try {
90
+ return parseSpaceRefScopeSegments(segments, ref);
91
+ } catch (err) {
92
+ if (err instanceof SpaceRefParseError) {
93
+ throw new XemaObjectRefParseError(ref, err.message);
94
+ }
95
+ throw err;
96
+ }
75
97
  }
76
98
 
77
99
  function requireSegment(
@@ -87,83 +109,6 @@ function requireSegment(
87
109
  return value;
88
110
  }
89
111
 
90
- /**
91
- * Parse the leading scope segments of a `xema://` ref path into a
92
- * {@link SpaceRef} plus the number of segments consumed. Shared by
93
- * {@link parseXemaObjectRef} and the search-source `parseSearchRef` helper so
94
- * the `xema://<scope-path>/...` scope grammar (plural `orgs`/`projects`, the
95
- * same one {@link spaceRefToPath} formats) lives in exactly ONE place. The
96
- * caller owns whatever follows the scope (a `<kind>/<slug>` for objects, a
97
- * `<docType>/<sourceId>` for search refs).
98
- */
99
- export function parseObjectRefScopeSegments(
100
- segments: string[],
101
- ref: string,
102
- ): ScopeParse {
103
- const head = requireSegment(segments, 0, ref, 'scope');
104
- switch (head) {
105
- case 'system':
106
- return { scope: { tier: SpaceKind.System }, consumed: 1 };
107
- case 'biomes': {
108
- const biomeId = requireSegment(segments, 1, ref, 'biomeId');
109
- return {
110
- scope: { tier: SpaceKind.Biome, biomeId },
111
- consumed: 2,
112
- };
113
- }
114
- case 'users': {
115
- const userId = requireSegment(segments, 1, ref, 'userId');
116
- return {
117
- scope: { tier: SpaceKind.User, userId },
118
- consumed: 2,
119
- };
120
- }
121
- case 'sessions': {
122
- const sessionId = requireSegment(segments, 1, ref, 'sessionId');
123
- return {
124
- scope: { tier: SpaceKind.Session, sessionId },
125
- consumed: 2,
126
- };
127
- }
128
- case 'orgs': {
129
- const orgId = requireSegment(segments, 1, ref, 'orgId');
130
- if (segments.length >= 4 && segments[2] === 'projects') {
131
- const projectId = requireSegment(segments, 3, ref, 'projectId');
132
- // …/projects/<p>/apps/<a> — the App tier (most specific under a project).
133
- if (segments.length >= 6 && segments[4] === 'apps') {
134
- const appId = requireSegment(segments, 5, ref, 'appId');
135
- return {
136
- scope: {
137
- tier: SpaceKind.App,
138
- orgId,
139
- projectId,
140
- appId,
141
- },
142
- consumed: 6,
143
- };
144
- }
145
- return {
146
- scope: {
147
- tier: SpaceKind.Project,
148
- orgId,
149
- projectId,
150
- },
151
- consumed: 4,
152
- };
153
- }
154
- return {
155
- scope: { tier: SpaceKind.Org, orgId },
156
- consumed: 2,
157
- };
158
- }
159
- default:
160
- throw new XemaObjectRefParseError(
161
- ref,
162
- `unknown scope head "${head}" (expected one of: system, biomes, orgs, users, sessions)`,
163
- );
164
- }
165
- }
166
-
167
112
  /**
168
113
  * Parse a `XemaObjectRef` into structured parts. Fail-fast on every
169
114
  * malformed input (no silent coercion). When the `@<version>` suffix
@@ -210,7 +155,7 @@ export function parseXemaObjectRef(ref: string): XemaObjectRefParts {
210
155
  );
211
156
  }
212
157
 
213
- const { scope, consumed } = parseObjectRefScopeSegments(segments, ref);
158
+ const { scope, consumed } = parseScope(segments, ref);
214
159
  const remaining = segments.slice(consumed);
215
160
  if (remaining.length < 2) {
216
161
  throw new XemaObjectRefParseError(
@@ -1,4 +1,6 @@
1
1
  // Pure, zod-free contract — safe to bundle into the browser-side bridge.
2
2
  export * from './lib/output-surface-inspect';
3
+ // Shared bounded console/network ring buffers (also zod-free).
4
+ export * from './lib/buffers';
3
5
  // Runtime zod validator — pulled in by agent-session-api's broker only.
4
6
  export * from './lib/step-schema';
@@ -0,0 +1,252 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════
2
+ // buffers.ts — bounded console / network ring buffers.
3
+ //
4
+ // SHARED, not per-target. Both inspect executors observe continuously and let
5
+ // the agent pull windows on demand:
6
+ // • the in-page bridge (`output-surface-inspector/bridge`) patches console
7
+ // and fetch/XHR in the user's tab;
8
+ // • the governed headless executor subscribes to the equivalent
9
+ // browser-level events.
10
+ // A `console` / `network` step must mean exactly the same thing on both, so
11
+ // the ring eviction, the consecutive-duplicate collapse, the filtering and the
12
+ // `{entries, collected, returned, dropped, truncated, cursor}` envelope live
13
+ // here once instead of being reimplemented per target.
14
+ //
15
+ // Deliberately zod-free and DOM-free so the browser bridge can bundle it.
16
+ // ═══════════════════════════════════════════════════════════════════════════
17
+
18
+ import {
19
+ clampInspectReadLimit,
20
+ INSPECT_BUFFER_MAX_BYTES,
21
+ INSPECT_BUFFER_MAX_ENTRIES,
22
+ INSPECT_CONSOLE_LEVEL_VALUES,
23
+ INSPECT_ENTRY_MAX_BYTES,
24
+ InspectConsoleLevel,
25
+ } from './output-surface-inspect';
26
+ import type {
27
+ InspectConsoleEntry,
28
+ InspectNetworkEntry,
29
+ InspectReadResult,
30
+ OutputSurfaceRuntimeErrorItem,
31
+ } from './output-surface-inspect';
32
+
33
+ function clip(text: string, max: number): string {
34
+ if (text.length <= max) {
35
+ return text;
36
+ }
37
+ return `${text.slice(0, max)}…[+${text.length - max} chars]`;
38
+ }
39
+
40
+ // Mutable entries — `cursor` is a monotonic id assigned on insert; `count`
41
+ // and `lastAt` mutate when consecutive identical console lines collapse.
42
+ interface ConsoleRecord extends Omit<InspectConsoleEntry, 'count' | 'lastAt'> {
43
+ cursor: number;
44
+ count: number;
45
+ lastAt: number;
46
+ }
47
+ interface NetworkRecord extends InspectNetworkEntry {
48
+ cursor: number;
49
+ }
50
+
51
+ abstract class RingBuffer<T extends { cursor: number }> {
52
+ protected entries: T[] = [];
53
+ protected nextCursor = 1;
54
+ /** Entries evicted by the ring (so reads can report `dropped`). */
55
+ protected evicted = 0;
56
+ private bytes = 0;
57
+
58
+ protected sizeOf(entry: T): number {
59
+ try {
60
+ return JSON.stringify(entry).length;
61
+ } catch {
62
+ return INSPECT_ENTRY_MAX_BYTES;
63
+ }
64
+ }
65
+
66
+ protected append(entry: T): void {
67
+ entry.cursor = this.nextCursor++;
68
+ this.entries.push(entry);
69
+ this.bytes += this.sizeOf(entry);
70
+ while (
71
+ this.entries.length > INSPECT_BUFFER_MAX_ENTRIES ||
72
+ (this.bytes > INSPECT_BUFFER_MAX_BYTES && this.entries.length > 1)
73
+ ) {
74
+ const dropped = this.entries.shift();
75
+ if (!dropped) {
76
+ break;
77
+ }
78
+ this.bytes -= this.sizeOf(dropped);
79
+ this.evicted += 1;
80
+ }
81
+ }
82
+
83
+ /** Total entries ever held minus what is currently retained. */
84
+ get totalCollected(): number {
85
+ return this.entries.length + this.evicted;
86
+ }
87
+ }
88
+
89
+ /** Filters shared by both windowed reads. `limit` is clamped by the buffer. */
90
+ export interface InspectBufferReadOptions {
91
+ sinceMs?: number | undefined;
92
+ sinceCursor?: number | undefined;
93
+ grep?: string | undefined;
94
+ limit?: number | undefined;
95
+ }
96
+
97
+ export interface InspectConsoleReadOptions extends InspectBufferReadOptions {
98
+ levels?: readonly InspectConsoleLevel[] | undefined;
99
+ }
100
+
101
+ export interface InspectNetworkReadOptions extends InspectBufferReadOptions {
102
+ failedOnly?: boolean | undefined;
103
+ }
104
+
105
+ /**
106
+ * Drop the internal `cursor` bookkeeping field. The cursor is a buffer detail —
107
+ * it is reported ONCE per read (as the envelope's `cursor`), never per entry.
108
+ */
109
+ function withoutCursor<TRecord extends { cursor: number }>(
110
+ record: TRecord,
111
+ ): Omit<TRecord, 'cursor'> {
112
+ const copy: Record<string, unknown> = { ...record };
113
+ delete copy.cursor;
114
+ return copy as Omit<TRecord, 'cursor'>;
115
+ }
116
+
117
+ /** Assemble the shared read envelope from a filtered set. */
118
+ function envelope<TEntry, TRecord extends { cursor: number }>(
119
+ all: readonly TRecord[],
120
+ matched: readonly TRecord[],
121
+ limit: number,
122
+ collected: number,
123
+ evicted: number,
124
+ sinceCursor: number | undefined,
125
+ ): InspectReadResult<TEntry> {
126
+ const window = matched.slice(-limit);
127
+ const maxCursor = all.reduce((m, e) => Math.max(m, e.cursor), sinceCursor ?? 0);
128
+ return {
129
+ entries: window.map((record) => withoutCursor(record) as TEntry),
130
+ collected,
131
+ returned: window.length,
132
+ dropped: evicted + (matched.length - window.length),
133
+ truncated: matched.length > window.length,
134
+ cursor: maxCursor,
135
+ };
136
+ }
137
+
138
+ export class ConsoleBuffer extends RingBuffer<ConsoleRecord> {
139
+ push(input: {
140
+ level: InspectConsoleLevel;
141
+ text: string;
142
+ source: InspectConsoleEntry['source'];
143
+ }): void {
144
+ const text = clip(input.text, INSPECT_ENTRY_MAX_BYTES);
145
+ const now = Date.now();
146
+ const last = this.entries[this.entries.length - 1];
147
+ // Collapse consecutive identical lines (level+source+text) into a count.
148
+ if (
149
+ last !== undefined &&
150
+ last.level === input.level &&
151
+ last.source === input.source &&
152
+ last.text === text
153
+ ) {
154
+ last.count += 1;
155
+ last.lastAt = now;
156
+ return;
157
+ }
158
+ this.append({
159
+ cursor: 0,
160
+ level: input.level,
161
+ text,
162
+ source: input.source,
163
+ count: 1,
164
+ firstAt: now,
165
+ lastAt: now,
166
+ });
167
+ }
168
+
169
+ /** Bounded error summary for the passive `xema:runtime-errors` push. */
170
+ errorSummary(maxDistinct: number): {
171
+ total: number;
172
+ top: OutputSurfaceRuntimeErrorItem[];
173
+ } {
174
+ const errors = this.entries.filter(
175
+ (e) => e.level === InspectConsoleLevel.Error,
176
+ );
177
+ const total = errors.reduce((sum, e) => sum + e.count, 0);
178
+ const top = [...errors]
179
+ .sort((a, b) => b.count - a.count)
180
+ .slice(0, maxDistinct)
181
+ .map((e) => ({ text: e.text, count: e.count, source: e.source }));
182
+ return { total, top };
183
+ }
184
+
185
+ read(opts: InspectConsoleReadOptions): InspectReadResult<InspectConsoleEntry> {
186
+ const cutoff = opts.sinceMs ? Date.now() - opts.sinceMs : 0;
187
+ const grep = opts.grep?.toLowerCase();
188
+ const levels =
189
+ opts.levels && opts.levels.length > 0
190
+ ? opts.levels
191
+ : INSPECT_CONSOLE_LEVEL_VALUES;
192
+ const matched = this.entries.filter(
193
+ (e) =>
194
+ e.lastAt >= cutoff &&
195
+ (opts.sinceCursor === undefined || e.cursor > opts.sinceCursor) &&
196
+ levels.includes(e.level) &&
197
+ (!grep || e.text.toLowerCase().includes(grep)),
198
+ );
199
+ return envelope<InspectConsoleEntry, ConsoleRecord>(
200
+ this.entries,
201
+ matched,
202
+ clampInspectReadLimit(opts.limit),
203
+ this.totalCollected,
204
+ this.evicted,
205
+ opts.sinceCursor,
206
+ );
207
+ }
208
+ }
209
+
210
+ export class NetworkBuffer extends RingBuffer<NetworkRecord> {
211
+ push(input: InspectNetworkEntry): void {
212
+ this.append({ cursor: 0, ...input });
213
+ }
214
+
215
+ /** Update the most recent matching in-flight request (by url+method). */
216
+ complete(
217
+ url: string,
218
+ method: string | undefined,
219
+ patch: Partial<InspectNetworkEntry>,
220
+ ): void {
221
+ for (let i = this.entries.length - 1; i >= 0; i--) {
222
+ const e = this.entries[i];
223
+ if (e === undefined) {
224
+ continue;
225
+ }
226
+ if (e.url === url && e.method === method && e.durationMs === undefined) {
227
+ Object.assign(e, patch);
228
+ return;
229
+ }
230
+ }
231
+ }
232
+
233
+ read(opts: InspectNetworkReadOptions): InspectReadResult<InspectNetworkEntry> {
234
+ const cutoff = opts.sinceMs ? Date.now() - opts.sinceMs : 0;
235
+ const grep = opts.grep?.toLowerCase();
236
+ const matched = this.entries.filter(
237
+ (e) =>
238
+ e.startedAt >= cutoff &&
239
+ (opts.sinceCursor === undefined || e.cursor > opts.sinceCursor) &&
240
+ (!grep || e.url.toLowerCase().includes(grep)) &&
241
+ (!opts.failedOnly || e.failed === true || (e.status ?? 0) >= 400),
242
+ );
243
+ return envelope<InspectNetworkEntry, NetworkRecord>(
244
+ this.entries,
245
+ matched,
246
+ clampInspectReadLimit(opts.limit),
247
+ this.totalCollected,
248
+ this.evicted,
249
+ opts.sinceCursor,
250
+ );
251
+ }
252
+ }
@@ -21,10 +21,19 @@
21
21
  // ─── Target backends ────────────────────────────────────────────────────────
22
22
 
23
23
  /**
24
- * Which browser the inspect batch runs against. Closed set. `USER_TAB` is the
25
- * only implemented provider; `HEADLESS` is reserved for a future background
26
- * navigator that drives its own Playwright browser adding it must not change
27
- * any other type in this file.
24
+ * Which browser the inspect batch runs against. Closed set.
25
+ *
26
+ * `UserTab` the user's own live output-surface tab. The broker relays each
27
+ * step to the injected bridge and results return out-of-band. The tab is
28
+ * ATTACHED: it already shows a page, and it can never leave the project
29
+ * origin (doing so loses the bridge — see {@link InspectStepStatus.OffDomain}).
30
+ * • `Headless` — a governed headless browser the platform opens for the batch
31
+ * (`web-access-api`). It is SELF-OPENED: there is no pre-existing page, so
32
+ * the batch MUST begin with a `navigate` step carrying an absolute `url`, and
33
+ * an off-origin page is inspectable instead of being a dead end.
34
+ *
35
+ * The step language, the limits, the statuses and {@link InspectBatchResult}
36
+ * are IDENTICAL for both — only the transport differs.
28
37
  */
29
38
  export enum InspectTargetKind {
30
39
  UserTab = 'user-tab',
@@ -143,10 +152,32 @@ export interface InspectWindowedRead {
143
152
  limit?: number | undefined;
144
153
  }
145
154
 
155
+ /**
156
+ * Go somewhere. Carries EXACTLY ONE of `path` or `url` — never both, never
157
+ * neither (enforced by `InspectStepSchema`, not by convention).
158
+ *
159
+ * The two are deliberately separate fields rather than one overloaded string:
160
+ * a target that cannot leave its origin (`InspectTargetKind.UserTab`) must be
161
+ * able to REFUSE an absolute navigation fail-fast, which is impossible if the
162
+ * caller's intent is hidden inside the syntax of a single value.
163
+ */
146
164
  export interface InspectNavigateStep {
147
165
  kind: InspectStepKind.Navigate;
148
- /** Path within the output surface app, e.g. `/checkout`. Not an absolute URL. */
149
- path: string;
166
+ /**
167
+ * Origin-RELATIVE path, e.g. `/checkout`, resolved against the page the step
168
+ * runs on. Requires a page to already be open, so it can never be the FIRST
169
+ * step of a self-opened (`Headless`) batch — there is no origin to resolve
170
+ * against yet.
171
+ */
172
+ path?: string | undefined;
173
+ /**
174
+ * ABSOLUTE `http(s)` URL. Only a target that opens its OWN browser
175
+ * (`InspectTargetKind.Headless`) can honour it: an attached user tab that
176
+ * left the project origin would lose the injected bridge, so the broker
177
+ * refuses a `url` navigate on `InspectTargetKind.UserTab` with an actionable
178
+ * error rather than producing an `off-domain` dead end.
179
+ */
180
+ url?: string | undefined;
150
181
  }
151
182
  export interface InspectReloadStep {
152
183
  kind: InspectStepKind.Reload;
@@ -417,6 +448,23 @@ export const INSPECT_BATCH_RESULT_MAX_BYTES = 32 * 1024;
417
448
  export const INSPECT_READ_DEFAULT_LIMIT = 50;
418
449
  export const INSPECT_READ_MAX_LIMIT = 200;
419
450
 
451
+ /**
452
+ * Max nodes ONE `snapshot` step returns. Shared by every target's executor so
453
+ * a snapshot means the same thing in the user's tab and in the headless
454
+ * browser — the agent's `ref` budget must not depend on which target ran.
455
+ */
456
+ export const INSPECT_SNAPSHOT_MAX_NODES = 200;
457
+ /**
458
+ * The elements a `snapshot` step collects: interactive controls plus the
459
+ * landmark/heading structure that makes the outline navigable. Shared for the
460
+ * same reason as {@link INSPECT_SNAPSHOT_MAX_NODES}.
461
+ */
462
+ export const INSPECT_SNAPSHOT_SELECTOR =
463
+ 'a[href],button,input,textarea,select,[role],[onclick],' +
464
+ 'h1,h2,h3,h4,h5,h6,nav,main,header,footer,form,[data-comment-id]';
465
+ /** Attribute an executor stamps on snapshotted elements to mint a stable `ref`. */
466
+ export const INSPECT_SNAPSHOT_REF_ATTR = 'data-xema-inspect-ref';
467
+
420
468
  /** True when the step is a navigation barrier (later steps run post-load). */
421
469
  export function isInspectNavigationStep(kind: InspectStepKind): boolean {
422
470
  return (
@@ -425,3 +473,14 @@ export function isInspectNavigationStep(kind: InspectStepKind): boolean {
425
473
  kind === InspectStepKind.Back
426
474
  );
427
475
  }
476
+
477
+ /**
478
+ * Resolve a windowed read's `limit` against the shared default/ceiling. One
479
+ * implementation so every target clamps identically.
480
+ */
481
+ export function clampInspectReadLimit(limit: number | undefined): number {
482
+ if (limit === undefined || limit <= 0) {
483
+ return INSPECT_READ_DEFAULT_LIMIT;
484
+ }
485
+ return Math.min(limit, INSPECT_READ_MAX_LIMIT);
486
+ }
@@ -33,11 +33,23 @@ const windowedReadShape = {
33
33
  limit: z.number().int().positive().optional(),
34
34
  };
35
35
 
36
- export const InspectStepSchema = z.discriminatedUnion('kind', [
36
+ /**
37
+ * Absolute-URL matcher for `navigate.url`. A deliberate literal scheme check
38
+ * rather than a generic "is this a URL" helper: the ONLY schemes an inspect
39
+ * target may open are http(s), and the service re-parses the value anyway.
40
+ */
41
+ const ABSOLUTE_HTTP_URL = /^https?:\/\//i;
42
+
43
+ const InspectStepVariantSchema = z.discriminatedUnion('kind', [
37
44
  z.object({
38
45
  kind: z.literal(InspectStepKind.Navigate),
39
- /** Path within the output surface app, e.g. `/checkout`. Not an absolute URL. */
40
- path: z.string().min(1).max(2000),
46
+ /**
47
+ * Origin-relative path, e.g. `/checkout`. Mutually exclusive with `url`
48
+ * (see the exactly-one refinement below).
49
+ */
50
+ path: z.string().min(1).max(2000).optional(),
51
+ /** Absolute http(s) URL. Only a self-opened target can honour it. */
52
+ url: z.string().min(1).max(2000).regex(ABSOLUTE_HTTP_URL).optional(),
41
53
  }),
42
54
  z.object({ kind: z.literal(InspectStepKind.Reload) }),
43
55
  z.object({ kind: z.literal(InspectStepKind.Back) }),
@@ -96,6 +108,27 @@ export const InspectStepSchema = z.discriminatedUnion('kind', [
96
108
  }),
97
109
  ]);
98
110
 
111
+ /**
112
+ * The step validator.
113
+ *
114
+ * The `navigate` verb carries EXACTLY ONE of `path` / `url`. That is enforced
115
+ * here, at the single source of truth, so no executor has to guess what an
116
+ * empty or doubly-specified navigate means. `path` and `url` are NOT
117
+ * interchangeable spellings of the same thing: `path` is an in-origin move a
118
+ * user tab can follow, `url` is an origin change only a self-opened
119
+ * (headless) target can honour.
120
+ */
121
+ export const InspectStepSchema = InspectStepVariantSchema.refine(
122
+ (step) =>
123
+ step.kind !== InspectStepKind.Navigate ||
124
+ (step.path === undefined) !== (step.url === undefined),
125
+ {
126
+ message:
127
+ 'A "navigate" step must carry exactly one of `path` (origin-relative, e.g. "/checkout") ' +
128
+ 'or `url` (absolute http(s), only honoured by a self-opened target such as "headless").',
129
+ },
130
+ );
131
+
99
132
  /**
100
133
  * Compile-time drift guard: the schema and the hand-authored `InspectStep`
101
134
  * union MUST stay mutually assignable. Add a step kind (or change a field) on
@@ -5,3 +5,4 @@
5
5
  // `step-schema` validator that the main `output-surface-inspect` entry also
6
6
  // re-exports. This `/runtime` subpath is that browser-safe surface.
7
7
  export * from '../lib/output-surface-inspect';
8
+ export * from '../lib/buffers';
@@ -16,4 +16,5 @@
16
16
  // contributed (declarative); INSTANCES are event-fed (runtime).
17
17
  // ═══════════════════════════════════════════════════════════════════════════
18
18
 
19
+ export * from './lib/route-template';
19
20
  export * from './lib/search-contribution';