@flowy-agent-store/client 0.1.0-beta.3 → 0.1.0-beta.5

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
@@ -1,4 +1,4 @@
1
- import { AgentDetail, AgentRunInput, AgentSummary, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorDetail, ConnectorProbeResult, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ImportDetail, ImportRequest, ImportResult, ImportSummary, InitializeResult, InstallRequest, InstallResult, InstallStatus, MarketplaceAddRequest, MarketplaceDetail, MarketplaceRefreshResult, MarketplaceRemoveResult, MarketplaceSummary, ModelSummary, OAuthStartResult, OAuthStatusView, RunEvent, RunEventsQuery, RunReceipt, RunResult, RunStatus, RunView, ServerNotification, SkillDetail, SkillSummary, SteerRunInput, StoreInstallResult, StoreList, TeamDetail, TeamSummary, WorkspaceRevokeResult, WorkspaceView } from "@flowy-agent-store/protocol";
1
+ import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorDetail, ConnectorProbeResult, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ImportDetail, ImportRequest, ImportResult, ImportSummary, InitializeResult, InstallOutcome, InstallRequest, InstallResult, InstallStatus, MarketplaceAddRequest, MarketplaceDetail, MarketplaceRefreshResult, MarketplaceRemoveResult, MarketplaceSummary, MentionRef, ModelSummary, OAuthStartResult, OAuthStatusView, ProviderWithModel, RunEvent, RunEventsQuery, RunPlan, RunReceipt, RunResult, RunStatus, RunView, ServerNotification, SkillDetail, SkillFileContent, SkillFileList, SkillSummary, SteerRunInput, StoreInstallResult, StoreItem, StoreItemKind, StoreList, TeamDetail, TeamRunInput, TeamRunReceipt, TeamSummary, WorkspaceRevokeResult, WorkspaceView } from "@flowy-agent-store/protocol";
2
2
  //#region src/transport.d.ts
3
3
  export type NotificationListener = (notification: ServerNotification) => void;
