@flowy-agent-store/client 0.1.0-beta.2 → 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.cjs +1206 -25
- package/dist/index.d.cts +500 -7
- package/dist/index.d.mts +500 -7
- package/dist/index.mjs +1202 -26
- package/package.json +41 -32
package/dist/index.d.cts
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, ModelSummary, OAuthStartResult, OAuthStatusView, 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
|
/**
|
|
@@ -15,15 +15,30 @@ export declare function isLoopbackUrl(raw: string): boolean;
|
|
|
15
15
|
* so business code never knows whether it runs over WebSocket (browser),
|
|
16
16
|
* stdio (Node/CLI, future) or a one-shot HTTP binding.
|
|
17
17
|
*/
|
|
18
|
+
/**
|
|
19
|
+
* Lifecycle of the underlying channel. `open` is emitted on every successful
|
|
20
|
+
* dial (the first one included), `closed` only when an established connection
|
|
21
|
+
* is lost — so `closed` → `open` is exactly a reconnect.
|
|
22
|
+
*/
|
|
23
|
+
export type TransportLifecycle = "open" | "closed";
|
|
24
|
+
export type TransportLifecycleListener = (state: TransportLifecycle) => void;
|
|
18
25
|
export interface Transport {
|
|
19
26
|
connect(): Promise<void>;
|
|
20
27
|
request<T>(method: string, params: unknown): Promise<T>;
|
|
21
28
|
notify(method: string, params: unknown): void;
|
|
22
29
|
onNotification(listener: NotificationListener): () => void;
|
|
23
30
|
close(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: observe the channel lifecycle so a host can show a disconnect
|
|
33
|
+
* banner and re-arm subscriptions on reconnect (docs/agent-store/16 T8/W8).
|
|
34
|
+
* Custom transports may omit it.
|
|
35
|
+
*/
|
|
36
|
+
onLifecycle?(listener: TransportLifecycleListener): () => void;
|
|
24
37
|
}
|
|
25
38
|
export interface TransportOptions {
|
|
26
39
|
requestTimeoutMs?: number;
|
|
40
|
+
/** Max time `connect()` may stay unopened before failing (default 10s). */
|
|
41
|
+
connectTimeoutMs?: number;
|
|
27
42
|
token?: string;
|
|
28
43
|
}
|
|
29
44
|
export declare class WebSocketTransport implements Transport {
|
|
@@ -31,10 +46,13 @@ export declare class WebSocketTransport implements Transport {
|
|
|
31
46
|
private nextId;
|
|
32
47
|
private pending;
|
|
33
48
|
private listeners;
|
|
34
|
-
private
|
|
35
|
-
private
|
|
49
|
+
private lifecycleListeners;
|
|
50
|
+
private lifecycle;
|
|
36
51
|
private closedByCaller;
|
|
52
|
+
/** Single in-flight connect shared by concurrent callers; null once settled. */
|
|
53
|
+
private connecting;
|
|
37
54
|
readonly requestTimeoutMs: number;
|
|
55
|
+
readonly connectTimeoutMs: number;
|
|
38
56
|
readonly url: string;
|
|
39
57
|
readonly token?: string;
|
|
40
58
|
constructor(url: string, options?: TransportOptions);
|
|
@@ -43,12 +61,130 @@ export declare class WebSocketTransport implements Transport {
|
|
|
43
61
|
request<T>(method: string, params: unknown): Promise<T>;
|
|
44
62
|
notify(method: string, params: unknown): void;
|
|
45
63
|
onNotification(listener: NotificationListener): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Observe the channel lifecycle. `closed` is only reported when an
|
|
66
|
+
* established connection is lost (a failed first dial is not a disconnect),
|
|
67
|
+
* and `open` follows it on the next successful dial — that pair is what a
|
|
68
|
+
* host should treat as a reconnect. Unlike `onNotification`, these listeners
|
|
69
|
+
* survive `close()`, since they exist to drive the reconnection itself.
|
|
70
|
+
*/
|
|
71
|
+
onLifecycle(listener: TransportLifecycleListener): () => void;
|
|
46
72
|
close(): void;
|
|
73
|
+
private setLifecycle;
|
|
74
|
+
/** Detach the current socket (if any) and close it; its later events are ignored. */
|
|
75
|
+
private detachSocket;
|
|
76
|
+
/** Settle the in-flight `connect()` successfully, when there is one. */
|
|
77
|
+
private resolveConnect;
|
|
78
|
+
/** Fail the in-flight `connect()`, when there is one. */
|
|
79
|
+
private rejectConnect;
|
|
47
80
|
private requireSocket;
|
|
48
81
|
private handleMessage;
|
|
49
82
|
private failAllPending;
|
|
50
83
|
}
|
|
51
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
|
|
52
188
|
//#region src/agents.d.ts
|
|
53
189
|
export declare class AgentClient {
|
|
54
190
|
private readonly transport;
|
|
@@ -69,30 +205,115 @@ export declare class ConversationClient {
|
|
|
69
205
|
list(limit?: number): Promise<ConversationView[]>;
|
|
70
206
|
get(conversationId: string): Promise<ConversationView>;
|
|
71
207
|
messages(query: ConversationMessagesQuery): Promise<ConversationMessagesPage>;
|
|
72
|
-
|
|
208
|
+
/**
|
|
209
|
+
* R15(W10):`attachments` 是**会话工作区内的绝对路径**(图片附件)。
|
|
210
|
+
*
|
|
211
|
+
* 缺省不传,保持老调用方的 wire 形状逐字不变;服务端只接受会话工作区内的真实
|
|
212
|
+
* 文件(越界 / 相对路径 / 不存在一律拒),具体准入见
|
|
213
|
+
* `nomifun-app-server` 的 `resolve_conversation_attachments`。
|
|
214
|
+
*/
|
|
215
|
+
send(conversationId: string, content: string, idempotencyKey: string, attachments?: string[]): Promise<ConversationSendReceipt>;
|
|
73
216
|
cancel(conversationId: string): Promise<ConversationView>;
|
|
74
217
|
delete(conversationId: string): Promise<{
|
|
75
218
|
conversation_id: string;
|
|
76
219
|
deleted: boolean;
|
|
77
220
|
}>;
|
|
78
|
-
|
|
221
|
+
/**
|
|
222
|
+
* Subscribe to one conversation's realtime stream (doc `16` R1).
|
|
223
|
+
*
|
|
224
|
+
* `options.fetchMessages` is what makes auto catch-up possible: a detected
|
|
225
|
+
* sequence gap or a `conversation/resync-required` triggers one transcript
|
|
226
|
+
* fetch whose result is handed to `onBackfill`. Conversations have no event
|
|
227
|
+
* replay API (`05` §「V1 不提供 after_cursor 事件追平」), so the transcript is
|
|
228
|
+
* the recovery medium — unlike Runs, where `run/events` replays the events
|
|
229
|
+
* themselves.
|
|
230
|
+
*/
|
|
231
|
+
follow(conversationId: string, options?: ConversationFollowOptions): Promise<ConversationSubscription>;
|
|
79
232
|
}
|
|
80
233
|
export type ConversationEventListener = (event: ConversationEvent) => void;
|
|
81
234
|
export type ConversationResyncListener = (reason: string) => void;
|
|
235
|
+
export type ConversationErrorListener = (error: unknown) => void;
|
|
236
|
+
/** Transcript snapshot handed back after a detected gap / resync (R1). */
|
|
237
|
+
export interface ConversationBackfill {
|
|
238
|
+
conversationId: string;
|
|
239
|
+
/** `gap` or the server's `conversation/resync-required` reason. */
|
|
240
|
+
reason: string;
|
|
241
|
+
/** Latest transcript page, newest last (`conversation/messages`). */
|
|
242
|
+
messages: ConversationMessage[];
|
|
243
|
+
/** Server-computed: an older page still exists. */
|
|
244
|
+
hasMore: boolean;
|
|
245
|
+
}
|
|
246
|
+
export type ConversationBackfillListener = (backfill: ConversationBackfill) => void;
|
|
247
|
+
/** Options for `follow()` — mirrors the Run subscription's `FollowOptions`. */
|
|
248
|
+
export interface ConversationFollowOptions {
|
|
249
|
+
/**
|
|
250
|
+
* Fetch the transcript automatically after a gap / resync (default `true`).
|
|
251
|
+
* Needs `fetchMessages`; when absent only `onResync` fires.
|
|
252
|
+
*/
|
|
253
|
+
autoResync?: boolean;
|
|
254
|
+
/** Events held before the first `onEvent` listener attaches (default 256). */
|
|
255
|
+
pendingLimit?: number;
|
|
256
|
+
/** Transcript fetcher; `follow()` wires the real one. */
|
|
257
|
+
fetchMessages?: (conversationId: string) => Promise<ConversationMessagesPage>;
|
|
258
|
+
}
|
|
82
259
|
export declare class ConversationSubscription {
|
|
83
260
|
private readonly transport;
|
|
84
261
|
readonly conversationId: string;
|
|
262
|
+
private readonly options;
|
|
85
263
|
private events;
|
|
86
264
|
private resyncs;
|
|
265
|
+
private errors;
|
|
266
|
+
private backfills;
|
|
87
267
|
private closed;
|
|
88
268
|
private lastSeenSequence;
|
|
89
269
|
private removeListener;
|
|
90
|
-
|
|
270
|
+
/**
|
|
271
|
+
* `follow()` resolves as soon as the server acknowledged the subscription,
|
|
272
|
+
* but the caller attaches listeners one tick later. Events landing in that
|
|
273
|
+
* window used to be dropped silently; they are held here (oldest first) and
|
|
274
|
+
* flushed to the first `onEvent` listener.
|
|
275
|
+
*/
|
|
276
|
+
private pending;
|
|
277
|
+
private pendingDropped;
|
|
278
|
+
private backfillInFlight;
|
|
279
|
+
constructor(transport: Transport, conversationId: string, options?: ConversationFollowOptions);
|
|
91
280
|
get lastSequence(): number;
|
|
281
|
+
/** Events dropped because nobody was listening and the buffer was full. */
|
|
282
|
+
get droppedPendingCount(): number;
|
|
92
283
|
onEvent(listener: ConversationEventListener): () => void;
|
|
93
284
|
onResync(listener: ConversationResyncListener): () => void;
|
|
285
|
+
/**
|
|
286
|
+
* Auto catch-up result (R1). Fires only when `autoResync` is on and the
|
|
287
|
+
* transcript fetch succeeded; the listener owns replacing the transcript.
|
|
288
|
+
*/
|
|
289
|
+
onBackfill(listener: ConversationBackfillListener): () => void;
|
|
290
|
+
/**
|
|
291
|
+
* Failures that used to be swallowed: the catch-up fetch, and a failed
|
|
292
|
+
* re-subscribe during `rearm()`. Listener isolation keeps one bad listener
|
|
293
|
+
* from hiding the error from the others.
|
|
294
|
+
*/
|
|
295
|
+
onError(listener: ConversationErrorListener): () => void;
|
|
94
296
|
close(): Promise<void>;
|
|
297
|
+
/**
|
|
298
|
+
* Re-arm after a reconnect (docs/agent-store/16 T8): re-register the
|
|
299
|
+
* notification listener, reset the cursor and re-issue
|
|
300
|
+
* `conversation/subscribe`. The server restarts a conversation's sequence at
|
|
301
|
+
* 1 for a fresh subscription, which is exactly why the cursor resets. On a
|
|
302
|
+
* failed re-subscribe the error reaches `onError` and the caller can still
|
|
303
|
+
* backfill the outage window via `conversation/messages`.
|
|
304
|
+
*/
|
|
305
|
+
rearm(): Promise<void>;
|
|
95
306
|
private dispatch;
|
|
307
|
+
private buffer;
|
|
308
|
+
private deliver;
|
|
309
|
+
private emitResync;
|
|
310
|
+
private emitError;
|
|
311
|
+
/**
|
|
312
|
+
* One in-flight catch-up at a time: a burst of resync signals collapses into
|
|
313
|
+
* a single transcript fetch (the fetch itself returns the newest page, so the
|
|
314
|
+
* result supersedes every signal that arrived before it).
|
|
315
|
+
*/
|
|
316
|
+
private requestBackfill;
|
|
96
317
|
}
|
|
97
318
|
//#endregion
|
|
98
319
|
//#region src/connectors.d.ts
|
|
@@ -116,6 +337,22 @@ export declare class ConnectorClient {
|
|
|
116
337
|
authStart(connectorId: string): Promise<OAuthStartResult>;
|
|
117
338
|
/** Revoke the OAuth token for a connector. */
|
|
118
339
|
logout(connectorId: string): Promise<void>;
|
|
340
|
+
/**
|
|
341
|
+
* Run one tool on a registered connector, through the host's own connection.
|
|
342
|
+
*
|
|
343
|
+
* The host holds the transport, its headers and its OAuth token; this sends a
|
|
344
|
+
* tool name and an argument object and nothing else — you cannot name a URL,
|
|
345
|
+
* a command or a header. Whether the pair is callable at all is the host's
|
|
346
|
+
* `[connector_proxy]` allowlist, so expect `policy_denied` on a host whose
|
|
347
|
+
* operator has not listed this tool (that is the default, not a
|
|
348
|
+
* misconfiguration).
|
|
349
|
+
*
|
|
350
|
+
* Resolves even when the tool itself failed — check `is_error`. It rejects
|
|
351
|
+
* only when the call never reached the tool: `connector_call_timeout`,
|
|
352
|
+
* `connector_call_failed`, `response_too_large`, `connector_unavailable`,
|
|
353
|
+
* `policy_denied`, `not_found`.
|
|
354
|
+
*/
|
|
355
|
+
call(connectorId: string, tool: string, args?: unknown): Promise<ConnectorCallResult>;
|
|
119
356
|
}
|
|
120
357
|
//#endregion
|
|
121
358
|
//#region src/runs.d.ts
|
|
@@ -124,10 +361,26 @@ export declare class RunClient {
|
|
|
124
361
|
constructor(transport: Transport);
|
|
125
362
|
/** Start an Agent run and return its asynchronous receipt. */
|
|
126
363
|
agent(input: AgentRunInput): Promise<RunReceipt>;
|
|
364
|
+
/**
|
|
365
|
+
* Start a Team run (`16` §7 决策 3): the server creates the Leader
|
|
366
|
+
* Conversation, materializes/binds the Team's execution template and returns
|
|
367
|
+
* the public run id of the execution the Leader started.
|
|
368
|
+
*
|
|
369
|
+
* The receipt is `TeamRunReceipt`, not `RunReceipt` — a Team Run has no lead
|
|
370
|
+
* preset revision/digest to report. Member pool and concurrency limits are not
|
|
371
|
+
* inputs; sending `planning` is rejected as `invalid_request`.
|
|
372
|
+
*/
|
|
373
|
+
team(input: TeamRunInput): Promise<TeamRunReceipt>;
|
|
127
374
|
/** Read the authoritative run state. */
|
|
128
375
|
get(runId: string): Promise<RunView>;
|
|
129
376
|
/** Read the terminal result (fails until the run reaches a terminal state). */
|
|
130
377
|
result(runId: string): Promise<RunResult>;
|
|
378
|
+
/**
|
|
379
|
+
* Read the authoritative plan / step snapshot (W4 / W6, resolves D-W6-1):
|
|
380
|
+
* titles, statuses, retries with their reason, errors and timings — none of
|
|
381
|
+
* which the append-only event log carries.
|
|
382
|
+
*/
|
|
383
|
+
plan(runId: string): Promise<RunPlan>;
|
|
131
384
|
/** Replay persisted events after a cursor. */
|
|
132
385
|
events(query: RunEventsQuery): Promise<RunEvent[]>;
|
|
133
386
|
/** Request cancellation; the terminal state is confirmed by events/run/get. */
|
|
@@ -137,6 +390,16 @@ export declare class RunClient {
|
|
|
137
390
|
* (runtime durable conversation effect; server resolves the opaque step).
|
|
138
391
|
*/
|
|
139
392
|
steer(input: SteerRunInput): Promise<RunView>;
|
|
393
|
+
/**
|
|
394
|
+
* Answer the pending decision of a `waiting_input` attempt.
|
|
395
|
+
*
|
|
396
|
+
* The three `expected*Version` tokens are mandatory CAS inputs, normally the
|
|
397
|
+
* ones projected on the pending `approval.requested` event. The server passes
|
|
398
|
+
* them straight to the engine's single answer gate, so a stale token is a
|
|
399
|
+
* `conflict`, never a silent overwrite. There is no `always_allow` here: the
|
|
400
|
+
* desktop confirmation route's approve-all flag is not part of this protocol.
|
|
401
|
+
*/
|
|
402
|
+
answerDecision(input: AnswerDecisionInput): Promise<RunView>;
|
|
140
403
|
/** Subscribe to best-effort realtime events for one run. */
|
|
141
404
|
follow(runId: string, options?: FollowOptions): Promise<EventSubscription>;
|
|
142
405
|
}
|
|
@@ -184,6 +447,22 @@ export declare class EventSubscription {
|
|
|
184
447
|
* Concurrent calls share one flight.
|
|
185
448
|
*/
|
|
186
449
|
resync(): Promise<RunEvent[]>;
|
|
450
|
+
/**
|
|
451
|
+
* Re-arm after the connection was re-established (docs/agent-store/16 T8).
|
|
452
|
+
* A new socket means the server dropped its subscriptions and the transport
|
|
453
|
+
* dropped its notification listeners, so this:
|
|
454
|
+
*
|
|
455
|
+
* 1. re-registers this subscription's notification listener,
|
|
456
|
+
* 2. resets the cursor to 0 and forgets the dedupe set, then
|
|
457
|
+
* 3. re-issues `run/subscribe` and replays **all** persisted events.
|
|
458
|
+
*
|
|
459
|
+
* The replay is deliberate (the plan's "重连后重置 lastSeenSequence"): a
|
|
460
|
+
* consumer must dedupe by `sequence`, since events seen before the outage
|
|
461
|
+
* are delivered again. Call it only once the session handshake has completed
|
|
462
|
+
* again — the transport's `open` event fires before `initialize`. A no-op
|
|
463
|
+
* once closed.
|
|
464
|
+
*/
|
|
465
|
+
rearm(): Promise<RunEvent[]>;
|
|
187
466
|
private markSeen;
|
|
188
467
|
private emitEvent;
|
|
189
468
|
private emitError;
|
|
@@ -199,6 +478,197 @@ export declare class SkillClient {
|
|
|
199
478
|
list(): Promise<SkillSummary[]>;
|
|
200
479
|
/** Fetch one Skill detail; instructions are a bounded public summary. */
|
|
201
480
|
get(skillId: string): Promise<SkillDetail>;
|
|
481
|
+
/**
|
|
482
|
+
* Every readable file inside a Skill's directory, with a tree digest.
|
|
483
|
+
*
|
|
484
|
+
* This is the only way to learn what a Skill actually ships: `get()` returns
|
|
485
|
+
* a bounded summary of `SKILL.md` and says nothing about the `references/`,
|
|
486
|
+
* `scripts/`, `templates/` and `assets/` beside it. Check
|
|
487
|
+
* `capabilities.skill_files` first — a host may wire the catalog without
|
|
488
|
+
* this face, in which case it answers `unsupported_operation`.
|
|
489
|
+
*/
|
|
490
|
+
files(skillId: string): Promise<SkillFileList>;
|
|
491
|
+
/**
|
|
492
|
+
* One skill file's bytes, over the WebSocket binding.
|
|
493
|
+
*
|
|
494
|
+
* The wire carries base64 because JSON has no byte string, so this decodes
|
|
495
|
+
* back to `Uint8Array` for you. The HTTP route
|
|
496
|
+
* (`GET /api/app-server/skills/{id}/files/{path}`) returns the raw body
|
|
497
|
+
* instead, but that is deliberately not on the typed client's JSON
|
|
498
|
+
* transport — use this method, or `fetch` the route directly if you are on a
|
|
499
|
+
* host token.
|
|
500
|
+
*/
|
|
501
|
+
readFile(skillId: string, path: string): Promise<Uint8Array>;
|
|
502
|
+
/** Same as {@link readFile}, but also returns the server's content type. */
|
|
503
|
+
readFileWithType(skillId: string, path: string): Promise<SkillFileContent>;
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/store.d.ts
|
|
507
|
+
/** Why a `waitForReady` check ended without every component being usable. */
|
|
508
|
+
export type StoreReadyIssue =
|
|
509
|
+
/** The host never reported the component ready inside the timeout. */
|
|
510
|
+
"ready_timeout" |
|
|
511
|
+
/** A connector needs the OAuth browser flow; the caller must drive it. */
|
|
512
|
+
"authorization_required";
|
|
513
|
+
/** Branchable client-side failures. Server-side failures arrive as outcomes. */
|
|
514
|
+
export type StoreErrorCode = "not_installed" | "item_blocked" | "aborted";
|
|
515
|
+
export declare class StoreError extends Error {
|
|
516
|
+
readonly code: StoreErrorCode;
|
|
517
|
+
constructor(code: StoreErrorCode, message: string);
|
|
518
|
+
}
|
|
519
|
+
export interface InstallOptions {
|
|
520
|
+
/**
|
|
521
|
+
* Wait until every component is actually usable before resolving.
|
|
522
|
+
*
|
|
523
|
+
* Defaults to `true` because a resolved `install()` that is not yet usable is
|
|
524
|
+
* the single most misleading thing this surface could do: a skill is usable
|
|
525
|
+
* as soon as it is copied, but a connector is not usable until the server has
|
|
526
|
+
* been switched on and probed.
|
|
527
|
+
*/
|
|
528
|
+
waitForReady?: boolean;
|
|
529
|
+
/** Readiness budget. Defaults to `StoreClientOptions.readyTimeoutMs`. */
|
|
530
|
+
timeoutMs?: number;
|
|
531
|
+
signal?: AbortSignal;
|
|
532
|
+
}
|
|
533
|
+
export interface UninstallOptions {
|
|
534
|
+
/** Narrow the release to these component ids (default: every installed one). */
|
|
535
|
+
componentIds?: string[];
|
|
536
|
+
}
|
|
537
|
+
export interface SetEnabledOptions {
|
|
538
|
+
/** Narrow the state change to these component ids (default: all installed). */
|
|
539
|
+
componentIds?: string[];
|
|
540
|
+
}
|
|
541
|
+
export interface StoreClientOptions {
|
|
542
|
+
/** Default readiness budget in ms. */
|
|
543
|
+
readyTimeoutMs?: number;
|
|
544
|
+
/** First readiness poll interval in ms; doubles up to `readyPollMaxMs`. */
|
|
545
|
+
readyPollMs?: number;
|
|
546
|
+
readyPollMaxMs?: number;
|
|
547
|
+
}
|
|
548
|
+
export interface StoreOperationOutcome {
|
|
549
|
+
/** The snapshot the operation acted on; `null` when nothing was imported. */
|
|
550
|
+
snapshotId: string | null;
|
|
551
|
+
/** The server reported there was nothing to do. */
|
|
552
|
+
reused: boolean;
|
|
553
|
+
/**
|
|
554
|
+
* The server's per-component detail, verbatim. Empty on a host that predates
|
|
555
|
+
* `outcomes` — treat that as "no detail available", never as "nothing ran".
|
|
556
|
+
*/
|
|
557
|
+
components: InstallOutcome[];
|
|
558
|
+
/** Every reported component reached the requested state. */
|
|
559
|
+
ok: boolean;
|
|
560
|
+
/**
|
|
561
|
+
* `undefined` when readiness was not checked. `true` when every component is
|
|
562
|
+
* usable now; `false` when it is not (see `readyIssue`).
|
|
563
|
+
*/
|
|
564
|
+
ready?: boolean;
|
|
565
|
+
readyIssue?: StoreReadyIssue;
|
|
566
|
+
/** The component that needs attention, when `readyIssue` is set. */
|
|
567
|
+
readyComponentId?: string;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* The slice of `AppServerClient` this layer orchestrates.
|
|
571
|
+
*
|
|
572
|
+
* Declared structurally rather than imported so this module does not depend on
|
|
573
|
+
* the client that owns it (the client imports this one). It also documents the
|
|
574
|
+
* exact surface the store layer is allowed to lean on.
|
|
575
|
+
*/
|
|
576
|
+
export interface StoreHost {
|
|
577
|
+
listStore(): Promise<StoreList>;
|
|
578
|
+
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
579
|
+
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
580
|
+
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
581
|
+
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
582
|
+
uninstallInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
583
|
+
readonly skills: {
|
|
584
|
+
list(): Promise<SkillSummary[]>;
|
|
585
|
+
};
|
|
586
|
+
readonly agents: {
|
|
587
|
+
get(id: string): Promise<AgentDetail>;
|
|
588
|
+
};
|
|
589
|
+
readonly teams: {
|
|
590
|
+
get(id: string): Promise<TeamDetail>;
|
|
591
|
+
};
|
|
592
|
+
readonly connectors: {
|
|
593
|
+
status(id: string): Promise<ConnectorStatusView>;
|
|
594
|
+
test(id: string): Promise<ConnectorProbeResult>;
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
export declare class StoreClient {
|
|
598
|
+
private readonly host;
|
|
599
|
+
private readonly readyTimeoutMs;
|
|
600
|
+
private readonly readyPollMs;
|
|
601
|
+
private readonly readyPollMaxMs;
|
|
602
|
+
constructor(host: StoreHost, options?: StoreClientOptions);
|
|
603
|
+
/** Every item across enabled marketplaces, with install state. */
|
|
604
|
+
list(): Promise<StoreItem[]>;
|
|
605
|
+
/** Case-insensitive substring search over the fields a user can see. */
|
|
606
|
+
search(query: string, filter?: {
|
|
607
|
+
kind?: StoreItemKind;
|
|
608
|
+
}): Promise<StoreItem[]>;
|
|
609
|
+
/** Items currently installed on this host. */
|
|
610
|
+
installed(): Promise<StoreItem[]>;
|
|
611
|
+
/**
|
|
612
|
+
* Installed items whose marketplace offers a different version.
|
|
613
|
+
*
|
|
614
|
+
* Both conditions matter: `update_available` is computed from the imported
|
|
615
|
+
* snapshot even when nothing is installed, so filtering on it alone would
|
|
616
|
+
* report an "update" for an entry the user has never installed.
|
|
617
|
+
*/
|
|
618
|
+
checkUpdates(): Promise<StoreItem[]>;
|
|
619
|
+
/**
|
|
620
|
+
* What a caller can offer for an item's pending update.
|
|
621
|
+
*
|
|
622
|
+
* `"none"` means there is nothing to do; `"uninstall_reinstall"` means the
|
|
623
|
+
* entry is installed at a different version and the only path the wire offers
|
|
624
|
+
* is to release it and install again (the version-aware re-import then picks
|
|
625
|
+
* up the current version). `"unknown"` is returned rather than guessing when
|
|
626
|
+
* the item carries no snapshot to release.
|
|
627
|
+
*/
|
|
628
|
+
updateHint(item: StoreItem): "none" | "uninstall_reinstall" | "unknown";
|
|
629
|
+
/**
|
|
630
|
+
* Import (when needed) and install one store item, then wait until it is
|
|
631
|
+
* usable.
|
|
632
|
+
*/
|
|
633
|
+
install(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
|
|
634
|
+
/**
|
|
635
|
+
* Release an item's runtime artifacts and clear its install record.
|
|
636
|
+
*
|
|
637
|
+
* The server only clears the record for components it actually released, so a
|
|
638
|
+
* partial failure leaves `ok: false` with the failing component named — and
|
|
639
|
+
* the item stays installed, which is what makes a retry meaningful.
|
|
640
|
+
*/
|
|
641
|
+
uninstall(item: StoreItem, options?: UninstallOptions): Promise<StoreOperationOutcome>;
|
|
642
|
+
/**
|
|
643
|
+
* Switch an item's components on or off.
|
|
644
|
+
*
|
|
645
|
+
* Idempotent: the server sets the state it is asked for instead of toggling.
|
|
646
|
+
* A skill reports `skill_disable_flag_only` because the skill corpus has no
|
|
647
|
+
* enable state — that code is passed through, not swallowed, so a caller can
|
|
648
|
+
* tell "switched off" from "marked in the catalogue".
|
|
649
|
+
*/
|
|
650
|
+
setEnabled(item: StoreItem, enabled: boolean, options?: SetEnabledOptions): Promise<StoreOperationOutcome>;
|
|
651
|
+
private assertUsable;
|
|
652
|
+
private requireSnapshot;
|
|
653
|
+
private installedComponentIds;
|
|
654
|
+
private outcomeFromStatus;
|
|
655
|
+
/**
|
|
656
|
+
* Poll until every component of the snapshot is usable.
|
|
657
|
+
*
|
|
658
|
+
* A timeout does **not** discard the install: the caller gets the successful
|
|
659
|
+
* install plus `ready: false` and `readyIssue: "ready_timeout"`, so a slow
|
|
660
|
+
* connector never turns into "the install failed".
|
|
661
|
+
*/
|
|
662
|
+
private awaitReady;
|
|
663
|
+
/**
|
|
664
|
+
* Is this one component usable yet?
|
|
665
|
+
*
|
|
666
|
+
* Each kind answers with the narrowest read the protocol already offers. An
|
|
667
|
+
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
668
|
+
* a component it does not understand, and a false timeout on a future kind
|
|
669
|
+
* would be worse than passing it through.
|
|
670
|
+
*/
|
|
671
|
+
private readiness;
|
|
202
672
|
}
|
|
203
673
|
//#endregion
|
|
204
674
|
//#region src/teams.d.ts
|
|
@@ -246,6 +716,8 @@ export interface AppServerClientOptions {
|
|
|
246
716
|
transport: Transport;
|
|
247
717
|
client: ClientInfo;
|
|
248
718
|
capabilities?: ClientCapabilities;
|
|
719
|
+
/** Readiness tuning for the `store` sub-client (`store.install`). */
|
|
720
|
+
store?: StoreClientOptions;
|
|
249
721
|
}
|
|
250
722
|
export declare class AppServerClient {
|
|
251
723
|
readonly transport: Transport;
|
|
@@ -265,11 +737,25 @@ export declare class AppServerClient {
|
|
|
265
737
|
readonly teams: TeamClient;
|
|
266
738
|
/** Public model directory (`models/list`, REQ-PAR-05b). */
|
|
267
739
|
readonly models: ModelClient;
|
|
740
|
+
/**
|
|
741
|
+
* Agent Store lifecycle as one state machine: search → install (wait until
|
|
742
|
+
* usable) → enable/disable → uninstall. Composes the flat methods below; it
|
|
743
|
+
* adds no wire surface.
|
|
744
|
+
*/
|
|
745
|
+
readonly store: StoreClient;
|
|
268
746
|
readonly clientInfo: ClientInfo;
|
|
269
747
|
readonly capabilities?: ClientCapabilities;
|
|
270
748
|
private initializeResult;
|
|
271
749
|
private notificationListeners;
|
|
750
|
+
/** Transport → client notification bridge; re-armed after a transport close. */
|
|
751
|
+
private unbridgeNotifications;
|
|
272
752
|
constructor(options: AppServerClientOptions);
|
|
753
|
+
/**
|
|
754
|
+
* Subscribe the client-level fan-out on the transport exactly once.
|
|
755
|
+
* `transport.close()` drops its listeners, so this is also called on
|
|
756
|
+
* `connect()`: otherwise a reconnect would leave notifications silent.
|
|
757
|
+
*/
|
|
758
|
+
private bridgeNotifications;
|
|
273
759
|
get ready(): boolean;
|
|
274
760
|
get initializeInfo(): InitializeResult | null;
|
|
275
761
|
/** Connect and perform the initialize/initialized handshake. */
|
|
@@ -286,7 +772,14 @@ export declare class AppServerClient {
|
|
|
286
772
|
runInstall(input: InstallRequest): Promise<InstallResult>;
|
|
287
773
|
/** Per-component installation state of a snapshot. */
|
|
288
774
|
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
289
|
-
/**
|
|
775
|
+
/**
|
|
776
|
+
* Disable installed components.
|
|
777
|
+
*
|
|
778
|
+
* The runtime state moves with the flag — a connector's MCP server is switched
|
|
779
|
+
* off, an expert's Preset is disabled — while the artifacts stay on disk. A
|
|
780
|
+
* skill is the documented exception: the corpus has no enable state, so it
|
|
781
|
+
* reports `skill_disable_flag_only` and the flag stays a catalogue marker.
|
|
782
|
+
*/
|
|
290
783
|
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
291
784
|
/** Re-enable previously disabled components. */
|
|
292
785
|
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|