@flowy-agent-store/protocol 0.1.0-beta.10

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,1925 @@
1
+ //#region src/protocol.d.ts
2
+ /**
3
+ * Agent Store App Server Protocol types.
4
+ *
5
+ * The chat surface uses only opaque public conversation/message IDs. Internal
6
+ * execution/session/step/attempt IDs never appear in this module.
7
+ */
8
+ /**
9
+ * A contract **fingerprint**, not a version number: it must differ from the
10
+ * previous value on any wire change at all, additive included. The shape is
11
+ * `fp-<n>` — a plain counter, so a bump just increments it and no value is ever
12
+ * reused by accident.
13
+ *
14
+ * So it is a **label, not a version**: neither a release number nor a date.
15
+ * Until `fp-1` the values were date stamps (kept below as history), and those
16
+ * dates were **not** the day of the change: consecutive changes advanced the
17
+ * stamp a day each, so they ran ahead of the calendar. The list below is keyed
18
+ * by *value* — read it as "which wire change is this?", never as "when did this
19
+ * ship".
20
+ *
21
+ * `2026-09-16` carried `StoreItem.published_at`; `2026-09-17` added the two MCP
22
+ * declaration write methods (`config/set-mcp`, `config/set-mcp-enabled`);
23
+ * `2026-09-18` adds `config/get-mcp`, the file editor's read of the same file;
24
+ * `2026-09-19` adds the `conversation/list-changed` notification; `2026-09-20`
25
+ * adds the Skill **file tree** read face (`skill/files`, `skill/file`) so a
26
+ * Skill's companion files are readable, not just its bounded manifest summary;
27
+ * `2026-09-21` adds the connector **call proxy** (`connector/call`), so a third
28
+ * party can run an installed MCP tool while the connection and its credentials
29
+ * stay on the host. **`fp-1` changes the shape only** (date stamp → counter): a
30
+ * `2026-…` value invites being read as a release date, and no wire behaviour
31
+ * changed with the rename. **`fp-2` carries the tools' parameters**
32
+ * (`ConnectorTool.input_schema`, plus `tools_truncated` on `ConnectorDetail`
33
+ * and `ConnectorProbeResult`): a caller that must *name* a tool to be granted it
34
+ * should be able to read what that tool takes. It is the counterpart of the
35
+ * host's `[connector_proxy]` grant moving from one tool at a time to the
36
+ * connector (doc `26`). **`fp-3` makes a Skill selectable per turn**:
37
+ * `conversation/send` gains an optional `mentions` list whose only honoured kind
38
+ * is `skill`, so one turn can mount a Skill's instructions without rewriting the
39
+ * conversation's create-time snapshot (doc `27` 阶段 1). **`fp-4` lets a
40
+ * conversation be created as an installed expert**: `conversation/create` gains
41
+ * an optional `agent_id`, whose Definition supplies the chat's preset identity
42
+ * plus its own Skill and Connector fences — frozen at creation, since none of
43
+ * those keys is mutable afterwards (doc `27` 阶段 2a). **`fp-5` opens a Team's
44
+ * Leader the same way**: an optional `team_id` runs the `team/run` orchestration
45
+ * (members, template, fences) but stops before the goal turn, so the client
46
+ * speaks first; `agent_id` and `team_id` are mutually exclusive (阶段 2b).
47
+ * **`fp-6` makes the model and the reasoning level selectable per call**:
48
+ * `conversation/send` and `agent/run` each gain an optional `model` and
49
+ * `reasoning_effort`. The scope is deliberately the **conversation** (send) and
50
+ * the **run** (agent/run), not "just this turn": the Nomi runtime is built from
51
+ * the persisted row, so a send-carried value is a sticky switch that takes
52
+ * effect on that very turn (doc `29` §4). `ConversationView` also gains
53
+ * `reasoning_effort`, because a setting that can be written by three methods but
54
+ * read back by none is not a setting a client can honour (doc `29` §5.5).
55
+ * **`fp-7` adds the `zip` marketplace source kind**: `market/add` accepts
56
+ * `source_kind: "zip"`, one archive whose root *is* the market root. The
57
+ * official bundles move to it because the old `url` form made a first fetch
58
+ * mirror 14,714 files (611 MiB) for `experts` alone (doc `30`).
59
+ * **`fp-8` adds expert definition export**: `agent/export` and `team/export`
60
+ * return an {@link ExpertPack} — the persona, model hints, skill references and
61
+ * (for a team) the roster with every member expanded. Nothing existing changes
62
+ * shape, but the response carries the Agent Markdown body, which the catalog
63
+ * faces deliberately never do; both methods are WebSocket-only, so the
64
+ * documented route split becomes `48 / 73` (doc `32`).
65
+ * **`fp-13` adds the market policy face** (doc `37`): `market/settings` reads the
66
+ * host's effective sweep cadence, auto-upgrade kind whitelist and last-sweep
67
+ * reading, and `market/settings-set` writes the named keys through to the host's
68
+ * `config.toml`. The same change makes the background sweep able to upgrade
69
+ * entries installed from an `auto_update` market (official sources only, and
70
+ * never a `connector` or a component the user switched off by hand). Both
71
+ * methods are mapped, so the documented route split becomes `55 / 80`.
72
+ * **`fp-14` gives the expert pack its plugin-level assets** (doc `38`):
73
+ * `import/assets` lists the `avatars/`, `bin/` and `scripts/` files of an
74
+ * immutable snapshot (sorted, sha256-digested) and `import/asset` serves one of
75
+ * them base64; `ExpertPack` gains an `assets` list and a declared `avatar` path
76
+ * (`pack_format 2`), so an exported team can materialize a directory the
77
+ * package's own scripts recognize as the plugin root. Both methods are
78
+ * WebSocket-only, so the documented route split becomes `55 / 82`.
79
+ */
80
+ export declare const APP_SERVER_PROTOCOL_VERSION = "fp-14";
81
+ export interface JsonRpcRequest {
82
+ jsonrpc: "2.0";
83
+ id: number | string;
84
+ method: string;
85
+ params?: unknown;
86
+ }
87
+ export interface JsonRpcNotification {
88
+ jsonrpc: "2.0";
89
+ method: string;
90
+ params?: unknown;
91
+ }
92
+ export interface JsonRpcResponse {
93
+ jsonrpc: "2.0";
94
+ id: number | string | null;
95
+ result?: unknown;
96
+ error?: WireError;
97
+ }
98
+ export interface JsonRpcEventNotification {
99
+ jsonrpc: "2.0";
100
+ method: "event";
101
+ params: RunEvent;
102
+ }
103
+ export interface ResyncRequiredNotification {
104
+ jsonrpc: "2.0";
105
+ method: "run/resync-required";
106
+ params: {
107
+ run_ids: string[];
108
+ reason: string;
109
+ };
110
+ }
111
+ export interface ConversationEventNotification {
112
+ jsonrpc: "2.0";
113
+ method: "conversation/event";
114
+ params: ConversationEvent;
115
+ }
116
+ export interface ConversationResyncRequiredNotification {
117
+ jsonrpc: "2.0";
118
+ method: "conversation/resync-required";
119
+ params: {
120
+ conversation_ids: string[];
121
+ reason: string;
122
+ };
123
+ }
124
+ /**
125
+ * 会话**列表**投影变更(`conversation/list-changed`,2026-09-19 加入)。
126
+ *
127
+ * 与 `conversation/event` 是两件事:那些是某条被订阅会话的转写帧、带 `sequence`;
128
+ * 这一条改的是侧栏那一整份列表(自动标题、重命名、删除),所以:
129
+ * - 不要求订阅该会话(用户此刻可能正看着另一个会话);
130
+ * - **不带 `sequence`**,客户端不得据此推进 `lastSeenSequence`;
131
+ * - 尽力而为:丢一条只是让界面晚一步刷新,`conversation/list` 始终是权威。
132
+ */
133
+ export interface ConversationListChangedNotification {
134
+ jsonrpc: "2.0";
135
+ method: "conversation/list-changed";
136
+ params: {
137
+ conversation_id: string;
138
+ action: "created" | "updated" | "deleted";
139
+ };
140
+ }
141
+ export type ServerNotification = JsonRpcEventNotification | ResyncRequiredNotification | ConversationEventNotification | ConversationResyncRequiredNotification | ConversationListChangedNotification;
142
+ export interface WireError {
143
+ code: string;
144
+ message: string;
145
+ retryable: boolean;
146
+ details: Record<string, unknown>;
147
+ request_id?: string | number | null;
148
+ }
149
+ export interface ClientInfo {
150
+ name: string;
151
+ version: string;
152
+ }
153
+ export interface ClientCapabilities {
154
+ events?: boolean;
155
+ approvals?: boolean;
156
+ team_runtime?: boolean;
157
+ artifacts?: boolean;
158
+ }
159
+ export interface InitializeRequest {
160
+ protocol_version: string;
161
+ client: ClientInfo;
162
+ auth?: {
163
+ mode?: string;
164
+ credential?: string;
165
+ };
166
+ capabilities?: ClientCapabilities;
167
+ }
168
+ export interface InitializeResult {
169
+ protocol_version: string;
170
+ server: {
171
+ name: string;
172
+ version: string;
173
+ };
174
+ auth_context: {
175
+ principal_id: string;
176
+ issuer: string;
177
+ audience: string;
178
+ scopes: string[];
179
+ };
180
+ capabilities: Capabilities;
181
+ connection_id: string;
182
+ }
183
+ export interface Capabilities {
184
+ agents: boolean;
185
+ teams: boolean;
186
+ team_runtime: boolean;
187
+ skills: boolean;
188
+ /**
189
+ * The Skill **file tree** read face (`skill/files` / `skill/file`).
190
+ *
191
+ * Separate from `skills` on purpose: a host can wire the catalog without the
192
+ * file provider, so `skills: true` alone does not mean `skill/files` will
193
+ * answer. Check this before offering file access.
194
+ */
195
+ skill_files: boolean;
196
+ connectors: boolean;
197
+ /**
198
+ * The connector **call proxy** (`connector/call`, doc 24 §5).
199
+ *
200
+ * Separate from `connectors` on purpose: the catalog can be wired without the
201
+ * proxy, and a host may wire the proxy while its `[connector_proxy]` table is
202
+ * absent or disabled, in which case every call answers `policy_denied`. This
203
+ * flag says the *method* exists, not that any tool is callable; the host
204
+ * operator's own config decides that (doc `26` §4).
205
+ */
206
+ connector_calls: boolean;
207
+ run_notifications: boolean;
208
+ approvals: boolean;
209
+ artifacts: boolean;
210
+ oauth: boolean;
211
+ imports: boolean;
212
+ installs: boolean;
213
+ marketplaces: boolean;
214
+ store: boolean;
215
+ models: boolean;
216
+ /**
217
+ * The expert **definition export** face (`agent/export` / `team/export`,
218
+ * doc 32).
219
+ *
220
+ * Reports the *seam*, not the policy — same split as `connector_calls`. A host
221
+ * wires it and then decides per request whether its `[expert_export]` table
222
+ * allows the id, so this flag says the methods exist, not that any given
223
+ * expert is exportable: a refusal arrives as `policy_denied`. Do not use it to
224
+ * grey out a button and assume that is the whole answer.
225
+ */
226
+ expert_export: boolean;
227
+ /**
228
+ * The plugin-level **snapshot asset** read face (`import/assets` /
229
+ * `import/asset`, doc 38) — and, because they are the same provider, the
230
+ * guarantee that an `ExpertPack.assets` list is meaningful. Separate from
231
+ * `expert_export` on purpose: a host can serve asset bytes without the
232
+ * export seam, and an export host without this provider emits packs with an
233
+ * empty `assets` list.
234
+ */
235
+ snapshot_assets: boolean;
236
+ }
237
+ export interface WorkspaceRef {
238
+ id: string;
239
+ }
240
+ export interface WorkspaceRegistration {
241
+ id: string;
242
+ }
243
+ /** Owner-scoped workspace projection returned by `workspace/list` / `workspace/create`. */
244
+ export interface WorkspaceView {
245
+ workspace_id: string;
246
+ /** Display label derived server-side from the directory basename (not a path). */
247
+ name: string;
248
+ /** Absolute canonical path. Only present on the same owner's authenticated connection. */
249
+ canonical_path: string;
250
+ created_at: number;
251
+ updated_at: number;
252
+ }
253
+ export interface WorkspaceCreateInput {
254
+ /** Absolute local directory path chosen by the owner; validated server-side. */
255
+ path: string;
256
+ }
257
+ /** Result of revoking (soft-deleting) an owner's active workspace. */
258
+ export interface WorkspaceRevokeResult {
259
+ workspace_id: string;
260
+ /** false when the workspace is foreign, missing, or already revoked. */
261
+ revoked: boolean;
262
+ }
263
+ /** `GET /api/fs/browse` — one directory listing entry (directories only by default). */
264
+ export interface BrowseEntry {
265
+ name: string;
266
+ path: string;
267
+ isDirectory: boolean;
268
+ isFile: boolean;
269
+ size?: number | null;
270
+ modified?: number | null;
271
+ }
272
+ /** `GET /api/fs/browse` response. */
273
+ export interface BrowseDirectoryResult {
274
+ currentPath: string;
275
+ parentPath?: string | null;
276
+ items: BrowseEntry[];
277
+ canGoUp: boolean;
278
+ truncated: boolean;
279
+ isRoot?: boolean | null;
280
+ }
281
+ /** `POST /api/fs/list` — one flat file entry under a workspace root (host file service). */
282
+ export interface WorkspaceFlatFile {
283
+ name: string;
284
+ full_path: string;
285
+ relative_path: string;
286
+ }
287
+ /** `POST /api/fs/metadata` response (host file service). */
288
+ export interface FileMetadata {
289
+ name: string;
290
+ path: string;
291
+ size: number;
292
+ /** Server-reported MIME type. */
293
+ type: string;
294
+ last_modified: number;
295
+ is_directory?: boolean | null;
296
+ }
297
+ export type RunStatus = "planning" | "running" | "completed" | "completed_with_failures" | "failed" | "cancelled" | "paused" | "waiting_input" | "awaiting_approval" | "recovery_required";
298
+ export type MentionKind = "agent" | "skill" | "connector";
299
+ export interface MentionRef {
300
+ kind: MentionKind;
301
+ id: string;
302
+ }
303
+ export interface AgentRunInput {
304
+ agentId: string;
305
+ agentVersion?: string;
306
+ goal?: string;
307
+ input?: {
308
+ text?: string;
309
+ } | Record<string, unknown>;
310
+ workspaceId?: string;
311
+ steps?: unknown[];
312
+ commandId?: string;
313
+ idempotencyKey?: string;
314
+ /** Structured `@` mentions resolved by the composer (docs/agent-store/05 §4.7). */
315
+ mentions?: MentionRef[];
316
+ /**
317
+ * Run-scoped model (doc `29` §6.1). Wins over the preset's own model **and**
318
+ * over the host default: explicit > preset > `~/.agent-store/config.toml`.
319
+ */
320
+ model?: ProviderWithModel;
321
+ /** Run-scoped OpenAI-style reasoning effort (doc `29` §6.1); applies to every attempt of the run. */
322
+ reasoningEffort?: ReasoningEffort | string;
323
+ }
324
+ export interface AgentRunRequestWire {
325
+ agent_id: string;
326
+ agent_version?: string;
327
+ goal?: string;
328
+ input?: unknown;
329
+ work_dir?: string;
330
+ workspace?: WorkspaceRef;
331
+ steps?: unknown[];
332
+ command_id?: string;
333
+ idempotency_key?: string;
334
+ mentions?: MentionRef[];
335
+ model?: ProviderWithModel;
336
+ reasoning_effort?: string;
337
+ }
338
+ export interface RunReceipt {
339
+ run_id: string;
340
+ status: RunStatus;
341
+ version: number;
342
+ preset_revision: number;
343
+ content_digest: string;
344
+ }
345
+ /**
346
+ * `team/run` — Leader Conversation + planned delegation (docs/agent-store/05
347
+ * §5.2, `16` §7 决策 3).
348
+ *
349
+ * There is deliberately **no** `planning` field: member pool, concurrency
350
+ * ceiling, routing constraints and authority come from the bound Team template
351
+ * and server policy. The wire DTO is `deny_unknown_fields`, so sending one is
352
+ * `invalid_request` rather than a silently ignored parameter.
353
+ */
354
+ export interface TeamRunInput {
355
+ teamId: string;
356
+ teamVersion?: string;
357
+ goal?: string;
358
+ input?: {
359
+ text?: string;
360
+ } | Record<string, unknown>;
361
+ workspaceId?: string;
362
+ commandId?: string;
363
+ idempotencyKey?: string;
364
+ }
365
+ export interface TeamRunRequestWire {
366
+ team_id: string;
367
+ team_version?: string;
368
+ goal?: string;
369
+ input?: unknown;
370
+ workspace?: WorkspaceRef;
371
+ command_id?: string;
372
+ idempotency_key?: string;
373
+ }
374
+ /**
375
+ * Deliberately **not** `RunReceipt`: a Team Run has no lead preset to report a
376
+ * revision/digest for — its authority is the Team's mutable execution template.
377
+ * Only what the server can honestly answer is on the wire.
378
+ */
379
+ export interface TeamRunReceipt {
380
+ run_id: string;
381
+ status: RunStatus;
382
+ }
383
+ export interface RunView {
384
+ run_id: string;
385
+ status: RunStatus;
386
+ version: number;
387
+ summary?: string | null;
388
+ output_files: string[];
389
+ preset_revision?: number | null;
390
+ content_digest?: string | null;
391
+ }
392
+ export type RunResult = RunView;
393
+ export interface CancelRunInput {
394
+ runId: string;
395
+ expectedVersion: number;
396
+ commandId?: string;
397
+ idempotencyKey?: string;
398
+ }
399
+ export interface SteerRunInput {
400
+ runId: string;
401
+ text: string;
402
+ expectedVersion: number;
403
+ commandId?: string;
404
+ idempotencyKey?: string;
405
+ }
406
+ export type AnswerDecisionInput = {
407
+ runId: string;
408
+ stepId: string;
409
+ attemptId: string;
410
+ answer: string;
411
+ /**
412
+ * The engine's three-way CAS tokens. They are mandatory: an answer that does
413
+ * not name the exact execution/step/attempt version it was written against is
414
+ * refused with a `Conflict` instead of being applied. `run/events` projects
415
+ * the current three onto every pending `approval.requested`, so a client
416
+ * echoes those rather than inventing versions.
417
+ */
418
+ expectedExecutionVersion: number;
419
+ expectedStepVersion: number;
420
+ expectedAttemptVersion: number;
421
+ };
422
+ /**
423
+ * One projected run event.
424
+ *
425
+ * `step_id` / `attempt_id` are present when the engine scoped the event to an
426
+ * attempt (notably `approval.requested`). A pending decision additionally
427
+ * carries the three CAS versions an accepted answer must echo; they are read
428
+ * from the authoritative rows when the event is projected, so an answer built
429
+ * from them races safely (a concurrent change turns it into a `Conflict`).
430
+ *
431
+ * There is deliberately no `always_allow` counterpart on the wire: answering a
432
+ * decision never widens a tool policy.
433
+ */
434
+ export interface RunEvent {
435
+ run_id: string;
436
+ sequence: number;
437
+ event_type: string;
438
+ payload: Record<string, unknown>;
439
+ step_id?: string | null;
440
+ attempt_id?: string | null;
441
+ expected_execution_version?: number | null;
442
+ expected_step_version?: number | null;
443
+ expected_attempt_version?: number | null;
444
+ }
445
+ export interface RunEventsQuery {
446
+ runId: string;
447
+ afterSequence?: number;
448
+ limit?: number;
449
+ }
450
+ export interface RunSubscriptionParams {
451
+ run_id: string;
452
+ }
453
+ /**
454
+ * W4 / W6 (doc 16 R10/R11, resolves D-W6-1): the authoritative plan snapshot.
455
+ *
456
+ * `run/events` is an append-only log of markers — step titles, failure reasons
457
+ * and timings never appear there. This projection reads the engine's own rows,
458
+ * so each step exists once with its current facts. Internal participant ids are
459
+ * deliberately not on the wire: member attribution is `role` + `model`.
460
+ */
461
+ export interface RunPlan {
462
+ run_id: string;
463
+ status: RunStatus;
464
+ version: number;
465
+ steps: RunPlanStep[];
466
+ dependencies: RunPlanDependency[];
467
+ }
468
+ export interface RunPlanStep {
469
+ step_id: string;
470
+ title: string;
471
+ kind: string;
472
+ status: string;
473
+ role?: string | null;
474
+ model?: string | null;
475
+ introduced_in_revision: number;
476
+ superseded_in_revision?: number | null;
477
+ created_at: number;
478
+ updated_at: number;
479
+ attempts: RunPlanAttempt[];
480
+ }
481
+ export interface RunPlanAttempt {
482
+ attempt_id: string;
483
+ attempt_no: number;
484
+ status: string;
485
+ trigger_reason: string;
486
+ role?: string | null;
487
+ model?: string | null;
488
+ question?: string | null;
489
+ error?: string | null;
490
+ output_summary?: string | null;
491
+ output_files: string[];
492
+ tokens?: number | null;
493
+ started_at?: number | null;
494
+ finished_at?: number | null;
495
+ }
496
+ export interface RunPlanDependency {
497
+ blocker_step_id: string;
498
+ blocked_step_id: string;
499
+ }
500
+ export interface ProviderWithModel {
501
+ provider_id: string;
502
+ model: string;
503
+ use_model?: string;
504
+ }
505
+ export interface ConversationCreateInput {
506
+ name?: string;
507
+ model?: ProviderWithModel;
508
+ workspaceId?: string;
509
+ reasoningEffort?: string;
510
+ agentId?: string;
511
+ teamId?: string;
512
+ }
513
+ export interface ConversationUpdateInput {
514
+ name?: string;
515
+ model?: ProviderWithModel;
516
+ reasoningEffort?: string;
517
+ }
518
+ export type ReasoningEffort = "low" | "medium" | "high" | "xhigh" | "max";
519
+ export interface ConversationModelOption {
520
+ name: string;
521
+ display_name?: string | null;
522
+ context_limit?: number | null;
523
+ /**
524
+ * models.dev catalog facts (W9 / R14). All of them are **absent** when the
525
+ * registry has no entry for this provider+model (an unmapped platform, e.g.
526
+ * the bundled `mimo`, never gets zeros standing in for "unknown").
527
+ */
528
+ cost_input?: number | null;
529
+ cost_output?: number | null;
530
+ catalog_context_window?: number | null;
531
+ supports_vision?: boolean | null;
532
+ }
533
+ export interface ProviderModelOption {
534
+ name: string;
535
+ models: ConversationModelOption[];
536
+ }
537
+ export interface ConversationModelSelection {
538
+ provider: string;
539
+ model: string;
540
+ }
541
+ export interface ConversationModelOptions {
542
+ default?: ConversationModelSelection | null;
543
+ providers: ProviderModelOption[];
544
+ reasoning_efforts: ReasoningEffort[];
545
+ }
546
+ export interface ConversationView {
547
+ conversation_id: string;
548
+ name: string;
549
+ model: ProviderWithModel;
550
+ /**
551
+ * The conversation's current reasoning effort (doc `29` §5.5). Absent = not
552
+ * specified. `create` / `update` / `send` can all write it, so the view has to
553
+ * say what it is — otherwise the setting is write-only.
554
+ */
555
+ reasoning_effort?: string | null;
556
+ status: string;
557
+ created_at: number;
558
+ modified_at: number;
559
+ is_processing: boolean;
560
+ /** Opaque owner-scoped workspace id; absent for legacy chats (UI groups them under "未归类"). */
561
+ workspace_id?: string | null;
562
+ /** Last server-measured context occupancy; null/absent = unknown. */
563
+ context_usage?: ContextUsage | null;
564
+ }
565
+ /** Measured context occupancy for one chat. `percent` is null when the engine reported no window. */
566
+ export interface ContextUsage {
567
+ used_tokens: number;
568
+ window_tokens: number;
569
+ percent?: number | null;
570
+ /**
571
+ * W9 / R14 ③: the runtime's own per-turn report for the **most recent**
572
+ * completed turn, persisted server-side next to the gauge so a reloaded
573
+ * WebUI can still show last turn's tokens (and catalog-priced cost). Both are
574
+ * absent when the runtime reported nothing — never a `0` standing in for
575
+ * "unknown". They are not occupancy: `used_tokens` is the last request's
576
+ * prompt size and must never be substituted for these.
577
+ */
578
+ last_turn_input_tokens?: number | null;
579
+ last_turn_output_tokens?: number | null;
580
+ updated_at: number;
581
+ source: "measured" | string;
582
+ }
583
+ /**
584
+ * Per-turn token accounting for ONE completed turn (W9 / R14, additive).
585
+ *
586
+ * The runtime's own report (`TurnCompleted`), projected onto the conversation
587
+ * event stream — field names mirror the Run-side `TurnUsage` so one vocabulary
588
+ * covers both surfaces. It is **not** context occupancy: `ContextUsage` is a
589
+ * gauge (the last request's prompt size) and cannot express what one turn
590
+ * cost. Absent whenever the runtime reported nothing (never a zero).
591
+ */
592
+ export interface TurnUsage {
593
+ input_tokens: number;
594
+ output_tokens: number;
595
+ total_tokens: number;
596
+ }
597
+ export interface ConversationDeleteResult {
598
+ conversation_id: string;
599
+ deleted: boolean;
600
+ }
601
+ export type ConversationMessageRole = "user" | "assistant" | "activity";
602
+ export interface ConversationMessage {
603
+ message_id: string;
604
+ conversation_id: string;
605
+ role: ConversationMessageRole;
606
+ content: unknown;
607
+ message_type: string;
608
+ status?: string | null;
609
+ created_at: number;
610
+ }
611
+ export interface ConversationSendReceipt {
612
+ conversation_id: string;
613
+ message_id: string;
614
+ turn_id?: string | null;
615
+ accepted: boolean;
616
+ replayed: boolean;
617
+ completed: boolean;
618
+ result_ok?: boolean | null;
619
+ result_text?: string | null;
620
+ result_error?: string | null;
621
+ result_error_code?: string | null;
622
+ result_error_retryable?: boolean | null;
623
+ }
624
+ /**
625
+ * The closed set of conversation event kinds the server emits (`05` §4).
626
+ *
627
+ * Deliberately **not** widened with `| string` (doc `16` R1): the escape made
628
+ * the union collapse to `string`, so exhaustive switches and type guards were
629
+ * impossible. A newer server kind reaches a client as a runtime value the
630
+ * switch does not match — which is handled by falling through — and widening
631
+ * this union is the deliberate, reviewable act that changes the contract.
632
+ */
633
+ export type ConversationEventType = "message.created" | "message.delta" | "message.thinking" | "message.tips" | "message.tool" | "message.error" | "message.activity" | "turn.status" | "context.usage";
634
+ export interface ConversationEvent {
635
+ conversation_id: string;
636
+ sequence: number;
637
+ event_type: ConversationEventType;
638
+ payload: Record<string, unknown>;
639
+ }
640
+ export interface ConversationMessagesQuery {
641
+ conversationId: string;
642
+ page?: number;
643
+ pageSize?: number;
644
+ cursor?: string;
645
+ }
646
+ export interface ConversationMessagesPage {
647
+ items: ConversationMessage[];
648
+ /** Exact server-computed flag: `true` when an older page still exists. */
649
+ has_more: boolean;
650
+ }
651
+ export interface ConversationSubscription {
652
+ conversation_id: string;
653
+ }
654
+ export type CompatibilityStatus = "compatible" | "compatible-with-adapter" | "manual-review" | "unsupported" | "pending-legal-review";
655
+ export interface SkillSummary {
656
+ id: string;
657
+ name: string;
658
+ description?: string | null;
659
+ version: string;
660
+ source: string;
661
+ /**
662
+ * Finer on-disk owner than `source` (`16` R17 / W12): `user` | `shared` |
663
+ * `companion` | `draft` | `marketplace` | `builtin` | `unmanaged`.
664
+ *
665
+ * `source` alone cannot separate a user skill from an installed marketplace
666
+ * product — both are `custom`.
667
+ *
668
+ * Optional so a client compiled against this package keeps working against a
669
+ * host that predates the field; treat a missing value as "unknown owner".
670
+ */
671
+ origin?: SkillOrigin;
672
+ /**
673
+ * Whether the store's write face accepts `skill/update` / `skill/delete` for
674
+ * this skill — `origin === "user"` and the directory is the canonical one.
675
+ * Absent means unknown, so a UI must not offer a write action on `!== true`.
676
+ */
677
+ writable?: boolean;
678
+ compatibility_status: CompatibilityStatus;
679
+ enabled: boolean;
680
+ required_connectors: string[];
681
+ /**
682
+ * Marketplace icon URL for a product installed from a marketplace
683
+ * (`/api/app-server/store/{mkt}/entries/{entry}/assets/…`). Absent for
684
+ * builtin / user skills and for markets that ship no icon for the entry.
685
+ */
686
+ avatar_url?: string | null;
687
+ }
688
+ /** Where a skill lives on the host (`SkillSummary.origin`). */
689
+ export type SkillOrigin = "user" | "shared" | "companion" | "draft" | "marketplace" | "builtin" | "unmanaged";
690
+ /** `skill/get` — the raw SKILL.md body and internal routing rules are never returned. */
691
+ export interface SkillDetail extends SkillSummary {
692
+ mode: string;
693
+ invocation_policy: string;
694
+ instructions_summary?: string | null;
695
+ }
696
+ /**
697
+ * One file inside a Skill directory (`skill/files`, doc 24 §4).
698
+ *
699
+ * A Skill is a *directory*, not a single document: `SKILL.md` plus whatever it
700
+ * ships alongside (`references/`, `scripts/`, `templates/`, `assets/`).
701
+ * `SkillDetail.instructions_summary` is a bounded summary of the manifest, so
702
+ * this is the read face for everything else.
703
+ */
704
+ export interface SkillFile {
705
+ /** Path relative to the skill directory, POSIX-separated. */
706
+ path: string;
707
+ size: number;
708
+ /** Single-file sha256, lowercase hex. */
709
+ digest: string;
710
+ }
711
+ /** `skill/files` response: the readable inventory of one Skill. */
712
+ export interface SkillFileList {
713
+ skill_id: string;
714
+ /** Sorted by `path`; directories are not listed. */
715
+ files: SkillFile[];
716
+ /**
717
+ * Tree digest of **this skill directory** (sorted relative paths + per-file
718
+ * sha256).
719
+ *
720
+ * Deliberately **not** the snapshot's `content_digest`, which covers the
721
+ * whole imported source tree — the two coincide only when the snapshot holds
722
+ * exactly this directory. Do not compare it against `import/get`.
723
+ */
724
+ content_digest: string;
725
+ /** The inventory hit its ceiling and is incomplete. Never silently truncated. */
726
+ truncated: boolean;
727
+ }
728
+ /**
729
+ * `skill/file` over the WebSocket binding: base64, because JSON has no byte
730
+ * string. The HTTP binding returns the raw body with a `content-type` header
731
+ * instead, so prefer `skills.readFile()` (HTTP) for bytes and this shape only
732
+ * when you are already on a socket.
733
+ */
734
+ export interface SkillFileContent {
735
+ skill_id: string;
736
+ path: string;
737
+ content_type: string;
738
+ encoding: "base64";
739
+ content: string;
740
+ }
741
+ /**
742
+ * Request/response shapes for the six **host-management** methods:
743
+ * `config/get` · `config/set` · `skill/create` · `skill/update` ·
744
+ * `skill/delete` · `skill/copy`.
745
+ *
746
+ * They live here because this package is the wire contract's single source of
747
+ * truth: a method that exists on the wire has a shape, and a caller that speaks
748
+ * the protocol must be able to type-check it whether or not the curated
749
+ * `@flowy-agent-store/client` surface offers a typed method for it. Declaring
750
+ * them only in the host app made that claim false.
751
+ *
752
+ * **Not a stability promise.** These are the most volatile methods in the
753
+ * protocol — they track the host's own settings file and its skills directory,
754
+ * and are the ones most likely to change without notice during beta
755
+ * (`16` D10=A: no backward-compatibility promise before release). The client
756
+ * package deliberately ships no typed method for them; call them through
757
+ * `transport.request` if you need them, and expect churn. What is *not* the
758
+ * reason is access control: `transport` is public and the server enforces
759
+ * writability from the on-disk origin, so this is a discoverability boundary,
760
+ * not a security one.
761
+ */
762
+ /**
763
+ * `config/get` / `config/set` view of the host's `~/.agent-store/config.toml`.
764
+ *
765
+ * Never carries a credential: the file's `[providers.<name>]` tables hold
766
+ * `api_key` or `base_url`, so a credential cannot reach the front end through
767
+ * this face.
768
+ */
769
+ export interface AgentStoreConfigView {
770
+ /** The file exists on this host; a save creates it when it does not. */
771
+ exists: boolean;
772
+ /** Declared `default_model`; explicit `null` = none declared in the file. */
773
+ default_model: string | null;
774
+ /** `[providers.<name>]` tables as declared in the file. */
775
+ providers: AgentStoreConfigProvider[];
776
+ /**
777
+ * `[memory]` table; `null` when the file declares no such table (so the UI
778
+ * can say "not configured" instead of inventing "off"). Additive field.
779
+ */
780
+ memory: AgentStoreConfigMemory | null;
781
+ /**
782
+ * `~/.agent-store/mcp.json` as the host read it (`20` §7.9 / `21` D14);
783
+ * `null` when there is no readable declaration file. Additive field, and the
784
+ * **only** read surface for a declared server: declarations never become
785
+ * `mcp_servers` rows, so they do not appear in `connector/*`.
786
+ */
787
+ mcp: AgentStoreConfigMcp | null;
788
+ }
789
+ /** One `mcpServers` entry the host accepted. */
790
+ export interface AgentStoreConfigMcpServer {
791
+ /** The `mcpServers` key — the `<server>` segment of `mcp__<server>__*`. */
792
+ name: string;
793
+ /** `stdio` | `http` | `sse` (already a label, not a discriminant to branch on). */
794
+ transport: string;
795
+ /** `enabled = false` keeps the entry declared but out of every session. */
796
+ enabled: boolean;
797
+ }
798
+ /** One `mcpServers` entry the host refused, and why. */
799
+ export interface AgentStoreConfigMcpRejection {
800
+ name: string;
801
+ /**
802
+ * The parser's own reason ("a field on the wrong transport", "an out-of-range
803
+ * timeout", …). Server-authored prose, not an i18n key.
804
+ */
805
+ reason: string;
806
+ }
807
+ /**
808
+ * The `mcp.json` projection. A refusal is reported rather than silently
809
+ * dropped: an ignored `enabledTools` would leave tools the user believes
810
+ * excluded still callable, which is why this half has to be visible.
811
+ */
812
+ export interface AgentStoreConfigMcp {
813
+ /**
814
+ * The declaration file is present. The host only sends this view after it has
815
+ * read the file, so this is `true` whenever `mcp` is not `null`; it is kept
816
+ * because the wire carries it, not because a branch should read it.
817
+ */
818
+ exists: boolean;
819
+ /**
820
+ * Whether **this host** feeds the file into agent sessions, as the launcher
821
+ * reported at startup. Omitted when the host did not say — `undefined` is
822
+ * "cannot tell", which is not the same answer as `false` ("this host does not
823
+ * read the file"). `servers` describes the file; this describes the host.
824
+ */
825
+ adopted?: boolean;
826
+ /** Accepted entries, ordered by server key. Never a credential value. */
827
+ servers: AgentStoreConfigMcpServer[];
828
+ /** Refused entries, with the reason the user has to fix. */
829
+ rejected: AgentStoreConfigMcpRejection[];
830
+ /**
831
+ * Why the **whole file** could not be read as declarations (invalid JSON,
832
+ * wrong top level). Omitted when the file parsed — without it a broken file
833
+ * and an empty one look the same.
834
+ */
835
+ error?: string;
836
+ }
837
+ /**
838
+ * `config/get-mcp`: the declaration file's own text, for the file editor.
839
+ *
840
+ * The only read that returns a declaration's values, and the reason it is a
841
+ * method of its own: `AgentStoreConfigMcp` describes the file's *verdict* and
842
+ * carries no `env` / `headers` value, while an editor cannot edit what it
843
+ * cannot see. `exists: false` (with `source` absent) is the normal answer for a
844
+ * host that never created the file; the text comes back whether or not it
845
+ * parses, because editing a broken file is the point.
846
+ */
847
+ export interface McpSourceView {
848
+ exists: boolean;
849
+ source?: string | null;
850
+ }
851
+ /** `[memory]` in the host settings file, as far as the wire exposes it. */
852
+ export interface AgentStoreConfigMemory {
853
+ /** `null` = the table exists without the key (upstream default applies). */
854
+ distill_enabled: boolean | null;
855
+ }
856
+ /** One provider table from the file (never a registered-provider row). */
857
+ export interface AgentStoreConfigProvider {
858
+ /** `[providers.<name>]` key — the left half of a `default_model`. */
859
+ name: string;
860
+ /** `enabled = false` in the file; `true` when the key is absent. */
861
+ enabled: boolean;
862
+ /** Model names declared for this provider in the file. */
863
+ models: string[];
864
+ }
865
+ /** The only keys `config/set` accepts (the server rejects anything else). */
866
+ export interface AgentStoreConfigPatch {
867
+ default_model?: string;
868
+ /** Writes `[memory] distill_enabled` — the switch the host reads at startup. */
869
+ memory?: {
870
+ distill_enabled: boolean;
871
+ };
872
+ }
873
+ /** `skill/create` — structured fields; the server assembles the frontmatter. */
874
+ export interface SkillCreateInput {
875
+ /** Becomes the skill's public id and its directory name. */
876
+ name: string;
877
+ description: string;
878
+ when_to_use?: string;
879
+ allowed_tools?: string;
880
+ paths?: string;
881
+ body?: string;
882
+ }
883
+ /**
884
+ * `skill/update` — a field-level patch.
885
+ *
886
+ * An absent field is left alone (`undefined`, not empty string). An **empty
887
+ * string** on one of the optional keys clears that key; `description` may not
888
+ * be emptied (`invalid_request`). There is deliberately no `name`.
889
+ */
890
+ export interface SkillUpdateInput {
891
+ skill_id: string;
892
+ description?: string;
893
+ when_to_use?: string;
894
+ allowed_tools?: string;
895
+ paths?: string;
896
+ /** Replaces the body wholesale — the read face never returned it, so an edit
897
+ * can only ever *replace* prose, not append to what it never saw. */
898
+ body?: string;
899
+ }
900
+ /** `skill/delete` — what the id resolves to after the delete. */
901
+ export interface SkillDeleteResult {
902
+ skill_id: string;
903
+ deleted: boolean;
904
+ /** Present when the id now resolves to another origin (e.g. a built-in the
905
+ * user skill was shadowing); absent when nothing is visible there any more. */
906
+ revealed_origin?: SkillOrigin | null;
907
+ }
908
+ export type ConnectorStatus = "installed" | "configured" | "authorization_required" | "authenticated" | "connected" | "degraded" | "error" | "reauthorization_required";
909
+ /** `connector/register` — a connector the host never imported (doc `34` §6.5). */
910
+ export interface ConnectorRegistration {
911
+ /** Its name on this host. Re-registering the same name updates it. */
912
+ name: string;
913
+ description?: string | null;
914
+ /**
915
+ * The template the host will store and later resolve — the same shape it keeps
916
+ * for a marketplace connector. Whatever `${secret:NAME}` its URL, headers or env
917
+ * name becomes the credential form; `values` carries the connector's own
918
+ * non-secret settings.
919
+ */
920
+ transport: ConnectorTransport;
921
+ }
922
+ /** The transport template, mirroring the host's own stored shape. */
923
+ export type ConnectorTransport = {
924
+ type: "http" | "sse";
925
+ url: string;
926
+ headers?: Record<string, string>;
927
+ values?: Record<string, string>;
928
+ } | {
929
+ type: "stdio";
930
+ command: string;
931
+ args?: string[];
932
+ env?: Record<string, string>;
933
+ };
934
+ export interface ConnectorSummary {
935
+ id: string;
936
+ name: string;
937
+ description?: string | null;
938
+ kind: string;
939
+ transport_summary: string;
940
+ auth_mode: string;
941
+ enabled: boolean;
942
+ status: ConnectorStatus;
943
+ /**
944
+ * Marketplace icon URL for a connector installed from a marketplace.
945
+ * Absent for builtin hosts and for markets that ship no icon for the entry.
946
+ */
947
+ avatar_url?: string | null;
948
+ /**
949
+ * What the connector needs the user to fill in, when the host can describe it
950
+ * (doc `34` §6.1). Absent for a connector with no declaration and nothing to
951
+ * ask for.
952
+ *
953
+ * **Never carries a secret value**: `fields[].value` exists only for `plain`
954
+ * fields, which are the connector's own settings (`HOST`, `PORT`), and
955
+ * `missing` holds key names.
956
+ */
957
+ credential?: ConnectorCredential | null;
958
+ }
959
+ /** Both languages, already resolved by the host (doc `34` §5.2). */
960
+ export interface LocalizedString {
961
+ zh: string;
962
+ en: string;
963
+ }
964
+ /** How a connector authenticates. */
965
+ export type CredentialMode = "none" | "oauth" | "token";
966
+ /** What the caller has to do about it. */
967
+ export type CredentialStatus = "not_required" | "requires_input" | "configured" | "error";
968
+ /** One row of the credential form. */
969
+ export interface CredentialField {
970
+ key: string;
971
+ /** `secret` (the host's credential store) or `plain` (the connector's own value). */
972
+ kind: "secret" | "plain";
973
+ required: boolean;
974
+ label: LocalizedString;
975
+ placeholder: LocalizedString;
976
+ description: LocalizedString;
977
+ /** Only for `plain`: the value in effect. A secret's value never crosses the wire. */
978
+ value?: string | null;
979
+ }
980
+ /** The credential form and its state, as this caller sees them (doc `34` §6.1). */
981
+ export interface ConnectorCredential {
982
+ connector_id: string;
983
+ mode: CredentialMode;
984
+ status: CredentialStatus;
985
+ /** Key names still missing for this caller — never values. */
986
+ missing: string[];
987
+ fields: CredentialField[];
988
+ /**
989
+ * Form-level text from the marketplace declaration (doc `34` §5.2).
990
+ *
991
+ * A `token-schema.json` declares its title, its description and its "where do I
992
+ * get a key" link **once, for the form** — the market ships no per-field links,
993
+ * and copying the form's onto every field reads as "how do I get a key" under
994
+ * `PORT`.
995
+ */
996
+ title?: LocalizedString | null;
997
+ description?: LocalizedString | null;
998
+ doc_url?: LocalizedString | null;
999
+ doc_label?: LocalizedString | null;
1000
+ }
1001
+ export interface ConnectorTool {
1002
+ name: string;
1003
+ description?: string | null;
1004
+ /**
1005
+ * The upstream `tools/list` `inputSchema`, **verbatim** (doc `26` §5).
1006
+ *
1007
+ * Absent when the server published none, or when it was omitted to stay
1008
+ * inside the host's tools budget — see `tools_truncated` on the response. It
1009
+ * carries no transport, header or env value: only the tool's own parameters.
1010
+ */
1011
+ input_schema?: unknown;
1012
+ }
1013
+ export interface OAuthStatusView {
1014
+ /**
1015
+ * `authenticated` | `authorization_pending` | `not_authenticated` |
1016
+ * `reauthorization_required` (the field stays open: a host may report a state
1017
+ * this client predates, and every consumer here treats an unknown one as
1018
+ * "keep waiting" rather than as an answer).
1019
+ *
1020
+ * `authorization_pending` means a browser flow for this connector is running
1021
+ * **right now** — the user may still be in the window. It outranks the stored
1022
+ * credential, because a caller polling for progress is asking "is it running?"
1023
+ * and a credential answers "is the previous one still usable?".
1024
+ */
1025
+ state: "authenticated" | "authorization_pending" | "not_authenticated" | "reauthorization_required" | string;
1026
+ error?: string | null;
1027
+ }
1028
+ /** `connector/get` — tools are namespaced public names only. */
1029
+ export interface ConnectorDetail extends ConnectorSummary {
1030
+ tool_filter?: string | null;
1031
+ tools: ConnectorTool[];
1032
+ /**
1033
+ * Some `input_schema` values were omitted to stay inside the host's tools
1034
+ * budget. Names and descriptions are always kept, and a schema is only ever
1035
+ * carried whole — never truncated. `false` also means "nothing was omitted".
1036
+ */
1037
+ tools_truncated: boolean;
1038
+ auth_status?: OAuthStatusView | null;
1039
+ source: string;
1040
+ compatibility_status: CompatibilityStatus;
1041
+ }
1042
+ export interface ConnectorStatusView {
1043
+ connector_id: string;
1044
+ status: ConnectorStatus;
1045
+ auth_status?: OAuthStatusView | null;
1046
+ last_error?: string | null;
1047
+ }
1048
+ export interface ConnectorProbeResult {
1049
+ connector_id: string;
1050
+ success: boolean;
1051
+ tools?: ConnectorTool[] | null;
1052
+ /** Same rule as `ConnectorDetail.tools_truncated`: an honest omission. */
1053
+ tools_truncated: boolean;
1054
+ error?: string | null;
1055
+ code?: string | null;
1056
+ }
1057
+ export interface OAuthStartResult {
1058
+ connector_id: string;
1059
+ state: "started" | "error" | string;
1060
+ error?: string | null;
1061
+ }
1062
+ /**
1063
+ * `connector/call` (doc 24 §5.2): the result of running one MCP tool on the
1064
+ * host's connection.
1065
+ *
1066
+ * **A tool-level failure is a result, not a rejection.** When the server
1067
+ * answers `isError: true` the promise still resolves, with `is_error` set; only
1068
+ * transport, protocol and budget failures reject. Branch on `is_error` to tell
1069
+ * "the tool said no" from "we never reached the tool".
1070
+ */
1071
+ export interface ConnectorCallResult {
1072
+ /** The upstream `isError` flag (`false` when the server omitted it). */
1073
+ is_error: boolean;
1074
+ /**
1075
+ * The upstream `tools/call` result object, **verbatim** — `content`,
1076
+ * `structuredContent` and anything a newer server adds all survive.
1077
+ *
1078
+ * It carries no transport, header or env value: the connection and its
1079
+ * credentials stay on the host. That is the whole point of a proxy — there is
1080
+ * deliberately no `connector/export`.
1081
+ */
1082
+ result: unknown;
1083
+ }
1084
+ export interface ConnectorQueryParams {
1085
+ connector_id: string;
1086
+ }
1087
+ export interface SkillQueryParams {
1088
+ skill_id: string;
1089
+ }
1090
+ export type CompatibilityTriple = {
1091
+ /** Wire values: compatible | compatible_with_adapter | manual_review | unsupported | pending_legal_review. */
1092
+ semantic_status: string;
1093
+ /** not-verified | adapter-verified | runtime-verified | release-eligible. */
1094
+ runtime_status: string;
1095
+ /** local-only (V1). */
1096
+ distribution_status: string;
1097
+ reasons: string[];
1098
+ };
1099
+ export type ImportSourceKind = "codebuddy-plugin" | "workbuddy-skill-market" | "workbuddy-connector-market" | "workbuddy-cli-connector";
1100
+ export interface ImportRequest {
1101
+ /** Absolute local directory path on the trusted host; validated server-side. */
1102
+ source_path: string;
1103
+ source_kind: ImportSourceKind;
1104
+ }
1105
+ export type ImportStatus = "completed" | "completed-with-warnings" | "blocked" | "failed";
1106
+ export interface ImportResult {
1107
+ snapshot_id: string;
1108
+ name: string;
1109
+ version: string;
1110
+ source_kind: string;
1111
+ status: ImportStatus;
1112
+ content_digest: string;
1113
+ component_status: CompatibilityTriple;
1114
+ component_count: number;
1115
+ imported_at: number;
1116
+ /** true when an identical digest was already imported (idempotent reuse). */
1117
+ reused: boolean;
1118
+ warnings: string[];
1119
+ errors: string[];
1120
+ }
1121
+ export interface ImportSummary {
1122
+ snapshot_id: string;
1123
+ name: string;
1124
+ version: string;
1125
+ source_kind: string;
1126
+ status: ImportStatus;
1127
+ component_count: number;
1128
+ imported_at: number;
1129
+ }
1130
+ export interface ImportComponent {
1131
+ id: string;
1132
+ kind: string;
1133
+ name: string;
1134
+ compatibility: CompatibilityTriple;
1135
+ warnings: string[];
1136
+ }
1137
+ export interface ImportDetail extends ImportSummary {
1138
+ content_digest: string;
1139
+ component_status: CompatibilityTriple;
1140
+ warnings: string[];
1141
+ errors: string[];
1142
+ components: ImportComponent[];
1143
+ }
1144
+ /**
1145
+ * `import/assets` response: the plugin-level asset inventory of one immutable
1146
+ * snapshot (doc 38).
1147
+ *
1148
+ * Exactly the address space `import/asset` accepts and an `ExpertPack.assets`
1149
+ * names — the three faces share one provider and one whitelist, so a consumer
1150
+ * can never see a path it cannot fetch. Persona text, skill bodies and the
1151
+ * manifest are deliberately not listed.
1152
+ */
1153
+ export interface ImportAssetList {
1154
+ snapshot_id: string;
1155
+ /** Sorted by `path`; only `avatars/`, `bin/` and `scripts/` are listed. */
1156
+ assets: ExpertAssetRef[];
1157
+ /** The inventory hit its ceiling and is incomplete. Never silently truncated. */
1158
+ truncated: boolean;
1159
+ }
1160
+ /**
1161
+ * `import/asset` over the WebSocket binding: base64, because JSON has no byte
1162
+ * string. Same envelope as `skill/file`.
1163
+ */
1164
+ export interface ImportAssetContent {
1165
+ snapshot_id: string;
1166
+ path: string;
1167
+ content_type: string;
1168
+ encoding: "base64";
1169
+ content: string;
1170
+ }
1171
+ export interface InstallRequest {
1172
+ snapshot_id: string;
1173
+ }
1174
+ export type InstallState = "not-installed" | "installed" | "disabled";
1175
+ export interface InstallComponent {
1176
+ id: string;
1177
+ kind: string;
1178
+ name: string;
1179
+ state: InstallState;
1180
+ runtime_location?: string | null;
1181
+ preset_id?: string | null;
1182
+ }
1183
+ export interface InstallResult {
1184
+ snapshot_id: string;
1185
+ name: string;
1186
+ version: string;
1187
+ installed_count: number;
1188
+ skipped: string[];
1189
+ warnings: string[];
1190
+ errors: string[];
1191
+ /** Per-component outcome. Absent on a host that predates the field — read a
1192
+ * missing value as "no detail available", never as "nothing ran". */
1193
+ outcomes?: InstallOutcome[];
1194
+ }
1195
+ export interface InstallStatus {
1196
+ snapshot_id: string;
1197
+ components: InstallComponent[];
1198
+ /** Per-component outcome of the mutation that produced this projection
1199
+ * (`install/uninstall` · `install/disable` · `install/enable`). Empty for a
1200
+ * plain `install/status` read. */
1201
+ outcomes?: InstallOutcome[];
1202
+ /** Human-readable failures; mirrors the `ok: false` outcomes. */
1203
+ errors?: string[];
1204
+ }
1205
+ /** What happened to one component during install / uninstall / enable / disable. */
1206
+ export type InstallOutcomeAction = "created" | "reused" | "enabled" | "disabled" |
1207
+ /** The flag moved but the runtime did not: the documented `skill` case, since
1208
+ * the skill corpus has no enable state (docs `05` §4.5). */
1209
+ "marked" | "removed" | "skipped" | "failed";
1210
+ export interface InstallOutcome {
1211
+ component_id: string;
1212
+ kind: string;
1213
+ action: InstallOutcomeAction;
1214
+ /** `false` only when the requested state was **not** reached. A component
1215
+ * that was already in the requested state reports `ok: true`. */
1216
+ ok: boolean;
1217
+ /**
1218
+ * Stable, documented token to branch on (`docs/agent-store/05` §4.5 lists the
1219
+ * closed set). `message` is for humans and nothing parses it — the field
1220
+ * exists so callers never have to match on prose.
1221
+ */
1222
+ code?: string;
1223
+ message?: string;
1224
+ }
1225
+ /** Wire-marketplace source kinds (Phase A: directory only; `zip` since `fp-7`). */
1226
+ export type MarketplaceSourceKind = "directory" | "github" | "git" | "url" | "zip";
1227
+ export interface MarketplaceAddRequest {
1228
+ name?: string;
1229
+ source_kind: MarketplaceSourceKind;
1230
+ /** Local directory path / owner/repo / Git URL / HTTP URL / archive URL. */
1231
+ source: string;
1232
+ }
1233
+ export interface MarketplaceSummary {
1234
+ marketplace_id: string;
1235
+ name: string;
1236
+ description?: string | null;
1237
+ source_kind: string;
1238
+ version?: string | null;
1239
+ auto_update: boolean;
1240
+ enabled: boolean;
1241
+ entry_count: number;
1242
+ added_at: number;
1243
+ /** Last resolved source revision (git commit / freshness marker). */
1244
+ resolved_revision?: string | null;
1245
+ /** Last successful freshness check (epoch ms); absent before the first one. */
1246
+ last_checked_at?: number | null;
1247
+ }
1248
+ /**
1249
+ * Snapshot produced by importing this entry, plus how much of it is installed.
1250
+ * Projected server-side on `market/get` (doc 16 D-W13-1 ①) so a client never
1251
+ * has to re-derive "what would a cascade removal take out?" from the store
1252
+ * listing.
1253
+ */
1254
+ export interface MarketplaceEntrySnapshot {
1255
+ snapshot_id: string;
1256
+ name: string;
1257
+ version: string;
1258
+ status: string;
1259
+ component_count: number;
1260
+ /** Components installed from this snapshot; `0` = imported but untouched. */
1261
+ installed_count: number;
1262
+ imported_at: number;
1263
+ }
1264
+ export interface MarketplaceEntry {
1265
+ name: string;
1266
+ source_kind: string;
1267
+ source: string;
1268
+ version?: string | null;
1269
+ description?: string | null;
1270
+ keywords: string[];
1271
+ category?: string | null;
1272
+ /**
1273
+ * Localized `<field>_<lang>` variants carried verbatim by the market
1274
+ * manifest (`description_zh`, `name_en`, `tags_zh`, `legacy_tags_en`, …).
1275
+ * Resolve with `pickEntryText` / `pickEntryTags` (doc 18 §4, D8=A);
1276
+ * absent when the manifest declared none.
1277
+ */
1278
+ localized?: Record<string, string | string[]> | null;
1279
+ /**
1280
+ * The entry's declared `strict` (doc `02` §8): `true` requires the plugin
1281
+ * source to carry its own `.codebuddy-plugin/plugin.json`. Absent = `false`.
1282
+ */
1283
+ strict?: boolean | null;
1284
+ /**
1285
+ * Why this entry cannot be imported (doc `02` §11.1), when discovery can
1286
+ * already tell. Present = listed for transparency, but not installable.
1287
+ */
1288
+ blocked_reason?: string | null;
1289
+ /** Absent until the entry has been imported from this marketplace. */
1290
+ snapshot?: MarketplaceEntrySnapshot | null;
1291
+ }
1292
+ export interface MarketplaceDetail extends MarketplaceSummary {
1293
+ entries: MarketplaceEntry[];
1294
+ }
1295
+ export interface MarketplaceRemoveResult {
1296
+ marketplace_id: string;
1297
+ /** Snapshot ids installed from this marketplace, uninstalled by the cascade. */
1298
+ snapshots: string[];
1299
+ uninstalled_components: string[];
1300
+ warnings: string[];
1301
+ }
1302
+ export interface MarketplaceRefreshResult {
1303
+ marketplace_id: string;
1304
+ /** true when the remote revision changed and the projection was rebuilt. */
1305
+ changed: boolean;
1306
+ resolved_revision: string;
1307
+ entry_count: number;
1308
+ warnings: string[];
1309
+ }
1310
+ export type StoreItemKind = "agent" | "team" | "skill" | "connector";
1311
+ /** One store item: a marketplace entry with display fidelity + install state. */
1312
+ export interface StoreItem {
1313
+ /** Stable composite id: `<marketplace_id>/<entry_name>`. */
1314
+ id: string;
1315
+ marketplace_id: string;
1316
+ marketplace_name: string;
1317
+ entry_name: string;
1318
+ kind: StoreItemKind;
1319
+ /** Display name: `displayName` (localized) when present, else entry name. */
1320
+ name: string;
1321
+ display_name?: LocalizedText | null;
1322
+ profession?: LocalizedText | null;
1323
+ description?: string | null;
1324
+ display_description?: LocalizedText | null;
1325
+ /** Empty arrays are omitted on the wire (`skip_serializing_if`).
1326
+ * Treat as optional: `item.tags ?? []`. */
1327
+ tags?: LocalizedText[] | null;
1328
+ quick_prompts?: LocalizedText[] | null;
1329
+ /**
1330
+ * The market entry's own `publishedAt`, `YYYY-MM-DD` (`18` §3).
1331
+ *
1332
+ * A calendar date the *market* declared, never a derived one: absent means
1333
+ * "this market declares no date", so a client must not substitute the import
1334
+ * time or render a placeholder. The host normalizes it on the way in and
1335
+ * drops anything that is not a calendar date.
1336
+ */
1337
+ published_at?: string | null;
1338
+ /** Public avatar URL (store asset endpoint); relative to the API host. */
1339
+ avatar_url?: string | null;
1340
+ version: string;
1341
+ source_kind: string;
1342
+ installed: boolean;
1343
+ update_available: boolean;
1344
+ snapshot_id?: string | null;
1345
+ installed_version?: string | null;
1346
+ /**
1347
+ * Why this item cannot be installed (doc `02` §11.1), re-derived from the
1348
+ * live source tree. Present = the item stays listed so the reason can be
1349
+ * read, but install must not be offered.
1350
+ */
1351
+ blocked_reason?: string | null;
1352
+ }
1353
+ export interface StoreList {
1354
+ items: StoreItem[];
1355
+ /** `true` while the builtin default marketplaces are still registering in the
1356
+ * background (D-SDK-1 ①) — the catalog may be incomplete. */
1357
+ markets_pending?: boolean;
1358
+ }
1359
+ /**
1360
+ * The host's effective market policy (doc `37` §3.2, `fp-13`).
1361
+ *
1362
+ * Two different questions, deliberately in one view: *when* the sweep runs
1363
+ * (`auto_update_interval_hours`) and *what it may touch*
1364
+ * (`entry_auto_update_kinds`). Both are read from the host's `config.toml` on
1365
+ * every call — nothing here is an in-memory override.
1366
+ */
1367
+ export interface MarketSettings {
1368
+ /**
1369
+ * Sweep cadence in hours. `null` = the background sweep is off.
1370
+ *
1371
+ * `0` is the wire spelling for "turn it off" on the way in
1372
+ * ({@link MarketSettingsPatch}), and reads back as `null`, because the host's
1373
+ * own `cadence()` already treats `0` as off rather than "every tick".
1374
+ */
1375
+ auto_update_interval_hours: number | null;
1376
+ /**
1377
+ * Entry kinds the sweep may auto-upgrade.
1378
+ *
1379
+ * Empty = the sweep only refreshes market indexes (the behaviour before doc
1380
+ * `37`). The default is `["agent", "team", "skill"]`: `"connector"` is never
1381
+ * implicit, because upgrading a connector reruns `upsert_server`, which marks
1382
+ * the server **disabled** whenever its configuration changed — an unattended
1383
+ * upgrade would silently disconnect a connector the user is using (`36` §3.4).
1384
+ */
1385
+ entry_auto_update_kinds: StoreItemKind[];
1386
+ /**
1387
+ * Whether this host actually runs a sweep at all.
1388
+ *
1389
+ * A host with no resolved `config.toml` path (the desktop host) reports
1390
+ * `false` and never sweeps, so a client must not read "the cadence is set" as
1391
+ * "updates will happen".
1392
+ */
1393
+ sweep_enabled: boolean;
1394
+ /** The most recent sweep, or `null` before the first one. In-memory only. */
1395
+ last_sweep: MarketSweepReport | null;
1396
+ }
1397
+ /**
1398
+ * `market/settings-set` patch. An **absent** field means "leave it alone" — the
1399
+ * two fields are independent, so a caller can change the cadence without
1400
+ * restating the whitelist.
1401
+ */
1402
+ export interface MarketSettingsPatch {
1403
+ /** Absent = unchanged; `0` = off; `> 0` = hours between sweeps. */
1404
+ auto_update_interval_hours?: number;
1405
+ /** Absent = unchanged; `[]` = upgrade nothing (index refresh only). */
1406
+ entry_auto_update_kinds?: StoreItemKind[];
1407
+ }
1408
+ /** One background sweep's reading (doc `37` §3.2, D8). */
1409
+ export interface MarketSweepReport {
1410
+ /** Epoch ms when the sweep finished. */
1411
+ at: number;
1412
+ /** Markets whose index refresh succeeded this round. */
1413
+ refreshed: number;
1414
+ /** Entries actually upgraded. */
1415
+ upgraded: number;
1416
+ /** Entries that were candidates and failed. Never silently dropped. */
1417
+ failed: MarketSweepFailure[];
1418
+ }
1419
+ /** One entry the sweep refused to upgrade, with the host's own reason. */
1420
+ export interface MarketSweepFailure {
1421
+ /** `<marketplace_id>/<entry_name>` — the {@link StoreItem.id} spelling. */
1422
+ id: string;
1423
+ /** Server-authored prose (not an i18n key). */
1424
+ error: string;
1425
+ }
1426
+ /** One model in the public catalog (`models/list`, REQ-PAR-05b). Provider
1427
+ * credentials, base URLs and health internals never cross this projection. */
1428
+ export interface ModelSummary {
1429
+ provider_id: string;
1430
+ provider_name: string;
1431
+ model: string;
1432
+ /** Provider-supplied display label when configured. */
1433
+ display_name?: string | null;
1434
+ /** True when an unbound preset resolves to this provider/model. */
1435
+ is_default: boolean;
1436
+ }
1437
+ /** `models/list` response: the public model directory over enabled providers. */
1438
+ export interface ModelList {
1439
+ items: ModelSummary[];
1440
+ }
1441
+ export interface StoreInstallResult {
1442
+ marketplace_id: string;
1443
+ entry_name: string;
1444
+ snapshot_id: string;
1445
+ version: string;
1446
+ /** true when the components were already registered (no-op install), or — for
1447
+ * an update — when the entry was already at the advertised version. */
1448
+ reused: boolean;
1449
+ installed_count: number;
1450
+ warnings: string[];
1451
+ errors: string[];
1452
+ /** Per-component outcome, forwarded from the installer's own report so a
1453
+ * store install is as branchable as a direct `install/run`. Absent on a host
1454
+ * that predates the field. */
1455
+ outcomes?: InstallOutcome[];
1456
+ /** Version the entry was installed at before this call (`store/update-entry`
1457
+ * only; absent means "this was an install"). */
1458
+ previous_version?: string | null;
1459
+ /** The snapshot that was replaced. Same rule as `previous_version`. */
1460
+ previous_snapshot_id?: string | null;
1461
+ /** Components of the replaced snapshot whose runtime artifacts were actually
1462
+ * released. `0` on an install, on a no-op update, and when a failed update
1463
+ * left the previous installation in place. */
1464
+ released_count?: number;
1465
+ }
1466
+ export interface LocalizedText {
1467
+ en?: string | null;
1468
+ zh?: string | null;
1469
+ }
1470
+ export interface AgentSummary {
1471
+ id: string;
1472
+ version: string;
1473
+ name: string;
1474
+ /** Installed preset id; present after `install/*` of the snapshot (Phase 2). */
1475
+ preset_id?: string | null;
1476
+ description?: string | null;
1477
+ skills: string[];
1478
+ connectors: string[];
1479
+ model_summary?: string | null;
1480
+ tool_policy_summary?: string | null;
1481
+ source: string;
1482
+ compatibility_status: CompatibilityStatus;
1483
+ display_name?: LocalizedText | null;
1484
+ profession?: LocalizedText | null;
1485
+ /** Public avatar URL (server-asset endpoint); relative to the API host. */
1486
+ avatar_url?: string | null;
1487
+ }
1488
+ /** `agent/get` — structured frontmatter fields only; raw prompts never cross. */
1489
+ export interface AgentDetail extends AgentSummary {
1490
+ effort?: string | null;
1491
+ max_turns?: number | null;
1492
+ disallowed_tools: string[];
1493
+ memory?: string | null;
1494
+ background?: string | null;
1495
+ isolation?: string | null;
1496
+ permission_mode_ignored: boolean;
1497
+ display_description?: LocalizedText | null;
1498
+ quick_prompts?: LocalizedText[];
1499
+ tags?: LocalizedText[];
1500
+ default_init_prompt?: LocalizedText | null;
1501
+ expert_type?: string | null;
1502
+ category_id?: string | null;
1503
+ }
1504
+ export interface TeamSummary {
1505
+ id: string;
1506
+ version: string;
1507
+ name: string;
1508
+ description?: string | null;
1509
+ lead_agent_id: string;
1510
+ member_agent_ids: string[];
1511
+ source: string;
1512
+ compatibility_status: CompatibilityStatus;
1513
+ }
1514
+ export interface TeamDetail extends TeamSummary {
1515
+ planner_policy: string;
1516
+ routing_constraints: string[];
1517
+ workflow_limits: Record<string, unknown>;
1518
+ team_runtime_capabilities: string[];
1519
+ /**
1520
+ * MCP server ids this Team's own snapshot installed **and** left enabled on
1521
+ * this host — the only Connector surface a Team Run may bind. Member Agents'
1522
+ * `mcpServers` are recorded, not mapped to grants (docs/agent-store/02 §5.1).
1523
+ */
1524
+ connectors: string[];
1525
+ }
1526
+ /**
1527
+ * Format version of {@link ExpertPack}.
1528
+ *
1529
+ * **Independent of {@link APP_SERVER_PROTOCOL_VERSION}** on purpose: the
1530
+ * fingerprint versions *our* wire compatibility, this versions the **artifact
1531
+ * contract for third-party runtimes**. Gate on this one; the fingerprint is
1532
+ * about whether your client can talk to the host at all.
1533
+ */
1534
+ export declare const APP_SERVER_EXPERT_PACK_FORMAT = 2;
1535
+ export type ExpertPackKind = "agent" | "team";
1536
+ /**
1537
+ * Which plugin-level asset directory a reference came from (doc 38).
1538
+ *
1539
+ * `script` covers `bin/` **and** `scripts/` — the importer's own script
1540
+ * component folds both into one. Honest label, not a permission: the host never
1541
+ * executes plugin-level scripts and the snapshot copies them 0644, so the bytes
1542
+ * arrive without an executable bit. This is the one field a consumer can use to
1543
+ * decline executable content without parsing paths.
1544
+ */
1545
+ export type ExpertAssetKind = "avatar" | "script";
1546
+ /**
1547
+ * One plugin-level asset the pack carries, **by reference** (doc 38).
1548
+ *
1549
+ * The files beside the skills — `avatars/`, `bin/`, `scripts/` — are part of
1550
+ * what a package ships, and the team's own instructions routinely assume the
1551
+ * plugin root holds them (`skills/md-to-html/scripts/render.py` resolves
1552
+ * `PLUGIN_ROOT/bin/init_task.py` two levels above its skill). The bytes stay on
1553
+ * `import/asset`; nothing is inlined, for the same reason skills are not.
1554
+ */
1555
+ export interface ExpertAssetRef {
1556
+ /** Snapshot-relative path, POSIX-separated, e.g. `bin/init_task.py`. */
1557
+ path: string;
1558
+ kind: ExpertAssetKind;
1559
+ size: number;
1560
+ /** Single-file sha256, lowercase hex — same rule as `skill/files`. */
1561
+ digest: string;
1562
+ }
1563
+ /** The expert's persona — the only body of text this face carries. */
1564
+ export interface ExpertPersona {
1565
+ /**
1566
+ * The Agent Markdown body, verbatim. The catalog face deliberately has no
1567
+ * field that could carry it (`AgentDetail`), so this is the only place it
1568
+ * appears — see doc 32 §2.
1569
+ */
1570
+ instructions: string;
1571
+ memory?: string | null;
1572
+ background?: string | null;
1573
+ }
1574
+ export interface ExpertModelRef {
1575
+ /** Host-local row id on the host that produced the pack; not portable. */
1576
+ provider_id?: string | null;
1577
+ model: string;
1578
+ }
1579
+ /**
1580
+ * Declared and resolved model hints. Neither is binding: `declared` is the
1581
+ * source frontmatter string and `resolved` is what the producing host resolved
1582
+ * it to. A consumer resolves its own model (doc 32 §5 R6).
1583
+ */
1584
+ export interface ExpertModel {
1585
+ declared?: string | null;
1586
+ resolved?: ExpertModelRef | null;
1587
+ effort?: string | null;
1588
+ max_turns?: number | null;
1589
+ }
1590
+ /**
1591
+ * One Skill the expert declares, **by reference** — fetch the bytes with
1592
+ * `skills.files()` / `skills.readFile()`. `name` and `id` are the same string:
1593
+ * `skill/list` publishes the Skill name as its id.
1594
+ */
1595
+ export interface ExpertSkillRef {
1596
+ name: string;
1597
+ id: string;
1598
+ }
1599
+ /**
1600
+ * One Connector a **team's own snapshot** installed and left enabled. Identity
1601
+ * and switch only — never transport, env, headers or token (doc 24 §2/§5.3);
1602
+ * tool schemas stay on `connector/get`.
1603
+ *
1604
+ * An **agent** pack's list is always empty, faithfully: this format has no
1605
+ * agent-level connector dependency (doc 02 §5.1).
1606
+ */
1607
+ export interface ExpertConnectorRef {
1608
+ id: string;
1609
+ name: string;
1610
+ enabled: boolean;
1611
+ }
1612
+ /** A declaration, never a permission boundary (doc 32 §5 R9). */
1613
+ export interface ExpertToolPolicy {
1614
+ tools: string[];
1615
+ disallowed_tools: string[];
1616
+ }
1617
+ export interface ExpertProvenance {
1618
+ source: string;
1619
+ snapshot_id: string;
1620
+ content_digest: string;
1621
+ preset_id?: string | null;
1622
+ preset_revision?: number | null;
1623
+ }
1624
+ /**
1625
+ * Which engine the definition was bound to on the producing host.
1626
+ *
1627
+ * `portable: false` is the normal case, not an error: an installed expert is a
1628
+ * Preset pinned to that host's runtime agent, which no other runtime has.
1629
+ */
1630
+ export interface ExpertRuntimeBinding {
1631
+ runtime: string;
1632
+ portable: boolean;
1633
+ }
1634
+ /** Team roster and policy. The roster is a hard boundary; the rest is intent. */
1635
+ export interface ExpertTeamPack {
1636
+ lead_agent_id: string;
1637
+ member_agent_ids: string[];
1638
+ planner_policy: string;
1639
+ routing_constraints: string[];
1640
+ workflow_limits: Record<string, unknown>;
1641
+ team_runtime_capabilities: string[];
1642
+ /** Fully expanded member packs, **leader first**. */
1643
+ members: ExpertPack[];
1644
+ }
1645
+ /**
1646
+ * A portable expert definition (`agent/export` / `team/export`, doc 32).
1647
+ *
1648
+ * A **definition, not execution semantics**: how a team plans and schedules its
1649
+ * steps, how tool and credential policy is applied, and the shape of its events
1650
+ * are all enforced by the runtime and are absent here. Doc 32 §5 (R1–R11) lists
1651
+ * what a consumer has to implement itself — answer that list rather than only
1652
+ * reading this shape.
1653
+ *
1654
+ * There is deliberately no export timestamp, so two exports of one snapshot are
1655
+ * byte-identical: `provenance.content_digest` is usable as a cache key, and two
1656
+ * exports can be diffed to answer "what changed upstream".
1657
+ */
1658
+ export interface ExpertPack {
1659
+ pack_format: number;
1660
+ kind: ExpertPackKind;
1661
+ id: string;
1662
+ version: string;
1663
+ name: string;
1664
+ display_name?: LocalizedText | null;
1665
+ description?: string | null;
1666
+ persona: ExpertPersona;
1667
+ model: ExpertModel;
1668
+ /**
1669
+ * The Definition's Skills, **by reference** — fetch the bytes with
1670
+ * `skills.files()` / `skills.readFile()`.
1671
+ *
1672
+ * Two sources, deliberately not merged on the wire: an **agent** pack's list is
1673
+ * that expert's own declaration, while a **team** pack's list is what the team's
1674
+ * own package installed, and each member keeps its own declaration in
1675
+ * `team.members[].skills`. An empty list means the package ships no skills, not
1676
+ * that you should go look at the members. `materializePack` unions both.
1677
+ */
1678
+ skills: ExpertSkillRef[];
1679
+ connectors: ExpertConnectorRef[];
1680
+ /**
1681
+ * The package's plugin-level assets (`avatars/`, `bin/`, `scripts/`), by
1682
+ * reference, sorted by `path` (doc 38).
1683
+ *
1684
+ * **Snapshot-scoped, so a member pack always carries an empty list**: the
1685
+ * whole roster ships inside one plugin. Empty also means "this host wired no
1686
+ * asset provider" — `capabilities.snapshot_assets` distinguishes that from
1687
+ * "this package ships none". Fetch the bytes with
1688
+ * `listImportAssets()` / `readImportAsset()`.
1689
+ */
1690
+ assets: ExpertAssetRef[];
1691
+ /**
1692
+ * The package's declared avatar as a **snapshot-relative path** (the
1693
+ * `plugin.json` `avatar` value, e.g. `avatars/team.png`). Top-level pack only,
1694
+ * and only when that exact file is listed in `assets` — the pack never names
1695
+ * a file a consumer cannot fetch. A host-local *URL* stays excluded (doc 32
1696
+ * §2); a path resolvable against the pack's own asset list is the portable
1697
+ * half of that decision.
1698
+ */
1699
+ avatar?: string | null;
1700
+ tool_policy: ExpertToolPolicy;
1701
+ /** Present exactly when `kind === "team"`. */
1702
+ team?: ExpertTeamPack | null;
1703
+ provenance: ExpertProvenance;
1704
+ runtime_binding: ExpertRuntimeBinding;
1705
+ }
1706
+ //#endregion
1707
+ //#region src/errors.d.ts
1708
+ export type ErrorCode = string;
1709
+ export declare class AppServerError extends Error {
1710
+ readonly code: ErrorCode;
1711
+ readonly requestId?: string | number | null;
1712
+ readonly retryable: boolean;
1713
+ readonly details: Record<string, unknown>;
1714
+ constructor(wire: WireError);
1715
+ }
1716
+ export type TransportPhase = "connect" | "send" | "receive" | "close";
1717
+ export declare class TransportError extends Error {
1718
+ readonly phase: TransportPhase;
1719
+ readonly retryable: boolean;
1720
+ readonly cause?: unknown;
1721
+ constructor(phase: TransportPhase, message: string, options?: {
1722
+ retryable?: boolean;
1723
+ cause?: unknown;
1724
+ });
1725
+ }
1726
+ export type ProtocolErrorKind = "invalid_message" | "version_mismatch" | "unexpected_response";
1727
+ export declare class ProtocolError extends Error {
1728
+ readonly kind: ProtocolErrorKind;
1729
+ constructor(kind: ProtocolErrorKind, message: string);
1730
+ }
1731
+ export declare class RequestTimeoutError extends Error {
1732
+ readonly method: string;
1733
+ readonly timeoutMs: number;
1734
+ constructor(method: string, timeoutMs: number);
1735
+ }
1736
+ export declare function isAppServerError(error: unknown): error is AppServerError;
1737
+ export declare function isRetryableTransportError(error: unknown): boolean;
1738
+ /**
1739
+ * Single human-readable rendering of a caught error.
1740
+ *
1741
+ * Every UI surface shares this one: a bare `Error` contributes only its
1742
+ * message, while a protocol error contributes its stable `code` plus the
1743
+ * server's retry hint. Components must not grow their own variants — a
1744
+ * second spelling means the two drift apart on the retry hint.
1745
+ */
1746
+ export declare function formatError(error: unknown): string;
1747
+ /** Whether the caught error is retryable — used by UI surfaces to append a
1748
+ * localized "(可重试)" hint without baking wording into this lib. */
1749
+ export declare function isRetryableError(error: unknown): boolean;
1750
+ //#endregion
1751
+ //#region src/localized.d.ts
1752
+ /** The two languages the store ships display text for. */
1753
+ export type LocalizedLang = "zh" | "en";
1754
+ /** The other language, i.e. the second link of every fallback chain. */
1755
+ export declare function otherLocalizedLang(lang: LocalizedLang): LocalizedLang;
1756
+ /**
1757
+ * Map an i18n language tag (`zh-CN`, `en-US`, `en`) to a variant language.
1758
+ * Anything that is not English resolves to `zh`, matching the app default.
1759
+ */
1760
+ export declare function toLocalizedLang(language: string | null | undefined): LocalizedLang;
1761
+ /** `{field}_{lang}` → `{field}_{other}` → `""`. */
1762
+ export declare function pickLocalized(text: LocalizedText | null | undefined, lang: LocalizedLang): string;
1763
+ /** Resolve a list of localized values, dropping the ones that resolve empty. */
1764
+ export declare function pickLocalizedList(list: readonly (LocalizedText | null | undefined)[] | null | undefined, lang: LocalizedLang): string[];
1765
+ /**
1766
+ * The `<field>_<lang>` variants of one `market/get` entry.
1767
+ *
1768
+ * Values are either a string (`description_zh`) or a list of strings
1769
+ * (`tags_en`, `legacy_tags_zh`, `examples_zh`).
1770
+ */
1771
+ export type LocalizedVariants = Readonly<Record<string, string | readonly string[] | null | undefined>> | null | undefined;
1772
+ /** `{field}_{lang}` → `{field}_{other}` as a string, when either carries text. */
1773
+ export declare function pickVariantText(variants: LocalizedVariants, field: string, lang: LocalizedLang): string | undefined;
1774
+ /** `{field}_{lang}` → `{field}_{other}` as a string list, when either is non-empty. */
1775
+ export declare function pickVariantList(variants: LocalizedVariants, field: string, lang: LocalizedLang): string[] | undefined;
1776
+ /**
1777
+ * Market entry tags. Per D8=A `tags_{lang}` outranks `legacy_tags_{lang}` —
1778
+ * the legacy field only fills in when the current one is absent, in either
1779
+ * language.
1780
+ */
1781
+ export declare function pickEntryTags(variants: LocalizedVariants, lang: LocalizedLang): string[] | undefined;
1782
+ /**
1783
+ * A market entry text field: variant first, then the baseline field
1784
+ * (`entry.description`, `entry.name`, …) as the last link of the chain.
1785
+ */
1786
+ export declare function pickEntryText(variants: LocalizedVariants, field: string, lang: LocalizedLang, baseline?: string | null): string | undefined;
1787
+ //#endregion
1788
+ //#region src/conversation-events.d.ts
1789
+ export type ThinkingData = {
1790
+ content: string;
1791
+ subject: string | null;
1792
+ status: string | null;
1793
+ duration: number | null;
1794
+ };
1795
+ export type TipsData = {
1796
+ content: string;
1797
+ tipType: string;
1798
+ };
1799
+ export type ToolCallData = {
1800
+ name: string | null;
1801
+ args?: unknown;
1802
+ output?: unknown;
1803
+ status: string | null;
1804
+ };
1805
+ /**
1806
+ * One plan step's status.
1807
+ *
1808
+ * The producer declares exactly these three (`nomi-tools/src/update_plan.rs`
1809
+ * `StepStatus`), and its doc comment names the frontend as the reason for the
1810
+ * snake_case spelling — so this mirrors a stated contract rather than guessing
1811
+ * at a vocabulary.
1812
+ */
1813
+ export type PlanStepStatus = "pending" | "in_progress" | "completed";
1814
+ export type PlanEntry = {
1815
+ content: string;
1816
+ status: PlanStepStatus;
1817
+ };
1818
+ /** Shape a plan body (`message_type` `plan`): the step list. */
1819
+ export type PlanData = {
1820
+ entries: PlanEntry[];
1821
+ };
1822
+ export declare function stringValue(value: unknown): string | null;
1823
+ export declare function numberValue(value: unknown): number | null;
1824
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
1825
+ /**
1826
+ * Strict boolean reader: a wire value that is not a real boolean reads as
1827
+ * `null` (unknown) rather than being coerced — "retryable" must never be
1828
+ * guessed from a truthy string.
1829
+ */
1830
+ export declare function booleanValue(value: unknown): boolean | null;
1831
+ /** Flatten a message/activity body to plain text, JSON-encoding anything else. */
1832
+ export declare function contentToText(value: unknown): string;
1833
+ /** Shape a thinking body (`message.thinking`, or `message.activity` kind `thinking`). */
1834
+ export declare function thinkingData(value: unknown): ThinkingData;
1835
+ /** Shape a tips body; `type` is accepted as an alias of `tip_type`. */
1836
+ export declare function tipsData(value: unknown): TipsData;
1837
+ /** Shape a tool-call body; `null` when the body carries nothing tool-shaped. */
1838
+ export declare function toolCallData(value: unknown): ToolCallData | null;
1839
+ /**
1840
+ * Shape a plan body; `null` when there is no usable step list.
1841
+ *
1842
+ * `step` is accepted as an alias of `content`: the tool's own argument is named
1843
+ * `step`, and the producer normalizes it to `content` for the plan row — a row
1844
+ * written before that normalization (or by a producer that skipped it) would
1845
+ * otherwise render as an empty plan.
1846
+ */
1847
+ export declare function planData(value: unknown): PlanData | null;
1848
+ /** Shape a `context.usage` payload; `null` when nothing was actually measured. */
1849
+ export declare function parseContextUsage(value: unknown): ContextUsage | null;
1850
+ /**
1851
+ * Shape a per-turn `usage` body (W9 / R14); `null` when the runtime reported
1852
+ * nothing usable.
1853
+ *
1854
+ * A missing side is **unknown**, not zero: half a bill is not a bill, so a
1855
+ * frame that only carries `input_tokens` decodes to `null` rather than to a
1856
+ * total that silently drops the output side. Same rule for an all-zero report —
1857
+ * "no tokens reported" must never render as "this turn was free".
1858
+ */
1859
+ export declare function parseTurnUsage(value: unknown): TurnUsage | null;
1860
+ /**
1861
+ * One decoded conversation event, discriminated by `kind`.
1862
+ *
1863
+ * `messageId` is the id the *server* supplied (`null` when it sent none) — a
1864
+ * consumer that needs a stable row for an id-less event applies its own
1865
+ * default, because that default is presentation, not protocol.
1866
+ *
1867
+ * `unknown` is the honest fall-through for a newer server kind: the raw
1868
+ * `eventType` is preserved so a caller can log or forward it instead of
1869
+ * silently dropping the frame.
1870
+ */
1871
+ type MessageFrameBase = {
1872
+ event: ConversationEvent;
1873
+ /** The id the server supplied; `null` when it sent none (consumer picks a default row). */
1874
+ messageId: string | null;
1875
+ /** Server timestamp when the payload carried one. */
1876
+ createdAt: number | null;
1877
+ };
1878
+ export type DecodedConversationEvent = ({
1879
+ kind: "message.created";
1880
+ content: unknown;
1881
+ } & MessageFrameBase) | ({
1882
+ kind: "message.delta";
1883
+ delta: string;
1884
+ replace: boolean;
1885
+ } & MessageFrameBase) | ({
1886
+ kind: "message.thinking";
1887
+ thinking: ThinkingData;
1888
+ replace: boolean;
1889
+ } & MessageFrameBase) | ({
1890
+ kind: "message.tool";
1891
+ tool: ToolCallData | null;
1892
+ } & MessageFrameBase) | ({
1893
+ kind: "message.tips";
1894
+ tips: TipsData;
1895
+ } & MessageFrameBase) | ({
1896
+ kind: "message.error";
1897
+ message: string | null;
1898
+ code: string | null;
1899
+ retryable: boolean | null;
1900
+ } & MessageFrameBase) | ({
1901
+ kind: "message.activity";
1902
+ activityKind: string;
1903
+ content: unknown;
1904
+ usage: TurnUsage | null;
1905
+ } & MessageFrameBase) | {
1906
+ kind: "turn.status";
1907
+ event: ConversationEvent;
1908
+ running: boolean;
1909
+ } | {
1910
+ kind: "context.usage";
1911
+ event: ConversationEvent;
1912
+ usage: ContextUsage | null;
1913
+ } | {
1914
+ kind: "unknown";
1915
+ event: ConversationEvent;
1916
+ eventType: string;
1917
+ };
1918
+ /**
1919
+ * Decode one `conversation/event` frame into a typed body.
1920
+ *
1921
+ * An activity frame whose `kind` is `thinking` is normalized to
1922
+ * `message.thinking` (the server has two spellings for the same thing).
1923
+ */
1924
+ export declare function decodeConversationEvent(event: ConversationEvent): DecodedConversationEvent;
1925
+ //#endregion