4
4
  /**
@@ -82,6 +82,109 @@ export declare class WebSocketTransport implements Transport {
82
82
  private failAllPending;
83
83
  }
84
84
  //#endregion
85
+ //#region src/http-transport.d.ts
86
+ type Verb = "GET" | "POST" | "DELETE";
87
+ interface HttpRoute {
88
+ verb: Verb;
89
+ /** `:name` segments are filled from the same-named param and dropped from the body/query. */
90
+ path: string;
91
+ /** Params carried in the query string (GET, and the query-configured POSTs). */
92
+ query?: readonly string[];
93
+ /** Params carried in the JSON body; every other param goes here for POST. */
94
+ body?: readonly string[];
95
+ /** Where this mapping was verified in the server source. */
96
+ source: string;
97
+ }
98
+ export interface HttpTransportOptions {
99
+ /**
100
+ * App Server base URL. Either the full `…/api/app-server` root or a
101
+ * `ws(s)://…/api/app-server/ws` endpoint (the `/ws` segment is stripped).
102
+ */
103
+ baseUrl: string;
104
+ /** Bearer token for the handshake (optional; hosts may inject auth instead). */
105
+ token?: string;
106
+ /** Injected `fetch` (defaults to the global); tests pass a stub. */
107
+ fetch?: typeof fetch;
108
+ /** Per-call budget covering handshake + business call (default 30s). */
109
+ requestTimeoutMs?: number;
110
+ /** Client name/version announced on `initialize`. */
111
+ client?: {
112
+ name: string;
113
+ version: string;
114
+ };
115
+ /** Client capabilities block echoed on `initialize` (hosts pass their own). */
116
+ capabilities?: unknown;
117
+ }
118
+ export declare class HttpTransport implements Transport {
119
+ private readonly routes;
120
+ private readonly options;
121
+ private closed;
122
+ constructor(options: HttpTransportOptions, routes?: Record<string, HttpRoute>);
123
+ /**
124
+ * No-op: the HTTP binding handshakes per call (`05` §2.1.1), so there is no
125
+ * connection to open. Kept for `Transport` compatibility.
126
+ */
127
+ connect(): Promise<void>;
128
+ /**
129
+ * Perform one business call over its own handshake.
130
+ *
131
+ * Throws `TransportError("send", …)` for a method with no HTTP binding —
132
+ * never a silent fallback to another binding.
133
+ */
134
+ request<T>(method: string, params: unknown): Promise<T>;
135
+ /**
136
+ * Not supported: HTTP carries no server-push. Throws instead of pretending.
137
+ */
138
+ notify(method: string, _params: unknown): void;
139
+ /**
140
+ * Returns a no-op unsubscribe: this binding receives **no** notifications.
141
+ *
142
+ * Not equivalent to the WebSocket binding — callers that need live events
143
+ * must use `WebSocketTransport` (or run both).
144
+ */
145
+ onNotification(_listener: NotificationListener): () => void;
146
+ /** No-op: every call is independent, so nothing is held open. */
147
+ close(): void;
148
+ /**
149
+ * One-shot handshake: `POST /initialize` (connection id header) →
150
+ * `POST /initialized`. Returns the connection id.
151
+ *
152
+ * Public because host-side services that sit *outside* the protocol surface
153
+ * (`/api/fs/*`, the HTTP-only `POST /workspaces` register route) still need a
154
+ * ready connection id; they are not protocol methods, so they cannot go
155
+ * through `request()`.
156
+ */
157
+ openConnection(): Promise<string>;
158
+ private splitParams;
159
+ private fetchWithTimeout;
160
+ /** 2xx → result body; otherwise the server's wire error becomes an `AppServerError`. */
161
+ private readResult;
162
+ private baseUrl;
163
+ }
164
+ /**
165
+ * Turn an HTTP failure body into the SDK error model.
166
+ *
167
+ * Shared by this transport and by host-side helpers (webui `lib/client.ts`) so
168
+ * a second spelling of the wire-error mapping cannot drift. Two wire contracts
169
+ * reach this function, both of them `AppError` on the server:
170
+ *
171
+ * - the App Server emits `{code, message, retryable, details, request_id}`
172
+ * (`into_wire_error`), which is exactly the `AppServerError` shape;
173
+ * - the standalone host file service (`/api/fs/*`) emits the `ErrorResponse`
174
+ * envelope `{success: false, error, code, details?}` — same error, another
175
+ * spelling: `error` carries the human message, and there is no retry hint, so
176
+ * a 5xx status stays the retry signal.
177
+ *
178
+ * Anything else becomes a retryable-on-5xx `TransportError`.
179
+ */
180
+ export declare function appServerErrorFromWire(payload: unknown, status: number, context?: string): unknown;
181
+ /** Exposed for tests and docs (R5): the verified method → route table. */
182
+ export declare function httpRouteTable(): Readonly<Record<string, {
183
+ verb: string;
184
+ path: string;
185
+ source: string;
186
+ }>>;
187
+ //#endregion
85
188
  //#region src/agents.d.ts
