@flowy-agent-store/client 0.1.0-beta.7 → 0.1.0-beta.8
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 +213 -16
- package/dist/index.d.cts +167 -9
- package/dist/index.d.mts +167 -9
- package/dist/index.mjs +213 -16
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -276,7 +276,7 @@ var WebSocketTransport = class {
|
|
|
276
276
|
/** Header the server mints the per-call connection id under. */
|
|
277
277
|
const CONNECTION_HEADER = "x-app-server-connection-id";
|
|
278
278
|
/** Protocol version this binding announces on the one-shot handshake. */
|
|
279
|
-
const PROTOCOL_VERSION = "fp-
|
|
279
|
+
const PROTOCOL_VERSION = "fp-12";
|
|
280
280
|
/**
|
|
281
281
|
* Protocol method → REST route. Verified from handler bodies, not from the
|
|
282
282
|
* route list (a route existing does not prove which method it mirrors).
|
|
@@ -471,6 +471,26 @@ const HTTP_ROUTES = {
|
|
|
471
471
|
path: "/connectors/:connector_id/auth-logout",
|
|
472
472
|
source: "connector_auth_logout_route() -> connector_auth_logout_impl"
|
|
473
473
|
},
|
|
474
|
+
"connector/credential/get": {
|
|
475
|
+
verb: "GET",
|
|
476
|
+
path: "/connectors/:connector_id/credential",
|
|
477
|
+
source: "connector_credential_get_route() -> connector_credential_get_impl"
|
|
478
|
+
},
|
|
479
|
+
"connector/credential/set": {
|
|
480
|
+
verb: "POST",
|
|
481
|
+
path: "/connectors/:connector_id/credential",
|
|
482
|
+
source: "connector_credential_set_route() -> connector_credential_set_impl"
|
|
483
|
+
},
|
|
484
|
+
"connector/credential/clear": {
|
|
485
|
+
verb: "POST",
|
|
486
|
+
path: "/connectors/:connector_id/credential/clear",
|
|
487
|
+
source: "connector_credential_clear_route() -> connector_credential_clear_impl"
|
|
488
|
+
},
|
|
489
|
+
"connector/register": {
|
|
490
|
+
verb: "POST",
|
|
491
|
+
path: "/connectors",
|
|
492
|
+
source: "register_connector_route() -> register_connector_impl"
|
|
493
|
+
},
|
|
474
494
|
"import/run": {
|
|
475
495
|
verb: "POST",
|
|
476
496
|
path: "/imports",
|
|
@@ -560,6 +580,11 @@ const HTTP_ROUTES = {
|
|
|
560
580
|
verb: "POST",
|
|
561
581
|
path: "/store/:marketplace_id/entries/:entry_name/install",
|
|
562
582
|
source: "store_install_entry_route() -> store_install_entry_impl"
|
|
583
|
+
},
|
|
584
|
+
"store/update-entry": {
|
|
585
|
+
verb: "POST",
|
|
586
|
+
path: "/store/:marketplace_id/entries/:entry_name/update",
|
|
587
|
+
source: "store_update_entry_route() -> store_update_entry_impl"
|
|
563
588
|
}
|
|
564
589
|
};
|
|
565
590
|
/** Methods that are WebSocket-only by design — named so the error explains itself. */
|
|
@@ -1080,6 +1105,13 @@ var ConversationSubscription = class {
|
|
|
1080
1105
|
};
|
|
1081
1106
|
//#endregion
|
|
1082
1107
|
//#region src/connectors.ts
|
|
1108
|
+
/**
|
|
1109
|
+
* How long {@link ConnectorClient.waitForAuth} waits by default: the host's own
|
|
1110
|
+
* callback window is 120s, so a shorter client budget would give up on flows the
|
|
1111
|
+
* host is still willing to finish.
|
|
1112
|
+
*/
|
|
1113
|
+
const DEFAULT_AUTH_TIMEOUT_MS = 12e4;
|
|
1114
|
+
const DEFAULT_AUTH_POLL_MS = 500;
|
|
1083
1115
|
var ConnectorClient = class {
|
|
1084
1116
|
transport;
|
|
1085
1117
|
constructor(transport) {
|
|
@@ -1113,17 +1145,117 @@ var ConnectorClient = class {
|
|
|
1113
1145
|
test(connectorId) {
|
|
1114
1146
|
return this.transport.request("connector/test", { connector_id: connectorId });
|
|
1115
1147
|
}
|
|
1116
|
-
/**
|
|
1148
|
+
/**
|
|
1149
|
+
* Check the current OAuth state for a connector.
|
|
1150
|
+
*
|
|
1151
|
+
* Read `error` too: a flow that failed after the browser step is reported as
|
|
1152
|
+
* `state: "not_authenticated"` **plus** `error`, because the wire has no
|
|
1153
|
+
* failing state to flip to. {@link waitForAuth} is the loop that does this.
|
|
1154
|
+
*/
|
|
1117
1155
|
authStatus(connectorId) {
|
|
1118
1156
|
return this.transport.request("connector/auth/status", { connector_id: connectorId });
|
|
1119
1157
|
}
|
|
1120
1158
|
/**
|
|
1159
|
+
* The credential form a connector needs filled in, as **this caller** sees it
|
|
1160
|
+
* (doc `34` §6.1).
|
|
1161
|
+
*
|
|
1162
|
+
* `mode` says which kind of authentication applies (`none` / `oauth` / `token`),
|
|
1163
|
+
* `status` whether anything is still missing, and `missing` names the keys.
|
|
1164
|
+
* `fields` is everything needed to render the form — labels, placeholders,
|
|
1165
|
+
* descriptions and the marketplace's "where do I get a key" link, in both
|
|
1166
|
+
* languages. No secret ever comes back: only `plain` fields carry a `value`.
|
|
1167
|
+
*/
|
|
1168
|
+
credentials(connectorId) {
|
|
1169
|
+
return this.transport.request("connector/credential/get", { connector_id: connectorId });
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Store what the user typed and get the new state back.
|
|
1173
|
+
*
|
|
1174
|
+
* Only keys the connector's own declaration names are accepted — the form is
|
|
1175
|
+
* the whole write surface. `secret` fields go to the host's credential store
|
|
1176
|
+
* under the caller's own namespace, `plain` fields into the connector's
|
|
1177
|
+
* configuration, so a shared host never resolves one user's token for another.
|
|
1178
|
+
*/
|
|
1179
|
+
setCredentials(connectorId, values) {
|
|
1180
|
+
return this.transport.request("connector/credential/set", {
|
|
1181
|
+
connector_id: connectorId,
|
|
1182
|
+
values
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Forget stored credentials. `keys` omitted = every secret field of this
|
|
1187
|
+
* connector. Idempotent: clearing what is not there succeeds.
|
|
1188
|
+
*/
|
|
1189
|
+
clearCredentials(connectorId, keys) {
|
|
1190
|
+
return this.transport.request("connector/credential/clear", {
|
|
1191
|
+
connector_id: connectorId,
|
|
1192
|
+
...keys ? { keys } : {}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Register a connector this host never imported — your own MCP server, handed
|
|
1197
|
+
* over together with the template that says what it needs (doc `34` §6.5).
|
|
1198
|
+
*
|
|
1199
|
+
* The **template is the whole declaration**: whatever `${secret:NAME}` the URL,
|
|
1200
|
+
* headers or env name becomes that connector's credential form, so there is no
|
|
1201
|
+
* `token-schema.json` to ship and nothing to install from a marketplace. The
|
|
1202
|
+
* connector's own non-secret settings travel in `values`.
|
|
1203
|
+
*
|
|
1204
|
+
* Secrets do **not** travel here: hand them over with {@link setCredentials},
|
|
1205
|
+
* which keeps exactly one write surface for values. The returned detail already
|
|
1206
|
+
* carries the credential block, so `missing` tells you which keys are still
|
|
1207
|
+
* needed.
|
|
1208
|
+
*
|
|
1209
|
+
* Re-registering the same name updates it; a builtin server's name is refused.
|
|
1210
|
+
* The row is created **disabled** — enabling it still requires a probe that
|
|
1211
|
+
* passes — and this method is on the installation-owner-only surface, because
|
|
1212
|
+
* choosing where the host reaches is the owner's call.
|
|
1213
|
+
*/
|
|
1214
|
+
register(registration) {
|
|
1215
|
+
return this.transport.request("connector/register", registration);
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1121
1218
|
* Kick off the OAuth browser flow on the trusted host. Returns the start
|
|
1122
|
-
* acknowledgement immediately;
|
|
1219
|
+
* acknowledgement immediately; follow it with {@link waitForAuth}.
|
|
1220
|
+
*
|
|
1221
|
+
* `state: "error"` here is a failure that happened **before** the browser was
|
|
1222
|
+
* opened (endpoint discovery, client identity, binding the callback, launching
|
|
1223
|
+
* the browser): nothing was shown to the user, so `error` is the whole story
|
|
1224
|
+
* and there is nothing to wait for.
|
|
1123
1225
|
*/
|
|
1124
1226
|
authStart(connectorId) {
|
|
1125
1227
|
return this.transport.request("connector/auth/start", { connector_id: connectorId });
|
|
1126
1228
|
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Wait for an authorization that was started (in the browser, or by a
|
|
1231
|
+
* previous `authStart`) to finish — and **report why** when it does not.
|
|
1232
|
+
*
|
|
1233
|
+
* A hand-rolled `while (state !== "authenticated") poll()` loop ends in "it
|
|
1234
|
+
* never became authenticated", with the reason sitting unread in the status it
|
|
1235
|
+
* already fetched: a token exchange the authorization server refused (a
|
|
1236
|
+
* throttling gateway answers `slow_down`), a callback timeout, a CSRF/path
|
|
1237
|
+
* mismatch. Those land in `authStatus().error` *only*, and the state stays
|
|
1238
|
+
* `not_authenticated` — there is no failing state on the wire.
|
|
1239
|
+
*
|
|
1240
|
+
* Resolves for every domain outcome (`authenticated` / `error` / `timeout`);
|
|
1241
|
+
* rejects only when the status read itself fails (transport or protocol), the
|
|
1242
|
+
* same rule as `connector/call`. The wait is bounded by `timeoutMs`; a caller
|
|
1243
|
+
* that needs to stop earlier can race the returned promise.
|
|
1244
|
+
*/
|
|
1245
|
+
async waitForAuth(connectorId, options = {}) {
|
|
1246
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS);
|
|
1247
|
+
const pollMs = options.pollMs ?? DEFAULT_AUTH_POLL_MS;
|
|
1248
|
+
for (;;) {
|
|
1249
|
+
const status = await this.authStatus(connectorId);
|
|
1250
|
+
if (status.state === "authenticated") return { state: "authenticated" };
|
|
1251
|
+
if (status.error) return {
|
|
1252
|
+
state: "error",
|
|
1253
|
+
error: status.error
|
|
1254
|
+
};
|
|
1255
|
+
if (Date.now() >= deadline) return { state: "timeout" };
|
|
1256
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1127
1259
|
/** Revoke the OAuth token for a connector. */
|
|
1128
1260
|
logout(connectorId) {
|
|
1129
1261
|
return this.transport.request("connector/auth/logout", { connector_id: connectorId }).then(() => void 0);
|
|
@@ -1543,6 +1675,7 @@ var StoreError = class extends Error {
|
|
|
1543
1675
|
const DEFAULT_READY_TIMEOUT_MS = 3e4;
|
|
1544
1676
|
const DEFAULT_READY_POLL_MS = 400;
|
|
1545
1677
|
const DEFAULT_READY_POLL_MAX_MS = 4e3;
|
|
1678
|
+
const DEFAULT_READY_PROBE_MS = 5e3;
|
|
1546
1679
|
/** Every string a `LocalizedText` carries, for haystack building. */
|
|
1547
1680
|
function localizedValues(text) {
|
|
1548
1681
|
if (typeof text === "string") return [text];
|
|
@@ -1584,11 +1717,13 @@ var StoreClient = class {
|
|
|
1584
1717
|
readyTimeoutMs;
|
|
1585
1718
|
readyPollMs;
|
|
1586
1719
|
readyPollMaxMs;
|
|
1720
|
+
readyProbeMs;
|
|
1587
1721
|
constructor(host, options = {}) {
|
|
1588
1722
|
this.host = host;
|
|
1589
1723
|
this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
1590
1724
|
this.readyPollMs = options.readyPollMs ?? DEFAULT_READY_POLL_MS;
|
|
1591
1725
|
this.readyPollMaxMs = options.readyPollMaxMs ?? DEFAULT_READY_POLL_MAX_MS;
|
|
1726
|
+
this.readyProbeMs = options.readyProbeMs ?? DEFAULT_READY_PROBE_MS;
|
|
1592
1727
|
}
|
|
1593
1728
|
/** Every item across enabled marketplaces, with install state. */
|
|
1594
1729
|
async list() {
|
|
@@ -1620,15 +1755,45 @@ var StoreClient = class {
|
|
|
1620
1755
|
/**
|
|
1621
1756
|
* What a caller can offer for an item's pending update.
|
|
1622
1757
|
*
|
|
1623
|
-
* `"none"` means there is nothing to do; `"
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
1627
|
-
* the item carries no snapshot to release.
|
|
1758
|
+
* `"none"` means there is nothing to do; `"update"` means the entry is
|
|
1759
|
+
* installed at a different version and `update()` is the action that lands it
|
|
1760
|
+
* (`store/update-entry`, `36` D3); `"unknown"` is returned rather than
|
|
1761
|
+
* guessing when the item carries no snapshot to replace.
|
|
1628
1762
|
*/
|
|
1629
1763
|
updateHint(item) {
|
|
1630
1764
|
if (!item.installed || !item.update_available) return "none";
|
|
1631
|
-
return item.snapshot_id ? "
|
|
1765
|
+
return item.snapshot_id ? "update" : "unknown";
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Upgrade an installed item to the version its marketplace advertises.
|
|
1769
|
+
*
|
|
1770
|
+
* The host does the ordering: new version installed first, previous one
|
|
1771
|
+
* released only when that succeeded. So a failure resolves `ok: false` with
|
|
1772
|
+
* the previous installation still in place (`releasedCount === 0`) — the
|
|
1773
|
+
* caller never has to reason about a half-upgraded item.
|
|
1774
|
+
*
|
|
1775
|
+
* Refuses before the round trip when there is nothing to replace: an item that
|
|
1776
|
+
* is not installed (`not_installed`) or is already current is not a failure of
|
|
1777
|
+
* *this* call, and `updateHint()` is the way to ask first.
|
|
1778
|
+
*/
|
|
1779
|
+
async update(item, options = {}) {
|
|
1780
|
+
this.assertUsable(item, options.signal);
|
|
1781
|
+
this.requireSnapshot(item);
|
|
1782
|
+
const result = await this.host.updateStoreEntry(item.marketplace_id, item.entry_name);
|
|
1783
|
+
const components = result.outcomes ?? [];
|
|
1784
|
+
const outcome = {
|
|
1785
|
+
snapshotId: result.snapshot_id,
|
|
1786
|
+
reused: result.reused,
|
|
1787
|
+
components,
|
|
1788
|
+
ok: result.errors.length === 0 && components.every((component) => component.ok),
|
|
1789
|
+
errors: result.errors,
|
|
1790
|
+
warnings: result.warnings,
|
|
1791
|
+
fromVersion: result.previous_version ?? void 0,
|
|
1792
|
+
toVersion: result.version,
|
|
1793
|
+
releasedCount: result.released_count ?? 0
|
|
1794
|
+
};
|
|
1795
|
+
if (options.waitForReady === false) return outcome;
|
|
1796
|
+
return this.awaitReady(outcome, options);
|
|
1632
1797
|
}
|
|
1633
1798
|
/**
|
|
1634
1799
|
* Import (when needed) and install one store item, then wait until it is
|
|
@@ -1642,7 +1807,9 @@ var StoreClient = class {
|
|
|
1642
1807
|
snapshotId: result.snapshot_id,
|
|
1643
1808
|
reused: result.reused,
|
|
1644
1809
|
components,
|
|
1645
|
-
ok: result.errors.length === 0 && components.every((component) => component.ok)
|
|
1810
|
+
ok: result.errors.length === 0 && components.every((component) => component.ok),
|
|
1811
|
+
errors: result.errors,
|
|
1812
|
+
warnings: result.warnings
|
|
1646
1813
|
};
|
|
1647
1814
|
if (options.waitForReady === false) return outcome;
|
|
1648
1815
|
return this.awaitReady(outcome, options);
|
|
@@ -1661,7 +1828,9 @@ var StoreClient = class {
|
|
|
1661
1828
|
snapshotId,
|
|
1662
1829
|
reused: true,
|
|
1663
1830
|
components: [],
|
|
1664
|
-
ok: true
|
|
1831
|
+
ok: true,
|
|
1832
|
+
errors: [],
|
|
1833
|
+
warnings: []
|
|
1665
1834
|
};
|
|
1666
1835
|
const status = await this.host.uninstallInstall(snapshotId, componentIds);
|
|
1667
1836
|
return this.outcomeFromStatus(snapshotId, status, false);
|
|
@@ -1681,7 +1850,9 @@ var StoreClient = class {
|
|
|
1681
1850
|
snapshotId,
|
|
1682
1851
|
reused: true,
|
|
1683
1852
|
components: [],
|
|
1684
|
-
ok: true
|
|
1853
|
+
ok: true,
|
|
1854
|
+
errors: [],
|
|
1855
|
+
warnings: []
|
|
1685
1856
|
};
|
|
1686
1857
|
const status = enabled ? await this.host.enableInstall(snapshotId, componentIds) : await this.host.disableInstall(snapshotId, componentIds);
|
|
1687
1858
|
return this.outcomeFromStatus(snapshotId, status, false);
|
|
@@ -1700,11 +1871,14 @@ var StoreClient = class {
|
|
|
1700
1871
|
}
|
|
1701
1872
|
outcomeFromStatus(snapshotId, status, reused) {
|
|
1702
1873
|
const components = status.outcomes ?? [];
|
|
1874
|
+
const errors = status.errors ?? [];
|
|
1703
1875
|
return {
|
|
1704
1876
|
snapshotId,
|
|
1705
1877
|
reused,
|
|
1706
1878
|
components,
|
|
1707
|
-
ok:
|
|
1879
|
+
ok: errors.length === 0 && components.every((component) => component.ok),
|
|
1880
|
+
errors,
|
|
1881
|
+
warnings: []
|
|
1708
1882
|
};
|
|
1709
1883
|
}
|
|
1710
1884
|
/**
|
|
@@ -1722,11 +1896,15 @@ var StoreClient = class {
|
|
|
1722
1896
|
if (connectors.length > 0) await this.host.enableInstall(snapshotId, connectors.map((component) => component.id));
|
|
1723
1897
|
const deadline = Date.now() + (options.timeoutMs ?? this.readyTimeoutMs);
|
|
1724
1898
|
let interval = this.readyPollMs;
|
|
1899
|
+
let nextProbeAt = 0;
|
|
1725
1900
|
let last = status.components;
|
|
1726
1901
|
for (;;) {
|
|
1902
|
+
const now = Date.now();
|
|
1903
|
+
const allowProbe = now >= nextProbeAt;
|
|
1904
|
+
if (allowProbe) nextProbeAt = now + this.readyProbeMs;
|
|
1727
1905
|
const blocked = (await Promise.all(last.filter((component) => component.state !== "not-installed").map(async (component) => ({
|
|
1728
1906
|
component,
|
|
1729
|
-
verdict: await this.readiness(component)
|
|
1907
|
+
verdict: await this.readiness(component, allowProbe)
|
|
1730
1908
|
})))).find((entry) => !entry.verdict.ready);
|
|
1731
1909
|
if (!blocked) return {
|
|
1732
1910
|
...outcome,
|
|
@@ -1756,8 +1934,12 @@ var StoreClient = class {
|
|
|
1756
1934
|
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
1757
1935
|
* a component it does not understand, and a false timeout on a future kind
|
|
1758
1936
|
* would be worse than passing it through.
|
|
1937
|
+
*
|
|
1938
|
+
* `allowProbe` gates the one kind whose readiness needs a real connection (a
|
|
1939
|
+
* connector); the other kinds answer from local reads, so the cadence does not
|
|
1940
|
+
* apply to them.
|
|
1759
1941
|
*/
|
|
1760
|
-
async readiness(component) {
|
|
1942
|
+
async readiness(component, allowProbe) {
|
|
1761
1943
|
try {
|
|
1762
1944
|
switch (component.kind) {
|
|
1763
1945
|
case "skill": return (await this.host.skills.list()).some((skill) => skill.name === component.name) ? { ready: true } : { ready: false };
|
|
@@ -1766,8 +1948,9 @@ var StoreClient = class {
|
|
|
1766
1948
|
await this.host.teams.get(component.id);
|
|
1767
1949
|
return { ready: true };
|
|
1768
1950
|
case "connector": {
|
|
1769
|
-
if ((await this.host.connectors.test(component.id)).success) return { ready: true };
|
|
1951
|
+
if (allowProbe && (await this.host.connectors.test(component.id)).success) return { ready: true };
|
|
1770
1952
|
const status = await this.host.connectors.status(component.id);
|
|
1953
|
+
if (status.status === "connected") return { ready: true };
|
|
1771
1954
|
if (status.status === "authorization_required" || status.status === "reauthorization_required") return {
|
|
1772
1955
|
ready: false,
|
|
1773
1956
|
terminal: "authorization_required"
|
|
@@ -2054,6 +2237,20 @@ var AppServerClient = class {
|
|
|
2054
2237
|
entry_name: entryName
|
|
2055
2238
|
});
|
|
2056
2239
|
}
|
|
2240
|
+
/**
|
|
2241
|
+
* Upgrade an installed entry to the version its marketplace advertises.
|
|
2242
|
+
*
|
|
2243
|
+
* The host installs the new version before releasing the old one, so a failure
|
|
2244
|
+
* leaves the previous installation usable (`released_count: 0` is the machine
|
|
2245
|
+
* readable form of that). Refused for an entry that is not installed — this is
|
|
2246
|
+
* never a second way to install something.
|
|
2247
|
+
*/
|
|
2248
|
+
async updateStoreEntry(marketplaceId, entryName) {
|
|
2249
|
+
return this.transport.request("store/update-entry", {
|
|
2250
|
+
marketplace_id: marketplaceId,
|
|
2251
|
+
entry_name: entryName
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2057
2254
|
};
|
|
2058
2255
|
//#endregion
|
|
2059
2256
|
//#region src/conversation-handle.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorDetail, ConnectorProbeResult, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ExpertPack, 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";
|
|
1
|
+
import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorCredential, ConnectorDetail, ConnectorProbeResult, ConnectorRegistration, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ExpertPack, 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
|
/**
|
|
@@ -374,6 +374,28 @@ export declare class ConversationSubscription {
|
|
|
374
374
|
}
|
|
375
375
|
//#endregion
|
|
376
376
|
//#region src/connectors.d.ts
|
|
377
|
+
export interface WaitForAuthOptions {
|
|
378
|
+
/** Overall budget in ms; defaults to `DEFAULT_AUTH_TIMEOUT_MS` (120s). */
|
|
379
|
+
timeoutMs?: number;
|
|
380
|
+
/** Poll interval in ms; defaults to 500. The read is a local DB projection. */
|
|
381
|
+
pollMs?: number;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* How a completed wait ended.
|
|
385
|
+
*
|
|
386
|
+
* `error` carries the host's own sentence. It is the shape of every failure that
|
|
387
|
+
* happens *after* `authStart` acknowledged the browser step — the state stays
|
|
388
|
+
* `not_authenticated` (there is no "failed" state on the wire), so the reason is
|
|
389
|
+
* the only signal there is.
|
|
390
|
+
*/
|
|
391
|
+
export type WaitForAuthOutcome = {
|
|
392
|
+
state: "authenticated";
|
|
393
|
+
} | {
|
|
394
|
+
state: "error";
|
|
395
|
+
error: string;
|
|
396
|
+
} | {
|
|
397
|
+
state: "timeout";
|
|
398
|
+
};
|
|
377
399
|
export declare class ConnectorClient {
|
|
378
400
|
private readonly transport;
|
|
379
401
|
constructor(transport: Transport);
|
|
@@ -397,13 +419,86 @@ export declare class ConnectorClient {
|
|
|
397
419
|
* the last segment of its wire method.
|
|
398
420
|
*/
|
|
399
421
|
test(connectorId: string): Promise<ConnectorProbeResult>;
|
|
400
|
-
/**
|
|
422
|
+
/**
|
|
423
|
+
* Check the current OAuth state for a connector.
|
|
424
|
+
*
|
|
425
|
+
* Read `error` too: a flow that failed after the browser step is reported as
|
|
426
|
+
* `state: "not_authenticated"` **plus** `error`, because the wire has no
|
|
427
|
+
* failing state to flip to. {@link waitForAuth} is the loop that does this.
|
|
428
|
+
*/
|
|
401
429
|
authStatus(connectorId: string): Promise<OAuthStatusView>;
|
|
430
|
+
/**
|
|
431
|
+
* The credential form a connector needs filled in, as **this caller** sees it
|
|
432
|
+
* (doc `34` §6.1).
|
|
433
|
+
*
|
|
434
|
+
* `mode` says which kind of authentication applies (`none` / `oauth` / `token`),
|
|
435
|
+
* `status` whether anything is still missing, and `missing` names the keys.
|
|
436
|
+
* `fields` is everything needed to render the form — labels, placeholders,
|
|
437
|
+
* descriptions and the marketplace's "where do I get a key" link, in both
|
|
438
|
+
* languages. No secret ever comes back: only `plain` fields carry a `value`.
|
|
439
|
+
*/
|
|
440
|
+
credentials(connectorId: string): Promise<ConnectorCredential>;
|
|
441
|
+
/**
|
|
442
|
+
* Store what the user typed and get the new state back.
|
|
443
|
+
*
|
|
444
|
+
* Only keys the connector's own declaration names are accepted — the form is
|
|
445
|
+
* the whole write surface. `secret` fields go to the host's credential store
|
|
446
|
+
* under the caller's own namespace, `plain` fields into the connector's
|
|
447
|
+
* configuration, so a shared host never resolves one user's token for another.
|
|
448
|
+
*/
|
|
449
|
+
setCredentials(connectorId: string, values: Record<string, string>): Promise<ConnectorCredential>;
|
|
450
|
+
/**
|
|
451
|
+
* Forget stored credentials. `keys` omitted = every secret field of this
|
|
452
|
+
* connector. Idempotent: clearing what is not there succeeds.
|
|
453
|
+
*/
|
|
454
|
+
clearCredentials(connectorId: string, keys?: string[]): Promise<ConnectorCredential>;
|
|
455
|
+
/**
|
|
456
|
+
* Register a connector this host never imported — your own MCP server, handed
|
|
457
|
+
* over together with the template that says what it needs (doc `34` §6.5).
|
|
458
|
+
*
|
|
459
|
+
* The **template is the whole declaration**: whatever `${secret:NAME}` the URL,
|
|
460
|
+
* headers or env name becomes that connector's credential form, so there is no
|
|
461
|
+
* `token-schema.json` to ship and nothing to install from a marketplace. The
|
|
462
|
+
* connector's own non-secret settings travel in `values`.
|
|
463
|
+
*
|
|
464
|
+
* Secrets do **not** travel here: hand them over with {@link setCredentials},
|
|
465
|
+
* which keeps exactly one write surface for values. The returned detail already
|
|
466
|
+
* carries the credential block, so `missing` tells you which keys are still
|
|
467
|
+
* needed.
|
|
468
|
+
*
|
|
469
|
+
* Re-registering the same name updates it; a builtin server's name is refused.
|
|
470
|
+
* The row is created **disabled** — enabling it still requires a probe that
|
|
471
|
+
* passes — and this method is on the installation-owner-only surface, because
|
|
472
|
+
* choosing where the host reaches is the owner's call.
|
|
473
|
+
*/
|
|
474
|
+
register(registration: ConnectorRegistration): Promise<ConnectorDetail>;
|
|
402
475
|
/**
|
|
403
476
|
* Kick off the OAuth browser flow on the trusted host. Returns the start
|
|
404
|
-
* acknowledgement immediately;
|
|
477
|
+
* acknowledgement immediately; follow it with {@link waitForAuth}.
|
|
478
|
+
*
|
|
479
|
+
* `state: "error"` here is a failure that happened **before** the browser was
|
|
480
|
+
* opened (endpoint discovery, client identity, binding the callback, launching
|
|
481
|
+
* the browser): nothing was shown to the user, so `error` is the whole story
|
|
482
|
+
* and there is nothing to wait for.
|
|
405
483
|
*/
|
|
406
484
|
authStart(connectorId: string): Promise<OAuthStartResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Wait for an authorization that was started (in the browser, or by a
|
|
487
|
+
* previous `authStart`) to finish — and **report why** when it does not.
|
|
488
|
+
*
|
|
489
|
+
* A hand-rolled `while (state !== "authenticated") poll()` loop ends in "it
|
|
490
|
+
* never became authenticated", with the reason sitting unread in the status it
|
|
491
|
+
* already fetched: a token exchange the authorization server refused (a
|
|
492
|
+
* throttling gateway answers `slow_down`), a callback timeout, a CSRF/path
|
|
493
|
+
* mismatch. Those land in `authStatus().error` *only*, and the state stays
|
|
494
|
+
* `not_authenticated` — there is no failing state on the wire.
|
|
495
|
+
*
|
|
496
|
+
* Resolves for every domain outcome (`authenticated` / `error` / `timeout`);
|
|
497
|
+
* rejects only when the status read itself fails (transport or protocol), the
|
|
498
|
+
* same rule as `connector/call`. The wait is bounded by `timeoutMs`; a caller
|
|
499
|
+
* that needs to stop earlier can race the returned promise.
|
|
500
|
+
*/
|
|
501
|
+
waitForAuth(connectorId: string, options?: WaitForAuthOptions): Promise<WaitForAuthOutcome>;
|
|
407
502
|
/** Revoke the OAuth token for a connector. */
|
|
408
503
|
logout(connectorId: string): Promise<void>;
|
|
409
504
|
/**
|
|
@@ -621,6 +716,20 @@ export interface StoreClientOptions {
|
|
|
621
716
|
/** First readiness poll interval in ms; doubles up to `readyPollMaxMs`. */
|
|
622
717
|
readyPollMs?: number;
|
|
623
718
|
readyPollMaxMs?: number;
|
|
719
|
+
/**
|
|
720
|
+
* Minimum gap between two *real probes* of the same connector while waiting
|
|
721
|
+
* for readiness, in ms. Defaults to `DEFAULT_READY_PROBE_MS`.
|
|
722
|
+
*
|
|
723
|
+
* A connector's readiness cannot be read: the status is derived from the last
|
|
724
|
+
* probe, so someone has to run one — but a probe is a real connection
|
|
725
|
+
* (`initialize` + `tools/list`) that also resolves the stored token, which
|
|
726
|
+
* means a refresh request when that token is near expiry, plus one more on a
|
|
727
|
+
* 401. Probing once per poll (the poll starts at `readyPollMs` = 400ms) turned
|
|
728
|
+
* a 30s readiness budget into ~10 connections and up to ~20 token requests
|
|
729
|
+
* against the connector's authorization server — with nobody having asked for
|
|
730
|
+
* anything. Between probes the loop reads the (free, local) status instead.
|
|
731
|
+
*/
|
|
732
|
+
readyProbeMs?: number;
|
|
624
733
|
}
|
|
625
734
|
export interface StoreOperationOutcome {
|
|
626
735
|
/** The snapshot the operation acted on; `null` when nothing was imported. */
|
|
@@ -634,6 +743,18 @@ export interface StoreOperationOutcome {
|
|
|
634
743
|
components: InstallOutcome[];
|
|
635
744
|
/** Every reported component reached the requested state. */
|
|
636
745
|
ok: boolean;
|
|
746
|
+
/**
|
|
747
|
+
* The server's own failure lines, verbatim.
|
|
748
|
+
*
|
|
749
|
+
* `ok: false` alone is not an answer: the host explains *why* (a digest
|
|
750
|
+
* conflict means the market changed content without raising the version; a
|
|
751
|
+
* `preset_update_failed` names the component). Dropping these is what made a
|
|
752
|
+
* refused import unexplainable in the UI (`18` §11 D10), so they are carried
|
|
753
|
+
* rather than folded into `ok`.
|
|
754
|
+
*/
|
|
755
|
+
errors: string[];
|
|
756
|
+
/** The server's non-fatal notes, verbatim (e.g. a component left disabled). */
|
|
757
|
+
warnings: string[];
|
|
637
758
|
/**
|
|
638
759
|
* `undefined` when readiness was not checked. `true` when every component is
|
|
639
760
|
* usable now; `false` when it is not (see `readyIssue`).
|
|
@@ -642,6 +763,16 @@ export interface StoreOperationOutcome {
|
|
|
642
763
|
readyIssue?: StoreReadyIssue;
|
|
643
764
|
/** The component that needs attention, when `readyIssue` is set. */
|
|
644
765
|
readyComponentId?: string;
|
|
766
|
+
/** `update()` only: the version that was installed before this call. */
|
|
767
|
+
fromVersion?: string;
|
|
768
|
+
/** `update()` only: the version now installed. */
|
|
769
|
+
toVersion?: string;
|
|
770
|
+
/**
|
|
771
|
+
* `update()` only: how many components of the replaced snapshot were actually
|
|
772
|
+
* released. `0` means the previous installation was not touched — an update
|
|
773
|
+
* that failed, or a no-op.
|
|
774
|
+
*/
|
|
775
|
+
releasedCount?: number;
|
|
645
776
|
}
|
|
646
777
|
/**
|
|
647
778
|
* The slice of `AppServerClient` this layer orchestrates.
|
|
@@ -653,6 +784,7 @@ export interface StoreOperationOutcome {
|
|
|
653
784
|
export interface StoreHost {
|
|
654
785
|
listStore(): Promise<StoreList>;
|
|
655
786
|
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
787
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
656
788
|
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
657
789
|
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
658
790
|
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
@@ -676,6 +808,7 @@ export declare class StoreClient {
|
|
|
676
808
|
private readonly readyTimeoutMs;
|
|
677
809
|
private readonly readyPollMs;
|
|
678
810
|
private readonly readyPollMaxMs;
|
|
811
|
+
private readonly readyProbeMs;
|
|
679
812
|
constructor(host: StoreHost, options?: StoreClientOptions);
|
|
680
813
|
/** Every item across enabled marketplaces, with install state. */
|
|
681
814
|
list(): Promise<StoreItem[]>;
|
|
@@ -696,13 +829,25 @@ export declare class StoreClient {
|
|
|
696
829
|
/**
|
|
697
830
|
* What a caller can offer for an item's pending update.
|
|
698
831
|
*
|
|
699
|
-
* `"none"` means there is nothing to do; `"
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
|
|
832
|
+
* `"none"` means there is nothing to do; `"update"` means the entry is
|
|
833
|
+
* installed at a different version and `update()` is the action that lands it
|
|
834
|
+
* (`store/update-entry`, `36` D3); `"unknown"` is returned rather than
|
|
835
|
+
* guessing when the item carries no snapshot to replace.
|
|
836
|
+
*/
|
|
837
|
+
updateHint(item: StoreItem): "none" | "update" | "unknown";
|
|
838
|
+
/**
|
|
839
|
+
* Upgrade an installed item to the version its marketplace advertises.
|
|
840
|
+
*
|
|
841
|
+
* The host does the ordering: new version installed first, previous one
|
|
842
|
+
* released only when that succeeded. So a failure resolves `ok: false` with
|
|
843
|
+
* the previous installation still in place (`releasedCount === 0`) — the
|
|
844
|
+
* caller never has to reason about a half-upgraded item.
|
|
845
|
+
*
|
|
846
|
+
* Refuses before the round trip when there is nothing to replace: an item that
|
|
847
|
+
* is not installed (`not_installed`) or is already current is not a failure of
|
|
848
|
+
* *this* call, and `updateHint()` is the way to ask first.
|
|
704
849
|
*/
|
|
705
|
-
|
|
850
|
+
update(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
|
|
706
851
|
/**
|
|
707
852
|
* Import (when needed) and install one store item, then wait until it is
|
|
708
853
|
* usable.
|
|
@@ -744,6 +889,10 @@ export declare class StoreClient {
|
|
|
744
889
|
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
745
890
|
* a component it does not understand, and a false timeout on a future kind
|
|
746
891
|
* would be worse than passing it through.
|
|
892
|
+
*
|
|
893
|
+
* `allowProbe` gates the one kind whose readiness needs a real connection (a
|
|
894
|
+
* connector); the other kinds answer from local reads, so the cadence does not
|
|
895
|
+
* apply to them.
|
|
747
896
|
*/
|
|
748
897
|
private readiness;
|
|
749
898
|
}
|
|
@@ -890,6 +1039,15 @@ export declare class AppServerClient {
|
|
|
890
1039
|
listStore(): Promise<StoreList>;
|
|
891
1040
|
/** One-click install: import (when missing) + register in one call. */
|
|
892
1041
|
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
1042
|
+
/**
|
|
1043
|
+
* Upgrade an installed entry to the version its marketplace advertises.
|
|
1044
|
+
*
|
|
1045
|
+
* The host installs the new version before releasing the old one, so a failure
|
|
1046
|
+
* leaves the previous installation usable (`released_count: 0` is the machine
|
|
1047
|
+
* readable form of that). Refused for an entry that is not installed — this is
|
|
1048
|
+
* never a second way to install something.
|
|
1049
|
+
*/
|
|
1050
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
893
1051
|
}
|
|
894
1052
|
//#endregion
|
|
895
1053
|
//#region src/conversation-handle.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorDetail, ConnectorProbeResult, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ExpertPack, 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";
|
|
1
|
+
import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorCredential, ConnectorDetail, ConnectorProbeResult, ConnectorRegistration, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ExpertPack, 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
|
/**
|
|
@@ -374,6 +374,28 @@ export declare class ConversationSubscription {
|
|
|
374
374
|
}
|
|
375
375
|
//#endregion
|
|
376
376
|
//#region src/connectors.d.ts
|
|
377
|
+
export interface WaitForAuthOptions {
|
|
378
|
+
/** Overall budget in ms; defaults to `DEFAULT_AUTH_TIMEOUT_MS` (120s). */
|
|
379
|
+
timeoutMs?: number;
|
|
380
|
+
/** Poll interval in ms; defaults to 500. The read is a local DB projection. */
|
|
381
|
+
pollMs?: number;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* How a completed wait ended.
|
|
385
|
+
*
|
|
386
|
+
* `error` carries the host's own sentence. It is the shape of every failure that
|
|
387
|
+
* happens *after* `authStart` acknowledged the browser step — the state stays
|
|
388
|
+
* `not_authenticated` (there is no "failed" state on the wire), so the reason is
|
|
389
|
+
* the only signal there is.
|
|
390
|
+
*/
|
|
391
|
+
export type WaitForAuthOutcome = {
|
|
392
|
+
state: "authenticated";
|
|
393
|
+
} | {
|
|
394
|
+
state: "error";
|
|
395
|
+
error: string;
|
|
396
|
+
} | {
|
|
397
|
+
state: "timeout";
|
|
398
|
+
};
|
|
377
399
|
export declare class ConnectorClient {
|
|
378
400
|
private readonly transport;
|
|
379
401
|
constructor(transport: Transport);
|
|
@@ -397,13 +419,86 @@ export declare class ConnectorClient {
|
|
|
397
419
|
* the last segment of its wire method.
|
|
398
420
|
*/
|
|
399
421
|
test(connectorId: string): Promise<ConnectorProbeResult>;
|
|
400
|
-
/**
|
|
422
|
+
/**
|
|
423
|
+
* Check the current OAuth state for a connector.
|
|
424
|
+
*
|
|
425
|
+
* Read `error` too: a flow that failed after the browser step is reported as
|
|
426
|
+
* `state: "not_authenticated"` **plus** `error`, because the wire has no
|
|
427
|
+
* failing state to flip to. {@link waitForAuth} is the loop that does this.
|
|
428
|
+
*/
|
|
401
429
|
authStatus(connectorId: string): Promise<OAuthStatusView>;
|
|
430
|
+
/**
|
|
431
|
+
* The credential form a connector needs filled in, as **this caller** sees it
|
|
432
|
+
* (doc `34` §6.1).
|
|
433
|
+
*
|
|
434
|
+
* `mode` says which kind of authentication applies (`none` / `oauth` / `token`),
|
|
435
|
+
* `status` whether anything is still missing, and `missing` names the keys.
|
|
436
|
+
* `fields` is everything needed to render the form — labels, placeholders,
|
|
437
|
+
* descriptions and the marketplace's "where do I get a key" link, in both
|
|
438
|
+
* languages. No secret ever comes back: only `plain` fields carry a `value`.
|
|
439
|
+
*/
|
|
440
|
+
credentials(connectorId: string): Promise<ConnectorCredential>;
|
|
441
|
+
/**
|
|
442
|
+
* Store what the user typed and get the new state back.
|
|
443
|
+
*
|
|
444
|
+
* Only keys the connector's own declaration names are accepted — the form is
|
|
445
|
+
* the whole write surface. `secret` fields go to the host's credential store
|
|
446
|
+
* under the caller's own namespace, `plain` fields into the connector's
|
|
447
|
+
* configuration, so a shared host never resolves one user's token for another.
|
|
448
|
+
*/
|
|
449
|
+
setCredentials(connectorId: string, values: Record<string, string>): Promise<ConnectorCredential>;
|
|
450
|
+
/**
|
|
451
|
+
* Forget stored credentials. `keys` omitted = every secret field of this
|
|
452
|
+
* connector. Idempotent: clearing what is not there succeeds.
|
|
453
|
+
*/
|
|
454
|
+
clearCredentials(connectorId: string, keys?: string[]): Promise<ConnectorCredential>;
|
|
455
|
+
/**
|
|
456
|
+
* Register a connector this host never imported — your own MCP server, handed
|
|
457
|
+
* over together with the template that says what it needs (doc `34` §6.5).
|
|
458
|
+
*
|
|
459
|
+
* The **template is the whole declaration**: whatever `${secret:NAME}` the URL,
|
|
460
|
+
* headers or env name becomes that connector's credential form, so there is no
|
|
461
|
+
* `token-schema.json` to ship and nothing to install from a marketplace. The
|
|
462
|
+
* connector's own non-secret settings travel in `values`.
|
|
463
|
+
*
|
|
464
|
+
* Secrets do **not** travel here: hand them over with {@link setCredentials},
|
|
465
|
+
* which keeps exactly one write surface for values. The returned detail already
|
|
466
|
+
* carries the credential block, so `missing` tells you which keys are still
|
|
467
|
+
* needed.
|
|
468
|
+
*
|
|
469
|
+
* Re-registering the same name updates it; a builtin server's name is refused.
|
|
470
|
+
* The row is created **disabled** — enabling it still requires a probe that
|
|
471
|
+
* passes — and this method is on the installation-owner-only surface, because
|
|
472
|
+
* choosing where the host reaches is the owner's call.
|
|
473
|
+
*/
|
|
474
|
+
register(registration: ConnectorRegistration): Promise<ConnectorDetail>;
|
|
402
475
|
/**
|
|
403
476
|
* Kick off the OAuth browser flow on the trusted host. Returns the start
|
|
404
|
-
* acknowledgement immediately;
|
|
477
|
+
* acknowledgement immediately; follow it with {@link waitForAuth}.
|
|
478
|
+
*
|
|
479
|
+
* `state: "error"` here is a failure that happened **before** the browser was
|
|
480
|
+
* opened (endpoint discovery, client identity, binding the callback, launching
|
|
481
|
+
* the browser): nothing was shown to the user, so `error` is the whole story
|
|
482
|
+
* and there is nothing to wait for.
|
|
405
483
|
*/
|
|
406
484
|
authStart(connectorId: string): Promise<OAuthStartResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Wait for an authorization that was started (in the browser, or by a
|
|
487
|
+
* previous `authStart`) to finish — and **report why** when it does not.
|
|
488
|
+
*
|
|
489
|
+
* A hand-rolled `while (state !== "authenticated") poll()` loop ends in "it
|
|
490
|
+
* never became authenticated", with the reason sitting unread in the status it
|
|
491
|
+
* already fetched: a token exchange the authorization server refused (a
|
|
492
|
+
* throttling gateway answers `slow_down`), a callback timeout, a CSRF/path
|
|
493
|
+
* mismatch. Those land in `authStatus().error` *only*, and the state stays
|
|
494
|
+
* `not_authenticated` — there is no failing state on the wire.
|
|
495
|
+
*
|
|
496
|
+
* Resolves for every domain outcome (`authenticated` / `error` / `timeout`);
|
|
497
|
+
* rejects only when the status read itself fails (transport or protocol), the
|
|
498
|
+
* same rule as `connector/call`. The wait is bounded by `timeoutMs`; a caller
|
|
499
|
+
* that needs to stop earlier can race the returned promise.
|
|
500
|
+
*/
|
|
501
|
+
waitForAuth(connectorId: string, options?: WaitForAuthOptions): Promise<WaitForAuthOutcome>;
|
|
407
502
|
/** Revoke the OAuth token for a connector. */
|
|
408
503
|
logout(connectorId: string): Promise<void>;
|
|
409
504
|
/**
|
|
@@ -621,6 +716,20 @@ export interface StoreClientOptions {
|
|
|
621
716
|
/** First readiness poll interval in ms; doubles up to `readyPollMaxMs`. */
|
|
622
717
|
readyPollMs?: number;
|
|
623
718
|
readyPollMaxMs?: number;
|
|
719
|
+
/**
|
|
720
|
+
* Minimum gap between two *real probes* of the same connector while waiting
|
|
721
|
+
* for readiness, in ms. Defaults to `DEFAULT_READY_PROBE_MS`.
|
|
722
|
+
*
|
|
723
|
+
* A connector's readiness cannot be read: the status is derived from the last
|
|
724
|
+
* probe, so someone has to run one — but a probe is a real connection
|
|
725
|
+
* (`initialize` + `tools/list`) that also resolves the stored token, which
|
|
726
|
+
* means a refresh request when that token is near expiry, plus one more on a
|
|
727
|
+
* 401. Probing once per poll (the poll starts at `readyPollMs` = 400ms) turned
|
|
728
|
+
* a 30s readiness budget into ~10 connections and up to ~20 token requests
|
|
729
|
+
* against the connector's authorization server — with nobody having asked for
|
|
730
|
+
* anything. Between probes the loop reads the (free, local) status instead.
|
|
731
|
+
*/
|
|
732
|
+
readyProbeMs?: number;
|
|
624
733
|
}
|
|
625
734
|
export interface StoreOperationOutcome {
|
|
626
735
|
/** The snapshot the operation acted on; `null` when nothing was imported. */
|
|
@@ -634,6 +743,18 @@ export interface StoreOperationOutcome {
|
|
|
634
743
|
components: InstallOutcome[];
|
|
635
744
|
/** Every reported component reached the requested state. */
|
|
636
745
|
ok: boolean;
|
|
746
|
+
/**
|
|
747
|
+
* The server's own failure lines, verbatim.
|
|
748
|
+
*
|
|
749
|
+
* `ok: false` alone is not an answer: the host explains *why* (a digest
|
|
750
|
+
* conflict means the market changed content without raising the version; a
|
|
751
|
+
* `preset_update_failed` names the component). Dropping these is what made a
|
|
752
|
+
* refused import unexplainable in the UI (`18` §11 D10), so they are carried
|
|
753
|
+
* rather than folded into `ok`.
|
|
754
|
+
*/
|
|
755
|
+
errors: string[];
|
|
756
|
+
/** The server's non-fatal notes, verbatim (e.g. a component left disabled). */
|
|
757
|
+
warnings: string[];
|
|
637
758
|
/**
|
|
638
759
|
* `undefined` when readiness was not checked. `true` when every component is
|
|
639
760
|
* usable now; `false` when it is not (see `readyIssue`).
|
|
@@ -642,6 +763,16 @@ export interface StoreOperationOutcome {
|
|
|
642
763
|
readyIssue?: StoreReadyIssue;
|
|
643
764
|
/** The component that needs attention, when `readyIssue` is set. */
|
|
644
765
|
readyComponentId?: string;
|
|
766
|
+
/** `update()` only: the version that was installed before this call. */
|
|
767
|
+
fromVersion?: string;
|
|
768
|
+
/** `update()` only: the version now installed. */
|
|
769
|
+
toVersion?: string;
|
|
770
|
+
/**
|
|
771
|
+
* `update()` only: how many components of the replaced snapshot were actually
|
|
772
|
+
* released. `0` means the previous installation was not touched — an update
|
|
773
|
+
* that failed, or a no-op.
|
|
774
|
+
*/
|
|
775
|
+
releasedCount?: number;
|
|
645
776
|
}
|
|
646
777
|
/**
|
|
647
778
|
* The slice of `AppServerClient` this layer orchestrates.
|
|
@@ -653,6 +784,7 @@ export interface StoreOperationOutcome {
|
|
|
653
784
|
export interface StoreHost {
|
|
654
785
|
listStore(): Promise<StoreList>;
|
|
655
786
|
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
787
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
656
788
|
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
657
789
|
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
658
790
|
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
@@ -676,6 +808,7 @@ export declare class StoreClient {
|
|
|
676
808
|
private readonly readyTimeoutMs;
|
|
677
809
|
private readonly readyPollMs;
|
|
678
810
|
private readonly readyPollMaxMs;
|
|
811
|
+
private readonly readyProbeMs;
|
|
679
812
|
constructor(host: StoreHost, options?: StoreClientOptions);
|
|
680
813
|
/** Every item across enabled marketplaces, with install state. */
|
|
681
814
|
list(): Promise<StoreItem[]>;
|
|
@@ -696,13 +829,25 @@ export declare class StoreClient {
|
|
|
696
829
|
/**
|
|
697
830
|
* What a caller can offer for an item's pending update.
|
|
698
831
|
*
|
|
699
|
-
* `"none"` means there is nothing to do; `"
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
|
|
832
|
+
* `"none"` means there is nothing to do; `"update"` means the entry is
|
|
833
|
+
* installed at a different version and `update()` is the action that lands it
|
|
834
|
+
* (`store/update-entry`, `36` D3); `"unknown"` is returned rather than
|
|
835
|
+
* guessing when the item carries no snapshot to replace.
|
|
836
|
+
*/
|
|
837
|
+
updateHint(item: StoreItem): "none" | "update" | "unknown";
|
|
838
|
+
/**
|
|
839
|
+
* Upgrade an installed item to the version its marketplace advertises.
|
|
840
|
+
*
|
|
841
|
+
* The host does the ordering: new version installed first, previous one
|
|
842
|
+
* released only when that succeeded. So a failure resolves `ok: false` with
|
|
843
|
+
* the previous installation still in place (`releasedCount === 0`) — the
|
|
844
|
+
* caller never has to reason about a half-upgraded item.
|
|
845
|
+
*
|
|
846
|
+
* Refuses before the round trip when there is nothing to replace: an item that
|
|
847
|
+
* is not installed (`not_installed`) or is already current is not a failure of
|
|
848
|
+
* *this* call, and `updateHint()` is the way to ask first.
|
|
704
849
|
*/
|
|
705
|
-
|
|
850
|
+
update(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
|
|
706
851
|
/**
|
|
707
852
|
* Import (when needed) and install one store item, then wait until it is
|
|
708
853
|
* usable.
|
|
@@ -744,6 +889,10 @@ export declare class StoreClient {
|
|
|
744
889
|
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
745
890
|
* a component it does not understand, and a false timeout on a future kind
|
|
746
891
|
* would be worse than passing it through.
|
|
892
|
+
*
|
|
893
|
+
* `allowProbe` gates the one kind whose readiness needs a real connection (a
|
|
894
|
+
* connector); the other kinds answer from local reads, so the cadence does not
|
|
895
|
+
* apply to them.
|
|
747
896
|
*/
|
|
748
897
|
private readiness;
|
|
749
898
|
}
|
|
@@ -890,6 +1039,15 @@ export declare class AppServerClient {
|
|
|
890
1039
|
listStore(): Promise<StoreList>;
|
|
891
1040
|
/** One-click install: import (when missing) + register in one call. */
|
|
892
1041
|
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
1042
|
+
/**
|
|
1043
|
+
* Upgrade an installed entry to the version its marketplace advertises.
|
|
1044
|
+
*
|
|
1045
|
+
* The host installs the new version before releasing the old one, so a failure
|
|
1046
|
+
* leaves the previous installation usable (`released_count: 0` is the machine
|
|
1047
|
+
* readable form of that). Refused for an entry that is not installed — this is
|
|
1048
|
+
* never a second way to install something.
|
|
1049
|
+
*/
|
|
1050
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
893
1051
|
}
|
|
894
1052
|
//#endregion
|
|
895
1053
|
//#region src/conversation-handle.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -275,7 +275,7 @@ var WebSocketTransport = class {
|
|
|
275
275
|
/** Header the server mints the per-call connection id under. */
|
|
276
276
|
const CONNECTION_HEADER = "x-app-server-connection-id";
|
|
277
277
|
/** Protocol version this binding announces on the one-shot handshake. */
|
|
278
|
-
const PROTOCOL_VERSION = "fp-
|
|
278
|
+
const PROTOCOL_VERSION = "fp-12";
|
|
279
279
|
/**
|
|
280
280
|
* Protocol method → REST route. Verified from handler bodies, not from the
|
|
281
281
|
* route list (a route existing does not prove which method it mirrors).
|
|
@@ -470,6 +470,26 @@ const HTTP_ROUTES = {
|
|
|
470
470
|
path: "/connectors/:connector_id/auth-logout",
|
|
471
471
|
source: "connector_auth_logout_route() -> connector_auth_logout_impl"
|
|
472
472
|
},
|
|
473
|
+
"connector/credential/get": {
|
|
474
|
+
verb: "GET",
|
|
475
|
+
path: "/connectors/:connector_id/credential",
|
|
476
|
+
source: "connector_credential_get_route() -> connector_credential_get_impl"
|
|
477
|
+
},
|
|
478
|
+
"connector/credential/set": {
|
|
479
|
+
verb: "POST",
|
|
480
|
+
path: "/connectors/:connector_id/credential",
|
|
481
|
+
source: "connector_credential_set_route() -> connector_credential_set_impl"
|
|
482
|
+
},
|
|
483
|
+
"connector/credential/clear": {
|
|
484
|
+
verb: "POST",
|
|
485
|
+
path: "/connectors/:connector_id/credential/clear",
|
|
486
|
+
source: "connector_credential_clear_route() -> connector_credential_clear_impl"
|
|
487
|
+
},
|
|
488
|
+
"connector/register": {
|
|
489
|
+
verb: "POST",
|
|
490
|
+
path: "/connectors",
|
|
491
|
+
source: "register_connector_route() -> register_connector_impl"
|
|
492
|
+
},
|
|
473
493
|
"import/run": {
|
|
474
494
|
verb: "POST",
|
|
475
495
|
path: "/imports",
|
|
@@ -559,6 +579,11 @@ const HTTP_ROUTES = {
|
|
|
559
579
|
verb: "POST",
|
|
560
580
|
path: "/store/:marketplace_id/entries/:entry_name/install",
|
|
561
581
|
source: "store_install_entry_route() -> store_install_entry_impl"
|
|
582
|
+
},
|
|
583
|
+
"store/update-entry": {
|
|
584
|
+
verb: "POST",
|
|
585
|
+
path: "/store/:marketplace_id/entries/:entry_name/update",
|
|
586
|
+
source: "store_update_entry_route() -> store_update_entry_impl"
|
|
562
587
|
}
|
|
563
588
|
};
|
|
564
589
|
/** Methods that are WebSocket-only by design — named so the error explains itself. */
|
|
@@ -1079,6 +1104,13 @@ var ConversationSubscription = class {
|
|
|
1079
1104
|
};
|
|
1080
1105
|
//#endregion
|
|
1081
1106
|
//#region src/connectors.ts
|
|
1107
|
+
/**
|
|
1108
|
+
* How long {@link ConnectorClient.waitForAuth} waits by default: the host's own
|
|
1109
|
+
* callback window is 120s, so a shorter client budget would give up on flows the
|
|
1110
|
+
* host is still willing to finish.
|
|
1111
|
+
*/
|
|
1112
|
+
const DEFAULT_AUTH_TIMEOUT_MS = 12e4;
|
|
1113
|
+
const DEFAULT_AUTH_POLL_MS = 500;
|
|
1082
1114
|
var ConnectorClient = class {
|
|
1083
1115
|
transport;
|
|
1084
1116
|
constructor(transport) {
|
|
@@ -1112,17 +1144,117 @@ var ConnectorClient = class {
|
|
|
1112
1144
|
test(connectorId) {
|
|
1113
1145
|
return this.transport.request("connector/test", { connector_id: connectorId });
|
|
1114
1146
|
}
|
|
1115
|
-
/**
|
|
1147
|
+
/**
|
|
1148
|
+
* Check the current OAuth state for a connector.
|
|
1149
|
+
*
|
|
1150
|
+
* Read `error` too: a flow that failed after the browser step is reported as
|
|
1151
|
+
* `state: "not_authenticated"` **plus** `error`, because the wire has no
|
|
1152
|
+
* failing state to flip to. {@link waitForAuth} is the loop that does this.
|
|
1153
|
+
*/
|
|
1116
1154
|
authStatus(connectorId) {
|
|
1117
1155
|
return this.transport.request("connector/auth/status", { connector_id: connectorId });
|
|
1118
1156
|
}
|
|
1119
1157
|
/**
|
|
1158
|
+
* The credential form a connector needs filled in, as **this caller** sees it
|
|
1159
|
+
* (doc `34` §6.1).
|
|
1160
|
+
*
|
|
1161
|
+
* `mode` says which kind of authentication applies (`none` / `oauth` / `token`),
|
|
1162
|
+
* `status` whether anything is still missing, and `missing` names the keys.
|
|
1163
|
+
* `fields` is everything needed to render the form — labels, placeholders,
|
|
1164
|
+
* descriptions and the marketplace's "where do I get a key" link, in both
|
|
1165
|
+
* languages. No secret ever comes back: only `plain` fields carry a `value`.
|
|
1166
|
+
*/
|
|
1167
|
+
credentials(connectorId) {
|
|
1168
|
+
return this.transport.request("connector/credential/get", { connector_id: connectorId });
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Store what the user typed and get the new state back.
|
|
1172
|
+
*
|
|
1173
|
+
* Only keys the connector's own declaration names are accepted — the form is
|
|
1174
|
+
* the whole write surface. `secret` fields go to the host's credential store
|
|
1175
|
+
* under the caller's own namespace, `plain` fields into the connector's
|
|
1176
|
+
* configuration, so a shared host never resolves one user's token for another.
|
|
1177
|
+
*/
|
|
1178
|
+
setCredentials(connectorId, values) {
|
|
1179
|
+
return this.transport.request("connector/credential/set", {
|
|
1180
|
+
connector_id: connectorId,
|
|
1181
|
+
values
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Forget stored credentials. `keys` omitted = every secret field of this
|
|
1186
|
+
* connector. Idempotent: clearing what is not there succeeds.
|
|
1187
|
+
*/
|
|
1188
|
+
clearCredentials(connectorId, keys) {
|
|
1189
|
+
return this.transport.request("connector/credential/clear", {
|
|
1190
|
+
connector_id: connectorId,
|
|
1191
|
+
...keys ? { keys } : {}
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* Register a connector this host never imported — your own MCP server, handed
|
|
1196
|
+
* over together with the template that says what it needs (doc `34` §6.5).
|
|
1197
|
+
*
|
|
1198
|
+
* The **template is the whole declaration**: whatever `${secret:NAME}` the URL,
|
|
1199
|
+
* headers or env name becomes that connector's credential form, so there is no
|
|
1200
|
+
* `token-schema.json` to ship and nothing to install from a marketplace. The
|
|
1201
|
+
* connector's own non-secret settings travel in `values`.
|
|
1202
|
+
*
|
|
1203
|
+
* Secrets do **not** travel here: hand them over with {@link setCredentials},
|
|
1204
|
+
* which keeps exactly one write surface for values. The returned detail already
|
|
1205
|
+
* carries the credential block, so `missing` tells you which keys are still
|
|
1206
|
+
* needed.
|
|
1207
|
+
*
|
|
1208
|
+
* Re-registering the same name updates it; a builtin server's name is refused.
|
|
1209
|
+
* The row is created **disabled** — enabling it still requires a probe that
|
|
1210
|
+
* passes — and this method is on the installation-owner-only surface, because
|
|
1211
|
+
* choosing where the host reaches is the owner's call.
|
|
1212
|
+
*/
|
|
1213
|
+
register(registration) {
|
|
1214
|
+
return this.transport.request("connector/register", registration);
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1120
1217
|
* Kick off the OAuth browser flow on the trusted host. Returns the start
|
|
1121
|
-
* acknowledgement immediately;
|
|
1218
|
+
* acknowledgement immediately; follow it with {@link waitForAuth}.
|
|
1219
|
+
*
|
|
1220
|
+
* `state: "error"` here is a failure that happened **before** the browser was
|
|
1221
|
+
* opened (endpoint discovery, client identity, binding the callback, launching
|
|
1222
|
+
* the browser): nothing was shown to the user, so `error` is the whole story
|
|
1223
|
+
* and there is nothing to wait for.
|
|
1122
1224
|
*/
|
|
1123
1225
|
authStart(connectorId) {
|
|
1124
1226
|
return this.transport.request("connector/auth/start", { connector_id: connectorId });
|
|
1125
1227
|
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Wait for an authorization that was started (in the browser, or by a
|
|
1230
|
+
* previous `authStart`) to finish — and **report why** when it does not.
|
|
1231
|
+
*
|
|
1232
|
+
* A hand-rolled `while (state !== "authenticated") poll()` loop ends in "it
|
|
1233
|
+
* never became authenticated", with the reason sitting unread in the status it
|
|
1234
|
+
* already fetched: a token exchange the authorization server refused (a
|
|
1235
|
+
* throttling gateway answers `slow_down`), a callback timeout, a CSRF/path
|
|
1236
|
+
* mismatch. Those land in `authStatus().error` *only*, and the state stays
|
|
1237
|
+
* `not_authenticated` — there is no failing state on the wire.
|
|
1238
|
+
*
|
|
1239
|
+
* Resolves for every domain outcome (`authenticated` / `error` / `timeout`);
|
|
1240
|
+
* rejects only when the status read itself fails (transport or protocol), the
|
|
1241
|
+
* same rule as `connector/call`. The wait is bounded by `timeoutMs`; a caller
|
|
1242
|
+
* that needs to stop earlier can race the returned promise.
|
|
1243
|
+
*/
|
|
1244
|
+
async waitForAuth(connectorId, options = {}) {
|
|
1245
|
+
const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_AUTH_TIMEOUT_MS);
|
|
1246
|
+
const pollMs = options.pollMs ?? DEFAULT_AUTH_POLL_MS;
|
|
1247
|
+
for (;;) {
|
|
1248
|
+
const status = await this.authStatus(connectorId);
|
|
1249
|
+
if (status.state === "authenticated") return { state: "authenticated" };
|
|
1250
|
+
if (status.error) return {
|
|
1251
|
+
state: "error",
|
|
1252
|
+
error: status.error
|
|
1253
|
+
};
|
|
1254
|
+
if (Date.now() >= deadline) return { state: "timeout" };
|
|
1255
|
+
await new Promise((resolve) => setTimeout(resolve, pollMs));
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1126
1258
|
/** Revoke the OAuth token for a connector. */
|
|
1127
1259
|
logout(connectorId) {
|
|
1128
1260
|
return this.transport.request("connector/auth/logout", { connector_id: connectorId }).then(() => void 0);
|
|
@@ -1542,6 +1674,7 @@ var StoreError = class extends Error {
|
|
|
1542
1674
|
const DEFAULT_READY_TIMEOUT_MS = 3e4;
|
|
1543
1675
|
const DEFAULT_READY_POLL_MS = 400;
|
|
1544
1676
|
const DEFAULT_READY_POLL_MAX_MS = 4e3;
|
|
1677
|
+
const DEFAULT_READY_PROBE_MS = 5e3;
|
|
1545
1678
|
/** Every string a `LocalizedText` carries, for haystack building. */
|
|
1546
1679
|
function localizedValues(text) {
|
|
1547
1680
|
if (typeof text === "string") return [text];
|
|
@@ -1583,11 +1716,13 @@ var StoreClient = class {
|
|
|
1583
1716
|
readyTimeoutMs;
|
|
1584
1717
|
readyPollMs;
|
|
1585
1718
|
readyPollMaxMs;
|
|
1719
|
+
readyProbeMs;
|
|
1586
1720
|
constructor(host, options = {}) {
|
|
1587
1721
|
this.host = host;
|
|
1588
1722
|
this.readyTimeoutMs = options.readyTimeoutMs ?? DEFAULT_READY_TIMEOUT_MS;
|
|
1589
1723
|
this.readyPollMs = options.readyPollMs ?? DEFAULT_READY_POLL_MS;
|
|
1590
1724
|
this.readyPollMaxMs = options.readyPollMaxMs ?? DEFAULT_READY_POLL_MAX_MS;
|
|
1725
|
+
this.readyProbeMs = options.readyProbeMs ?? DEFAULT_READY_PROBE_MS;
|
|
1591
1726
|
}
|
|
1592
1727
|
/** Every item across enabled marketplaces, with install state. */
|
|
1593
1728
|
async list() {
|
|
@@ -1619,15 +1754,45 @@ var StoreClient = class {
|
|
|
1619
1754
|
/**
|
|
1620
1755
|
* What a caller can offer for an item's pending update.
|
|
1621
1756
|
*
|
|
1622
|
-
* `"none"` means there is nothing to do; `"
|
|
1623
|
-
*
|
|
1624
|
-
*
|
|
1625
|
-
*
|
|
1626
|
-
* the item carries no snapshot to release.
|
|
1757
|
+
* `"none"` means there is nothing to do; `"update"` means the entry is
|
|
1758
|
+
* installed at a different version and `update()` is the action that lands it
|
|
1759
|
+
* (`store/update-entry`, `36` D3); `"unknown"` is returned rather than
|
|
1760
|
+
* guessing when the item carries no snapshot to replace.
|
|
1627
1761
|
*/
|
|
1628
1762
|
updateHint(item) {
|
|
1629
1763
|
if (!item.installed || !item.update_available) return "none";
|
|
1630
|
-
return item.snapshot_id ? "
|
|
1764
|
+
return item.snapshot_id ? "update" : "unknown";
|
|
1765
|
+
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Upgrade an installed item to the version its marketplace advertises.
|
|
1768
|
+
*
|
|
1769
|
+
* The host does the ordering: new version installed first, previous one
|
|
1770
|
+
* released only when that succeeded. So a failure resolves `ok: false` with
|
|
1771
|
+
* the previous installation still in place (`releasedCount === 0`) — the
|
|
1772
|
+
* caller never has to reason about a half-upgraded item.
|
|
1773
|
+
*
|
|
1774
|
+
* Refuses before the round trip when there is nothing to replace: an item that
|
|
1775
|
+
* is not installed (`not_installed`) or is already current is not a failure of
|
|
1776
|
+
* *this* call, and `updateHint()` is the way to ask first.
|
|
1777
|
+
*/
|
|
1778
|
+
async update(item, options = {}) {
|
|
1779
|
+
this.assertUsable(item, options.signal);
|
|
1780
|
+
this.requireSnapshot(item);
|
|
1781
|
+
const result = await this.host.updateStoreEntry(item.marketplace_id, item.entry_name);
|
|
1782
|
+
const components = result.outcomes ?? [];
|
|
1783
|
+
const outcome = {
|
|
1784
|
+
snapshotId: result.snapshot_id,
|
|
1785
|
+
reused: result.reused,
|
|
1786
|
+
components,
|
|
1787
|
+
ok: result.errors.length === 0 && components.every((component) => component.ok),
|
|
1788
|
+
errors: result.errors,
|
|
1789
|
+
warnings: result.warnings,
|
|
1790
|
+
fromVersion: result.previous_version ?? void 0,
|
|
1791
|
+
toVersion: result.version,
|
|
1792
|
+
releasedCount: result.released_count ?? 0
|
|
1793
|
+
};
|
|
1794
|
+
if (options.waitForReady === false) return outcome;
|
|
1795
|
+
return this.awaitReady(outcome, options);
|
|
1631
1796
|
}
|
|
1632
1797
|
/**
|
|
1633
1798
|
* Import (when needed) and install one store item, then wait until it is
|
|
@@ -1641,7 +1806,9 @@ var StoreClient = class {
|
|
|
1641
1806
|
snapshotId: result.snapshot_id,
|
|
1642
1807
|
reused: result.reused,
|
|
1643
1808
|
components,
|
|
1644
|
-
ok: result.errors.length === 0 && components.every((component) => component.ok)
|
|
1809
|
+
ok: result.errors.length === 0 && components.every((component) => component.ok),
|
|
1810
|
+
errors: result.errors,
|
|
1811
|
+
warnings: result.warnings
|
|
1645
1812
|
};
|
|
1646
1813
|
if (options.waitForReady === false) return outcome;
|
|
1647
1814
|
return this.awaitReady(outcome, options);
|
|
@@ -1660,7 +1827,9 @@ var StoreClient = class {
|
|
|
1660
1827
|
snapshotId,
|
|
1661
1828
|
reused: true,
|
|
1662
1829
|
components: [],
|
|
1663
|
-
ok: true
|
|
1830
|
+
ok: true,
|
|
1831
|
+
errors: [],
|
|
1832
|
+
warnings: []
|
|
1664
1833
|
};
|
|
1665
1834
|
const status = await this.host.uninstallInstall(snapshotId, componentIds);
|
|
1666
1835
|
return this.outcomeFromStatus(snapshotId, status, false);
|
|
@@ -1680,7 +1849,9 @@ var StoreClient = class {
|
|
|
1680
1849
|
snapshotId,
|
|
1681
1850
|
reused: true,
|
|
1682
1851
|
components: [],
|
|
1683
|
-
ok: true
|
|
1852
|
+
ok: true,
|
|
1853
|
+
errors: [],
|
|
1854
|
+
warnings: []
|
|
1684
1855
|
};
|
|
1685
1856
|
const status = enabled ? await this.host.enableInstall(snapshotId, componentIds) : await this.host.disableInstall(snapshotId, componentIds);
|
|
1686
1857
|
return this.outcomeFromStatus(snapshotId, status, false);
|
|
@@ -1699,11 +1870,14 @@ var StoreClient = class {
|
|
|
1699
1870
|
}
|
|
1700
1871
|
outcomeFromStatus(snapshotId, status, reused) {
|
|
1701
1872
|
const components = status.outcomes ?? [];
|
|
1873
|
+
const errors = status.errors ?? [];
|
|
1702
1874
|
return {
|
|
1703
1875
|
snapshotId,
|
|
1704
1876
|
reused,
|
|
1705
1877
|
components,
|
|
1706
|
-
ok:
|
|
1878
|
+
ok: errors.length === 0 && components.every((component) => component.ok),
|
|
1879
|
+
errors,
|
|
1880
|
+
warnings: []
|
|
1707
1881
|
};
|
|
1708
1882
|
}
|
|
1709
1883
|
/**
|
|
@@ -1721,11 +1895,15 @@ var StoreClient = class {
|
|
|
1721
1895
|
if (connectors.length > 0) await this.host.enableInstall(snapshotId, connectors.map((component) => component.id));
|
|
1722
1896
|
const deadline = Date.now() + (options.timeoutMs ?? this.readyTimeoutMs);
|
|
1723
1897
|
let interval = this.readyPollMs;
|
|
1898
|
+
let nextProbeAt = 0;
|
|
1724
1899
|
let last = status.components;
|
|
1725
1900
|
for (;;) {
|
|
1901
|
+
const now = Date.now();
|
|
1902
|
+
const allowProbe = now >= nextProbeAt;
|
|
1903
|
+
if (allowProbe) nextProbeAt = now + this.readyProbeMs;
|
|
1726
1904
|
const blocked = (await Promise.all(last.filter((component) => component.state !== "not-installed").map(async (component) => ({
|
|
1727
1905
|
component,
|
|
1728
|
-
verdict: await this.readiness(component)
|
|
1906
|
+
verdict: await this.readiness(component, allowProbe)
|
|
1729
1907
|
})))).find((entry) => !entry.verdict.ready);
|
|
1730
1908
|
if (!blocked) return {
|
|
1731
1909
|
...outcome,
|
|
@@ -1755,8 +1933,12 @@ var StoreClient = class {
|
|
|
1755
1933
|
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
1756
1934
|
* a component it does not understand, and a false timeout on a future kind
|
|
1757
1935
|
* would be worse than passing it through.
|
|
1936
|
+
*
|
|
1937
|
+
* `allowProbe` gates the one kind whose readiness needs a real connection (a
|
|
1938
|
+
* connector); the other kinds answer from local reads, so the cadence does not
|
|
1939
|
+
* apply to them.
|
|
1758
1940
|
*/
|
|
1759
|
-
async readiness(component) {
|
|
1941
|
+
async readiness(component, allowProbe) {
|
|
1760
1942
|
try {
|
|
1761
1943
|
switch (component.kind) {
|
|
1762
1944
|
case "skill": return (await this.host.skills.list()).some((skill) => skill.name === component.name) ? { ready: true } : { ready: false };
|
|
@@ -1765,8 +1947,9 @@ var StoreClient = class {
|
|
|
1765
1947
|
await this.host.teams.get(component.id);
|
|
1766
1948
|
return { ready: true };
|
|
1767
1949
|
case "connector": {
|
|
1768
|
-
if ((await this.host.connectors.test(component.id)).success) return { ready: true };
|
|
1950
|
+
if (allowProbe && (await this.host.connectors.test(component.id)).success) return { ready: true };
|
|
1769
1951
|
const status = await this.host.connectors.status(component.id);
|
|
1952
|
+
if (status.status === "connected") return { ready: true };
|
|
1770
1953
|
if (status.status === "authorization_required" || status.status === "reauthorization_required") return {
|
|
1771
1954
|
ready: false,
|
|
1772
1955
|
terminal: "authorization_required"
|
|
@@ -2053,6 +2236,20 @@ var AppServerClient = class {
|
|
|
2053
2236
|
entry_name: entryName
|
|
2054
2237
|
});
|
|
2055
2238
|
}
|
|
2239
|
+
/**
|
|
2240
|
+
* Upgrade an installed entry to the version its marketplace advertises.
|
|
2241
|
+
*
|
|
2242
|
+
* The host installs the new version before releasing the old one, so a failure
|
|
2243
|
+
* leaves the previous installation usable (`released_count: 0` is the machine
|
|
2244
|
+
* readable form of that). Refused for an entry that is not installed — this is
|
|
2245
|
+
* never a second way to install something.
|
|
2246
|
+
*/
|
|
2247
|
+
async updateStoreEntry(marketplaceId, entryName) {
|
|
2248
|
+
return this.transport.request("store/update-entry", {
|
|
2249
|
+
marketplace_id: marketplaceId,
|
|
2250
|
+
entry_name: entryName
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2056
2253
|
};
|
|
2057
2254
|
//#endregion
|
|
2058
2255
|
//#region src/conversation-handle.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flowy-agent-store/client",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.8",
|
|
4
4
|
"description": "Transport-agnostic App Server client (docs/agent-store/07 §2.2)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dist"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@flowy-agent-store/protocol": "0.1.0-beta.
|
|
28
|
+
"@flowy-agent-store/protocol": "0.1.0-beta.8"
|
|
29
29
|
},
|
|
30
30
|
"scripts": {
|
|
31
31
|
"build": "tsdown",
|