@ego-z/contracts 0.15.3 → 0.15.6

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/README.md CHANGED
@@ -58,6 +58,30 @@ import type { AskRequestBody, AskResponseData, AskStreamEvent } from '@ego-z/con
58
58
  their generated `.d.ts` automatically — end users see the types as if
59
59
  they were declared in the SDK itself.
60
60
 
61
+ ## ⚠️ Do not pin `0.15.5`
62
+
63
+ `0.15.5` was published with a `dependencies` entry naming **itself**
64
+ (`"@ego-z/contracts": "^0.15.0"`). It is harmless while 0.15.5 is the newest
65
+ 0.15.x — npm resolves that range back to the same copy and dedupes it:
66
+
67
+ ```
68
+ `-- @ego-z/contracts@0.15.5
69
+ `-- @ego-z/contracts@0.15.5 deduped
70
+ ```
71
+
72
+ **It stops being harmless the moment a newer 0.15.x exists.** That range can
73
+ then resolve a *different* version nested underneath 0.15.5, putting two
74
+ copies of the same types in one tree — the classic "these two identical
75
+ interfaces are not assignable" failure, from a cause nobody would think to
76
+ look for.
77
+
78
+ So: **when the next patch ships, move off 0.15.5 rather than staying on it.**
79
+ Don't pin it, and don't leave a lockfile resolving to it.
80
+
81
+ The source is already fixed — the package declares no runtime dependencies at
82
+ all — so the next publish for any reason clears it. It was not worth a
83
+ version bump on its own mid-integration.
84
+
61
85
  ## When to bump
62
86
 
63
87
  - **Adding** an optional field — non-breaking, no version bump required
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ego-z/contracts",
3
- "version": "0.15.3",
3
+ "version": "0.15.6",
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
@@ -25,6 +25,21 @@ import type { Intent, JsonSchemaDefinition, ResponseFormat, TokenUsage } from '.
25
25
  * These are intentionally NOT body fields — they live where HTTP puts them.
26
26
  */
27
27
  export interface AskRequestBody {
28
+ /**
29
+ * The CALLER'S own tenant for this turn, in their id space — required on
30
+ * a `gateway` integration (see `TenantScope`), meaningless otherwise.
31
+ *
32
+ * Scopes every per-user read on the turn and guards the resume: a
33
+ * conversation belonging to a different one of the caller's tenants
34
+ * fails as `ThreadMismatch` (409) rather than `NotFound`, because a
35
+ * gateway that hears "not found" for a real conversation bootstraps its
36
+ * own record under the wrong tenant and forks the transcript.
37
+ *
38
+ * On the wire rather than derived from `metadata`: a scoping key
39
+ * inferred from one caller's metadata field name would make EgoZ's
40
+ * privacy boundary depend on that caller's vocabulary.
41
+ */
42
+ externalTenantId?: string;
28
43
  /** End-user's message for this turn. Required, non-empty. */
29
44
  message: string;
30
45
 
@@ -242,6 +242,53 @@ export interface InitContextTenant {
242
242
  features?: Record<string, boolean>;
243
243
  }
244
244
 
245
+ /**
246
+ * A short recap of the user's PREVIOUS conversation, so the agent can open
247
+ * with continuity instead of as though it has never met them.
248
+ *
249
+ * ── Supplied by the caller, deliberately ─────────────────────────────────
250
+ *
251
+ * EgoZ stores conversations too, so it looks like EgoZ should resolve this
252
+ * itself — but on a gateway integration EgoZ's own copy is keyed by
253
+ * `(tenantId, externalUserId)`, which spans every one of the caller's
254
+ * tenants a person belongs to. Sourcing it there would recap store B's
255
+ * conversation into store A's prompt. The caller's store is per-tenant by
256
+ * construction, so it can answer the question safely, and a conversation the
257
+ * user deleted stops being quotable immediately with no propagation chain.
258
+ *
259
+ * ── Keep it SHORT ────────────────────────────────────────────────────────
260
+ *
261
+ * `lastMessage` is a truncated snippet, not a transcript. It rides in the
262
+ * system prompt on every turn of the new conversation, so a long one buys
263
+ * a little continuity at the cost of crowding out the context that makes
264
+ * the agent useful. Cap it at the sender.
265
+ *
266
+ * ── Recency matters more than it looks ───────────────────────────────────
267
+ *
268
+ * "Last time we spoke…" about a conversation from three months ago reads as
269
+ * the assistant having lost the plot, not as continuity. Callers should omit
270
+ * this entirely past a sensible window rather than send something stale.
271
+ */
272
+ export interface InitContextLastConversation {
273
+ /** The previous conversation's title, if it has one. */
274
+ title?: string | null;
275
+ /** ISO 8601 — when it was last active. Lets the agent judge recency. */
276
+ updatedAt?: string | null;
277
+ /**
278
+ * A TRUNCATED snippet of the last message in it. Not a transcript —
279
+ * see the type docs on why the cap belongs at the sender.
280
+ */
281
+ lastMessage?: string | null;
282
+ /**
283
+ * Whether the agent replied to that last message.
284
+ *
285
+ * `false` is the interesting value: the user said something and never
286
+ * got an answer, which is worth opening on. Distinguishing it from
287
+ * "unknown" is why this is optional rather than defaulting to `true`.
288
+ */
289
+ answered?: boolean;
290
+ }
291
+
245
292
  /**
246
293
  * The full init context. Every field optional — see the file header on
247
294
  * additive evolution.
@@ -251,6 +298,8 @@ export interface InitContext {
251
298
  locale?: InitContextLocale;
252
299
  ui?: InitContextUi;
253
300
  tenant?: InitContextTenant;
301
+ /** A short recap of the user's previous conversation, if there is one. */
302
+ lastConversation?: InitContextLastConversation;
254
303
  /** Anything the contract doesn't model yet. Rendered as untrusted data. */
255
304
  notes?: string | null;
256
305
  /**
package/src/thread.d.ts CHANGED
@@ -115,6 +115,18 @@ export interface ThreadWire {
115
115
  * migration 017.
116
116
  */
117
117
  externalThreadId: string | null;
118
+ /**
119
+ * The CALLER'S own tenant this conversation belongs to, in their id
120
+ * space (migration 056). Populated on `gateway` projects — where one
121
+ * EgoZ project fronts many of the caller's tenants — and `null`
122
+ * otherwise, including for every row predating the column.
123
+ *
124
+ * It supplies the third part of the per-user scoping key. Without it,
125
+ * `(tenantId, externalUserId)` spans every one of the caller's tenants
126
+ * a person belongs to, because their end-user id is identical across
127
+ * all of them. See `TenantScope`.
128
+ */
129
+ externalTenantId?: string | null;
118
130
  title: string | null;
119
131
  metadata: Record<string, unknown>;
120
132
  isActive: boolean;