86
189
  export declare class AgentClient {
87
190
  private readonly transport;
@@ -93,6 +196,46 @@ export declare class AgentClient {
93
196
  }
94
197
  //#endregion
95
198
  //#region src/conversations.d.ts
199
+ /**
200
+ * Options for `send()`.
201
+ *
202
+ * Two scopes, deliberately different (doc `27` §4.1 and doc `29` §5.1):
203
+ *
204
+ * - **per turn** — `attachments` and `mentions` are this turn's payload, and
205
+ * nothing else changes;
206
+ * - **per conversation, taking effect this turn** — `model` and
207
+ * `reasoningEffort` are *sticky*: they are written to the conversation and
208
+ * every later turn keeps them, because the Nomi runtime is built from the
209
+ * persisted row. The host refuses them while a turn is running (`conflict`).
210
+ *
211
+ * Every field is **additive**: absent means the exact wire shape callers sent
212
+ * before that field existed, and a plain `send()` never writes the conversation.
213
+ */
214
+ export interface ConversationSendOptions {
215
+ /** Absolute paths inside the conversation's workspace (images only). */
216
+ attachments?: string[];
217
+ /**
218
+ * Skills to mount for **this turn** (`kind: "skill"`).
219
+ *
220
+ * The ids are `skill/list`'s ids (the skill's own name, not an
221
+ * `install/status` component id). A turn is the only scope a Skill has here:
222
+ * the conversation's frozen snapshot is create-time and immutable.
223
+ */
224
+ mentions?: MentionRef[];
225
+ /**
226
+ * Model to switch the conversation to **from this turn on** (doc `29` §5.1).
227
+ *
228
+ * Sticky, not per-turn: the Nomi runtime is built from the persisted row, so
229
+ * the value is written to the conversation and every later turn keeps it.
230
+ * Omit it and the conversation's current model stays untouched.
231
+ */
232
+ model?: ProviderWithModel;
233
+ /**
234
+ * Reasoning level to switch the conversation to **from this turn on**
235
+ * (doc `29` §5.1). Same sticky scope as `model`.
236
+ */
237
+ reasoningEffort?: string;
238
+ }
96
239
  export declare class ConversationClient {
97
240
  private readonly transport;
98
241
  constructor(transport: Transport);
@@ -102,39 +245,121 @@ export declare class ConversationClient {
102
245
  list(limit?: number): Promise<ConversationView[]>;
103
246
  get(conversationId: string): Promise<ConversationView>;
104
247
  messages(query: ConversationMessagesQuery): Promise<ConversationMessagesPage>;
105
- send(conversationId: string, content: string, idempotencyKey: string): Promise<ConversationSendReceipt>;
248
+ /**
249
+ * R15(W10):`attachments` 是**会话工作区内的绝对路径**(图片附件)。
250
+ * doc `27` §4.1:`mentions` 是**本轮挂载的技能**(`kind: "skill"`)——技能的正文与
251
+ * 不可变快照都随这一轮走;`agent` / `connector` 在 send 上没有载体,服务端会以
252
+ * `invalid_request` **明确拒绝**(不是静默不挂)。
253
+ *
254
+ * 两者缺省都不上 wire,保持老调用方的形状逐字不变;服务端只接受会话工作区内的真实
255
+ * 文件(越界 / 相对路径 / 不存在一律拒),准入见 `nomifun-app-server` 的
256
+ * `resolve_conversation_attachments` 与 `send_mention_skills`。
257
+ *
258
+ * 第 4 个参数同时接受旧的 `string[]`(附件)与选项对象——再堆一个位置参数会让调用点
259
+ * 变成一串没有名字的布尔/数组。
260
+ */
261
+ send(conversationId: string, content: string, idempotencyKey: string, options?: string[] | ConversationSendOptions): Promise<ConversationSendReceipt>;
106
262
  cancel(conversationId: string): Promise<ConversationView>;
107
263
  delete(conversationId: string): Promise<{
108
264
  conversation_id: string;
109
265
  deleted: boolean;
110
266
  }>;
111
- follow(conversationId: string): Promise<ConversationSubscription>;
267
+ /**
268
+ * Subscribe to one conversation's realtime stream (doc `16` R1).
269
+ *
270
+ * `options.fetchMessages` is what makes auto catch-up possible: a detected
271
+ * sequence gap or a `conversation/resync-required` triggers one transcript
272
+ * fetch whose result is handed to `onBackfill`. Conversations have no event
273
+ * replay API (`05` §「V1 不提供 after_cursor 事件追平」), so the transcript is
274
+ * the recovery medium — unlike Runs, where `run/events` replays the events
275
+ * themselves.
276
+ */
277
+ follow(conversationId: string, options?: ConversationFollowOptions): Promise<ConversationSubscription>;
112
278
  }
113
279
  export type ConversationEventListener = (event: ConversationEvent) => void;
114
280
  export type ConversationResyncListener = (reason: string) => void;
281
+ export type ConversationErrorListener = (error: unknown) => void;
282
+ /** Transcript snapshot handed back after a detected gap / resync (R1). */
283
+ export interface ConversationBackfill {
284
+ conversationId: string;
285
+ /** `gap` or the server's `conversation/resync-required` reason. */
286
+ reason: string;
287
+ /** Latest transcript page, newest last (`conversation/messages`). */
288
+ messages: ConversationMessage[];
289
+ /** Server-computed: an older page still exists. */
290
+ hasMore: boolean;
291
+ }
292
+ export type ConversationBackfillListener = (backfill: ConversationBackfill) => void;
293
+ /** Options for `follow()` — mirrors the Run subscription's `FollowOptions`. */
294
+ export interface ConversationFollowOptions {
295
+ /**
296
+ * Fetch the transcript automatically after a gap / resync (default `true`).
297
+ * Needs `fetchMessages`; when absent only `onResync` fires.
298
+ */
299
+ autoResync?: boolean;
300
+ /** Events held before the first `onEvent` listener attaches (default 256). */
301
+ pendingLimit?: number;
302
+ /** Transcript fetcher; `follow()` wires the real one. */
303
+ fetchMessages?: (conversationId: string) => Promise<ConversationMessagesPage>;
304
+ }
115
305
  export declare class ConversationSubscription {
116
306
  private readonly transport;
117
307
  readonly conversationId: string;
308
+ private readonly options;
118
309
  private events;
119
310
  private resyncs;
311
+ private errors;
312
+ private backfills;
120
313
  private closed;
121
314
  private lastSeenSequence;
122
315
  private removeListener;
123
- constructor(transport: Transport, conversationId: string);
316
+ /**
317
+ * `follow()` resolves as soon as the server acknowledged the subscription,
318
+ * but the caller attaches listeners one tick later. Events landing in that
319
+ * window used to be dropped silently; they are held here (oldest first) and
320
+ * flushed to the first `onEvent` listener.
321
+ */
322
+ private pending;
323
+ private pendingDropped;
324
+ private backfillInFlight;
325
+ constructor(transport: Transport, conversationId: string, options?: ConversationFollowOptions);
124
326
  get lastSequence(): number;
327
+ /** Events dropped because nobody was listening and the buffer was full. */
328
+ get droppedPendingCount(): number;
125
329
  onEvent(listener: ConversationEventListener): () => void;
126
330
  onResync(listener: ConversationResyncListener): () => void;
331
+ /**
332
+ * Auto catch-up result (R1). Fires only when `autoResync` is on and the
333
+ * transcript fetch succeeded; the listener owns replacing the transcript.
334
+ */
335
+ onBackfill(listener: ConversationBackfillListener): () => void;
336
+ /**
337
+ * Failures that used to be swallowed: the catch-up fetch, and a failed
338
+ * re-subscribe during `rearm()`. Listener isolation keeps one bad listener
339
+ * from hiding the error from the others.
340
+ */
341
+ onError(listener: ConversationErrorListener): () => void;
127
342
  close(): Promise<void>;
128
343
  /**
129
344
  * Re-arm after a reconnect (docs/agent-store/16 T8): re-register the
130
345
  * notification listener, reset the cursor and re-issue
131
- * `conversation/subscribe`. This subscription has no event-replay API, so the
132
- * caller must still backfill the transcript for the outage window
133
- * (`conversation/messages`). Call it once the handshake completed; a no-op
134
- * once closed.
346
+ * `conversation/subscribe`. The server restarts a conversation's sequence at
347
+ * 1 for a fresh subscription, which is exactly why the cursor resets. On a
348
+ * failed re-subscribe the error reaches `onError` and the caller can still
349
+ * backfill the outage window via `conversation/messages`.
135
350
  */
136
351
  rearm(): Promise<void>;
137
352
  private dispatch;
353
+ private buffer;
354
+ private deliver;
355
+ private emitResync;
356
+ private emitError;
357
+ /**
358
+ * One in-flight catch-up at a time: a burst of resync signals collapses into
359
+ * a single transcript fetch (the fetch itself returns the newest page, so the
360
+ * result supersedes every signal that arrived before it).
361
+ */
362
+ private requestBackfill;
138
363
  }
139
364
  //#endregion
140
365
  //#region src/connectors.d.ts
@@ -147,7 +372,19 @@ export declare class ConnectorClient {
147
372
  get(connectorId: string): Promise<ConnectorDetail>;
148
373
  /** Combined auth × probe status. `connected` only when auth is ready AND the last probe succeeded. */
149
374
  status(connectorId: string): Promise<ConnectorStatusView>;
150
- /** Run a connection probe; the server persists the result. */
375
+ /**
376
+ * Run a connection probe and report what the connector currently offers —
377
+ * including each tool's `input_schema`.
378
+ *
379
+ * It really connects: a stdio connector gets its child process spawned, an
380
+ * HTTP/SSE one gets `initialize` + `tools/list`. The result is **persisted**,
381
+ * so this is also what refreshes the tool list `get()` reads back.
382
+ *
383
+ * Named `test` and not `probe` on purpose: this repo's split is `test` for the
384
+ * action (wire `connector/test`, `test_connection`) and `probe` for its
385
+ * artifact (`ConnectorProbeResult`, `probe_status`), and every method here is
386
+ * the last segment of its wire method.
387
+ */
151
388
  test(connectorId: string): Promise<ConnectorProbeResult>;
152
389
  /** Check the current OAuth state for a connector. */
153
390
  authStatus(connectorId: string): Promise<OAuthStatusView>;
@@ -158,6 +395,30 @@ export declare class ConnectorClient {
158
395
  authStart(connectorId: string): Promise<OAuthStartResult>;
159
396
  /** Revoke the OAuth token for a connector. */
160
397
  logout(connectorId: string): Promise<void>;
398
+ /**
399
+ * Run one tool on a registered connector, through the host's own connection.
400
+ *
401
+ * The host holds the transport, its headers and its OAuth token; this sends a
402
+ * tool name and an argument object and nothing else — you cannot name a URL,
403
+ * a command or a header. Whether the pair is callable at all is the host's
404
+ * `[connector_proxy]` policy: once its operator turns the proxy on, the
405
+ * enabled connectors are callable, and `allow` / `deny` are that operator's
406
+ * optional narrowing and subtraction. So `policy_denied` means either that
407
+ * the host never turned the proxy on, or that this pair was narrowed out or
408
+ * explicitly denied — not that a tool name was misspelled in a list you were
409
+ * supposed to maintain.
410
+ *
411
+ * Arguments are passed through verbatim (there is no client-side schema
412
+ * check); `get()` / `test()` return each tool's `input_schema` so you can see
413
+ * what it takes, and a parameter the server rejects comes back as
414
+ * `is_error: true`, not as a rejected promise.
415
+ *
416
+ * Resolves even when the tool itself failed — check `is_error`. It rejects
417
+ * only when the call never reached the tool: `connector_call_timeout`,
418
+ * `connector_call_failed`, `response_too_large`, `connector_unavailable`,
419
+ * `policy_denied`, `not_found`.
420
+ */
421
+ call(connectorId: string, tool: string, args?: unknown): Promise<ConnectorCallResult>;
161
422
  }
162
423
  //#endregion
163
424
  //#region src/runs.d.ts
@@ -166,10 +427,26 @@ export declare class RunClient {
166
427
  constructor(transport: Transport);
167
428
  /** Start an Agent run and return its asynchronous receipt. */
168
429
  agent(input: AgentRunInput): Promise<RunReceipt>;
430
+ /**
431
+ * Start a Team run (`16` §7 决策 3): the server creates the Leader
432
+ * Conversation, materializes/binds the Team's execution template and returns
433
+ * the public run id of the execution the Leader started.
434
+ *
435
+ * The receipt is `TeamRunReceipt`, not `RunReceipt` — a Team Run has no lead
436
+ * preset revision/digest to report. Member pool and concurrency limits are not
437
+ * inputs; sending `planning` is rejected as `invalid_request`.
438
+ */
439
+ team(input: TeamRunInput): Promise<TeamRunReceipt>;
169
440
  /** Read the authoritative run state. */
170
441
  get(runId: string): Promise<RunView>;
171
442
  /** Read the terminal result (fails until the run reaches a terminal state). */
172
443
  result(runId: string): Promise<RunResult>;
444
+ /**
445
+ * Read the authoritative plan / step snapshot (W4 / W6, resolves D-W6-1):
446
+ * titles, statuses, retries with their reason, errors and timings — none of
447
+ * which the append-only event log carries.
448
+ */
449
+ plan(runId: string): Promise<RunPlan>;
173
450
  /** Replay persisted events after a cursor. */
174
451
  events(query: RunEventsQuery): Promise<RunEvent[]>;
175
452
  /** Request cancellation; the terminal state is confirmed by events/run/get. */
@@ -179,6 +456,16 @@ export declare class RunClient {
179
456
  * (runtime durable conversation effect; server resolves the opaque step).
180
457
  */
181
458
  steer(input: SteerRunInput): Promise<RunView>;
459
+ /**
460
+ * Answer the pending decision of a `waiting_input` attempt.
461
+ *
462
+ * The three `expected*Version` tokens are mandatory CAS inputs, normally the
463
+ * ones projected on the pending `approval.requested` event. The server passes
464
+ * them straight to the engine's single answer gate, so a stale token is a
465
+ * `conflict`, never a silent overwrite. There is no `always_allow` here: the
466
+ * desktop confirmation route's approve-all flag is not part of this protocol.
467
+ */
468
+ answerDecision(input: AnswerDecisionInput): Promise<RunView>;
182
469
  /** Subscribe to best-effort realtime events for one run. */
183
470
  follow(runId: string, options?: FollowOptions): Promise<EventSubscription>;
184
471
  }
@@ -257,6 +544,197 @@ export declare class SkillClient {
257
544
  list(): Promise<SkillSummary[]>;
258
545
  /** Fetch one Skill detail; instructions are a bounded public summary. */
259
546
  get(skillId: string): Promise<SkillDetail>;
547
+ /**
548
+ * Every readable file inside a Skill's directory, with a tree digest.
549
+ *
550
+ * This is the only way to learn what a Skill actually ships: `get()` returns
551
+ * a bounded summary of `SKILL.md` and says nothing about the `references/`,
552
+ * `scripts/`, `templates/` and `assets/` beside it. Check
553
+ * `capabilities.skill_files` first — a host may wire the catalog without
554
+ * this face, in which case it answers `unsupported_operation`.
555
+ */
556
+ files(skillId: string): Promise<SkillFileList>;
557
+ /**
558
+ * One skill file's bytes, over the WebSocket binding.
559
+ *
560
+ * The wire carries base64 because JSON has no byte string, so this decodes
561
+ * back to `Uint8Array` for you. The HTTP route
562
+ * (`GET /api/app-server/skills/{id}/files/{path}`) returns the raw body
563
+ * instead, but that is deliberately not on the typed client's JSON
564
+ * transport — use this method, or `fetch` the route directly if you are on a
565
+ * host token.
566
+ */
567
+ readFile(skillId: string, path: string): Promise<Uint8Array>;
568
+ /** Same as {@link readFile}, but also returns the server's content type. */
569
+ readFileWithType(skillId: string, path: string): Promise<SkillFileContent>;
570
+ }
571
+ //#endregion
572
+ //#region src/store.d.ts
573
+ /** Why a `waitForReady` check ended without every component being usable. */
574
+ export type StoreReadyIssue =
575
+ /** The host never reported the component ready inside the timeout. */
576
+ "ready_timeout" |
577
+ /** A connector needs the OAuth browser flow; the caller must drive it. */
578
+ "authorization_required";
579
+ /** Branchable client-side failures. Server-side failures arrive as outcomes. */
580
+ export type StoreErrorCode = "not_installed" | "item_blocked" | "aborted";
581
+ export declare class StoreError extends Error {
582
+ readonly code: StoreErrorCode;
583
+ constructor(code: StoreErrorCode, message: string);
584
+ }
585
+ export interface InstallOptions {
586
+ /**
587
+ * Wait until every component is actually usable before resolving.
588
+ *
589
+ * Defaults to `true` because a resolved `install()` that is not yet usable is
590
+ * the single most misleading thing this surface could do: a skill is usable
591
+ * as soon as it is copied, but a connector is not usable until the server has
592
+ * been switched on and probed.
593
+ */
594
+ waitForReady?: boolean;
595
+ /** Readiness budget. Defaults to `StoreClientOptions.readyTimeoutMs`. */
596
+ timeoutMs?: number;
597
+ signal?: AbortSignal;
598
+ }
599
+ export interface UninstallOptions {
600
+ /** Narrow the release to these component ids (default: every installed one). */
601
+ componentIds?: string[];
602
+ }
603
+ export interface SetEnabledOptions {
604
+ /** Narrow the state change to these component ids (default: all installed). */
605
+ componentIds?: string[];
606
+ }
607
+ export interface StoreClientOptions {
608
+ /** Default readiness budget in ms. */
609
+ readyTimeoutMs?: number;
610
+ /** First readiness poll interval in ms; doubles up to `readyPollMaxMs`. */
611
+ readyPollMs?: number;
612
+ readyPollMaxMs?: number;
613
+ }
614
+ export interface StoreOperationOutcome {
615
+ /** The snapshot the operation acted on; `null` when nothing was imported. */
616
+ snapshotId: string | null;
617
+ /** The server reported there was nothing to do. */
618
+ reused: boolean;
619
+ /**
620
+ * The server's per-component detail, verbatim. Empty on a host that predates
621
+ * `outcomes` — treat that as "no detail available", never as "nothing ran".
622
+ */
623
+ components: InstallOutcome[];
624
+ /** Every reported component reached the requested state. */
625
+ ok: boolean;
626
+ /**
627
+ * `undefined` when readiness was not checked. `true` when every component is
628
+ * usable now; `false` when it is not (see `readyIssue`).
629
+ */
630
+ ready?: boolean;
631
+ readyIssue?: StoreReadyIssue;
632
+ /** The component that needs attention, when `readyIssue` is set. */
633
+ readyComponentId?: string;
634
+ }
635
+ /**
636
+ * The slice of `AppServerClient` this layer orchestrates.
637
+ *
638
+ * Declared structurally rather than imported so this module does not depend on
639
+ * the client that owns it (the client imports this one). It also documents the
640
+ * exact surface the store layer is allowed to lean on.
641
+ */
642
+ export interface StoreHost {
643
+ listStore(): Promise<StoreList>;
644
+ installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
645
+ getInstallStatus(snapshotId: string): Promise<InstallStatus>;
646
+ disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
647
+ enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
648
+ uninstallInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
649
+ readonly skills: {
650
+ list(): Promise<SkillSummary[]>;
651
+ };
652
+ readonly agents: {
653
+ get(id: string): Promise<AgentDetail>;
654
+ };
655
+ readonly teams: {
656
+ get(id: string): Promise<TeamDetail>;
657
+ };
658
+ readonly connectors: {
659
+ status(id: string): Promise<ConnectorStatusView>;
660
+ test(id: string): Promise<ConnectorProbeResult>;
661
+ };
662
+ }
663
+ export declare class StoreClient {
664
+ private readonly host;
665
+ private readonly readyTimeoutMs;
666
+ private readonly readyPollMs;
667
+ private readonly readyPollMaxMs;
668
+ constructor(host: StoreHost, options?: StoreClientOptions);
669
+ /** Every item across enabled marketplaces, with install state. */
670
+ list(): Promise<StoreItem[]>;
671
+ /** Case-insensitive substring search over the fields a user can see. */
672
+ search(query: string, filter?: {
673
+ kind?: StoreItemKind;
674
+ }): Promise<StoreItem[]>;
675
+ /** Items currently installed on this host. */
676
+ installed(): Promise<StoreItem[]>;
677
+ /**
678
+ * Installed items whose marketplace offers a different version.
679
+ *
680
+ * Both conditions matter: `update_available` is computed from the imported
681
+ * snapshot even when nothing is installed, so filtering on it alone would
682
+ * report an "update" for an entry the user has never installed.
683
+ */
684
+ checkUpdates(): Promise<StoreItem[]>;
685
+ /**
686
+ * What a caller can offer for an item's pending update.
687
+ *
688
+ * `"none"` means there is nothing to do; `"uninstall_reinstall"` means the
689
+ * entry is installed at a different version and the only path the wire offers
690
+ * is to release it and install again (the version-aware re-import then picks
691
+ * up the current version). `"unknown"` is returned rather than guessing when
692
+ * the item carries no snapshot to release.
693
+ */
694
+ updateHint(item: StoreItem): "none" | "uninstall_reinstall" | "unknown";
695
+ /**
696
+ * Import (when needed) and install one store item, then wait until it is
697
+ * usable.
698
+ */
699
+ install(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
700
+ /**
701
+ * Release an item's runtime artifacts and clear its install record.
702
+ *
703
+ * The server only clears the record for components it actually released, so a
704
+ * partial failure leaves `ok: false` with the failing component named — and
705
+ * the item stays installed, which is what makes a retry meaningful.
706
+ */
707
+ uninstall(item: StoreItem, options?: UninstallOptions): Promise<StoreOperationOutcome>;
708
+ /**
709
+ * Switch an item's components on or off.
710
+ *
711
+ * Idempotent: the server sets the state it is asked for instead of toggling.
712
+ * A skill reports `skill_disable_flag_only` because the skill corpus has no
713
+ * enable state — that code is passed through, not swallowed, so a caller can
714
+ * tell "switched off" from "marked in the catalogue".
715
+ */
716
+ setEnabled(item: StoreItem, enabled: boolean, options?: SetEnabledOptions): Promise<StoreOperationOutcome>;
717
+ private assertUsable;
718
+ private requireSnapshot;
719
+ private installedComponentIds;
720
+ private outcomeFromStatus;
721
+ /**
722
+ * Poll until every component of the snapshot is usable.
723
+ *
724
+ * A timeout does **not** discard the install: the caller gets the successful
725
+ * install plus `ready: false` and `readyIssue: "ready_timeout"`, so a slow
726
+ * connector never turns into "the install failed".
727
+ */
728
+ private awaitReady;
729
+ /**
730
+ * Is this one component usable yet?
731
+ *
732
+ * Each kind answers with the narrowest read the protocol already offers. An
733
+ * unknown kind is reported ready rather than blocking: this layer cannot judge
734
+ * a component it does not understand, and a false timeout on a future kind
735
+ * would be worse than passing it through.
736
+ */
737
+ private readiness;
260
738
  }
261
739
  //#endregion
262
740
  //#region src/teams.d.ts
@@ -304,6 +782,8 @@ export interface AppServerClientOptions {
304
782
  transport: Transport;
305
783
  client: ClientInfo;
306
784
  capabilities?: ClientCapabilities;
785
+ /** Readiness tuning for the `store` sub-client (`store.install`). */
786
+ store?: StoreClientOptions;
307
787
  }
308
788
  export declare class AppServerClient {
309
789
  readonly transport: Transport;
@@ -323,6 +803,12 @@ export declare class AppServerClient {
323
803
  readonly teams: TeamClient;
324
804
  /** Public model directory (`models/list`, REQ-PAR-05b). */
325
805
  readonly models: ModelClient;
806
+ /**
807
+ * Agent Store lifecycle as one state machine: search → install (wait until
808
+ * usable) → enable/disable → uninstall. Composes the flat methods below; it
809
+ * adds no wire surface.
810
+ */
811
+ readonly store: StoreClient;
326
812
  readonly clientInfo: ClientInfo;
327
813
  readonly capabilities?: ClientCapabilities;
328
814
  private initializeResult;
@@ -352,7 +838,14 @@ export declare class AppServerClient {
352
838
  runInstall(input: InstallRequest): Promise<InstallResult>;
353
839
  /** Per-component installation state of a snapshot. */
354
840
  getInstallStatus(snapshotId: string): Promise<InstallStatus>;
355
- /** Disable installed components (runtime artifacts stay). */
841
+ /**
842
+ * Disable installed components.
843
+ *
844
+ * The runtime state moves with the flag — a connector's MCP server is switched
845
+ * off, an expert's Preset is disabled — while the artifacts stay on disk. A
846
+ * skill is the documented exception: the corpus has no enable state, so it
847
+ * reports `skill_disable_flag_only` and the flag stays a catalogue marker.
848
+ */
356
849
  disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
357
850
  /** Re-enable previously disabled components. */
358
851
  enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;