@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,425 @@
1
+ /**
2
+ * The `xeno.core.agent` contract โ€” **the session-event shape this panel consumes.**
3
+ *
4
+ * ## ๐Ÿ”ด Read this before comparing it to `xeno-agent-interface`
5
+ *
6
+ * This file is NOT a copy of that repo's types, and deliberately so. `AgentHostEvent.payload` is
7
+ * declared `unknown` there and session events ride `event: Record<string, unknown>`, so there is
8
+ * nothing to import and nothing a compatibility claim could be checked against โ€” the governing
9
+ * spec says exactly that (`XENO AGENT PANEL - SPEC.md` ยงD3: *"none of it is typecheckable today"*).
10
+ *
11
+ * So this is **the panel declaring its own input port contract**, which is how every other panel in
12
+ * this catalog works: `xeno.core.runs` did not import a workflow's execution model, it declared
13
+ * `XenoRunDelta` and hosts adapt. The obligation this creates is the host's and it is stated
14
+ * plainly in `INTEGRATION.md`: *the host adapts its stream to this shape.* When the host's payloads
15
+ * are eventually typed, the adapter that already has to exist becomes a typed function instead of
16
+ * an untyped one โ€” nothing here has to change, and nothing here was ever a guess about a shape
17
+ * somebody else owns.
18
+ *
19
+ * ## What is deliberately NOT modelled here
20
+ *
21
+ * A permission, a diff and a terminal write already have canonical shapes, in
22
+ * `@xenosystem/panel-consent`, `@xenosystem/panel-diff` and `@xenosystem/panel-terminal`. Restating
23
+ * them would create a second copy of somebody else's contract โ€” the exact failure
24
+ * `WELL_KNOWN_PORT_SCHEMAS.QUERY_REQUEST` records โ€” so those payloads are **forwarded verbatim**
25
+ * and typed here only by the fields this package itself reads. Where a shape is described as
26
+ * "forwarded verbatim", the owning panel's contract is the authority and this one is a courier.
27
+ *
28
+ * @module
29
+ */
30
+ /**
31
+ * Which execution lane runs a turn.
32
+ *
33
+ * Three peers behind one seam, matching the three `AgentTurnExecutionAdapter` implementations that
34
+ * ship today (`XENO AGENT PANEL - SPEC.md` ยงD2). โš ๏ธ They are NOT layered over one another: `cloud`
35
+ * and `local` never touch ACP, and a panel that presented them as a hierarchy would misdescribe
36
+ * the only architectural fact a user needs from this control โ€” where their code and their credits
37
+ * go.
38
+ */
39
+ type XenoAgentLane = 'cloud' | 'local' | 'acp';
40
+ /** One model a provider offers. */
41
+ interface XenoAgentModel {
42
+ id: string;
43
+ label: string;
44
+ /** Free-form host copy ("128k context", "reasoning"). Rendered verbatim, never parsed. */
45
+ note?: string;
46
+ }
47
+ /**
48
+ * One selectable agent.
49
+ *
50
+ * ๐Ÿ”ด **`unavailable` is a sentence, not a boolean.** The provider registry ships runnable providers
51
+ * beside placeholders the capability gate refuses, and "why can I not pick this one?" is the whole
52
+ * question a user has when they see one greyed out. A boolean answers it with silence.
53
+ */
54
+ interface XenoAgentProvider {
55
+ id: string;
56
+ label: string;
57
+ lane: XenoAgentLane;
58
+ models?: XenoAgentModel[];
59
+ /** Host-authored reason the provider cannot be selected. Absent โ‡’ runnable. */
60
+ unavailable?: string;
61
+ }
62
+ /** The providers the host will accept, and what is currently selected. */
63
+ interface XenoAgentCatalog {
64
+ providers: XenoAgentProvider[];
65
+ /** Selected provider id. The HOST is authoritative โ€” the panel never selects on its own. */
66
+ activeProviderId?: string;
67
+ activeModelId?: string;
68
+ }
69
+ /**
70
+ * Where a turn is.
71
+ *
72
+ * โš ๏ธ Chosen so the mapping onto `XenoRunStatus` is total and mechanical (`turnStatusToRunStatus` in
73
+ * `./projections`). The three that used to have no run-panel equivalent โ€” a turn that has been
74
+ * asked for but not started, one stopped on a person, one stopped on a grant โ€” are exactly the
75
+ * three the governing spec asked `xeno.core.runs` to gain, and it has them (`queued`,
76
+ * `awaiting-decision`, `awaiting-permission`).
77
+ */
78
+ type XenoAgentTurnState = 'idle' | 'queued' | 'running' | 'waiting-for-user' | 'waiting-for-permission' | 'succeeded' | 'failed' | 'cancelled';
79
+ /** One conversation with one agent. */
80
+ interface XenoAgentSession {
81
+ id: string;
82
+ /** Tab/row label. Absent โ‡’ the panel shows the id, never a fabricated title. */
83
+ title?: string;
84
+ providerId?: string;
85
+ modelId?: string;
86
+ lane?: XenoAgentLane;
87
+ /** Working directory, when the lane has one. Rendered verbatim. */
88
+ cwd?: string;
89
+ /** Epoch ms the session was created. Sessions are ordered by this, newest first. */
90
+ createdAt?: number;
91
+ /** Current turn state. Absent โ‡’ `idle`. */
92
+ turn?: XenoAgentTurnState;
93
+ /** Host-authored sentence for `failed`, rendered verbatim. */
94
+ message?: string;
95
+ }
96
+ /** A message's author. `system` covers host notices; the panel never invents one. */
97
+ type XenoAgentRole = 'user' | 'assistant' | 'system' | 'error';
98
+ /** A typed failure. Never a bare string โ€” a consumer must be able to branch and to group. */
99
+ interface XenoAgentError {
100
+ code: string;
101
+ message: string;
102
+ detail?: string;
103
+ retryable?: boolean;
104
+ }
105
+ /**
106
+ * A message opened, extended or closed.
107
+ *
108
+ * ๐Ÿ”ด **`text` and `append` are different channels and the difference IS the streaming case.**
109
+ * `text` sets the body outright; `append` concatenates. A token-streamed reply is one `message`
110
+ * event carrying `text` (usually `''`, which opens the record) followed by many carrying `append`
111
+ * โ€” which is exactly the `xeno.core.console` `patch` / `appendMessage` path, and the reason that
112
+ * path had to exist at all. A producer that sent one event per token with `text` instead would
113
+ * defeat the console's adjacent-duplicate collapsing and render one row per token.
114
+ */
115
+ interface XenoAgentMessageEvent {
116
+ type: 'message';
117
+ sessionId: string;
118
+ /** The turn this belongs to, when it belongs to one. */
119
+ turnId?: string;
120
+ /** Stable id. The console row key, and the address of every later `append`. */
121
+ messageId: string;
122
+ role: XenoAgentRole;
123
+ /** Epoch ms. Absent โ‡’ the projection stamps arrival time. */
124
+ ts?: number;
125
+ /** Replace the message body. */
126
+ text?: string;
127
+ /** Concatenate onto it โ€” the streaming channel. Applied after `text` within one event. */
128
+ append?: string;
129
+ /** No more text is coming. Presentation only; never treated as a turn ending. */
130
+ done?: boolean;
131
+ }
132
+ /**
133
+ * A tool call's lifecycle.
134
+ *
135
+ * `parentCallId` is what makes this a TREE. A subagent's own tool calls sit at depth 3 under a
136
+ * turn, which is the case `xeno.core.runs` gained a recursive step model for; flattening it here
137
+ * would throw the nesting away one layer above the panel that can finally render it.
138
+ */
139
+ interface XenoAgentToolEvent {
140
+ type: 'tool';
141
+ sessionId: string;
142
+ /** The run this step belongs to. A tool call outside a turn has nowhere to be drawn. */
143
+ turnId: string;
144
+ /** Stable id, unique within the turn. */
145
+ callId: string;
146
+ /** The enclosing call, when this one was made by a subagent or a nested tool. */
147
+ parentCallId?: string;
148
+ /** Display label. Required on the event that OPENS a call; optional on later updates. */
149
+ label?: string;
150
+ state: XenoAgentTurnState;
151
+ /** `0`โ€“`1`. Absent means indeterminate, which is not the same as `0`. */
152
+ progress?: number;
153
+ /** Pre-formatted detail lines. The host formats; the panel places. */
154
+ detail?: Record<string, string>;
155
+ /** Set when `state === 'failed'`. */
156
+ error?: XenoAgentError;
157
+ startedAt?: number;
158
+ endedAt?: number;
159
+ }
160
+ /** A turn opened, changed state, or ended. */
161
+ interface XenoAgentTurnEvent {
162
+ type: 'turn';
163
+ sessionId: string;
164
+ /** Stable id. Becomes the RUN id in `xeno.core.runs`. */
165
+ turnId: string;
166
+ state: XenoAgentTurnState;
167
+ /** Display label โ€” usually the prompt's first line. */
168
+ label?: string;
169
+ startedAt?: number;
170
+ endedAt?: number;
171
+ error?: XenoAgentError;
172
+ }
173
+ /** A session appeared or changed. Upsert semantics; the host is authoritative. */
174
+ interface XenoAgentSessionEvent {
175
+ type: 'session';
176
+ session: XenoAgentSession;
177
+ }
178
+ /** A session went away. */
179
+ interface XenoAgentSessionClosedEvent {
180
+ type: 'session-closed';
181
+ sessionId: string;
182
+ }
183
+ /**
184
+ * Something the agent needs a human to answer.
185
+ *
186
+ * ๐Ÿ”ด **The payload is `@xenosystem/panel-consent`'s `XenoElicitation`, forwarded verbatim**, and
187
+ * only the two fields this package itself reads are typed. Restating that union here would put a
188
+ * second copy of a security contract in a package that does not own it โ€” and the copy would be the
189
+ * one that drifts, because nothing here could notice.
190
+ *
191
+ * โš ๏ธ So the honest statement is: **whether an ask renders at all is `panel-consent`'s decision, not
192
+ * this panel's.** It refuses a shape it cannot render, visibly, and this panel does not
193
+ * second-guess that.
194
+ */
195
+ interface XenoAgentAskEvent {
196
+ type: 'ask';
197
+ sessionId: string;
198
+ turnId?: string;
199
+ /** The elicitation, in the consent panel's shape. `id` and `title` are all this package reads. */
200
+ ask: {
201
+ id: string;
202
+ title: string;
203
+ [key: string]: unknown;
204
+ };
205
+ }
206
+ /** An ask the agent no longer needs answered. */
207
+ interface XenoAgentAskWithdrawEvent {
208
+ type: 'ask-withdraw';
209
+ sessionId: string;
210
+ askId: string;
211
+ }
212
+ /** One file the agent proposes to change. */
213
+ interface XenoAgentPatchFile {
214
+ path: string;
215
+ /** Previous path, when renamed. */
216
+ oldPath?: string;
217
+ status: 'added' | 'removed' | 'modified' | 'renamed';
218
+ /**
219
+ * A unified diff for THIS file โ€” `@@` headers and `+`/`-`/` ` lines.
220
+ *
221
+ * ๐Ÿ”ด The reason this package ships a parser. The spec's ยงD3 measured the mismatch: the host emits
222
+ * raw unified-diff strings while `xeno.core.diff` requires `XenoDiffLine[]` and *"ships no engine
223
+ * by charter"*. Its verdict was that the parser is **adapter-side**, and this panel is the
224
+ * adapter.
225
+ */
226
+ diff?: string;
227
+ /** Language hint, forwarded to the diff panel. Never used to load anything. */
228
+ language?: string;
229
+ /**
230
+ * Why there are no hunks, when the reason is not equality.
231
+ *
232
+ * ๐Ÿ”ด Forwarded because the diff panel's whole `omitted` field exists to stop an unread file
233
+ * reading as an unchanged one. An agent's patch set is precisely where that matters: a binary
234
+ * blob shown as "no changes" is content nobody looked at, approved.
235
+ */
236
+ omitted?: 'binary' | 'tooLarge' | 'unreadable' | 'identical';
237
+ }
238
+ /** A proposed change set, awaiting review. */
239
+ interface XenoAgentPatchEvent {
240
+ type: 'patch';
241
+ sessionId: string;
242
+ turnId?: string;
243
+ /** Stable id for the change set โ€” the correlation key a decision comes back on. */
244
+ patchId: string;
245
+ files: XenoAgentPatchFile[];
246
+ /** Labels for the two sides ("HEAD" / "Agent proposal"). */
247
+ oldLabel?: string;
248
+ newLabel?: string;
249
+ }
250
+ /**
251
+ * Raw terminal output, or a terminal's lifecycle.
252
+ *
253
+ * Both halves are forwarded verbatim to `xeno.core.terminal`: `data` is raw bytes as a string with
254
+ * escape sequences intact (**never pre-parsed โ€” the emulator is the parser**) and `session` is that
255
+ * panel's `XenoTerminalSession`.
256
+ */
257
+ interface XenoAgentTerminalEvent {
258
+ type: 'terminal';
259
+ sessionId: string;
260
+ /** The terminal instance id, as `xeno.core.terminal` addresses it. */
261
+ terminalId: string;
262
+ /** Raw bytes as a string. */
263
+ data?: string;
264
+ /** Lifecycle, in the terminal panel's shape. Forwarded verbatim. */
265
+ session?: {
266
+ id: string;
267
+ status: string;
268
+ [key: string]: unknown;
269
+ };
270
+ }
271
+ /** Everything a host can deliver on the `session` port. */
272
+ type XenoAgentEvent = XenoAgentSessionEvent | XenoAgentSessionClosedEvent | XenoAgentTurnEvent | XenoAgentMessageEvent | XenoAgentToolEvent | XenoAgentAskEvent | XenoAgentAskWithdrawEvent | XenoAgentPatchEvent | XenoAgentTerminalEvent;
273
+ /**
274
+ * A batch of events.
275
+ *
276
+ * โš ๏ธ A LIST, not one event per envelope, for the same reason every other panel in this catalog
277
+ * takes a delta: a streaming turn produces events far faster than a wire should carry envelopes,
278
+ * and one envelope per token is the thousand-array-copies non-starter in a second costume.
279
+ */
280
+ interface XenoAgentSessionDelta {
281
+ /** Monotonic revision. A delta whose rev has not advanced is dropped. */
282
+ rev?: number;
283
+ events?: XenoAgentEvent[];
284
+ /** Replace the session index outright (the host reconnected). */
285
+ replace?: XenoAgentSession[];
286
+ /** Drop everything. Wins over `events` in the same delta. */
287
+ clear?: boolean;
288
+ }
289
+ /** Whether the panel is connected to an agent host at all. */
290
+ type XenoAgentConnection = 'idle' | 'connecting' | 'ready' | 'failed';
291
+ /** Connection state, out of band. */
292
+ interface XenoAgentStatus {
293
+ status: XenoAgentConnection;
294
+ /** Host-authored sentence, rendered verbatim. */
295
+ message?: string;
296
+ }
297
+ /**
298
+ * The single thing the host acts on.
299
+ *
300
+ * ๐Ÿ”ด **One output port, not eight.** The answers this panel forwards โ€” a consent decision, a hunk
301
+ * decision, a run action, a terminal keystroke โ€” arrive from the canonical panels carrying no
302
+ * session id, because none of those panels knows sessions exist. Something has to correlate them,
303
+ * and the only place that already holds the map is the translator that minted the ask. Handing the
304
+ * host eight raw streams instead would move that bookkeeping into every host โ€” which is how the
305
+ * four bespoke agent UIs the spec measured came to exist.
306
+ */
307
+ type XenoAgentIntent =
308
+ /** Run a prompt. โš ๏ธ The panel clears its composer only once the host confirms a turn. */
309
+ {
310
+ type: 'prompt';
311
+ sessionId: string;
312
+ text: string;
313
+ providerId?: string;
314
+ modelId?: string;
315
+ }
316
+ /** Stop the running turn. */
317
+ | {
318
+ type: 'cancel';
319
+ sessionId: string;
320
+ turnId?: string;
321
+ }
322
+ /**
323
+ * Steer a running turn.
324
+ *
325
+ * โš ๏ธ **Lands at the agent's NEXT decision point, not instantly** โ€” proven against real Claude
326
+ * Code, and recorded as a caveat in the ADE spec. A view must not imply otherwise.
327
+ */
328
+ | {
329
+ type: 'steer';
330
+ sessionId: string;
331
+ text: string;
332
+ } | {
333
+ type: 'select-session';
334
+ sessionId: string;
335
+ } | {
336
+ type: 'new-session';
337
+ providerId?: string;
338
+ modelId?: string;
339
+ } | {
340
+ type: 'select-provider';
341
+ sessionId: string;
342
+ providerId: string;
343
+ modelId?: string;
344
+ }
345
+ /** A consent decision or elicitation result, correlated back to its session. Payload verbatim. */
346
+ | {
347
+ type: 'answer';
348
+ sessionId: string;
349
+ askId: string;
350
+ answer: Record<string, unknown>;
351
+ }
352
+ /** A hunk decision, correlated back to its session and change set. Payload verbatim. */
353
+ | {
354
+ type: 'patch-decision';
355
+ sessionId: string;
356
+ patchId: string;
357
+ decision: Record<string, unknown>;
358
+ }
359
+ /** Cancel or retry a turn, or one of its tool calls. */
360
+ | {
361
+ type: 'run-action';
362
+ sessionId: string;
363
+ turnId: string;
364
+ action: 'cancel' | 'retry';
365
+ /** The tool call, when a step was targeted rather than the whole turn. */
366
+ callId?: string;
367
+ }
368
+ /** A terminal intent, correlated back to its session. Payload verbatim. */
369
+ | {
370
+ type: 'terminal';
371
+ sessionId: string;
372
+ terminal: Record<string, unknown>;
373
+ };
374
+ /** Persisted panel state. **Preferences plus the unsent draft** โ€” never a session, never a transcript. */
375
+ interface AgentPanelState {
376
+ activeSessionId?: string | null;
377
+ /**
378
+ * The unsent composer text.
379
+ *
380
+ * โš ๏ธ Persisted deliberately, and it is the ONE piece of content here. A half-typed prompt lost to
381
+ * a layout change is the single most annoying thing a chat surface can do, and it is the user's
382
+ * own text rather than host state. Nothing else is kept โ€” a transcript belongs to the host, and
383
+ * `.xapp` is a plain JSON file that may contain nothing sensitive.
384
+ */
385
+ draft?: string;
386
+ }
387
+ /** What the view renders. */
388
+ interface AgentViewState {
389
+ sessions: XenoAgentSession[];
390
+ /** The session being shown, or `null` when there is none. */
391
+ active: XenoAgentSession | null;
392
+ /** Its turn state, resolved. `idle` when there is no session. */
393
+ turn: XenoAgentTurnState;
394
+ connection: XenoAgentConnection;
395
+ /** Host-authored sentence for a failed connection or a failed turn. */
396
+ message: string | null;
397
+ providers: XenoAgentProvider[];
398
+ activeProvider: XenoAgentProvider | null;
399
+ activeModelId: string | null;
400
+ draft: string;
401
+ /**
402
+ * Would `submit()` do anything right now?
403
+ *
404
+ * Computed from the SAME predicate `submit()` enforces, so an enabled button cannot be a button
405
+ * that refuses โ€” the affordance and the guard can never disagree. (The rule `xeno.core.consent`
406
+ * arrived at, applied here for the same reason.)
407
+ */
408
+ canSubmit: boolean;
409
+ /** Is a turn in flight, so the composer offers Stop rather than Send? */
410
+ busy: boolean;
411
+ /** How many asks are outstanding across every session. */
412
+ pendingAsks: number;
413
+ }
414
+ /** Is the agent doing something a Stop would interrupt? */
415
+ declare function isBusy(turn: XenoAgentTurnState): boolean;
416
+ /**
417
+ * Is the turn stopped on a human?
418
+ *
419
+ * Separate from {@link isBusy} because folding the two together is how a surface reports a working
420
+ * agent while it sits on a permission prompt nobody has seen โ€” the same distinction
421
+ * `xeno.core.runs` draws between `active` and `waiting`, and for the same reason.
422
+ */
423
+ declare function isWaitingOnUser(turn: XenoAgentTurnState): boolean;
424
+
425
+ export { type AgentViewState as A, type XenoAgentSessionDelta as X, type XenoAgentEvent as a, type XenoAgentTurnState as b, type XenoAgentCatalog as c, type XenoAgentConnection as d, type AgentPanelState as e, type XenoAgentAskEvent as f, type XenoAgentAskWithdrawEvent as g, type XenoAgentError as h, type XenoAgentIntent as i, type XenoAgentLane as j, type XenoAgentMessageEvent as k, type XenoAgentModel as l, type XenoAgentPatchEvent as m, type XenoAgentPatchFile as n, type XenoAgentProvider as o, type XenoAgentRole as p, type XenoAgentSession as q, type XenoAgentSessionClosedEvent as r, type XenoAgentSessionEvent as s, type XenoAgentStatus as t, type XenoAgentTerminalEvent as u, type XenoAgentToolEvent as v, type XenoAgentTurnEvent as w, isBusy as x, isWaitingOnUser as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenosystem/blocks",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "XENO Blocks \u2014 rung 3 of the front ladder: components composed into a contract-bearing unit (manifest: ports, intents, commands, agent surface), rendered by @xenosystem/workbench. ONE package, one subpath per family (/auth, /edit, /data, /time, /ops, /trust, /agent), one named export per block \u2014 XENO PACKAGE NAMING - STANDARD.md.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -37,6 +37,14 @@
37
37
  "./edit": {
38
38
  "types": "./dist/edit/index.d.ts",
39
39
  "import": "./dist/edit/index.js"
40
+ },
41
+ "./agent": {
42
+ "types": "./dist/agent/index.d.ts",
43
+ "import": "./dist/agent/index.js"
44
+ },
45
+ "./agent/host-source": {
46
+ "types": "./dist/agent/host-source.d.ts",
47
+ "import": "./dist/agent/host-source.js"
40
48
  }
41
49
  },
