@ego-z/contracts 0.15.10 → 0.15.12

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ego-z/contracts",
3
- "version": "0.15.10",
3
+ "version": "0.15.12",
4
4
  "description": "Wire-format type contracts shared between EgoZ backend, SDK, MCP and console. Type-only — no runtime artifacts.",
5
5
  "types": "./src/index.d.ts",
6
6
  "exports": {
package/src/ask.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { Intent, JsonSchemaDefinition, ResponseFormat, TokenUsage } from './envelope';
9
- import type { InitContextItem, InitContextUi } from './draft-conv';
9
+ import type { InitContextItem, InitContextItemAccepted, InitContextUi } from './draft-conv';
10
10
  import type { AskQuestionSet } from './questions';
11
11
 
12
12
  // ============================================================================
@@ -211,6 +211,102 @@ export interface AskRequestBody {
211
211
  * Optional and additive: omit it and nothing changes.
212
212
  */
213
213
  contextItems?: InitContextItem[];
214
+
215
+ /**
216
+ * Output a MACHINE produced for this turn — the result of something the
217
+ * user asked to have run, not anything the user said.
218
+ *
219
+ * ── Why this needs a channel of its own ──────────────────────────────
220
+ *
221
+ * Before this field there were exactly two ways in, and both are wrong
222
+ * for a machine:
223
+ *
224
+ * - `message` persists as `role: 'user'`. A script that prints a
225
+ * customer's CSV row arrives labelled as the account owner speaking,
226
+ * and **every protection that keys off who is speaking is bypassed by
227
+ * a program printing a sentence.**
228
+ * - `contextItems` is worse, because it does not merely keep the user's
229
+ * voice — it upgrades it. Those items are rendered as things the user
230
+ * *chose deliberately* and *likely referents for anything they mention*,
231
+ * so routing `stderr` there promotes program output to something
232
+ * adjacent to instruction.
233
+ *
234
+ * `role: 'tool'` exists internally, but only EgoZ mints those and only
235
+ * from its own executions; nothing on the wire can produce one. So this
236
+ * is the channel: it is rendered to the agent as **quoted data with an
237
+ * explicit statement that a machine produced it**, never as speech and
238
+ * never as instruction.
239
+ *
240
+ * ⚠️ **You are asserting provenance when you send this.** Put here only
241
+ * what your own system produced. Anything a person typed belongs in
242
+ * `message`, whatever it is about — mislabelling user text as machine
243
+ * output is the same failure as the reverse, pointed the other way.
244
+ *
245
+ * Optional and additive: omit it and nothing changes.
246
+ */
247
+ machineOutput?: AskMachineOutput[];
248
+ }
249
+
250
+ /**
251
+ * One piece of output a machine produced for this turn.
252
+ *
253
+ * ── Truncated, never dropped — the opposite of `InitContextItem` ─────────
254
+ *
255
+ * An over-long `excerpt` on a context item is DROPPED, because a shortened
256
+ * address is a different valid address and acting on it is worse than not
257
+ * having it. Output is the other case entirely: **a clipped `stderr` is
258
+ * still evidence, and a missing one is a lie.** So this is capped by
259
+ * truncation and the truncation is declared — the agent is told the text was
260
+ * cut rather than left to conclude a command printed nothing.
261
+ *
262
+ * That asymmetry is deliberate and it is the reason these are two shapes
263
+ * rather than one generous one.
264
+ *
265
+ * ── No acceptance receipt, unlike `contextItems` ─────────────────────────
266
+ *
267
+ * `contextItemsAccepted` exists because acceptance there is lossy in ways
268
+ * the caller cannot observe — items vanish, excerpts disappear — so a
269
+ * "shared with the agent" display would otherwise be confidently wrong.
270
+ * Nothing here vanishes: an entry is either rendered or the request is
271
+ * rejected, and the one lossy case declares itself **to the reader who
272
+ * matters**, in-band, where the agent is told the text was cut. Adding a
273
+ * receipt would be a second copy of that fact, for a caller that already
274
+ * holds the full output it just sent.
275
+ */
276
+ export interface AskMachineOutput {
277
+ /**
278
+ * Your own name for whatever produced this — `"terminal"`, `"build"`.
279
+ *
280
+ * ⚠️ Free-form, and **never rendered into the prompt**, exactly like
281
+ * `InitContextItem.kind` and for the same reason: free-form caller text
282
+ * in the instruction block is an injection surface. Used for grouping
283
+ * and for your own logs.
284
+ */
285
+ source?: string;
286
+
287
+ /**
288
+ * What was run, in the user's terms — a command line, a job name.
289
+ * Rendered, because output with no statement of what produced it invites
290
+ * the agent to guess which of its own suggestions ran.
291
+ */
292
+ invocation?: string;
293
+
294
+ /**
295
+ * The text the machine produced. Required — an entry with nothing in it
296
+ * says a program ran and reported nothing, which is a claim, not an
297
+ * omission. Truncated to a server cap if long; see the header.
298
+ */
299
+ output: string;
300
+
301
+ /**
302
+ * The producer's own result status, when it has one — a process exit
303
+ * code, conventionally 0 for success.
304
+ *
305
+ * Worth sending even when `output` is empty, and especially then: it is
306
+ * the difference between *"it worked and printed nothing"* and *"it
307
+ * failed silently"*, which no amount of text can express.
308
+ */
309
+ exitCode?: number;
214
310
  }
215
311
 
216
312
  /**
@@ -346,4 +442,23 @@ export interface AskResponseData {
346
442
  */
347
443
  memoryInjected?: boolean;
348
444
  memoryProposed?: boolean;
445
+ /**
446
+ * What EgoZ actually accepted from `AskRequestBody.contextItems` this
447
+ * turn, in the order the agent saw them.
448
+ *
449
+ * Present whenever the request carried `contextItems` — including as an
450
+ * empty array, meaning "you sent a list and nothing in it was accepted".
451
+ *
452
+ * ⚠️ ABSENT MEANS THE REQUEST CARRIED NO `contextItems`. It never means
453
+ * "none were accepted". A consumer that reads absence as rejection
454
+ * reports a failure that did not happen; one that reads an empty array as
455
+ * absence reports a success that did not either.
456
+ *
457
+ * Render this — not the list you sent — anywhere you show the user what
458
+ * was shared with the agent. See `InitContextItemAccepted` for why the
459
+ * two can differ and how to join them.
460
+ *
461
+ * Additive: consumers that ignore it are unaffected.
462
+ */
463
+ contextItemsAccepted?: InitContextItemAccepted[];
349
464
  }
