@ego-z/contracts 0.14.1 → 0.14.4

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.14.1",
3
+ "version": "0.14.4",
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/stream.d.ts CHANGED
@@ -97,8 +97,37 @@ export interface AskStreamDeltaEvent {
97
97
  * deltas it produced. **Live mode only** — buffered streams never emit it,
98
98
  * so a consumer that does not opt in sees a byte-identical wire.
99
99
  *
100
- * Emitted at iteration end, *before* any `tool.calling` for that iteration,
101
- * so a consumer's timeline always settles before the next thing moves.
100
+ * ## Ordering guarantees
101
+ *
102
+ * 1. **Before any `tool.calling` for that iteration** — so a consumer's
103
+ * timeline always settles before the next thing moves.
104
+ * 2. **Before `done`, for every block that produced text.** Structural, not
105
+ * conventional: every `iteration.end` is emitted inside the orchestrator's
106
+ * tool loop, and `done` is emitted only after that loop returns. So a
107
+ * terminal block — which by definition has no following `tool.calling` and
108
+ * is therefore not covered by (1) — is still guaranteed resolved before the
109
+ * stream completes normally.
110
+ *
111
+ * ⚠️ **Neither guarantee covers the `error` path.** A turn that fails
112
+ * mid-iteration terminates with an `error` event, and a block that was
113
+ * streaming `'pending'` at that moment is never resolved.
114
+ *
115
+ * **An unresolved block at `error` is expected, not a defect.** It means
116
+ * what it looks like: the model was mid-thought when the turn died, and
117
+ * nothing — here or downstream — knows which channel that text would have
118
+ * belonged to. Render it as the partial thought it is. Do NOT synthesise a
119
+ * phase for it, and in particular do not "fix" it by promoting it to
120
+ * `'final'` at the error site: that presents a half-finished thought as the
121
+ * answer with the error hidden, which is precisely the failure this channel
122
+ * exists to prevent.
123
+ *
124
+ * The two terminal frames are therefore asymmetric, and anything monitoring
125
+ * the stream should treat them as different signals:
126
+ *
127
+ * - unresolved at `done` → **invariant violation.** (2) says this cannot
128
+ * happen; if it does, the loop was restructured.
129
+ * - unresolved at `error` → **expected.** Tells you how much text was
130
+ * stranded, and nothing more.
102
131
  *
103
132
  * `phase` is authoritative: it reports whether the orchestrator is actually
104
133
  * going on to dispatch tools (`'thinking'`) or this was the terminal answer
package/src/thread.d.ts CHANGED
@@ -40,6 +40,26 @@ export type MessageFailureReason =
40
40
  | 'max_calls_per_turn'
41
41
  | 'unrecoverable_error';
42
42
 
43
+ /**
44
+ * Conversation lifecycle state. Mirrors the CHECK constraint on
45
+ * `egoz_threads.status` (migration 054).
46
+ *
47
+ * - `'draft'` — minted by the ghosted start (`POST /egoz/conversations/draft`)
48
+ * before the user has typed anything. Carries the caller's
49
+ * init context, holds a conversation id the caller can keep,
50
+ * and is swept after the draft TTL if it never promotes.
51
+ * - `'active'` — a real conversation. Every thread created by `/ask` is born
52
+ * active, and a draft becomes active on its first user
53
+ * message. An active thread is never swept.
54
+ *
55
+ * ORTHOGONAL TO `isActive`, which means *archived* and predates this field.
56
+ * A draft is not an archived thread and an archived thread was never a draft;
57
+ * the two were deliberately not collapsed into one boolean, because the
58
+ * draft sweeper's delete predicate would then also match archived user
59
+ * conversations.
60
+ */
61
+ export type ThreadStatus = 'draft' | 'active';
62
+
43
63
  // ============================================================================
44
64
  // Supporting shapes
45
65
  // ============================================================================
@@ -89,6 +109,13 @@ export interface ThreadWire {
89
109
  title: string | null;
90
110
  metadata: Record<string, unknown>;
91
111
  isActive: boolean;
112
+ /**
113
+ * Conversation lifecycle — `'draft'` (ghosted start, not yet spoken to)
114
+ * or `'active'`. See `ThreadStatus`; distinct from `isActive`, which
115
+ * means archived. Rows created before migration 054 read as `'active'`,
116
+ * which is what they are.
117
+ */
118
+ status: ThreadStatus;
92
119
  /** ISO 8601 timestamp. */
93
120
  createdAt: string;
94
121
  /** ISO 8601 timestamp. */