@xenosystem/blocks 0.6.0 โ†’ 0.8.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.
@@ -0,0 +1,568 @@
1
+ import { PanelModule, PanelManifest } from '@xenosystem/block-sdk';
2
+ import { a as XenoAgentEvent, b as XenoAgentTurnState, A as AgentViewState, X as XenoAgentSessionDelta, c as XenoAgentCatalog, d as XenoAgentConnection, e as AgentPanelState } from '../types-C5T3uVwd.js';
3
+ export { f as XenoAgentAskEvent, g as XenoAgentAskWithdrawEvent, h as XenoAgentError, i as XenoAgentIntent, j as XenoAgentLane, k as XenoAgentMessageEvent, l as XenoAgentModel, m as XenoAgentPatchEvent, n as XenoAgentPatchFile, o as XenoAgentProvider, p as XenoAgentRole, q as XenoAgentSession, r as XenoAgentSessionClosedEvent, s as XenoAgentSessionEvent, t as XenoAgentStatus, u as XenoAgentTerminalEvent, v as XenoAgentToolEvent, w as XenoAgentTurnEvent, x as isBusy, y as isWaitingOnUser } from '../types-C5T3uVwd.js';
4
+ import { ReactNode } from 'react';
5
+
6
+ /**
7
+ * The translation: ONE agent session-event stream โ†’ the canonical panels that already render it.
8
+ *
9
+ * ## ๐Ÿ”ด This file is the reason the package exists
10
+ *
11
+ * `XENO AGENT PANEL - SPEC.md` ยงD3 is explicit: **no agent logic in the panel**, and the display
12
+ * work belongs to `runs`, `console`, `consent`, `diff` and `terminal`. What it does not say โ€” and
13
+ * what building it made obvious โ€” is that *something* has to turn one stream into five, and turn
14
+ * five answers back into one. That translation is the whole of the agent-specific work, it is
15
+ * framework-free, and it is the part every one of the four bespoke agent UIs wrote for itself.
16
+ *
17
+ * So this module is pure and testable in Node with no DOM, no React and no panel dependency: it
18
+ * takes events in and returns `{portId, value}` emissions out. Everything it emits is a shape one
19
+ * of the canonical panels already declares โ€” see `WIRING.md` for the port-by-port map, and
20
+ * `test/wiring.test.ts` for the gate that checks the two agree against those panels' real
21
+ * manifests rather than against this comment.
22
+ *
23
+ * ## The correlation, and why dropping is the safe answer
24
+ *
25
+ * Every panel downstream answers in its own vocabulary and **none of them carries a session id**,
26
+ * because none of them knows sessions exist. This module holds the maps that put one back โ€” and
27
+ * when a map misses, it **drops the answer and counts it** rather than attaching it to whatever
28
+ * session happens to be active. A consent decision routed to the wrong session applies a human's
29
+ * approval to work they never saw; there is no reading of "be helpful" that survives that.
30
+ *
31
+ * @module
32
+ */
33
+
34
+ /**
35
+ * The output ports this module drives.
36
+ *
37
+ * โš ๏ธ These strings are also the manifest's output port ids and the `from` half of every wire in
38
+ * `WIRING.md`. They are declared once, here, and the manifest derives from them โ€” a port id
39
+ * restated in three places is the three-place edit `xeno.core.runs` records as a real defect.
40
+ */
41
+ declare const AGENT_FANOUT_PORTS: readonly ["runs", "records", "elicitations", "withdraw", "diffModel", "terminalData", "terminalSession"];
42
+ /** One of the fan-out ports. */
43
+ type AgentFanoutPort = (typeof AGENT_FANOUT_PORTS)[number];
44
+ /** A value bound for one port. */
45
+ interface AgentEmission {
46
+ portId: AgentFanoutPort;
47
+ value: unknown;
48
+ }
49
+ /** Map a turn or tool state onto the run panel's lifecycle vocabulary. */
50
+ declare function turnStatusToRunStatus(state: XenoAgentTurnState): string;
51
+ /** Construction options. */
52
+ interface AgentProjectorOptions {
53
+ /** Injected so a test can assert timestamps instead of tolerating them. */
54
+ now?: () => number;
55
+ }
56
+ /**
57
+ * Turns agent events into panel deltas, and panel answers back into addressed intents.
58
+ *
59
+ * Stateful, and only as stateful as correlation requires: which turn belongs to which session,
60
+ * where a tool call sits in its run's step tree, which session asked a question, and which change
61
+ * set the diff panel is currently showing. **It holds no transcript, no run list and no log
62
+ * buffer** โ€” those live in the panels that render them, which is the point of not reimplementing
63
+ * them here.
64
+ */
65
+ declare class AgentProjector {
66
+ private readonly now;
67
+ /** turnId โ†’ sessionId. The address a run action comes back on. */
68
+ private readonly turnSession;
69
+ /** callId โ†’ the step path inside its run, so a nested call can be patched in place. */
70
+ private readonly callPath;
71
+ /** messageId โ†’ seen, so the second event for a message patches rather than re-appends. */
72
+ private readonly messages;
73
+ /** askId โ†’ sessionId. */
74
+ private readonly askSession;
75
+ /** terminalId โ†’ sessionId. */
76
+ private readonly terminalSession;
77
+ /**
78
+ * File path โ†’ the change set it belongs to.
79
+ *
80
+ * โš ๏ธ **Replaced wholesale on every `patch` event, because the diff panel's model is too.** The
81
+ * map and the thing it describes move together, so a decision can never address a change set the
82
+ * user is no longer looking at.
83
+ */
84
+ private patchPaths;
85
+ /**
86
+ * Answers that addressed nothing known, since the last clear.
87
+ *
88
+ * Surfaced rather than swallowed, for the reason `xeno.core.console` surfaces its dropped
89
+ * patches: a mis-wired return path and a dead one look identical from the outside, and this is
90
+ * the one number that tells them apart.
91
+ */
92
+ private dropped;
93
+ constructor(options?: AgentProjectorOptions);
94
+ /** How many answers were dropped for want of a correlation. */
95
+ get droppedAnswers(): number;
96
+ /** Forget everything. Used when the host replaces or clears the session index. */
97
+ reset(): void;
98
+ /** How many asks are still outstanding. */
99
+ get pendingAsks(): number;
100
+ /** Which session an ask belongs to, or `undefined`. */
101
+ sessionForAsk(askId: string): string | undefined;
102
+ /** Which session a turn belongs to, or `undefined`. */
103
+ sessionForTurn(turnId: string): string | undefined;
104
+ /** Which session a terminal belongs to, or `undefined`. */
105
+ sessionForTerminal(terminalId: string): string | undefined;
106
+ /** Which change set a reviewed file belongs to, or `undefined`. */
107
+ patchForPath(path: string): {
108
+ sessionId: string;
109
+ patchId: string;
110
+ } | undefined;
111
+ /** Record that an ask has been settled, so a second answer for it is dropped rather than resent. */
112
+ settleAsk(askId: string): void;
113
+ /** Count an answer that could not be correlated. */
114
+ dropAnswer(): void;
115
+ /**
116
+ * Project one event.
117
+ *
118
+ * @param event - The event.
119
+ * @returns Zero or more emissions, in the order they must be delivered. **Order matters**: a
120
+ * console record must be appended before it is patched, and a step must be announced before it
121
+ * is addressed โ€” both panels drop an update for something they do not hold.
122
+ */
123
+ project(event: XenoAgentEvent): AgentEmission[];
124
+ /** One file of a change set โ†’ the diff panel's file shape. */
125
+ private projectFile;
126
+ }
127
+
128
+ /**
129
+ * The controller โ€” framework-free. No React, no DOM.
130
+ *
131
+ * It owns three things and nothing else:
132
+ *
133
+ * 1. **The session index** โ€” which conversations exist, which one is shown, what state its turn is
134
+ * in. Small, and genuinely agent-specific.
135
+ * 2. **The composer** โ€” the user's unsent text, and the single predicate that decides whether it
136
+ * can be sent.
137
+ * 3. **The translation**, delegated to {@link AgentProjector}: events fan out to the canonical
138
+ * panels, and their answers come back correlated to a session.
139
+ *
140
+ * ๐Ÿ”ด **It holds no transcript, no run list, no log buffer and no diff.** Those belong to
141
+ * `xeno.core.console`, `xeno.core.runs` and `xeno.core.diff`, which already ring-buffer, filter,
142
+ * virtualize and redact them. Keeping a second copy here would be the reimplementation the
143
+ * governing spec's ยงD3 forbids, and it would be the copy without the ring buffer.
144
+ *
145
+ * @module
146
+ */
147
+
148
+ /** The host seam. Narrow on purpose: a test double is one function. */
149
+ interface AgentHostBridge {
150
+ emit(portId: string, value: unknown): void;
151
+ }
152
+ /** Construction options. */
153
+ interface AgentControllerOptions {
154
+ host: AgentHostBridge;
155
+ /** Injected so a test can assert timestamps rather than tolerate them. */
156
+ now?: () => number;
157
+ }
158
+ /** The panel controller. */
159
+ declare class AgentController {
160
+ private readonly host;
161
+ private readonly projector;
162
+ private sessions;
163
+ private activeSessionId;
164
+ private connection;
165
+ private connectionMessage;
166
+ private catalog;
167
+ private draft;
168
+ /** Last delta revision applied. A delta whose rev has not advanced is dropped. */
169
+ private rev;
170
+ private readonly listeners;
171
+ /**
172
+ * Memoized view state.
173
+ *
174
+ * `useSyncExternalStore` compares by identity and loops forever if `getState` returns a fresh
175
+ * object each call. Every mutation clears this; nothing else may.
176
+ */
177
+ private snapshot;
178
+ constructor(options: AgentControllerOptions);
179
+ subscribe: (listener: () => void) => (() => void);
180
+ getState: () => AgentViewState;
181
+ private notify;
182
+ private fanOut;
183
+ /** Emit the one thing the host acts on. */
184
+ private intent;
185
+ /**
186
+ * Apply a session delta.
187
+ *
188
+ * The host is authoritative: sessions are upserted, never merged cleverly, and the panel keeps no
189
+ * "better" older value. A panel that second-guesses its host is a second source of truth.
190
+ *
191
+ * @param delta - The delta.
192
+ */
193
+ ingest(delta: XenoAgentSessionDelta): void;
194
+ private upsertSession;
195
+ private setTurnState;
196
+ /** Install the provider catalogue. The host decides what is selectable; the panel renders it. */
197
+ setCatalog(catalog: XenoAgentCatalog): void;
198
+ /**
199
+ * Report the connection out of band.
200
+ *
201
+ * Separate from the session delta so a host never has to express "not connected" as an empty
202
+ * session list โ€” the distinction `xeno.core.runs` and `xeno.core.diff` both draw, and for the
203
+ * same reason: zero sessions means "there are none" only when someone looked.
204
+ */
205
+ setStatus(status: XenoAgentConnection, message?: string): void;
206
+ setDraft(text: string): void;
207
+ /**
208
+ * Would {@link submit} do anything?
209
+ *
210
+ * ๐Ÿ”ด The view's `disabled` derives from THIS, so an enabled button cannot be a button that
211
+ * refuses. The affordance and the guard are one predicate.
212
+ *
213
+ * โš ๏ธ `connection === 'idle'` does NOT block. Idle means *the host has never reported a
214
+ * connection state*, and inventing a blocker out of silence would make the panel unusable in
215
+ * every host that does not wire the optional `status` port. Silence is not a refusal; the host
216
+ * refuses if it must.
217
+ */
218
+ private canSubmit;
219
+ /**
220
+ * Ask the host to run the draft.
221
+ *
222
+ * ๐Ÿ”ด **The draft is NOT cleared here.** The panel does not know the turn started โ€” the host may
223
+ * refuse, the provider may be unavailable, a permission check may reject it. Clearing on emit
224
+ * would show success before anything settled, which is a claim the panel cannot back; the draft
225
+ * clears when the host reports a `running` turn. (`panel-template`'s rule, applied to the one
226
+ * place in this catalog where a user would notice losing their text.)
227
+ *
228
+ * @returns Whether an intent was emitted.
229
+ */
230
+ submit(): boolean;
231
+ /**
232
+ * Ask the host to steer the running turn.
233
+ *
234
+ * โš ๏ธ **Steering lands at the agent's next decision point, not instantly.** That was measured
235
+ * against real Claude Code and recorded in the ADE spec, and it is why this is a separate verb
236
+ * from {@link submit} rather than "submit while busy": the two have different latencies and
237
+ * different failure modes, and a control that hid the difference would be lying about which one
238
+ * the user got.
239
+ *
240
+ * @returns Whether an intent was emitted.
241
+ */
242
+ steer(): boolean;
243
+ /** Ask the host to stop the running turn. */
244
+ cancel(): boolean;
245
+ /** Show another session. Panel-local AND an intent โ€” the host may need to attach to it. */
246
+ selectSession(sessionId: string): boolean;
247
+ /** Ask the host to start a session. The panel does not create one โ€” it has no way to. */
248
+ newSession(providerId?: string, modelId?: string): void;
249
+ /**
250
+ * Ask the host to switch the active session's agent.
251
+ *
252
+ * Refused for a provider the host declared unavailable: the panel must not offer what the host
253
+ * will not honour, which is the rule `xeno.core.consent` applies to durations.
254
+ *
255
+ * @returns Whether an intent was emitted.
256
+ */
257
+ selectProvider(providerId: string, modelId?: string): boolean;
258
+ /**
259
+ * A consent decision or an elicitation result, from `xeno.core.consent`.
260
+ *
261
+ * ๐Ÿ”ด **Dropped when the id correlates to nothing.** A decision routed to a guessed session
262
+ * applies a human's approval to work they never saw. Both of consent's output ports arrive here
263
+ * because both are answers to an ask this panel minted, and the panel forwards each verbatim โ€”
264
+ * it does not read `decision`, and must not: interpreting an allow/deny would make this a second
265
+ * place where a grant is decided.
266
+ *
267
+ * @param answer - The decision or result, verbatim.
268
+ * @returns Whether an intent was emitted.
269
+ */
270
+ answer(answer: unknown): boolean;
271
+ /**
272
+ * A hunk decision, from `xeno.core.diff`.
273
+ *
274
+ * @param decision - `{path, hunkId, action, comment?}`, verbatim.
275
+ * @returns Whether an intent was emitted.
276
+ */
277
+ patchDecision(decision: unknown): boolean;
278
+ /**
279
+ * A run action, from `xeno.core.runs`.
280
+ *
281
+ * โš ๏ธ The targeted step is read from `stepPath`'s LAST element, falling back to `stepId`. Both are
282
+ * emitted together by that panel and mean the same thing at depth 1; only the path survives
283
+ * nesting, which is the case an agent's subagent calls produce.
284
+ *
285
+ * @param action - `{runId, action, stepId?, stepPath?}`, verbatim.
286
+ * @returns Whether an intent was emitted.
287
+ */
288
+ runAction(action: unknown): boolean;
289
+ /**
290
+ * A terminal intent, from `xeno.core.terminal`.
291
+ *
292
+ * @param intent - The terminal panel's intent, verbatim.
293
+ * @returns Whether an intent was emitted.
294
+ */
295
+ terminalIntent(intent: unknown): boolean;
296
+ /** Answers that could not be correlated, since the last reset. Surfaced, never swallowed. */
297
+ get droppedAnswers(): number;
298
+ /**
299
+ * Serialize.
300
+ *
301
+ * ๐Ÿ”ด **Preferences plus the unsent draft, and nothing else.** No transcript, no run list, no
302
+ * session content โ€” that is host state which has moved on, and `.xapp` is a plain JSON file that
303
+ * may contain nothing sensitive.
304
+ */
305
+ serialize(): AgentPanelState;
306
+ /** Restore preferences. Sessions are NOT restored; the host re-pushes them. */
307
+ deserialize(state: unknown): void;
308
+ /** Release everything. */
309
+ dispose(): void;
310
+ }
311
+
312
+ /**
313
+ * The `PanelModule`.
314
+ *
315
+ * Every line marked ๐Ÿ”ด below exists because its absence was a shipped bug somewhere in this
316
+ * catalog: ten panels shipped unguarded input casts, sixteen read `host.config` once and never
317
+ * looked again, several leaked on dispose. `panel-template` carries the full account.
318
+ *
319
+ * @module
320
+ */
321
+
322
+ /** Everything a renderer needs: the controller plus the resolved config. */
323
+ interface AgentRenderContext {
324
+ controller: AgentController;
325
+ config: {
326
+ submitOnEnter: boolean;
327
+ showLane: boolean;
328
+ showSessions: boolean;
329
+ emptyHint: string;
330
+ };
331
+ }
332
+ /** Options for {@link createAgentPanel}. */
333
+ interface CreateAgentPanelOptions {
334
+ /** Override the view. Rarely needed โ€” mounting inside the package keeps React singular. */
335
+ render?: (root: HTMLElement, context: AgentRenderContext) => () => void;
336
+ /** Injected clock, so a host or a test can make timestamps deterministic. */
337
+ now?: () => number;
338
+ }
339
+ /**
340
+ * Build the panel module.
341
+ *
342
+ * @param options - Optional renderer override and clock.
343
+ * @returns The module.
344
+ */
345
+ declare function createAgentPanel(options?: CreateAgentPanelOptions): PanelModule;
346
+ /** The default module โ€” view already wired. Register THIS, not the factory. */
347
+ declare const agentPanel: PanelModule;
348
+
349
+ /**
350
+ * The `xeno.core.agent` manifest.
351
+ *
352
+ * Capabilities: `storage.local` only. The panel runs nothing, spawns nothing, and grants nothing โ€”
353
+ * it emits intents and the host executes them. That is the same boundary `xeno.core.terminal` draws
354
+ * for a PTY, and it is what lets this panel run unchanged in an `iframe-quickjs` sandbox: it was
355
+ * never anything but a host-brokered stream.
356
+ *
357
+ * ## The port set, and why it is this shape
358
+ *
359
+ * ๐Ÿ”ด **Seven of the outputs are the canonical panels' input shapes.** The panel does not render a
360
+ * tool timeline, a log, a permission queue, a diff or a terminal โ€” five panels already do, better,
361
+ * with ring buffers and virtualization and redaction this one would have to reinvent. So the agent
362
+ * panel's job on that axis is to be a wire SOURCE, and `WIRING.md` is the map. A host that wires
363
+ * none of them still gets a working composer, session switcher and lane picker.
364
+ *
365
+ * @module
366
+ */
367
+
368
+ /** The canonical manifest id. */
369
+ declare const AGENT_PANEL_ID = "xeno.core.agent";
370
+ /**
371
+ * The shape tag for the `session` input.
372
+ *
373
+ * Coined here, and it belongs in `WELL_KNOWN_PORT_SCHEMAS` alongside the rest โ€” promoting it is a
374
+ * `@xenosystem/block-sdk` edit and therefore a coordinated cross-package change. โš ๏ธ The STRING must
375
+ * not change when it moves, or every wire built against it silently stops attaching.
376
+ */
377
+ declare const AGENT_SESSION_SCHEMA = "xeno.agentsession@1";
378
+ /** The shape tag for the `catalog` input. Same registration note as {@link AGENT_SESSION_SCHEMA}. */
379
+ declare const AGENT_CATALOG_SCHEMA = "xeno.agentcatalog@1";
380
+ /** The shape tag for the `intent` output. Same registration note. */
381
+ declare const AGENT_INTENT_SCHEMA = "xeno.agentintent@1";
382
+ /**
383
+ * `@xenosystem/panel-consent`'s elicitation tags, restated as literals.
384
+ *
385
+ * ๐Ÿ”ด **Not a copy of that panel's TYPES โ€” a copy of its two tag STRINGS**, which is the one thing
386
+ * that must be byte-identical for a wire to attach at all. They are declared in that package (not
387
+ * in the SDK registry) and this package takes no dependency on it, so the literal is the only route
388
+ * available. `test/wiring.test.ts` reads consent's real manifest and asserts these still match, so
389
+ * a rename there fails here instead of silently detaching the wire.
390
+ */
391
+ declare const CONSENT_ELICITATION_SCHEMA = "xeno.elicitation@1";
392
+ /** See {@link CONSENT_ELICITATION_SCHEMA}. */
393
+ declare const CONSENT_ELICITATION_RESULT_SCHEMA = "xeno.elicitationresult@1";
394
+ /** The `xeno.core.agent` manifest. */
395
+ declare const agentManifest: PanelManifest;
396
+
397
+ /**
398
+ * The composition, **as data**.
399
+ *
400
+ * ## The decision this file records
401
+ *
402
+ * `panel-agent` is a THIN panel plus a declared wiring, not one panel that internally renders the
403
+ * other five. Four reasons, and the first is the one that settles it:
404
+ *
405
+ * 1. **The workbench docks panels; a panel that mounts panels takes that away.** A user must be
406
+ * able to drag the log to the bottom, the diff to centre, and close the terminal. Nesting five
407
+ * panes inside one pane makes the dock model โ€” the entire reason `@xenosystem/workbench` exists
408
+ * โ€” unreachable for exactly the surface that needs it most.
409
+ * 2. **It would take five runtime dependencies**, which rule 8 forbids and which would impose the
410
+ * xterm bundle on every host that mounts an agent panel and never opens a terminal.
411
+ * 3. **Every panel already owns its own config, `serialize`, state store and instance id.** A
412
+ * nesting parent would have to re-implement the host's per-panel bookkeeping โ€” badly, because
413
+ * it cannot see the `.xapp`.
414
+ * 4. **The manifest would become the union of six panels' ports** and nobody could wire it.
415
+ *
416
+ * โš ๏ธ **The honest cost:** a host must place six panels and connect eleven wires rather than one
417
+ * panel and none. That cost is paid ONCE, in the ~40 lines of `INTEGRATION.md` ยง3, and it is why
418
+ * this file exists at all โ€” the wiring is shipped as data so a host applies it rather than deriving
419
+ * it, and `test/wiring.test.ts` checks every wire against the target panels' REAL manifests. A
420
+ * wire this file gets wrong fails in CI here, not silently in a host.
421
+ *
422
+ * @module
423
+ */
424
+ /** One end of a wire: a panel's manifest id and one of its port ids. */
425
+ interface AgentWireEnd {
426
+ /** The panel's MANIFEST id. A host with several instances substitutes its own instance ids. */
427
+ panel: string;
428
+ port: string;
429
+ }
430
+ /** One connection a host should make. */
431
+ interface AgentWire {
432
+ from: AgentWireEnd;
433
+ to: AgentWireEnd;
434
+ /** What breaks if this wire is missing. Rendered into `WIRING.md`; read it before skipping one. */
435
+ why: string;
436
+ /**
437
+ * The panel is optional and the agent surface still works without it.
438
+ *
439
+ * โš ๏ธ Only the two directions of ONE panel are ever optional together. Wiring an ask INTO consent
440
+ * without wiring its answer back leaves a user staring at a question whose Allow button does
441
+ * nothing โ€” worse than never showing the question, because it looks like the agent ignored them.
442
+ */
443
+ optional: boolean;
444
+ }
445
+ /** The canonical manifest ids this wiring targets. */
446
+ declare const AGENT_WIRING_TARGETS: {
447
+ readonly RUNS: "xeno.core.runs";
448
+ readonly CONSOLE: "xeno.core.console";
449
+ readonly CONSENT: "xeno.core.consent";
450
+ readonly DIFF: "xeno.core.diff";
451
+ readonly TERMINAL: "xeno.core.terminal";
452
+ };
453
+ /**
454
+ * Every wire between `xeno.core.agent` and the canonical panels.
455
+ *
456
+ * ๐Ÿ”ด **Both directions, always paired.** A one-way wiring is the failure mode this list is shaped
457
+ * to prevent: asks that cannot be answered, hunks that cannot be accepted, a terminal that renders
458
+ * output and swallows keystrokes.
459
+ */
460
+ declare const AGENT_PANEL_WIRING: readonly AgentWire[];
461
+ /**
462
+ * The wires a host must add to reach one panel, both directions.
463
+ *
464
+ * @param panelId - A manifest id from {@link AGENT_WIRING_TARGETS}.
465
+ * @returns Every wire touching it, in the order they should be created.
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * for (const wire of wiringFor('xeno.core.consent')) connect(wire.from, wire.to)
470
+ * ```
471
+ */
472
+ declare function wiringFor(panelId: string): AgentWire[];
473
+
474
+ /**
475
+ * A unified-diff parser, because the adapter is where one belongs.
476
+ *
477
+ * ## Why this is here and not in `xeno.core.diff`
478
+ *
479
+ * The diff panel ships **no diff engine, by charter** โ€” it renders `XenoDiffLine[]` that somebody
480
+ * else computed. The agent host on the other side emits raw unified-diff text. `XENO AGENT PANEL -
481
+ * SPEC.md` ยงD3 measured exactly that gap and gave the verdict: *"A parser must be written โ€”
482
+ * adapter-side, not a panel edit."* This package is the adapter, so this is the adapter side.
483
+ *
484
+ * ## What it does NOT do
485
+ *
486
+ * It does not compute a diff. It reads one that already exists. There is no LCS here, no word-level
487
+ * segmentation, and no file access โ€” a `+` line is an added line because the producer said so.
488
+ *
489
+ * โš ๏ธ **It also does not verify the diff against any file.** A hunk header claiming
490
+ * `@@ -41,3 +41,4 @@` is taken at its word; if the producer's arithmetic is wrong, the rendered
491
+ * line numbers are wrong in exactly the same way. Verifying would mean reading the user's files,
492
+ * which is a capability neither this package nor the diff panel has, and a syntactic check that
493
+ * happily passes a wrong-but-well-formed header would read like verification while proving nothing.
494
+ *
495
+ * @module
496
+ */
497
+ /** What happened to a line. Mirrors `XenoDiffLineKind`. */
498
+ type XenoDiffLineKind = 'context' | 'added' | 'removed';
499
+ /** One rendered line. Mirrors `XenoDiffLine`. */
500
+ interface XenoDiffLine {
501
+ kind: XenoDiffLineKind;
502
+ /** 1-based line number in the original. Absent on an added line. */
503
+ oldLineNumber?: number;
504
+ /** 1-based line number in the modified file. Absent on a removed line. */
505
+ newLineNumber?: number;
506
+ text: string;
507
+ }
508
+ /** A contiguous change region. Mirrors `XenoDiffHunk`. */
509
+ interface XenoDiffHunk {
510
+ id: string;
511
+ oldStart: number;
512
+ oldLines: number;
513
+ newStart: number;
514
+ newLines: number;
515
+ lines: XenoDiffLine[];
516
+ /** The raw `@@` header, kept verbatim โ€” it is what the producer said, and it reads well. */
517
+ header?: string;
518
+ }
519
+ /**
520
+ * Parse one file's unified diff into hunks.
521
+ *
522
+ * @param path - The file's path. Part of every hunk id, so two files' hunks cannot collide.
523
+ * @param diff - Unified diff text for THIS file. File headers are tolerated and skipped.
524
+ * @returns The hunks, in order. **`[]` when the text contains no hunk header at all** โ€” see the
525
+ * caller's note: an empty result is reported as `omitted: 'unreadable'`, never as "no changes".
526
+ *
527
+ * @example
528
+ * ```ts
529
+ * parseUnifiedDiff('src/app.ts', '@@ -1,2 +1,2 @@\n-const a = 1\n+const a = 2\n context')
530
+ * // โ†’ [{ id: 'src/app.ts#โ€ฆ', oldStart: 1, oldLines: 2, newStart: 1, newLines: 2, lines: [...] }]
531
+ * ```
532
+ */
533
+ declare function parseUnifiedDiff(path: string, diff: string): XenoDiffHunk[];
534
+ /** How many lines a hunk adds and removes. Cheap, and the diff panel renders per-file counts. */
535
+ declare function countChanges(hunks: readonly XenoDiffHunk[]): {
536
+ additions: number;
537
+ deletions: number;
538
+ };
539
+
540
+ /**
541
+ * The view โ€” thin. Every decision lives in the controller.
542
+ *
543
+ * Composes `@xenosystem/workbench/primitives`, so it inherits `DESIGN_SYSTEM.md` for free. The host
544
+ * must load `@xenosystem/workbench/primitives.css`; without it this renders unstyled with no error.
545
+ *
546
+ * ๐Ÿ”ด **What is NOT here is the point.** No transcript, no tool timeline, no diff, no terminal โ€” the
547
+ * canonical panels render those and this one wires to them (`WIRING.md`). What remains is the small
548
+ * amount of UI that is genuinely agent-specific: a composer, a lane and provider picker, and a
549
+ * session switcher.
550
+ *
551
+ * @module
552
+ */
553
+
554
+ /** Props for {@link AgentPanelView}. */
555
+ interface AgentPanelViewProps {
556
+ controller: AgentController;
557
+ /** Enter submits, Shift+Enter newlines. `false` swaps them. */
558
+ submitOnEnter?: boolean;
559
+ /** Render the lane badge. */
560
+ showLane?: boolean;
561
+ /** Render the session switcher. */
562
+ showSessions?: boolean;
563
+ emptyHint?: string;
564
+ }
565
+ /** The view. */
566
+ declare function AgentPanelView({ controller, submitOnEnter, showLane, showSessions, emptyHint, }: AgentPanelViewProps): ReactNode;
567
+
568
+ export { AGENT_CATALOG_SCHEMA, AGENT_FANOUT_PORTS, AGENT_INTENT_SCHEMA, AGENT_PANEL_ID, AGENT_PANEL_WIRING, AGENT_SESSION_SCHEMA, AGENT_WIRING_TARGETS, AgentController, type AgentControllerOptions, type AgentEmission, type AgentFanoutPort, type AgentHostBridge, AgentPanelState, AgentPanelView, type AgentPanelViewProps, AgentProjector, type AgentProjectorOptions, type AgentRenderContext, AgentViewState, type AgentWire, type AgentWireEnd, CONSENT_ELICITATION_RESULT_SCHEMA, CONSENT_ELICITATION_SCHEMA, type CreateAgentPanelOptions, XenoAgentCatalog, XenoAgentConnection, XenoAgentEvent, XenoAgentSessionDelta, XenoAgentTurnState, type XenoDiffHunk, type XenoDiffLine, type XenoDiffLineKind, agentManifest, agentPanel, countChanges, createAgentPanel, parseUnifiedDiff, turnStatusToRunStatus, wiringFor };