@flowy-agent-store/protocol 0.1.0-beta.3 → 0.1.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -5,7 +5,32 @@
5
5
  * The chat surface uses only opaque public conversation/message IDs. Internal
6
6
  * execution/session/step/attempt IDs never appear in this module.
7
7
  */
8
- export declare const APP_SERVER_PROTOCOL_VERSION = "2026-08-26";
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.
32
+ */
33
+ export declare const APP_SERVER_PROTOCOL_VERSION = "fp-1";
9
34
  export interface JsonRpcRequest {
10
35
  jsonrpc: "2.0";
11
36
  id: number | string;
@@ -49,7 +74,24 @@ export interface ConversationResyncRequiredNotification {
49
74
  reason: string;
50
75
  };
51
76
  }
52
- export type ServerNotification = JsonRpcEventNotification | ResyncRequiredNotification | ConversationEventNotification | ConversationResyncRequiredNotification;
77
+ /**
78
+ * 会话**列表**投影变更(`conversation/list-changed`,2026-09-19 加入)。
79
+ *
80
+ * 与 `conversation/event` 是两件事:那些是某条被订阅会话的转写帧、带 `sequence`;
81
+ * 这一条改的是侧栏那一整份列表(自动标题、重命名、删除),所以:
82
+ * - 不要求订阅该会话(用户此刻可能正看着另一个会话);
83
+ * - **不带 `sequence`**,客户端不得据此推进 `lastSeenSequence`;
84
+ * - 尽力而为:丢一条只是让界面晚一步刷新,`conversation/list` 始终是权威。
85
+ */
86
+ export interface ConversationListChangedNotification {
87
+ jsonrpc: "2.0";
88
+ method: "conversation/list-changed";
89
+ params: {
90
+ conversation_id: string;
91
+ action: "created" | "updated" | "deleted";
92
+ };
93
+ }
94
+ export type ServerNotification = JsonRpcEventNotification | ResyncRequiredNotification | ConversationEventNotification | ConversationResyncRequiredNotification | ConversationListChangedNotification;
53
95
  export interface WireError {
54
96
  code: string;
55
97
  message: string;
@@ -96,7 +138,25 @@ export interface Capabilities {
96
138
  teams: boolean;
97
139
  team_runtime: boolean;
98
140
  skills: boolean;
141
+ /**
142
+ * The Skill **file tree** read face (`skill/files` / `skill/file`).
143
+ *
144
+ * Separate from `skills` on purpose: a host can wire the catalog without the
145
+ * file provider, so `skills: true` alone does not mean `skill/files` will
146
+ * answer. Check this before offering file access.
147
+ */
148
+ skill_files: boolean;
99
149
  connectors: boolean;
150
+ /**
151
+ * The connector **call proxy** (`connector/call`, doc 24 §5).
152
+ *
153
+ * Separate from `connectors` on purpose: the catalog can be wired without the
154
+ * proxy, and — more importantly — a host may wire the proxy while its
155
+ * `[connector_proxy]` allowlist is empty, in which case every call answers
156
+ * `policy_denied`. This flag says the *method* exists, not that any tool is
157
+ * callable; only the host operator can make a pair callable.
158
+ */
159
+ connector_calls: boolean;
100
160
  run_notifications: boolean;
101
161
  approvals: boolean;
102
162
  artifacts: boolean;
@@ -151,6 +211,22 @@ export interface BrowseDirectoryResult {
151
211
  truncated: boolean;
152
212
  isRoot?: boolean | null;
153
213
  }
214
+ /** `POST /api/fs/list` — one flat file entry under a workspace root (host file service). */
215
+ export interface WorkspaceFlatFile {
216
+ name: string;
217
+ full_path: string;
218
+ relative_path: string;
219
+ }
220
+ /** `POST /api/fs/metadata` response (host file service). */
221
+ export interface FileMetadata {
222
+ name: string;
223
+ path: string;
224
+ size: number;
225
+ /** Server-reported MIME type. */
226
+ type: string;
227
+ last_modified: number;
228
+ is_directory?: boolean | null;
229
+ }
154
230
  export type RunStatus = "planning" | "running" | "completed" | "completed_with_failures" | "failed" | "cancelled" | "paused" | "waiting_input" | "awaiting_approval" | "recovery_required";
155
231
  export type MentionKind = "agent" | "skill" | "connector";
156
232
  export interface MentionRef {
@@ -190,6 +266,44 @@ export interface RunReceipt {
190
266
  preset_revision: number;
191
267
  content_digest: string;
192
268
  }
269
+ /**
270
+ * `team/run` — Leader Conversation + planned delegation (docs/agent-store/05
271
+ * §5.2, `16` §7 决策 3).
272
+ *
273
+ * There is deliberately **no** `planning` field: member pool, concurrency
274
+ * ceiling, routing constraints and authority come from the bound Team template
275
+ * and server policy. The wire DTO is `deny_unknown_fields`, so sending one is
276
+ * `invalid_request` rather than a silently ignored parameter.
277
+ */
278
+ export interface TeamRunInput {
279
+ teamId: string;
280
+ teamVersion?: string;
281
+ goal?: string;
282
+ input?: {
283
+ text?: string;
284
+ } | Record<string, unknown>;
285
+ workspaceId?: string;
286
+ commandId?: string;
287
+ idempotencyKey?: string;
288
+ }
289
+ export interface TeamRunRequestWire {
290
+ team_id: string;
291
+ team_version?: string;
292
+ goal?: string;
293
+ input?: unknown;
294
+ workspace?: WorkspaceRef;
295
+ command_id?: string;
296
+ idempotency_key?: string;
297
+ }
298
+ /**
299
+ * Deliberately **not** `RunReceipt`: a Team Run has no lead preset to report a
300
+ * revision/digest for — its authority is the Team's mutable execution template.
301
+ * Only what the server can honestly answer is on the wire.
302
+ */
303
+ export interface TeamRunReceipt {
304
+ run_id: string;
305
+ status: RunStatus;
306
+ }
193
307
  export interface RunView {
194
308
  run_id: string;
195
309
  status: RunStatus;
@@ -213,11 +327,44 @@ export interface SteerRunInput {
213
327
  commandId?: string;
214
328
  idempotencyKey?: string;
215
329
  }
330
+ export type AnswerDecisionInput = {
331
+ runId: string;
332
+ stepId: string;
333
+ attemptId: string;
334
+ answer: string;
335
+ /**
336
+ * The engine's three-way CAS tokens. They are mandatory: an answer that does
337
+ * not name the exact execution/step/attempt version it was written against is
338
+ * refused with a `Conflict` instead of being applied. `run/events` projects
339
+ * the current three onto every pending `approval.requested`, so a client
340
+ * echoes those rather than inventing versions.
341
+ */
342
+ expectedExecutionVersion: number;
343
+ expectedStepVersion: number;
344
+ expectedAttemptVersion: number;
345
+ };
346
+ /**
347
+ * One projected run event.
348
+ *
349
+ * `step_id` / `attempt_id` are present when the engine scoped the event to an
350
+ * attempt (notably `approval.requested`). A pending decision additionally
351
+ * carries the three CAS versions an accepted answer must echo; they are read
352
+ * from the authoritative rows when the event is projected, so an answer built
353
+ * from them races safely (a concurrent change turns it into a `Conflict`).
354
+ *
355
+ * There is deliberately no `always_allow` counterpart on the wire: answering a
356
+ * decision never widens a tool policy.
357
+ */
216
358
  export interface RunEvent {
217
359
  run_id: string;
218
360
  sequence: number;
219
361
  event_type: string;
220
362
  payload: Record<string, unknown>;
363
+ step_id?: string | null;
364
+ attempt_id?: string | null;
365
+ expected_execution_version?: number | null;
366
+ expected_step_version?: number | null;
367
+ expected_attempt_version?: number | null;
221
368
  }
222
369
  export interface RunEventsQuery {
223
370
  runId: string;
@@ -227,6 +374,53 @@ export interface RunEventsQuery {
227
374
  export interface RunSubscriptionParams {
228
375
  run_id: string;
229
376
  }
377
+ /**
378
+ * W4 / W6 (doc 16 R10/R11, resolves D-W6-1): the authoritative plan snapshot.
379
+ *
380
+ * `run/events` is an append-only log of markers — step titles, failure reasons
381
+ * and timings never appear there. This projection reads the engine's own rows,
382
+ * so each step exists once with its current facts. Internal participant ids are
383
+ * deliberately not on the wire: member attribution is `role` + `model`.
384
+ */
385
+ export interface RunPlan {
386
+ run_id: string;
387
+ status: RunStatus;
388
+ version: number;
389
+ steps: RunPlanStep[];
390
+ dependencies: RunPlanDependency[];
391
+ }
392
+ export interface RunPlanStep {
393
+ step_id: string;
394
+ title: string;
395
+ kind: string;
396
+ status: string;
397
+ role?: string | null;
398
+ model?: string | null;
399
+ introduced_in_revision: number;
400
+ superseded_in_revision?: number | null;
401
+ created_at: number;
402
+ updated_at: number;
403
+ attempts: RunPlanAttempt[];
404
+ }
405
+ export interface RunPlanAttempt {
406
+ attempt_id: string;
407
+ attempt_no: number;
408
+ status: string;
409
+ trigger_reason: string;
410
+ role?: string | null;
411
+ model?: string | null;
412
+ question?: string | null;
413
+ error?: string | null;
414
+ output_summary?: string | null;
415
+ output_files: string[];
416
+ tokens?: number | null;
417
+ started_at?: number | null;
418
+ finished_at?: number | null;
419
+ }
420
+ export interface RunPlanDependency {
421
+ blocker_step_id: string;
422
+ blocked_step_id: string;
423
+ }
230
424
  export interface ProviderWithModel {
231
425
  provider_id: string;
232
426
  model: string;
@@ -248,6 +442,15 @@ export interface ConversationModelOption {
248
442
  name: string;
249
443
  display_name?: string | null;
250
444
  context_limit?: number | null;
445
+ /**
446
+ * models.dev catalog facts (W9 / R14). All of them are **absent** when the
447
+ * registry has no entry for this provider+model (an unmapped platform, e.g.
448
+ * the bundled `mimo`, never gets zeros standing in for "unknown").
449
+ */
450
+ cost_input?: number | null;
451
+ cost_output?: number | null;
452
+ catalog_context_window?: number | null;
453
+ supports_vision?: boolean | null;
251
454
  }
252
455
  export interface ProviderModelOption {
253
456
  name: string;
@@ -280,9 +483,33 @@ export interface ContextUsage {
280
483
  used_tokens: number;
281
484
  window_tokens: number;
282
485
  percent?: number | null;
486
+ /**
487
+ * W9 / R14 ③: the runtime's own per-turn report for the **most recent**
488
+ * completed turn, persisted server-side next to the gauge so a reloaded
489
+ * WebUI can still show last turn's tokens (and catalog-priced cost). Both are
490
+ * absent when the runtime reported nothing — never a `0` standing in for
491
+ * "unknown". They are not occupancy: `used_tokens` is the last request's
492
+ * prompt size and must never be substituted for these.
493
+ */
494
+ last_turn_input_tokens?: number | null;
495
+ last_turn_output_tokens?: number | null;
283
496
  updated_at: number;
284
497
  source: "measured" | string;
285
498
  }
499
+ /**
500
+ * Per-turn token accounting for ONE completed turn (W9 / R14, additive).
501
+ *
502
+ * The runtime's own report (`TurnCompleted`), projected onto the conversation
503
+ * event stream — field names mirror the Run-side `TurnUsage` so one vocabulary
504
+ * covers both surfaces. It is **not** context occupancy: `ContextUsage` is a
505
+ * gauge (the last request's prompt size) and cannot express what one turn
506
+ * cost. Absent whenever the runtime reported nothing (never a zero).
507
+ */
508
+ export interface TurnUsage {
509
+ input_tokens: number;
510
+ output_tokens: number;
511
+ total_tokens: number;
512
+ }
286
513
  export interface ConversationDeleteResult {
287
514
  conversation_id: string;
288
515
  deleted: boolean;
@@ -310,10 +537,20 @@ export interface ConversationSendReceipt {
310
537
  result_error_code?: string | null;
311
538
  result_error_retryable?: boolean | null;
312
539
  }
540
+ /**
541
+ * The closed set of conversation event kinds the server emits (`05` §4).
542
+ *
543
+ * Deliberately **not** widened with `| string` (doc `16` R1): the escape made
544
+ * the union collapse to `string`, so exhaustive switches and type guards were
545
+ * impossible. A newer server kind reaches a client as a runtime value the
546
+ * switch does not match — which is handled by falling through — and widening
547
+ * this union is the deliberate, reviewable act that changes the contract.
548
+ */
549
+ export type ConversationEventType = "message.created" | "message.delta" | "message.thinking" | "message.tips" | "message.tool" | "message.error" | "message.activity" | "turn.status" | "context.usage";
313
550
  export interface ConversationEvent {
314
551
  conversation_id: string;
315
552
  sequence: number;
316
- event_type: "message.created" | "message.delta" | "message.thinking" | "message.tips" | "message.tool" | "message.error" | "message.activity" | "turn.status" | "context.usage" | string;
553
+ event_type: ConversationEventType;
317
554
  payload: Record<string, unknown>;
318
555
  }
319
556
  export interface ConversationMessagesQuery {
@@ -337,16 +574,253 @@ export interface SkillSummary {
337
574
  description?: string | null;
338
575
  version: string;
339
576
  source: string;
577
+ /**
578
+ * Finer on-disk owner than `source` (`16` R17 / W12): `user` | `shared` |
579
+ * `companion` | `draft` | `marketplace` | `builtin` | `unmanaged`.
580
+ *
581
+ * `source` alone cannot separate a user skill from an installed marketplace
582
+ * product — both are `custom`.
583
+ *
584
+ * Optional so a client compiled against this package keeps working against a
585
+ * host that predates the field; treat a missing value as "unknown owner".
586
+ */
587
+ origin?: SkillOrigin;
588
+ /**
589
+ * Whether the store's write face accepts `skill/update` / `skill/delete` for
590
+ * this skill — `origin === "user"` and the directory is the canonical one.
591
+ * Absent means unknown, so a UI must not offer a write action on `!== true`.
592
+ */
593
+ writable?: boolean;
340
594
  compatibility_status: CompatibilityStatus;
341
595
  enabled: boolean;
342
596
  required_connectors: string[];
597
+ /**
598
+ * Marketplace icon URL for a product installed from a marketplace
599
+ * (`/api/app-server/store/{mkt}/entries/{entry}/assets/…`). Absent for
600
+ * builtin / user skills and for markets that ship no icon for the entry.
601
+ */
602
+ avatar_url?: string | null;
343
603
  }
604
+ /** Where a skill lives on the host (`SkillSummary.origin`). */
605
+ export type SkillOrigin = "user" | "shared" | "companion" | "draft" | "marketplace" | "builtin" | "unmanaged";
344
606
  /** `skill/get` — the raw SKILL.md body and internal routing rules are never returned. */
345
607
  export interface SkillDetail extends SkillSummary {
346
608
  mode: string;
347
609
  invocation_policy: string;
348
610
  instructions_summary?: string | null;
349
611
  }
612
+ /**
613
+ * One file inside a Skill directory (`skill/files`, doc 24 §4).
614
+ *
615
+ * A Skill is a *directory*, not a single document: `SKILL.md` plus whatever it
616
+ * ships alongside (`references/`, `scripts/`, `templates/`, `assets/`).
617
+ * `SkillDetail.instructions_summary` is a bounded summary of the manifest, so
618
+ * this is the read face for everything else.
619
+ */
620
+ export interface SkillFile {
621
+ /** Path relative to the skill directory, POSIX-separated. */
622
+ path: string;
623
+ size: number;
624
+ /** Single-file sha256, lowercase hex. */
625
+ digest: string;
626
+ }
627
+ /** `skill/files` response: the readable inventory of one Skill. */
628
+ export interface SkillFileList {
629
+ skill_id: string;
630
+ /** Sorted by `path`; directories are not listed. */
631
+ files: SkillFile[];
632
+ /**
633
+ * Tree digest of **this skill directory** (sorted relative paths + per-file
634
+ * sha256).
635
+ *
636
+ * Deliberately **not** the snapshot's `content_digest`, which covers the
637
+ * whole imported source tree — the two coincide only when the snapshot holds
638
+ * exactly this directory. Do not compare it against `import/get`.
639
+ */
640
+ content_digest: string;
641
+ /** The inventory hit its ceiling and is incomplete. Never silently truncated. */
642
+ truncated: boolean;
643
+ }
644
+ /**
645
+ * `skill/file` over the WebSocket binding: base64, because JSON has no byte
646
+ * string. The HTTP binding returns the raw body with a `content-type` header
647
+ * instead, so prefer `skills.readFile()` (HTTP) for bytes and this shape only
648
+ * when you are already on a socket.
649
+ */
650
+ export interface SkillFileContent {
651
+ skill_id: string;
652
+ path: string;
653
+ content_type: string;
654
+ encoding: "base64";
655
+ content: string;
656
+ }
657
+ /**
658
+ * Request/response shapes for the six **host-management** methods:
659
+ * `config/get` · `config/set` · `skill/create` · `skill/update` ·
660
+ * `skill/delete` · `skill/copy`.
661
+ *
662
+ * They live here because this package is the wire contract's single source of
663
+ * truth: a method that exists on the wire has a shape, and a caller that speaks
664
+ * the protocol must be able to type-check it whether or not the curated
665
+ * `@flowy-agent-store/client` surface offers a typed method for it. Declaring
666
+ * them only in the host app made that claim false.
667
+ *
668
+ * **Not a stability promise.** These are the most volatile methods in the
669
+ * protocol — they track the host's own settings file and its skills directory,
670
+ * and are the ones most likely to change without notice during beta
671
+ * (`16` D10=A: no backward-compatibility promise before release). The client
672
+ * package deliberately ships no typed method for them; call them through
673
+ * `transport.request` if you need them, and expect churn. What is *not* the
674
+ * reason is access control: `transport` is public and the server enforces
675
+ * writability from the on-disk origin, so this is a discoverability boundary,
676
+ * not a security one.
677
+ */
678
+ /**
679
+ * `config/get` / `config/set` view of the host's `~/.agent-store/config.toml`.
680
+ *
681
+ * Never carries a credential: the file's `[providers.<name>]` tables hold
682
+ * `api_key` or `base_url`, so a credential cannot reach the front end through
683
+ * this face.
684
+ */
685
+ export interface AgentStoreConfigView {
686
+ /** The file exists on this host; a save creates it when it does not. */
687
+ exists: boolean;
688
+ /** Declared `default_model`; explicit `null` = none declared in the file. */
689
+ default_model: string | null;
690
+ /** `[providers.<name>]` tables as declared in the file. */
691
+ providers: AgentStoreConfigProvider[];
692
+ /**
693
+ * `[memory]` table; `null` when the file declares no such table (so the UI
694
+ * can say "not configured" instead of inventing "off"). Additive field.
695
+ */
696
+ memory: AgentStoreConfigMemory | null;
697
+ /**
698
+ * `~/.agent-store/mcp.json` as the host read it (`20` §7.9 / `21` D14);
699
+ * `null` when there is no readable declaration file. Additive field, and the
700
+ * **only** read surface for a declared server: declarations never become
701
+ * `mcp_servers` rows, so they do not appear in `connector/*`.
702
+ */
703
+ mcp: AgentStoreConfigMcp | null;
704
+ }
705
+ /** One `mcpServers` entry the host accepted. */
706
+ export interface AgentStoreConfigMcpServer {
707
+ /** The `mcpServers` key — the `<server>` segment of `mcp__<server>__*`. */
708
+ name: string;
709
+ /** `stdio` | `http` | `sse` (already a label, not a discriminant to branch on). */
710
+ transport: string;
711
+ /** `enabled = false` keeps the entry declared but out of every session. */
712
+ enabled: boolean;
713
+ }
714
+ /** One `mcpServers` entry the host refused, and why. */
715
+ export interface AgentStoreConfigMcpRejection {
716
+ name: string;
717
+ /**
718
+ * The parser's own reason ("a field on the wrong transport", "an out-of-range
719
+ * timeout", …). Server-authored prose, not an i18n key.
720
+ */
721
+ reason: string;
722
+ }
723
+ /**
724
+ * The `mcp.json` projection. A refusal is reported rather than silently
725
+ * dropped: an ignored `enabledTools` would leave tools the user believes
726
+ * excluded still callable, which is why this half has to be visible.
727
+ */
728
+ export interface AgentStoreConfigMcp {
729
+ /**
730
+ * The declaration file is present. The host only sends this view after it has
731
+ * read the file, so this is `true` whenever `mcp` is not `null`; it is kept
732
+ * because the wire carries it, not because a branch should read it.
733
+ */
734
+ exists: boolean;
735
+ /**
736
+ * Whether **this host** feeds the file into agent sessions, as the launcher
737
+ * reported at startup. Omitted when the host did not say — `undefined` is
738
+ * "cannot tell", which is not the same answer as `false` ("this host does not
739
+ * read the file"). `servers` describes the file; this describes the host.
740
+ */
741
+ adopted?: boolean;
742
+ /** Accepted entries, ordered by server key. Never a credential value. */
743
+ servers: AgentStoreConfigMcpServer[];
744
+ /** Refused entries, with the reason the user has to fix. */
745
+ rejected: AgentStoreConfigMcpRejection[];
746
+ /**
747
+ * Why the **whole file** could not be read as declarations (invalid JSON,
748
+ * wrong top level). Omitted when the file parsed — without it a broken file
749
+ * and an empty one look the same.
750
+ */
751
+ error?: string;
752
+ }
753
+ /**
754
+ * `config/get-mcp`: the declaration file's own text, for the file editor.
755
+ *
756
+ * The only read that returns a declaration's values, and the reason it is a
757
+ * method of its own: `AgentStoreConfigMcp` describes the file's *verdict* and
758
+ * carries no `env` / `headers` value, while an editor cannot edit what it
759
+ * cannot see. `exists: false` (with `source` absent) is the normal answer for a
760
+ * host that never created the file; the text comes back whether or not it
761
+ * parses, because editing a broken file is the point.
762
+ */
763
+ export interface McpSourceView {
764
+ exists: boolean;
765
+ source?: string | null;
766
+ }
767
+ /** `[memory]` in the host settings file, as far as the wire exposes it. */
768
+ export interface AgentStoreConfigMemory {
769
+ /** `null` = the table exists without the key (upstream default applies). */
770
+ distill_enabled: boolean | null;
771
+ }
772
+ /** One provider table from the file (never a registered-provider row). */
773
+ export interface AgentStoreConfigProvider {
774
+ /** `[providers.<name>]` key — the left half of a `default_model`. */
775
+ name: string;
776
+ /** `enabled = false` in the file; `true` when the key is absent. */
777
+ enabled: boolean;
778
+ /** Model names declared for this provider in the file. */
779
+ models: string[];
780
+ }
781
+ /** The only keys `config/set` accepts (the server rejects anything else). */
782
+ export interface AgentStoreConfigPatch {
783
+ default_model?: string;
784
+ /** Writes `[memory] distill_enabled` — the switch the host reads at startup. */
785
+ memory?: {
786
+ distill_enabled: boolean;
787
+ };
788
+ }
789
+ /** `skill/create` — structured fields; the server assembles the frontmatter. */
790
+ export interface SkillCreateInput {
791
+ /** Becomes the skill's public id and its directory name. */
792
+ name: string;
793
+ description: string;
794
+ when_to_use?: string;
795
+ allowed_tools?: string;
796
+ paths?: string;
797
+ body?: string;
798
+ }
799
+ /**
800
+ * `skill/update` — a field-level patch.
801
+ *
802
+ * An absent field is left alone (`undefined`, not empty string). An **empty
803
+ * string** on one of the optional keys clears that key; `description` may not
804
+ * be emptied (`invalid_request`). There is deliberately no `name`.
805
+ */
806
+ export interface SkillUpdateInput {
807
+ skill_id: string;
808
+ description?: string;
809
+ when_to_use?: string;
810
+ allowed_tools?: string;
811
+ paths?: string;
812
+ /** Replaces the body wholesale — the read face never returned it, so an edit
813
+ * can only ever *replace* prose, not append to what it never saw. */
814
+ body?: string;
815
+ }
816
+ /** `skill/delete` — what the id resolves to after the delete. */
817
+ export interface SkillDeleteResult {
818
+ skill_id: string;
819
+ deleted: boolean;
820
+ /** Present when the id now resolves to another origin (e.g. a built-in the
821
+ * user skill was shadowing); absent when nothing is visible there any more. */
822
+ revealed_origin?: SkillOrigin | null;
823
+ }
350
824
  export type ConnectorStatus = "installed" | "configured" | "authorization_required" | "authenticated" | "connected" | "degraded" | "error" | "reauthorization_required";
351
825
  export interface ConnectorSummary {
352
826
  id: string;
@@ -357,6 +831,11 @@ export interface ConnectorSummary {
357
831
  auth_mode: string;
358
832
  enabled: boolean;
359
833
  status: ConnectorStatus;
834
+ /**
835
+ * Marketplace icon URL for a connector installed from a marketplace.
836
+ * Absent for builtin hosts and for markets that ship no icon for the entry.
837
+ */
838
+ avatar_url?: string | null;
360
839
  }
361
840
  export interface ConnectorTool {
362
841
  name: string;
@@ -392,6 +871,28 @@ export interface OAuthStartResult {
392
871
  state: "started" | "error" | string;
393
872
  error?: string | null;
394
873
  }
874
+ /**
875
+ * `connector/call` (doc 24 §5.2): the result of running one MCP tool on the
876
+ * host's connection.
877
+ *
878
+ * **A tool-level failure is a result, not a rejection.** When the server
879
+ * answers `isError: true` the promise still resolves, with `is_error` set; only
880
+ * transport, protocol and budget failures reject. Branch on `is_error` to tell
881
+ * "the tool said no" from "we never reached the tool".
882
+ */
883
+ export interface ConnectorCallResult {
884
+ /** The upstream `isError` flag (`false` when the server omitted it). */
885
+ is_error: boolean;
886
+ /**
887
+ * The upstream `tools/call` result object, **verbatim** — `content`,
888
+ * `structuredContent` and anything a newer server adds all survive.
889
+ *
890
+ * It carries no transport, header or env value: the connection and its
891
+ * credentials stay on the host. That is the whole point of a proxy — there is
892
+ * deliberately no `connector/export`.
893
+ */
894
+ result: unknown;
895
+ }
395
896
  export interface ConnectorQueryParams {
396
897
  connector_id: string;
397
898
  }
@@ -472,10 +973,39 @@ export interface InstallResult {
472
973
  skipped: string[];
473
974
  warnings: string[];
474
975
  errors: string[];
976
+ /** Per-component outcome. Absent on a host that predates the field — read a
977
+ * missing value as "no detail available", never as "nothing ran". */
978
+ outcomes?: InstallOutcome[];
475
979
  }
476
980
  export interface InstallStatus {
477
981
  snapshot_id: string;
478
982
  components: InstallComponent[];
983
+ /** Per-component outcome of the mutation that produced this projection
984
+ * (`install/uninstall` · `install/disable` · `install/enable`). Empty for a
985
+ * plain `install/status` read. */
986
+ outcomes?: InstallOutcome[];
987
+ /** Human-readable failures; mirrors the `ok: false` outcomes. */
988
+ errors?: string[];
989
+ }
990
+ /** What happened to one component during install / uninstall / enable / disable. */
991
+ export type InstallOutcomeAction = "created" | "reused" | "enabled" | "disabled" |
992
+ /** The flag moved but the runtime did not: the documented `skill` case, since
993
+ * the skill corpus has no enable state (docs `05` §4.5). */
994
+ "marked" | "removed" | "skipped" | "failed";
995
+ export interface InstallOutcome {
996
+ component_id: string;
997
+ kind: string;
998
+ action: InstallOutcomeAction;
999
+ /** `false` only when the requested state was **not** reached. A component
1000
+ * that was already in the requested state reports `ok: true`. */
1001
+ ok: boolean;
1002
+ /**
1003
+ * Stable, documented token to branch on (`docs/agent-store/05` §4.5 lists the
1004
+ * closed set). `message` is for humans and nothing parses it — the field
1005
+ * exists so callers never have to match on prose.
1006
+ */
1007
+ code?: string;
1008
+ message?: string;
479
1009
  }
480
1010
  /** Wire-marketplace source kinds (Phase A: directory only). */
481
1011
  export type MarketplaceSourceKind = "directory" | "github" | "git" | "url";
@@ -495,6 +1025,26 @@ export interface MarketplaceSummary {
495
1025
  enabled: boolean;
496
1026
  entry_count: number;
497
1027
  added_at: number;
1028
+ /** Last resolved source revision (git commit / freshness marker). */
1029
+ resolved_revision?: string | null;
1030
+ /** Last successful freshness check (epoch ms); absent before the first one. */
1031
+ last_checked_at?: number | null;
1032
+ }
1033
+ /**
1034
+ * Snapshot produced by importing this entry, plus how much of it is installed.
1035
+ * Projected server-side on `market/get` (doc 16 D-W13-1 ①) so a client never
1036
+ * has to re-derive "what would a cascade removal take out?" from the store
1037
+ * listing.
1038
+ */
1039
+ export interface MarketplaceEntrySnapshot {
1040
+ snapshot_id: string;
1041
+ name: string;
1042
+ version: string;
1043
+ status: string;
1044
+ component_count: number;
1045
+ /** Components installed from this snapshot; `0` = imported but untouched. */
1046
+ installed_count: number;
1047
+ imported_at: number;
498
1048
  }
499
1049
  export interface MarketplaceEntry {
500
1050
  name: string;
@@ -504,6 +1054,25 @@ export interface MarketplaceEntry {
504
1054
  description?: string | null;
505
1055
  keywords: string[];
506
1056
  category?: string | null;
1057
+ /**
1058
+ * Localized `<field>_<lang>` variants carried verbatim by the market
1059
+ * manifest (`description_zh`, `name_en`, `tags_zh`, `legacy_tags_en`, …).
1060
+ * Resolve with `pickEntryText` / `pickEntryTags` (doc 18 §4, D8=A);
1061
+ * absent when the manifest declared none.
1062
+ */
1063
+ localized?: Record<string, string | string[]> | null;
1064
+ /**
1065
+ * The entry's declared `strict` (doc `02` §8): `true` requires the plugin
1066
+ * source to carry its own `.codebuddy-plugin/plugin.json`. Absent = `false`.
1067
+ */
1068
+ strict?: boolean | null;
1069
+ /**
1070
+ * Why this entry cannot be imported (doc `02` §11.1), when discovery can
1071
+ * already tell. Present = listed for transparency, but not installable.
1072
+ */
1073
+ blocked_reason?: string | null;
1074
+ /** Absent until the entry has been imported from this marketplace. */
1075
+ snapshot?: MarketplaceEntrySnapshot | null;
507
1076
  }
508
1077
  export interface MarketplaceDetail extends MarketplaceSummary {
509
1078
  entries: MarketplaceEntry[];
@@ -542,6 +1111,15 @@ export interface StoreItem {
542
1111
  * Treat as optional: `item.tags ?? []`. */
543
1112
  tags?: LocalizedText[] | null;
544
1113
  quick_prompts?: LocalizedText[] | null;
1114
+ /**
1115
+ * The market entry's own `publishedAt`, `YYYY-MM-DD` (`18` §3).
1116
+ *
1117
+ * A calendar date the *market* declared, never a derived one: absent means
1118
+ * "this market declares no date", so a client must not substitute the import
1119
+ * time or render a placeholder. The host normalizes it on the way in and
1120
+ * drops anything that is not a calendar date.
1121
+ */
1122
+ published_at?: string | null;
545
1123
  /** Public avatar URL (store asset endpoint); relative to the API host. */
546
1124
  avatar_url?: string | null;
547
1125
  version: string;
@@ -550,9 +1128,18 @@ export interface StoreItem {
550
1128
  update_available: boolean;
551
1129
  snapshot_id?: string | null;
552
1130
  installed_version?: string | null;
1131
+ /**
1132
+ * Why this item cannot be installed (doc `02` §11.1), re-derived from the
1133
+ * live source tree. Present = the item stays listed so the reason can be
1134
+ * read, but install must not be offered.
1135
+ */
1136
+ blocked_reason?: string | null;
553
1137
  }
554
1138
  export interface StoreList {
555
1139
  items: StoreItem[];
1140
+ /** `true` while the builtin default marketplaces are still registering in the
1141
+ * background (D-SDK-1 ①) — the catalog may be incomplete. */
1142
+ markets_pending?: boolean;
556
1143
  }
557
1144
  /** One model in the public catalog (`models/list`, REQ-PAR-05b). Provider
558
1145
  * credentials, base URLs and health internals never cross this projection. */
@@ -579,6 +1166,10 @@ export interface StoreInstallResult {
579
1166
  installed_count: number;
580
1167
  warnings: string[];
581
1168
  errors: string[];
1169
+ /** Per-component outcome, forwarded from the installer's own report so a
1170
+ * store install is as branchable as a direct `install/run`. Absent on a host
1171
+ * that predates the field. */
1172
+ outcomes?: InstallOutcome[];
582
1173
  }
583
1174
  export interface LocalizedText {
584
1175
  en?: string | null;
@@ -633,6 +1224,12 @@ export interface TeamDetail extends TeamSummary {
633
1224
  routing_constraints: string[];
634
1225
  workflow_limits: Record<string, unknown>;
635
1226
  team_runtime_capabilities: string[];
1227
+ /**
1228
+ * MCP server ids this Team's own snapshot installed **and** left enabled on
1229
+ * this host — the only Connector surface a Team Run may bind. Member Agents'
1230
+ * `mcpServers` are recorded, not mapped to grants (docs/agent-store/02 §5.1).
1231
+ */
1232
+ connectors: string[];
636
1233
  }
637
1234
  //#endregion
638
1235
  //#region src/errors.d.ts
@@ -678,4 +1275,179 @@ export declare function formatError(error: unknown): string;
678
1275
  /** Whether the caught error is retryable — used by UI surfaces to append a
679
1276
  * localized "(可重试)" hint without baking wording into this lib. */
680
1277
  export declare function isRetryableError(error: unknown): boolean;
1278
+ //#endregion
1279
+ //#region src/localized.d.ts
1280
+ /** The two languages the store ships display text for. */
1281
+ export type LocalizedLang = "zh" | "en";
1282
+ /** The other language, i.e. the second link of every fallback chain. */
1283
+ export declare function otherLocalizedLang(lang: LocalizedLang): LocalizedLang;
1284
+ /**
1285
+ * Map an i18n language tag (`zh-CN`, `en-US`, `en`) to a variant language.
1286
+ * Anything that is not English resolves to `zh`, matching the app default.
1287
+ */
1288
+ export declare function toLocalizedLang(language: string | null | undefined): LocalizedLang;
1289
+ /** `{field}_{lang}` → `{field}_{other}` → `""`. */
1290
+ export declare function pickLocalized(text: LocalizedText | null | undefined, lang: LocalizedLang): string;
1291
+ /** Resolve a list of localized values, dropping the ones that resolve empty. */
1292
+ export declare function pickLocalizedList(list: readonly (LocalizedText | null | undefined)[] | null | undefined, lang: LocalizedLang): string[];
1293
+ /**
1294
+ * The `<field>_<lang>` variants of one `market/get` entry.
1295
+ *
1296
+ * Values are either a string (`description_zh`) or a list of strings
1297
+ * (`tags_en`, `legacy_tags_zh`, `examples_zh`).
1298
+ */
1299
+ export type LocalizedVariants = Readonly<Record<string, string | readonly string[] | null | undefined>> | null | undefined;
1300
+ /** `{field}_{lang}` → `{field}_{other}` as a string, when either carries text. */
1301
+ export declare function pickVariantText(variants: LocalizedVariants, field: string, lang: LocalizedLang): string | undefined;
1302
+ /** `{field}_{lang}` → `{field}_{other}` as a string list, when either is non-empty. */
1303
+ export declare function pickVariantList(variants: LocalizedVariants, field: string, lang: LocalizedLang): string[] | undefined;
1304
+ /**
1305
+ * Market entry tags. Per D8=A `tags_{lang}` outranks `legacy_tags_{lang}` —
1306
+ * the legacy field only fills in when the current one is absent, in either
1307
+ * language.
1308
+ */
1309
+ export declare function pickEntryTags(variants: LocalizedVariants, lang: LocalizedLang): string[] | undefined;
1310
+ /**
1311
+ * A market entry text field: variant first, then the baseline field
1312
+ * (`entry.description`, `entry.name`, …) as the last link of the chain.
1313
+ */
1314
+ export declare function pickEntryText(variants: LocalizedVariants, field: string, lang: LocalizedLang, baseline?: string | null): string | undefined;
1315
+ //#endregion
1316
+ //#region src/conversation-events.d.ts
1317
+ export type ThinkingData = {
1318
+ content: string;
1319
+ subject: string | null;
1320
+ status: string | null;
1321
+ duration: number | null;
1322
+ };
1323
+ export type TipsData = {
1324
+ content: string;
1325
+ tipType: string;
1326
+ };
1327
+ export type ToolCallData = {
1328
+ name: string | null;
1329
+ args?: unknown;
1330
+ output?: unknown;
1331
+ status: string | null;
1332
+ };
1333
+ /**
1334
+ * One plan step's status.
1335
+ *
1336
+ * The producer declares exactly these three (`nomi-tools/src/update_plan.rs`
1337
+ * `StepStatus`), and its doc comment names the frontend as the reason for the
1338
+ * snake_case spelling — so this mirrors a stated contract rather than guessing
1339
+ * at a vocabulary.
1340
+ */
1341
+ export type PlanStepStatus = "pending" | "in_progress" | "completed";
1342
+ export type PlanEntry = {
1343
+ content: string;
1344
+ status: PlanStepStatus;
1345
+ };
1346
+ /** Shape a plan body (`message_type` `plan`): the step list. */
1347
+ export type PlanData = {
1348
+ entries: PlanEntry[];
1349
+ };
1350
+ export declare function stringValue(value: unknown): string | null;
1351
+ export declare function numberValue(value: unknown): number | null;
1352
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
1353
+ /**
1354
+ * Strict boolean reader: a wire value that is not a real boolean reads as
1355
+ * `null` (unknown) rather than being coerced — "retryable" must never be
1356
+ * guessed from a truthy string.
1357
+ */
1358
+ export declare function booleanValue(value: unknown): boolean | null;
1359
+ /** Flatten a message/activity body to plain text, JSON-encoding anything else. */
1360
+ export declare function contentToText(value: unknown): string;
1361
+ /** Shape a thinking body (`message.thinking`, or `message.activity` kind `thinking`). */
1362
+ export declare function thinkingData(value: unknown): ThinkingData;
1363
+ /** Shape a tips body; `type` is accepted as an alias of `tip_type`. */
1364
+ export declare function tipsData(value: unknown): TipsData;
1365
+ /** Shape a tool-call body; `null` when the body carries nothing tool-shaped. */
1366
+ export declare function toolCallData(value: unknown): ToolCallData | null;
1367
+ /**
1368
+ * Shape a plan body; `null` when there is no usable step list.
1369
+ *
1370
+ * `step` is accepted as an alias of `content`: the tool's own argument is named
1371
+ * `step`, and the producer normalizes it to `content` for the plan row — a row
1372
+ * written before that normalization (or by a producer that skipped it) would
1373
+ * otherwise render as an empty plan.
1374
+ */
1375
+ export declare function planData(value: unknown): PlanData | null;
1376
+ /** Shape a `context.usage` payload; `null` when nothing was actually measured. */
1377
+ export declare function parseContextUsage(value: unknown): ContextUsage | null;
1378
+ /**
1379
+ * Shape a per-turn `usage` body (W9 / R14); `null` when the runtime reported
1380
+ * nothing usable.
1381
+ *
1382
+ * A missing side is **unknown**, not zero: half a bill is not a bill, so a
1383
+ * frame that only carries `input_tokens` decodes to `null` rather than to a
1384
+ * total that silently drops the output side. Same rule for an all-zero report —
1385
+ * "no tokens reported" must never render as "this turn was free".
1386
+ */
1387
+ export declare function parseTurnUsage(value: unknown): TurnUsage | null;
1388
+ /**
1389
+ * One decoded conversation event, discriminated by `kind`.
1390
+ *
1391
+ * `messageId` is the id the *server* supplied (`null` when it sent none) — a
1392
+ * consumer that needs a stable row for an id-less event applies its own
1393
+ * default, because that default is presentation, not protocol.
1394
+ *
1395
+ * `unknown` is the honest fall-through for a newer server kind: the raw
1396
+ * `eventType` is preserved so a caller can log or forward it instead of
1397
+ * silently dropping the frame.
1398
+ */
1399
+ type MessageFrameBase = {
1400
+ event: ConversationEvent;
1401
+ /** The id the server supplied; `null` when it sent none (consumer picks a default row). */
1402
+ messageId: string | null;
1403
+ /** Server timestamp when the payload carried one. */
1404
+ createdAt: number | null;
1405
+ };
1406
+ export type DecodedConversationEvent = ({
1407
+ kind: "message.created";
1408
+ content: unknown;
1409
+ } & MessageFrameBase) | ({
1410
+ kind: "message.delta";
1411
+ delta: string;
1412
+ replace: boolean;
1413
+ } & MessageFrameBase) | ({
1414
+ kind: "message.thinking";
1415
+ thinking: ThinkingData;
1416
+ replace: boolean;
1417
+ } & MessageFrameBase) | ({
1418
+ kind: "message.tool";
1419
+ tool: ToolCallData | null;
1420
+ } & MessageFrameBase) | ({
1421
+ kind: "message.tips";
1422
+ tips: TipsData;
1423
+ } & MessageFrameBase) | ({
1424
+ kind: "message.error";
1425
+ message: string | null;
1426
+ code: string | null;
1427
+ retryable: boolean | null;
1428
+ } & MessageFrameBase) | ({
1429
+ kind: "message.activity";
1430
+ activityKind: string;
1431
+ content: unknown;
1432
+ usage: TurnUsage | null;
1433
+ } & MessageFrameBase) | {
1434
+ kind: "turn.status";
1435
+ event: ConversationEvent;
1436
+ running: boolean;
1437
+ } | {
1438
+ kind: "context.usage";
1439
+ event: ConversationEvent;
1440
+ usage: ContextUsage | null;
1441
+ } | {
1442
+ kind: "unknown";
1443
+ event: ConversationEvent;
1444
+ eventType: string;
1445
+ };
1446
+ /**
1447
+ * Decode one `conversation/event` frame into a typed body.
1448
+ *
1449
+ * An activity frame whose `kind` is `thinking` is normalized to
1450
+ * `message.thinking` (the server has two spellings for the same thing).
1451
+ */
1452
+ export declare function decodeConversationEvent(event: ConversationEvent): DecodedConversationEvent;
681
1453
  //#endregion