@@ -93,11 +93,125 @@ export interface InitContextItem {
93
93
  label?: string;
94
94
 
95
95
  /**
96
- * A short description of the item. Optional, and truncated rather than
97
- * rejected if long — but keep it to what the thing IS, not its full
98
- * record. Everything here displaces conversation from the context window.
96
+ * A short description of the item. Optional but keep it to what the
97
+ * thing IS, not its full record. Everything here displaces conversation
98
+ * from the context window.
99
+ *
100
+ * ⚠️ AN OVER-LONG EXCERPT IS DROPPED, NOT TRUNCATED, and the item still
101
+ * rides without it. Unlike every other cap here, which truncates or
102
+ * rejects.
103
+ *
104
+ * Because you resolve the item yourself, this is sometimes prose
105
+ * (`"£45, in stock"`) and sometimes an ADDRESS the agent will act on
106
+ * (`"projects/api/deploy.sh"`). Shortening prose loses a few words;
107
+ * shortening an address does not produce an invalid address, it produces
108
+ * a **different valid one**, which the agent then reads and reports on
109
+ * with no error anywhere. Nothing server-side can tell the two apart —
110
+ * `kind` is opaque and never resolved — so one behaviour covers both and
111
+ * it is the safe one.
112
+ *
113
+ * `contextItemsAccepted` on the response tells you when it happened.
99
114
  */
100
115
  excerpt?: string;
116
+
117
+ /**
118
+ * The exact string a tool needs to act on this item — a file path, a
119
+ * record id, whatever your tools take. Optional.
120
+ *
121
+ * ── Why this is not just an `excerpt` ────────────────────────────────
122
+ *
123
+ * Because the two are read differently and fail differently, and until
124
+ * this field existed they shared one. An excerpt is prose ABOUT the
125
+ * thing; an address is a HANDLE TO it, and the agent is expected to
126
+ * carry it verbatim into a tool call. Shortening prose loses a few
127
+ * words. Shortening an address produces a **different valid address** —
128
+ * so the caps differ, and so does what a consumer may do with the value.
129
+ *
130
+ * ⚠️ AN OVER-LONG ADDRESS IS DROPPED, NOT TRUNCATED, and the item still
131
+ * rides without it, exactly like `excerpt`. `addressOmitted` on the
132
+ * receipt tells you it happened — worth reading, because the agent then
133
+ * has the item's NAME and no way to act on it, which usually reads to a
134
+ * user as the agent being unable rather than under-informed.
135
+ *
136
+ * ── Send the address, or the agent will construct one ────────────────
137
+ *
138
+ * Measured, twice, on live traffic: given an item it can name but not
139
+ * address, an agent does not stop — it produces a plausible-looking
140
+ * identifier from the meaning it has. A product slug built from the
141
+ * product's name, a window key passed where a record id was wanted.
142
+ * Both fail at the tool, and the failure reads as a broken feature.
143
+ *
144
+ * So an absent address is not neutral. It is the condition under which
145
+ * an agent invents one.
146
+ *
147
+ * ── It is rendered, and that is a deliberate exception ───────────────
148
+ *
149
+ * `kind` and `id` never reach the prompt: free-form caller text in the
150
+ * instruction block is an injection surface. `address` does reach it,
151
+ * for the same reason `label` does — a handle nobody can read is not a
152
+ * handle. Send an identifier or a path, never a sentence, and never
153
+ * anything a user typed and you did not resolve.
154
+ */
155
+ address?: string;
156
+ }
157
+
158
+ /**
159
+ * One entry in the response's `contextItemsAccepted` receipt — what EgoZ
160
+ * actually took from a `contextItems` you sent.
161
+ *
162
+ * ── Why a receipt exists at all ──────────────────────────────────────────
163
+ *
164
+ * Acceptance is LOSSY BY DESIGN, in three ways you cannot observe from the
165
+ * caller: an over-long list is truncated from the end, an item with no
166
+ * `label` is dropped individually, and an over-long `excerpt` is dropped
167
+ * while its item rides on. Each is the right behaviour — none of them should
168
+ * fail a user's turn — but each means the list you sent is not necessarily
169
+ * the list the agent saw.
170
+ *
171
+ * So if you display "what was shared with the agent" anywhere, render THIS,
172
+ * not the list you sent. Rendering your own list produces a display that is
173
+ * confident and wrong in exactly the cases that matter.
174
+ *
175
+ * ⚠️ AN ITEM MISSING FROM THE RECEIPT WAS NOT ACCEPTED. Absence is the only
176
+ * channel that reports the list truncation and the labelless drop, so a
177
+ * consumer that treats this as best-effort or partially populated silently
178
+ * goes back to claiming delivery it cannot verify.
179
+ *
180
+ * Match entries to your own items on `kind` + `id`. Do NOT fall back to
181
+ * matching on `label` — labels are deliberately not unique, so that join is
182
+ * usually right, and "usually right" is the failure this field exists to
183
+ * remove. Render an unmatched entry as unmatched instead.
184
+ */
185
+ export interface InitContextItemAccepted {
186
+ /** Echo of the item's `kind`. Still never rendered into the prompt. */
187
+ kind?: string;
188
+ /** Echo of the item's `id`. */
189
+ id?: string;
190
+ /** The label the agent saw. Always present — a labelless item is dropped. */
191
+ label: string;
192
+ /**
193
+ * The item was accepted but its `excerpt` was not — it exceeded the cap
194
+ * and was dropped rather than truncated (see `InitContextItem.excerpt`).
195
+ *
196
+ * Absent means the excerpt survived, or there was never one to carry.
197
+ * Distinguishing those two is not what this flag is for: it exists so a
198
+ * "shared with the agent" display can say the agent got a NAME and no
199
+ * detail, instead of implying it got both.
200
+ */
201
+ excerptOmitted?: boolean;
202
+
203
+ /**
204
+ * The item was accepted but its `address` was not — it exceeded the cap
205
+ * and was dropped rather than truncated (see `InitContextItem.address`).
206
+ *
207
+ * ⚠️ Worth surfacing more loudly than `excerptOmitted`. A lost excerpt
208
+ * costs detail; a lost address costs the agent the only way to ACT on
209
+ * the item, and what a user sees is not "it knew less about this" but
210
+ * "it couldn't do it" — or, worse, an invented identifier that fails at
211
+ * the tool. If you show a "shared with the agent" display at all, this
212
+ * is the flag that changes what the user should expect.
213
+ */
214
+ addressOmitted?: boolean;
101
215
  }