42
50
  "files": [
@@ -52,12 +60,10 @@
52
60
  "peerDependencies": {
53
61
  "@xenosystem/components": "^0.6.0",
54
62
  "@xenosystem/elements-react": ">=0.0.1",
55
- "@xenosystem/panel-account": "^0.1.0",
56
- "@xenosystem/panel-sdk": "^1.1.0",
57
63
  "@xenosystem/workbench": "^1.0.0",
58
64
  "react": ">=18.0.0",
59
65
  "react-dom": ">=18.0.0",
60
- "@xenosystem/block-sdk": "^0.1.1",
66
+ "@xenosystem/block-sdk": "^0.2.0",
61
67
  "@xenosystem/tree-core": "^0.1.0",
62
68
  "@xterm/xterm": ">=5.5.0",
63
69
  "@xterm/addon-fit": ">=0.10.0",
@@ -71,8 +77,6 @@
71
77
  "@types/react-dom": "^19.0.0",
72
78
  "@xenosystem/components": "^0.6.0",
73
79
  "@xenosystem/elements-react": "0.0.1",
74
- "@xenosystem/panel-account": "^0.1.0",
75
- "@xenosystem/panel-sdk": "^1.1.0",
76
80
  "@xenosystem/workbench": "^1.0.0",
77
81
  "jsdom": "^26.0.0",
78
82
  "react": "^19.0.0",
@@ -80,7 +84,7 @@
80
84
  "tsup": "^8.0.0",
81
85
  "typescript": "^5.4.0",
82
86
  "vitest": "^4.1.0",
83
- "@xenosystem/block-sdk": "^0.1.1",
87
+ "@xenosystem/block-sdk": "^0.2.0",
84
88
  "@xenosystem/tree-core": "^0.1.0",
85
89
  "@xterm/xterm": "^6.0.0",
86
90
  "@xterm/addon-fit": "^0.11.0",