@sigrun/shared 0.0.0-stage → 0.1.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.
- package/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +9 -2
- package/dist/access.d.ts +55 -0
- package/dist/access.js +72 -0
- package/dist/ambient.d.ts +111 -0
- package/dist/ambient.js +23 -0
- package/dist/commands.d.ts +75 -0
- package/dist/commands.js +25 -0
- package/dist/conversations.d.ts +31 -0
- package/dist/conversations.js +94 -0
- package/dist/interactions.d.ts +91 -0
- package/dist/interactions.js +21 -0
- package/dist/jobs.d.ts +100 -0
- package/dist/jobs.js +19 -0
- package/dist/json.d.ts +20 -0
- package/dist/json.js +13 -0
- package/dist/session-meta.d.ts +103 -0
- package/dist/session-meta.js +13 -0
- package/dist/settings.d.ts +401 -0
- package/dist/settings.js +19 -0
- package/dist/themes.d.ts +50 -0
- package/dist/themes.js +156 -0
- package/dist/turns.d.ts +102 -0
- package/dist/turns.js +166 -0
- package/dist/view-delta.d.ts +54 -0
- package/dist/view-delta.js +135 -0
- package/dist/views.d.ts +742 -0
- package/dist/views.js +38 -0
- package/package.json +82 -5
package/dist/views.d.ts
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §5 wire types (Tasks 9a–9d) — THE channel contract.
|
|
3
|
+
*
|
|
4
|
+
* Every HTTP/SSE payload shape both clients (webui, tui) consume is
|
|
5
|
+
* declared or re-exported here, and both import it directly (the tui
|
|
6
|
+
* imports from `@sigrun/shared`, never from server source). It is the
|
|
7
|
+
* intended basis for a generated OpenAPI schema later, so the purity
|
|
8
|
+
* rules are enforced by `test/views-purity.test.ts`:
|
|
9
|
+
*
|
|
10
|
+
* - imports and re-exports are `import type`/`export type` from SIBLING
|
|
11
|
+
* shared modules only, and type-only — they erase at compile time and
|
|
12
|
+
* contribute no runtime (the only runtime exports here are local pure
|
|
13
|
+
* constants);
|
|
14
|
+
* - no external package imports, no server/framework types, no route
|
|
15
|
+
* logic (the server's `src/server/views.ts` owns the RUNTIME
|
|
16
|
+
* serializer and re-exports this module);
|
|
17
|
+
* - no `node:` imports (the webui compiles DOM-only against this file).
|
|
18
|
+
*
|
|
19
|
+
* Spec §5: the server serves pi-durable's NATIVE views, not a bespoke
|
|
20
|
+
* endpoint list — so the conversation wire payload IS the view shape
|
|
21
|
+
* (`{ conversation, entries, docs }`; JSON-safe by construction) PLUS
|
|
22
|
+
* the sigrun-side `steer` field. The upstream view's inner shapes are
|
|
23
|
+
* re-declared STRUCTURALLY below (`ViewConversation`, `ViewEntry`,
|
|
24
|
+
* `ViewMessage`, `WireBlock`): deliberately loose where channels only
|
|
25
|
+
* read a subset, and structurally compatible with the upstream types so
|
|
26
|
+
* the server's serializer (which spreads an upstream
|
|
27
|
+
* `ConversationView`) typechecks against this declaration — the
|
|
28
|
+
* captured-schema fixture (`src/server/schema-fixture.ts`,
|
|
29
|
+
* `view-schema.json`) pins the UPSTREAM shape, and
|
|
30
|
+
* `test/schema-drift.test.ts` trips on upstream drift.
|
|
31
|
+
*
|
|
32
|
+
* `serializeConversationView` (src/server/views.ts) is the single
|
|
33
|
+
* chokepoint every §5 endpoint that returns a conversation goes through
|
|
34
|
+
* — including each SSE watch frame (T9d) — so the fixture continues to
|
|
35
|
+
* describe the wire.
|
|
36
|
+
*/
|
|
37
|
+
import type { JsonObject } from "./json.js";
|
|
38
|
+
import type { Sharing } from "./access.js";
|
|
39
|
+
import type { JobWire } from "./jobs.js";
|
|
40
|
+
import type { InteractionAnswer, InteractionBody, InteractionDiff, InteractionField, InteractionOption, InteractionRecord, InteractionStatus } from "./interactions.js";
|
|
41
|
+
import type { AmbientBody, AmbientGauge, AmbientLedgerEntry, AmbientLine, AmbientMetrics, AmbientRecord, AmbientRow } from "./ambient.js";
|
|
42
|
+
import type { EffectiveServerSetting, SettingsFile } from "./settings.js";
|
|
43
|
+
/** A model reference as served on the wire (pi-durable `ModelRef`). */
|
|
44
|
+
export interface ModelRefWire {
|
|
45
|
+
readonly provider: string;
|
|
46
|
+
readonly modelId: string;
|
|
47
|
+
}
|
|
48
|
+
/** A model's thinking level (pi-ai `ModelThinkingLevel` union). */
|
|
49
|
+
export type ThinkingLevelWire = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
50
|
+
/** One content block of a message (`{type: "text", text}`, `{type:
|
|
51
|
+
* "thinking", thinking}`, image/file parts, tool calls) — the loose
|
|
52
|
+
* block shape every renderer dispatches on. */
|
|
53
|
+
export interface WireBlock {
|
|
54
|
+
readonly type: string;
|
|
55
|
+
readonly text?: string;
|
|
56
|
+
/** The thinking text on `{type: "thinking"}` blocks. */
|
|
57
|
+
readonly thinking?: string;
|
|
58
|
+
/** Tool-call identity on `{type: "toolCall"}` blocks. */
|
|
59
|
+
readonly id?: string;
|
|
60
|
+
readonly name?: string;
|
|
61
|
+
readonly arguments?: unknown;
|
|
62
|
+
}
|
|
63
|
+
/** One model message carried by a view entry (pi-ai JSON shape). */
|
|
64
|
+
export interface ViewMessage {
|
|
65
|
+
readonly role: string;
|
|
66
|
+
readonly content: string | readonly WireBlock[];
|
|
67
|
+
/** Tool correlation — present on `toolResult`-role messages. */
|
|
68
|
+
readonly toolName?: string;
|
|
69
|
+
readonly toolCallId?: string;
|
|
70
|
+
readonly isError?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** One entry of the view's entry list. */
|
|
73
|
+
export interface ViewEntry {
|
|
74
|
+
readonly id: number;
|
|
75
|
+
readonly conversationId?: number;
|
|
76
|
+
/** Application-defined entry discriminator (`pi.user`, `pi.assistant`, …). */
|
|
77
|
+
readonly kind: string;
|
|
78
|
+
/** Messages contributed to model context; absent on display/bookkeeping entries. */
|
|
79
|
+
readonly model?: readonly ViewMessage[];
|
|
80
|
+
/** The entry's kind-specific JSON payload, when it carries one. */
|
|
81
|
+
readonly data?: unknown;
|
|
82
|
+
}
|
|
83
|
+
/** One conversation as pi-durable serves it (the upstream view). */
|
|
84
|
+
export interface ViewConversation {
|
|
85
|
+
readonly id: number;
|
|
86
|
+
/** Fork lineage (the upstream `parent`), when this conversation is a fork. */
|
|
87
|
+
readonly parent?: {
|
|
88
|
+
readonly conversationId?: number;
|
|
89
|
+
readonly at?: number;
|
|
90
|
+
};
|
|
91
|
+
/** Creator edge, when the conversation was produced by durable work. */
|
|
92
|
+
readonly owner?: unknown;
|
|
93
|
+
}
|
|
94
|
+
/** The upstream view, as the channel reads it. */
|
|
95
|
+
export interface ConversationViewBase {
|
|
96
|
+
readonly conversation: ViewConversation;
|
|
97
|
+
readonly entries: readonly ViewEntry[];
|
|
98
|
+
/** Built-in conversation documents keyed by kind; absent documents are absent. */
|
|
99
|
+
readonly docs: Readonly<Record<string, JsonObject>>;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The per-conversation steer field (spec §5: "reflecting submit-while-busy
|
|
103
|
+
* queue semantics"). `busy` = a run is live; `queued` = submissions
|
|
104
|
+
* waiting in the inbox behind it.
|
|
105
|
+
*/
|
|
106
|
+
export interface SteerField {
|
|
107
|
+
readonly busy: boolean;
|
|
108
|
+
readonly queued: number;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* One conversation as served over the wire: the upstream view plus the
|
|
112
|
+
* derived `steer` field (see the server's
|
|
113
|
+
* `serializeConversationView` — every §5 endpoint that returns a
|
|
114
|
+
* conversation, including each SSE watch frame, goes through it).
|
|
115
|
+
*/
|
|
116
|
+
export type ConversationViewWire = ConversationViewBase & {
|
|
117
|
+
readonly steer: SteerField;
|
|
118
|
+
};
|
|
119
|
+
/** One list item of `GET /v1/conversations` (Task 9a: ids, not full views). */
|
|
120
|
+
export interface ConversationSummary {
|
|
121
|
+
readonly id: number;
|
|
122
|
+
/** The conversation this summary describes is the reserved root. */
|
|
123
|
+
readonly root: boolean;
|
|
124
|
+
/**
|
|
125
|
+
* The conversation's session-meta owner, when stamped (Task 2's
|
|
126
|
+
* `X-Sigrun-User` seam — Task 3 surfaced it here so a picker can show
|
|
127
|
+
* who owns what). Absent when the doc never carried one.
|
|
128
|
+
*/
|
|
129
|
+
readonly owner?: string;
|
|
130
|
+
/**
|
|
131
|
+
* The display name: the user's `meta.name`, else the server-owned
|
|
132
|
+
* `meta.autoName` (A2's auto-namer). Absent when neither was written.
|
|
133
|
+
*/
|
|
134
|
+
readonly name?: string;
|
|
135
|
+
/**
|
|
136
|
+
* The server-owned auto-generated name (A2), when written and no user
|
|
137
|
+
* `name` overrides it. Surfaced so `conversation.changed` events can
|
|
138
|
+
* report an auto-name landing without a user edit.
|
|
139
|
+
*/
|
|
140
|
+
readonly autoName?: string;
|
|
141
|
+
/**
|
|
142
|
+
* The session-meta source — who created the session (spec §9.5):
|
|
143
|
+
* `"user"` for HTTP creates, the extension slug for channel-bridge
|
|
144
|
+
* creates through `host.conversations`. Absent when the doc was never
|
|
145
|
+
* written (defaults are free).
|
|
146
|
+
*/
|
|
147
|
+
readonly source?: string;
|
|
148
|
+
/**
|
|
149
|
+
* The last activity instant, ISO 8601 (activity.ts stamps it at
|
|
150
|
+
* submission admission and run settlement). Absent on a conversation
|
|
151
|
+
* that was never used.
|
|
152
|
+
*/
|
|
153
|
+
readonly lastActivityAt?: string;
|
|
154
|
+
/**
|
|
155
|
+
* Fork lineage: the source conversation id and the entry the history
|
|
156
|
+
* was forked at, when this conversation is a fork. Absent on a root
|
|
157
|
+
* or plainly created conversation.
|
|
158
|
+
*/
|
|
159
|
+
readonly parent?: {
|
|
160
|
+
readonly id: number;
|
|
161
|
+
readonly at: number;
|
|
162
|
+
};
|
|
163
|
+
/** The session-meta archive flag (the sidebar's hide), default false. */
|
|
164
|
+
readonly archived: boolean;
|
|
165
|
+
/** The session-meta pin flag (pinned conversations float to top), default false. */
|
|
166
|
+
readonly pinned: boolean;
|
|
167
|
+
/**
|
|
168
|
+
* The §5.9 sharing state, when the conversation carries one (absent =
|
|
169
|
+
* private). Surfaced so a picker can show what is shared.
|
|
170
|
+
*/
|
|
171
|
+
readonly sharing?: Sharing;
|
|
172
|
+
/**
|
|
173
|
+
* THIS caller's resolved access to the conversation (§5.9 `accessOf`):
|
|
174
|
+
* conversations the caller cannot see are filtered from the list
|
|
175
|
+
* entirely, so every listed row is at least `read`.
|
|
176
|
+
*/
|
|
177
|
+
readonly access: "read" | "write";
|
|
178
|
+
}
|
|
179
|
+
/** The list response of `GET /v1/conversations`. */
|
|
180
|
+
export interface ConversationList {
|
|
181
|
+
readonly conversations: readonly ConversationSummary[];
|
|
182
|
+
}
|
|
183
|
+
/** The create response of `POST /v1/conversations`. */
|
|
184
|
+
export interface ConversationCreateResponse {
|
|
185
|
+
readonly id: number;
|
|
186
|
+
readonly view: ConversationViewWire;
|
|
187
|
+
}
|
|
188
|
+
/** `GET /v1/jobs`: the caller-visible jobs (`accessOf ≠ "none"`), each row
|
|
189
|
+
* carrying the caller's resolved `access`. */
|
|
190
|
+
export interface JobsResponse {
|
|
191
|
+
readonly jobs: readonly JobWire[];
|
|
192
|
+
}
|
|
193
|
+
/** `POST /v1/jobs/:id/run`: the fire outcome plus the run's conversation
|
|
194
|
+
* when one exists (a skipped/blocked fire may have none). */
|
|
195
|
+
export interface JobRunResponse {
|
|
196
|
+
readonly outcome: "running" | "skipped" | "blocked";
|
|
197
|
+
readonly conversationId?: number;
|
|
198
|
+
}
|
|
199
|
+
/** The `Content-Type` of the JSONL export/import wire (Task 5b/5c). */
|
|
200
|
+
export declare const JSONL_CONTENT_TYPE = "application/x-ndjson";
|
|
201
|
+
/**
|
|
202
|
+
* The fork response of `POST /v1/conversations/:id/fork` (Task 5a): the
|
|
203
|
+
* create shape — a NEW conversation id plus its first view frame.
|
|
204
|
+
*/
|
|
205
|
+
export type ConversationForkResponse = ConversationCreateResponse;
|
|
206
|
+
/**
|
|
207
|
+
* The import response of `POST /v1/conversations/import` (Task 5c): the
|
|
208
|
+
* create shape — the new conversation id plus its view with the appended
|
|
209
|
+
* entries in order.
|
|
210
|
+
*/
|
|
211
|
+
export type ConversationImportResponse = ConversationCreateResponse;
|
|
212
|
+
/**
|
|
213
|
+
* The durable submission record on the wire (§7.1): the record as it
|
|
214
|
+
* stands when read — `status` is `queued` | `placed` | `done` |
|
|
215
|
+
* `unanswered`; `entry`/`answer`/`reason`/`detail` appear per status.
|
|
216
|
+
*/
|
|
217
|
+
export interface SubmissionRecordWire {
|
|
218
|
+
readonly id: number;
|
|
219
|
+
readonly conversationId: number;
|
|
220
|
+
/** Host-provided deduplication key, scoped to the conversation. */
|
|
221
|
+
readonly requestId?: string;
|
|
222
|
+
readonly type: string;
|
|
223
|
+
readonly status: string;
|
|
224
|
+
readonly entry?: number;
|
|
225
|
+
readonly answer?: number;
|
|
226
|
+
readonly reason?: string;
|
|
227
|
+
readonly detail?: unknown;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The admit response of `POST /v1/conversations/:id/submissions` (§7.1):
|
|
231
|
+
* `202 {submission}` AT ADMISSION — the record as it stands the moment
|
|
232
|
+
* the submission is admitted. `?waitMs=<n>` (a positive int ≤ 60 000)
|
|
233
|
+
* opts into a bounded settlement wait; `GET …/submissions/:submissionId`
|
|
234
|
+
* polls the record later.
|
|
235
|
+
*/
|
|
236
|
+
export interface SubmissionResponse {
|
|
237
|
+
readonly submission: SubmissionRecordWire;
|
|
238
|
+
}
|
|
239
|
+
/** The upload response of `POST /v1/conversations/:id/uploads` (Task 9b). */
|
|
240
|
+
export interface UploadResponse {
|
|
241
|
+
/** The workspace-relative path to use in submission content parts. */
|
|
242
|
+
readonly path: string;
|
|
243
|
+
}
|
|
244
|
+
/** One `GET /v1/conversations/:id/files` entry (upstream `FileInfo`). */
|
|
245
|
+
export interface FileEntry {
|
|
246
|
+
readonly name: string;
|
|
247
|
+
readonly path: string;
|
|
248
|
+
readonly kind: "file" | "directory" | "symlink";
|
|
249
|
+
readonly size: number;
|
|
250
|
+
readonly mtimeMs: number;
|
|
251
|
+
}
|
|
252
|
+
/** The list response of `GET /v1/conversations/:id/files` (Task 9b). */
|
|
253
|
+
export interface FileListResponse {
|
|
254
|
+
readonly entries: readonly FileEntry[];
|
|
255
|
+
}
|
|
256
|
+
/** The read response of `GET /v1/conversations/:id/files/content`. */
|
|
257
|
+
export interface FileContentResponse {
|
|
258
|
+
/** `"text"` (valid UTF-8) or `"base64"` (binary). */
|
|
259
|
+
readonly encoding: "text" | "base64";
|
|
260
|
+
readonly content: string;
|
|
261
|
+
/** Size in bytes. */
|
|
262
|
+
readonly size: number;
|
|
263
|
+
}
|
|
264
|
+
/** One `GET /v1/models` entry (Task 9b): picker fields, no provider internals. */
|
|
265
|
+
export interface ModelEntry {
|
|
266
|
+
/** `"provider/modelId"` — the id submission/configure `model` takes. */
|
|
267
|
+
readonly id: string;
|
|
268
|
+
readonly provider: string;
|
|
269
|
+
readonly modelId: string;
|
|
270
|
+
readonly name: string;
|
|
271
|
+
readonly contextWindow: number;
|
|
272
|
+
readonly maxTokens: number;
|
|
273
|
+
readonly reasoning: boolean;
|
|
274
|
+
readonly input: readonly string[];
|
|
275
|
+
readonly cost: {
|
|
276
|
+
readonly input: number;
|
|
277
|
+
readonly output: number;
|
|
278
|
+
readonly cacheRead: number;
|
|
279
|
+
readonly cacheWrite: number;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/** The response of `GET /v1/models`. */
|
|
283
|
+
export interface ModelsResponse {
|
|
284
|
+
readonly models: readonly ModelEntry[];
|
|
285
|
+
}
|
|
286
|
+
/** The configured agent surface `POST .../configure` returns (T9c-i). */
|
|
287
|
+
export interface ConfigureResponse {
|
|
288
|
+
/** Absent when unset — no model means no generation. */
|
|
289
|
+
readonly model?: ModelRefWire;
|
|
290
|
+
readonly thinkingLevel: ThinkingLevelWire;
|
|
291
|
+
}
|
|
292
|
+
/** The result mapping `POST .../abort` returns (T9c-i). */
|
|
293
|
+
export type AbortResponse = {
|
|
294
|
+
readonly result: "aborted";
|
|
295
|
+
readonly submissionId: number;
|
|
296
|
+
} | {
|
|
297
|
+
readonly result: "already_placed";
|
|
298
|
+
readonly submissionId: number;
|
|
299
|
+
} | {
|
|
300
|
+
readonly result: "settled";
|
|
301
|
+
readonly submissionId: number;
|
|
302
|
+
};
|
|
303
|
+
/**
|
|
304
|
+
* The admit response of `POST /v1/conversations/:id/compact` (Task 4, pi
|
|
305
|
+
* parity): the durable compaction task's id. The task is conversation-owned
|
|
306
|
+
* (manual): it compacts even when idle and places its summary through a
|
|
307
|
+
* write submission; the SSE stream carries the `pi.compaction` entry.
|
|
308
|
+
*/
|
|
309
|
+
export interface CompactResponse {
|
|
310
|
+
readonly taskId: number;
|
|
311
|
+
}
|
|
312
|
+
/** One interaction record on the wire (the envelope + body + details). */
|
|
313
|
+
export type InteractionWire = InteractionRecord;
|
|
314
|
+
/** The response of `GET /v1/conversations/:id/interactions`. */
|
|
315
|
+
export interface InteractionList {
|
|
316
|
+
readonly interactions: readonly InteractionWire[];
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* One pending interaction in the cross-conversation inbox (§7.5): the
|
|
320
|
+
* record plus the conversation it waits in, so a client can answer
|
|
321
|
+
* without opening each conversation (answers still go through the
|
|
322
|
+
* per-conversation answer route).
|
|
323
|
+
*/
|
|
324
|
+
export type InboxInteraction = InteractionWire & {
|
|
325
|
+
readonly conversationId: number;
|
|
326
|
+
};
|
|
327
|
+
/**
|
|
328
|
+
* The response of `GET /v1/interactions?status=pending` (§7.5): every
|
|
329
|
+
* pending interaction in a conversation the caller can see, sorted by
|
|
330
|
+
* `createdAt` ascending. `pending` is the only indexed (and accepted)
|
|
331
|
+
* status.
|
|
332
|
+
*/
|
|
333
|
+
export interface InboxResponse {
|
|
334
|
+
readonly interactions: readonly InboxInteraction[];
|
|
335
|
+
}
|
|
336
|
+
/** The response of `POST .../interactions/:callId/answer`. */
|
|
337
|
+
export interface InteractionAnswered {
|
|
338
|
+
readonly callId: string;
|
|
339
|
+
readonly status: "answered";
|
|
340
|
+
readonly answer: InteractionAnswer;
|
|
341
|
+
}
|
|
342
|
+
/** One ambient surface on the wire (envelope + body + details). */
|
|
343
|
+
export type AmbientWire = AmbientRecord;
|
|
344
|
+
/** The response of `GET /v1/conversations/:id/ambient`. */
|
|
345
|
+
export interface AmbientList {
|
|
346
|
+
readonly ambient: readonly AmbientWire[];
|
|
347
|
+
}
|
|
348
|
+
export type { InteractionAnswer, InteractionBody, InteractionDiff, InteractionField, InteractionOption, InteractionStatus, };
|
|
349
|
+
export type { AmbientBody, AmbientGauge, AmbientLedgerEntry, AmbientLine, AmbientMetrics, AmbientRow, };
|
|
350
|
+
/**
|
|
351
|
+
* The `GET/PATCH /v1/settings` payload (Task 8b): the settings file's
|
|
352
|
+
* effective contents — the `HarnessSettings` pass-throughs plus the
|
|
353
|
+
* sigrun-resolved fields — plus `toolRoster`, the registry-driven roster
|
|
354
|
+
* the settings panel renders `defaultTools` over (Task 3, codemode
|
|
355
|
+
* parity). Absent file = every default; the roster is SERVER-COMPUTED
|
|
356
|
+
* from the live registry snapshot per request (never persisted, never
|
|
357
|
+
* snapshotted at boot), so extension tools installed later — codemode,
|
|
358
|
+
* tool_search — appear without panel changes. `toolRoster` is NOT a
|
|
359
|
+
* settable key: the store's validation rejects it on PATCH (400
|
|
360
|
+
* `invalid_field`), and the wire patch type Omits it.
|
|
361
|
+
*/
|
|
362
|
+
export interface SettingsResponse extends SettingsFile {
|
|
363
|
+
/** The live registry snapshot's composed tool names, deduplicated. */
|
|
364
|
+
readonly toolRoster: readonly string[];
|
|
365
|
+
/**
|
|
366
|
+
* The resolved server section per flattened dotted key
|
|
367
|
+
* (`"server.port"`, `"server.compaction.reserveTokens"`, …), each
|
|
368
|
+
* with its resolved value and which tier supplied it — `"env"`,
|
|
369
|
+
* `"file"`, or `"default"` (spec §6.6: the generalization of the
|
|
370
|
+
* build spec's webui-key special case; since the bootstrap persists
|
|
371
|
+
* only env-set values, this block is where the full resolved section
|
|
372
|
+
* is visible). Computed per request, never persisted. NOT a settable
|
|
373
|
+
* key: the store's validation rejects it on PATCH (400
|
|
374
|
+
* `invalid_field`), and the wire patch type Omits it.
|
|
375
|
+
*/
|
|
376
|
+
readonly effective: Readonly<Record<string, EffectiveServerSetting>>;
|
|
377
|
+
}
|
|
378
|
+
/** Spec §3 health status: ok unless the surface knows it is degraded. */
|
|
379
|
+
export type HealthStatus = "ok" | "degraded";
|
|
380
|
+
/**
|
|
381
|
+
* One idle worker an extension declared (memory-seam Task 5): the bus's
|
|
382
|
+
* per-worker observability record as `/v1/meta` serves it.
|
|
383
|
+
*/
|
|
384
|
+
export type WorkerStatus = {
|
|
385
|
+
readonly name: string;
|
|
386
|
+
readonly lastRunAt?: number;
|
|
387
|
+
readonly lastError?: string;
|
|
388
|
+
};
|
|
389
|
+
/** The `GET /v1/meta` payload (spec §5; the health block is Task 9e). */
|
|
390
|
+
export interface MetaResponse {
|
|
391
|
+
readonly harnessVersion: string;
|
|
392
|
+
readonly schemaVersion: string;
|
|
393
|
+
readonly extensions: readonly {
|
|
394
|
+
readonly name: string;
|
|
395
|
+
readonly status: string;
|
|
396
|
+
readonly error?: string;
|
|
397
|
+
/**
|
|
398
|
+
* The extension's idle workers (memory-seam Task 5), when it
|
|
399
|
+
* declared any; `lastRunAt`/`lastError` stay absent until the
|
|
400
|
+
* worker's first run.
|
|
401
|
+
*/
|
|
402
|
+
readonly workers?: readonly WorkerStatus[];
|
|
403
|
+
}[];
|
|
404
|
+
/**
|
|
405
|
+
* §5 health: pure aggregation over boot-retained §3 producers (spec
|
|
406
|
+
* §3) — synchronous status reads, never network. The one deliberate
|
|
407
|
+
* exception is `backup` (§6.8): its §6.8 derivation reads the small
|
|
408
|
+
* `backup/last-success.json` marker from the state root per request
|
|
409
|
+
* (the CronJob is a separate process; the filesystem is the only
|
|
410
|
+
* fresh-enough view).
|
|
411
|
+
*/
|
|
412
|
+
readonly health: {
|
|
413
|
+
/**
|
|
414
|
+
* §6.8 backup honesty: presence-derived when no target is
|
|
415
|
+
* configured ("unconfigured"); with a target, "configured" only
|
|
416
|
+
* while the last-success marker is present and younger than
|
|
417
|
+
* `server.backup.staleAfterHours` (default 36 h) — absent, corrupt,
|
|
418
|
+
* or stale all read "degraded".
|
|
419
|
+
*/
|
|
420
|
+
readonly backup: "configured" | "unconfigured" | "degraded";
|
|
421
|
+
/** The memory extension's §3 producer, else "ok" (no-op default). */
|
|
422
|
+
readonly memory: HealthStatus;
|
|
423
|
+
/** Any contributor's §3 producer degraded → "degraded"; empty → "ok". */
|
|
424
|
+
readonly providers: HealthStatus;
|
|
425
|
+
};
|
|
426
|
+
/**
|
|
427
|
+
/**
|
|
428
|
+
* The auth block (core hardening W1 Task 6, spec §5.7): the effective
|
|
429
|
+
* authenticator list (`ctx.methods` — loopback-derived or explicit) and
|
|
430
|
+
* the onboarding state (`claimed` = the user directory has its first
|
|
431
|
+
* user). A pure boot+store derivation, like the rest of meta — clients
|
|
432
|
+
* may cache it only per boot, since the claim flips it.
|
|
433
|
+
*/
|
|
434
|
+
readonly auth: {
|
|
435
|
+
readonly methods: readonly ("token" | "proxy")[];
|
|
436
|
+
readonly claimed: boolean;
|
|
437
|
+
};
|
|
438
|
+
/**
|
|
439
|
+
* §6.8: the last successful backup's timestamp (the marker's `at`),
|
|
440
|
+
* surfaced whenever a readable marker exists against a configured
|
|
441
|
+
* target — including a STALE one (the degraded badge's "when was the
|
|
442
|
+
* last success"). Absent otherwise.
|
|
443
|
+
*/
|
|
444
|
+
readonly backup: {
|
|
445
|
+
readonly lastSuccessAt?: string;
|
|
446
|
+
};
|
|
447
|
+
readonly capabilities: {
|
|
448
|
+
readonly backup: boolean;
|
|
449
|
+
readonly channels: readonly string[];
|
|
450
|
+
};
|
|
451
|
+
/**
|
|
452
|
+
* The seam inventory (docs/approval-ux-seams.md): per extension, the
|
|
453
|
+
* interaction kinds / ambient surface keys its factory declared.
|
|
454
|
+
* Extensions that declared nothing are absent — an absent entry means
|
|
455
|
+
* the extension writes no seam records, which is exactly the fail-open
|
|
456
|
+
* posture (channels render what is declared, nothing more).
|
|
457
|
+
*/
|
|
458
|
+
readonly seam: {
|
|
459
|
+
readonly interactions: readonly {
|
|
460
|
+
readonly extension: string;
|
|
461
|
+
readonly kinds: readonly string[];
|
|
462
|
+
}[];
|
|
463
|
+
readonly ambient: readonly {
|
|
464
|
+
readonly extension: string;
|
|
465
|
+
readonly keys: readonly string[];
|
|
466
|
+
}[];
|
|
467
|
+
};
|
|
468
|
+
/**
|
|
469
|
+
* Boot-merge collisions (build-and-webui Task 6): name collisions among
|
|
470
|
+
* extension-contributed webui bundles, the provider-catalog merge sink,
|
|
471
|
+
* and extension COMMAND names (W5 Task 3). All three merges are
|
|
472
|
+
* last-registered-wins by design — the record is the operator's
|
|
473
|
+
* visibility into a co-installed replacement, never an error; empty
|
|
474
|
+
* arrays are the no-collision boot. A command colliding with a CORE
|
|
475
|
+
* name is DROPPED with a boot warning and deliberately NOT recorded
|
|
476
|
+
* here (core commands are reserved, not replaceable — there is no
|
|
477
|
+
* "winner" to report).
|
|
478
|
+
*/
|
|
479
|
+
readonly collisions: {
|
|
480
|
+
readonly webui: readonly {
|
|
481
|
+
readonly kind: "webui-bundle";
|
|
482
|
+
/** The bundle name that collided. */
|
|
483
|
+
readonly name: string;
|
|
484
|
+
/** Extension slug that previously owned the name. */
|
|
485
|
+
readonly previous: string;
|
|
486
|
+
/** Extension slug whose contribution won. */
|
|
487
|
+
readonly contributor: string;
|
|
488
|
+
}[];
|
|
489
|
+
readonly providers: readonly {
|
|
490
|
+
readonly kind: "provider";
|
|
491
|
+
/** The provider id that collided. */
|
|
492
|
+
readonly provider: string;
|
|
493
|
+
/** Model ids present in both the previous and replacing catalog. */
|
|
494
|
+
readonly modelIds: readonly string[];
|
|
495
|
+
/** Contributor name that previously owned the provider. */
|
|
496
|
+
readonly previous: string;
|
|
497
|
+
/** Contributor name whose contribution won. */
|
|
498
|
+
readonly contributor: string;
|
|
499
|
+
}[];
|
|
500
|
+
readonly commands: readonly {
|
|
501
|
+
readonly kind: "command";
|
|
502
|
+
/** The command name that collided. */
|
|
503
|
+
readonly name: string;
|
|
504
|
+
/** Extension slug that previously owned the name. */
|
|
505
|
+
readonly previous: string;
|
|
506
|
+
/** Extension slug whose contribution won. */
|
|
507
|
+
readonly contributor: string;
|
|
508
|
+
}[];
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Provider auth readiness statuses (Task 6, spec §6.4 + provider plugins
|
|
513
|
+
* Task 6): statuses only. The legacy vocabulary (`ready` | `not_ready` |
|
|
514
|
+
* `invalid`) is preserved VERBATIM for every derivation that exists today
|
|
515
|
+
* — the legacy producer mapping (tracker-less contributors) and the
|
|
516
|
+
* degenerate env-presence mapping — so old consumers keep working; the
|
|
517
|
+
* provider-plugins three-state vocabulary (`not_configured` |
|
|
518
|
+
* `degraded` | `ready`) is ADDED for tracker-backed providers, where
|
|
519
|
+
* `ready` is the shared spelling.
|
|
520
|
+
*/
|
|
521
|
+
export type AuthStatus = "ready" | "not_ready" | "invalid" | "not_configured" | "degraded";
|
|
522
|
+
/**
|
|
523
|
+
* One `GET /v1/providers` entry — provider id + status, never key
|
|
524
|
+
* material. `detail` (provider plugins Task 6) is the optional
|
|
525
|
+
* tracker-facing operator detail; the legacy derivations never set it.
|
|
526
|
+
* Core hardening W3 Task 6 (spec §7.6) adds the catalog `models` count
|
|
527
|
+
* and the owning extension `owner` (absent for the env-degenerate
|
|
528
|
+
* provider) — the deprecated `/v1/auth/check` alias serves this same
|
|
529
|
+
* entry, one shape for both routes.
|
|
530
|
+
*/
|
|
531
|
+
export interface AuthCheckProvider {
|
|
532
|
+
readonly provider: string;
|
|
533
|
+
readonly status: AuthStatus;
|
|
534
|
+
/** How many models the provider currently serves (best-effort count). */
|
|
535
|
+
readonly models: number;
|
|
536
|
+
/** The owning contributor's name; absent for the env-degenerate provider. */
|
|
537
|
+
readonly owner?: string;
|
|
538
|
+
readonly detail?: string;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* The `GET /v1/providers` payload (core hardening W1 Task 6, spec
|
|
542
|
+
* §5.7/§7.6): the auth-check derivation under its new home — `/v1/auth`
|
|
543
|
+
* is API auth only, and the secret-free readiness report lives at
|
|
544
|
+
* `/v1/providers`. `GET /v1/auth/check` serves this same payload as a
|
|
545
|
+
* deprecated alias for one release (its responses carry a `Deprecation`
|
|
546
|
+
* header).
|
|
547
|
+
*/
|
|
548
|
+
export interface ProvidersResponse {
|
|
549
|
+
readonly providers: readonly AuthCheckProvider[];
|
|
550
|
+
}
|
|
551
|
+
/** The `GET /v1/auth/check` payload (Task 6): per-provider statuses, never credentials. */
|
|
552
|
+
export interface AuthCheckResponse {
|
|
553
|
+
readonly providers: readonly AuthCheckProvider[];
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* One `GET /v1/extensions` entry: a loaded outcome enriched with the
|
|
557
|
+
* declared seam kinds, the idle-worker projection, the package
|
|
558
|
+
* manifest `version` (absent when the entry carries no readable
|
|
559
|
+
* manifest), and the declared HTTP routes (W5 Task 2 — present on
|
|
560
|
+
* every served entry, empty when the extension declared none). The
|
|
561
|
+
* webui's Extensions settings section renders this list.
|
|
562
|
+
*/
|
|
563
|
+
export interface ExtensionInfo {
|
|
564
|
+
readonly name: string;
|
|
565
|
+
readonly status: string;
|
|
566
|
+
readonly error?: string;
|
|
567
|
+
readonly version?: string;
|
|
568
|
+
/** The interaction kinds the factory declared (empty when none). */
|
|
569
|
+
readonly interactionKinds: readonly string[];
|
|
570
|
+
/** The ambient surface keys the factory declared (empty when none). */
|
|
571
|
+
readonly ambientKeys: readonly string[];
|
|
572
|
+
/** The extension's idle workers (empty when it declared none). */
|
|
573
|
+
readonly workers: readonly WorkerStatus[];
|
|
574
|
+
/** The declared HTTP routes, one `{method, path, auth}` each (W5 Task 2). */
|
|
575
|
+
routes?: readonly ExtensionRouteInfo[];
|
|
576
|
+
/**
|
|
577
|
+
* Built-in extensions (Task 2, pi parity): `true` marks a core-shipped
|
|
578
|
+
* built-in (codemode / MCP / tool search); ABSENT on configured
|
|
579
|
+
* extensions. A replaced built-in reports its row with `builtin: true`
|
|
580
|
+
* and `replacedBy` (its status is the skip, never `"ok"`); a disabled
|
|
581
|
+
* built-in is absent from the list entirely.
|
|
582
|
+
*/
|
|
583
|
+
readonly builtin?: boolean;
|
|
584
|
+
/** The configured specifier that replaced this built-in (replacement rule). */
|
|
585
|
+
readonly replacedBy?: string;
|
|
586
|
+
}
|
|
587
|
+
/** One `GET /v1/extensions` entry's route rows (W5 Task 2, spec §9.2). */
|
|
588
|
+
export interface ExtensionRouteInfo {
|
|
589
|
+
readonly method: string;
|
|
590
|
+
/** The declared relative path — mounted at `/v1/ext/<slug>/<path>`. */
|
|
591
|
+
readonly path: string;
|
|
592
|
+
readonly auth: string;
|
|
593
|
+
}
|
|
594
|
+
/** The `GET /v1/extensions` payload (spec §7.7). */
|
|
595
|
+
export interface ExtensionsResponse {
|
|
596
|
+
readonly extensions: readonly ExtensionInfo[];
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* A `GET /v1/extensions/:slug/secrets` entry: the key and its winning
|
|
600
|
+
* source — NEVER the value (the API lists and writes secrets; it never
|
|
601
|
+
* returns them).
|
|
602
|
+
*/
|
|
603
|
+
export interface ExtensionSecret {
|
|
604
|
+
readonly key: string;
|
|
605
|
+
readonly source: "stored" | "env";
|
|
606
|
+
}
|
|
607
|
+
/** The `GET`/`PUT`/`DELETE `/v1/extensions/:slug/secrets` payloads: the key list. */
|
|
608
|
+
export interface ExtensionSecretsResponse {
|
|
609
|
+
readonly secrets: readonly ExtensionSecret[];
|
|
610
|
+
}
|
|
611
|
+
export type { CommandsResponse, CommandRunResponse, SlashCommand } from "./commands.js";
|
|
612
|
+
export type { SessionMeta, SessionMetaPatch } from "./session-meta.js";
|
|
613
|
+
/**
|
|
614
|
+
* The `GET /v1/auth/whoami` payload (core hardening W1 Task 3, spec
|
|
615
|
+
* §5.8): the resolved principal, verbatim. `user` is absent only under
|
|
616
|
+
* the naive empty-method path with no `X-Sigrun-User` header;
|
|
617
|
+
* `service` marks the env service token.
|
|
618
|
+
*/
|
|
619
|
+
export interface WhoamiResponse {
|
|
620
|
+
readonly user?: string;
|
|
621
|
+
readonly name?: string;
|
|
622
|
+
readonly email?: string;
|
|
623
|
+
readonly admin: boolean;
|
|
624
|
+
readonly service: boolean;
|
|
625
|
+
readonly method: "none" | "token" | "proxy";
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* The `GET /v1/onboarding` payload (core hardening W1 Task 4, spec
|
|
629
|
+
* §5.8): what an unauthenticated client needs to drive the claim flow.
|
|
630
|
+
* `codeRequired` is false on a loopback bind or when the request itself
|
|
631
|
+
* carries a trusted proxy identity (either is trust enough — no code);
|
|
632
|
+
* `proxyIdentity` is present exactly then, carrying what the headers
|
|
633
|
+
* vouch for so the webui can render its confirm screen.
|
|
634
|
+
*/
|
|
635
|
+
export interface OnboardingState {
|
|
636
|
+
readonly state: "unclaimed" | "claimed";
|
|
637
|
+
readonly methods: readonly ("token" | "proxy")[];
|
|
638
|
+
readonly codeRequired: boolean;
|
|
639
|
+
readonly proxyIdentity?: {
|
|
640
|
+
readonly user: string;
|
|
641
|
+
readonly name?: string;
|
|
642
|
+
readonly email?: string;
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* One directory user on the wire (Task 4): the full record — emails are
|
|
647
|
+
* admin-visible material and the routes that serve this type are
|
|
648
|
+
* admin-gated. `createdAt` is epoch ms.
|
|
649
|
+
*/
|
|
650
|
+
export interface UserWire {
|
|
651
|
+
readonly user: string;
|
|
652
|
+
readonly name?: string;
|
|
653
|
+
readonly email?: string;
|
|
654
|
+
readonly admin: boolean;
|
|
655
|
+
readonly createdAt: number;
|
|
656
|
+
readonly source: "proxy" | "invite" | "cli";
|
|
657
|
+
}
|
|
658
|
+
/** The `POST /v1/onboarding/claim` payload (Task 4): the user (plus the
|
|
659
|
+
* minted token when `issueToken` was requested — shown once, never
|
|
660
|
+
* persisted). */
|
|
661
|
+
export interface ClaimResponse {
|
|
662
|
+
readonly user: UserWire;
|
|
663
|
+
readonly token?: string;
|
|
664
|
+
}
|
|
665
|
+
/** The `POST /v1/users/invites` payload (Task 4): the one-time code is
|
|
666
|
+
* shown once, never stored in the clear. */
|
|
667
|
+
export interface InviteResponse {
|
|
668
|
+
readonly id: string;
|
|
669
|
+
readonly code: string;
|
|
670
|
+
readonly expiresAt: number;
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* One API token on the wire (Task 4): the token store's record minus the
|
|
674
|
+
* `sha256` digest — digests never leave the server. Created by the
|
|
675
|
+
* claim route (`issueToken`) and `POST /v1/auth/tokens`.
|
|
676
|
+
*/
|
|
677
|
+
export interface TokenWire {
|
|
678
|
+
readonly id: string;
|
|
679
|
+
readonly name: string;
|
|
680
|
+
readonly user: string;
|
|
681
|
+
readonly createdAt: number;
|
|
682
|
+
}
|
|
683
|
+
/** The `POST /v1/auth/tokens` payload (Task 4): the secret once, the
|
|
684
|
+
* digest-free record alongside it. */
|
|
685
|
+
export interface TokenCreateResponse {
|
|
686
|
+
readonly token: string;
|
|
687
|
+
readonly record: TokenWire;
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* One host event on the `GET /v1/events` SSE wire: `event: <type>` plus
|
|
691
|
+
* this object as the single-line `data:` JSON. Conversation-scoped
|
|
692
|
+
* events are delivered only when the connecting principal's `accessOf`
|
|
693
|
+
* against a FRESH SessionMeta snapshot is not `"none"` (per-event
|
|
694
|
+
* re-check: share → delivered, un-share → withheld);
|
|
695
|
+
* `providers.changed` reaches every authenticated principal;
|
|
696
|
+
* `job.changed` carries the changed job's `JobWire` summary WITHOUT
|
|
697
|
+
* `access` — the events stream computes and adds it per principal before
|
|
698
|
+
* delivery (the hub's copy is access-neutral).
|
|
699
|
+
* No replay: a client that reconnects refetches (the list GET); the
|
|
700
|
+
* stream only nudges.
|
|
701
|
+
*/
|
|
702
|
+
export type HostEventWire = {
|
|
703
|
+
readonly type: "conversation.created";
|
|
704
|
+
readonly conversationId: number;
|
|
705
|
+
} | {
|
|
706
|
+
readonly type: "conversation.changed";
|
|
707
|
+
readonly conversationId: number;
|
|
708
|
+
/** The summary fields that changed, with their new values. */
|
|
709
|
+
readonly fields: Partial<ConversationSummary>;
|
|
710
|
+
} | {
|
|
711
|
+
readonly type: "run.admitted";
|
|
712
|
+
readonly conversationId: number;
|
|
713
|
+
readonly submissionId: number;
|
|
714
|
+
} | {
|
|
715
|
+
readonly type: "run.settled";
|
|
716
|
+
readonly conversationId: number;
|
|
717
|
+
readonly outcome: "answered" | "unanswered";
|
|
718
|
+
readonly reason?: string;
|
|
719
|
+
} | {
|
|
720
|
+
readonly type: "interaction.pending" | "interaction.settled";
|
|
721
|
+
readonly conversationId: number;
|
|
722
|
+
readonly callId: string;
|
|
723
|
+
readonly kind: string;
|
|
724
|
+
} | {
|
|
725
|
+
readonly type: "ambient.changed";
|
|
726
|
+
readonly conversationId: number;
|
|
727
|
+
readonly key: string;
|
|
728
|
+
} | {
|
|
729
|
+
readonly type: "providers.changed";
|
|
730
|
+
readonly provider: string;
|
|
731
|
+
readonly status: string;
|
|
732
|
+
} | {
|
|
733
|
+
/** A task table write with a TERMINAL status (jobs Task 3, §8.1): the jobs observer settles `task`-action run records from it — `completed` → `succeeded`, any other terminal → `failed` + `error`. Hub-side only (not an SSE wire event): the task table is host machinery, not principal-facing surface. */
|
|
734
|
+
readonly type: "taskSettled";
|
|
735
|
+
readonly taskId: number;
|
|
736
|
+
readonly status: string;
|
|
737
|
+
readonly error?: string;
|
|
738
|
+
} | {
|
|
739
|
+
/** W4 Task 4 (§8.4): the changed job's summary, WITHOUT `access` — the events stream computes `accessOf` per principal and adds it before delivery (a hidden job's event is withheld entirely). */
|
|
740
|
+
readonly type: "job.changed";
|
|
741
|
+
readonly job: Omit<JobWire, "access">;
|
|
742
|
+
};
|