102
216
 
103
217
  /** One open window in the caller's UI. */
package/src/stream.d.ts CHANGED
@@ -13,6 +13,7 @@
13
13
 
14
14
  import type { Intent, TokenUsage } from './envelope';
15
15
  import type { AskQuestionSet } from './questions';
16
+ import type { InitContextItemAccepted } from './draft-conv';
16
17
 
17
18
  /**
18
19
  * Discriminated union of every event the streaming `/ask` endpoint emits.
@@ -352,6 +353,21 @@ export interface AskStreamDoneEvent {
352
353
  */
353
354
  memoryInjected?: boolean;
354
355
  memoryProposed?: boolean;
356
+ /**
357
+ * What EgoZ accepted from this turn's `contextItems`. Same semantics +
358
+ * shape as `AskResponseData.contextItemsAccepted` — surfaced on the
359
+ * terminal event so streaming consumers can render "what was shared"
360
+ * without parsing the final payload separately.
361
+ *
362
+ * On the terminal frame rather than mid-stream for the same reason as
363
+ * `questionSet`: it describes the settled turn, and a consumer would
364
+ * discard every earlier variant anyway.
365
+ *
366
+ * ⚠️ Absent means the request carried no `contextItems` — never that none
367
+ * were accepted. An empty array is the second thing, and they must not be
368
+ * collapsed by anything relaying this frame.
369
+ */
370
+ contextItemsAccepted?: InitContextItemAccepted[];
355
371
  }
356
372
 
357
373
  export interface AskStreamErrorEvent {