@covia/covia-sdk 1.8.0 → 1.9.0

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.mjs CHANGED
@@ -43,6 +43,15 @@ var GridError = class extends CoviaError {
43
43
  this.responseBody = responseBody;
44
44
  }
45
45
  };
46
+ var UnsupportedVenueFeatureError = class extends CoviaError {
47
+ constructor(feature) {
48
+ super(
49
+ `The connected venue cannot serve ${feature} without creating a job. Invoke the corresponding operation explicitly if creating a persisted job is intended.`
50
+ );
51
+ this.feature = feature;
52
+ this.name = "UnsupportedVenueFeatureError";
53
+ }
54
+ };
46
55
  var CoviaConnectionError = class extends CoviaError {
47
56
  constructor(message, options = {}) {
48
57
  super(message);
@@ -112,6 +121,14 @@ var JobNotFoundError = class extends NotFoundError {
112
121
  this.jobId = jobId;
113
122
  }
114
123
  };
124
+ var AgentSessionNotFoundError = class extends CoviaError {
125
+ constructor(agentId, sessionId) {
126
+ super(`Agent session not found: ${agentId}/${sessionId}`, 404);
127
+ this.agentId = agentId;
128
+ this.sessionId = sessionId;
129
+ this.name = "AgentSessionNotFoundError";
130
+ }
131
+ };
115
132
 
116
133
  // node_modules/.pnpm/@noble+ed25519@3.1.0/node_modules/@noble/ed25519/index.js
117
134
  var ed25519_CURVE = Object.freeze({
@@ -1031,9 +1048,13 @@ function privateKeyToHex(key) {
1031
1048
  return Array.from(key).map((b) => b.toString(16).padStart(2, "0")).join("");
1032
1049
  }
1033
1050
  function hexToPrivateKey(hex) {
1034
- const bytes = new Uint8Array(hex.length / 2);
1051
+ const cleaned = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
1052
+ if (!/^[0-9a-fA-F]{64}$/.test(cleaned)) {
1053
+ throw new Error("Invalid Ed25519 private key: expected 64 hex characters (32 bytes)");
1054
+ }
1055
+ const bytes = new Uint8Array(32);
1035
1056
  for (let i = 0; i < bytes.length; i++) {
1036
- bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
1057
+ bytes[i] = parseInt(cleaned.substring(i * 2, i * 2 + 2), 16);
1037
1058
  }
1038
1059
  return bytes;
1039
1060
  }
@@ -1147,6 +1168,14 @@ function createEdDSAJWT(privateKey, lifetimeSeconds = 300, audience) {
1147
1168
  }
1148
1169
 
1149
1170
  // src/Credentials.ts
1171
+ function base64Encode(text) {
1172
+ const bytes = new TextEncoder().encode(text);
1173
+ let binary = "";
1174
+ for (let i = 0; i < bytes.length; i++) {
1175
+ binary += String.fromCharCode(bytes[i]);
1176
+ }
1177
+ return btoa(binary);
1178
+ }
1150
1179
  var Auth = class {
1151
1180
  };
1152
1181
  var NoAuth = class extends Auth {
@@ -1169,7 +1198,7 @@ var BasicAuth = class extends Auth {
1169
1198
  this._password = password;
1170
1199
  }
1171
1200
  apply(headers, _audience) {
1172
- const credentials = Buffer.from(`${this._username}:${this._password}`, "utf-8").toString("base64");
1201
+ const credentials = base64Encode(`${this._username}:${this._password}`);
1173
1202
  headers["Authorization"] = `Basic ${credentials}`;
1174
1203
  }
1175
1204
  };
@@ -1186,7 +1215,13 @@ var Ed25519Auth = class _Ed25519Auth extends Auth {
1186
1215
  this._lifetime = tokenLifetimeSeconds;
1187
1216
  }
1188
1217
  apply(headers, audience) {
1189
- const jwt = createEdDSAJWT(this._privateKey, this._lifetime, this._audience ?? audience);
1218
+ const aud = this._audience ?? (audience || void 0);
1219
+ if (!aud) {
1220
+ throw new CoviaError(
1221
+ "Ed25519Auth requires a venue audience to bind the token to: connect via Grid.connect()/Venue.connect() so the venue DID is known, or pin auth.audience explicitly."
1222
+ );
1223
+ }
1224
+ const jwt = createEdDSAJWT(this._privateKey, this._lifetime, aud);
1190
1225
  headers["Authorization"] = `Bearer ${jwt}`;
1191
1226
  }
1192
1227
  /** The caller's DID derived from the public key. */
@@ -1300,17 +1335,24 @@ async function parseErrorBody(response) {
1300
1335
  let body = null;
1301
1336
  let message = `Request failed with status ${response.status}`;
1302
1337
  try {
1303
- body = await response.json();
1338
+ if (typeof response.text === "function") {
1339
+ const text = await response.text();
1340
+ if (text) {
1341
+ try {
1342
+ body = JSON.parse(text);
1343
+ } catch {
1344
+ body = text;
1345
+ message = text;
1346
+ }
1347
+ }
1348
+ } else {
1349
+ body = await response.json();
1350
+ }
1304
1351
  if (body && typeof body === "object" && "error" in body) {
1305
1352
  const err2 = body.error;
1306
1353
  if (typeof err2 === "string") message = err2;
1307
1354
  }
1308
1355
  } catch {
1309
- try {
1310
- const text = await response.text();
1311
- if (text) message = text;
1312
- } catch {
1313
- }
1314
1356
  }
1315
1357
  return { message, body };
1316
1358
  }
@@ -1604,13 +1646,82 @@ var AdapterManager = class {
1604
1646
  }
1605
1647
  };
1606
1648
 
1607
- // src/AgentManager.ts
1649
+ // src/VenueTransport.ts
1650
+ function requestOptions(venue, options) {
1651
+ const {
1652
+ contentType,
1653
+ headers: suppliedHeaders = {},
1654
+ ...request
1655
+ } = options;
1656
+ const headers = { ...suppliedHeaders };
1657
+ const effectiveContentType = contentType !== void 0 ? contentType : request.body != null ? "application/json" : null;
1658
+ if (effectiveContentType && headers["Content-Type"] === void 0) {
1659
+ headers["Content-Type"] = effectiveContentType;
1660
+ }
1661
+ venue.auth.apply(headers, venue.venueId);
1662
+ return { ...request, headers };
1663
+ }
1664
+ function venueJson(venue, path, options = {}) {
1665
+ return fetchWithError(
1666
+ `${venue.baseUrl}${path}`,
1667
+ requestOptions(venue, options)
1668
+ );
1669
+ }
1670
+ function venueStream(venue, path, options = {}) {
1671
+ return fetchStreamWithError(
1672
+ `${venue.baseUrl}${path}`,
1673
+ requestOptions(venue, options)
1674
+ );
1675
+ }
1676
+
1677
+ // src/venue-features.ts
1608
1678
  function versionAtLeast(version, major, minor) {
1609
1679
  const m = version?.match(/^(\d+)\.(\d+)/);
1610
1680
  if (!m) return true;
1611
1681
  const [maj, min] = [Number(m[1]), Number(m[2])];
1612
1682
  return maj > major || maj === major && min >= minor;
1613
1683
  }
1684
+ var ROUTE_MISSING_404 = /\bEndpoint (GET|POST|PUT|DELETE|PATCH|HEAD) /;
1685
+
1686
+ // src/values-util.ts
1687
+ function record(value) {
1688
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1689
+ }
1690
+ var SLICE_PAGE_SIZE = 200;
1691
+ async function sliceAll(workspace, path, pageSize = SLICE_PAGE_SIZE) {
1692
+ const items = [];
1693
+ let offset = 0;
1694
+ for (; ; ) {
1695
+ const page = await workspace.slice(path, offset, pageSize);
1696
+ if (!page.exists) {
1697
+ if (items.length === 0) return items;
1698
+ throw new CoviaError(`Sliceable value at ${path} disappeared mid-read`);
1699
+ }
1700
+ const values = page.values ?? [];
1701
+ items.push(...values);
1702
+ const nextOffset = (page.offset ?? offset) + values.length;
1703
+ const done = page.count !== void 0 ? nextOffset >= page.count : values.length < pageSize;
1704
+ if (values.length === 0 || done) break;
1705
+ if (nextOffset <= offset) {
1706
+ throw new CoviaError(`Venue returned a slice of ${path} that did not advance`);
1707
+ }
1708
+ offset = nextOffset;
1709
+ }
1710
+ return items;
1711
+ }
1712
+
1713
+ // src/AgentManager.ts
1714
+ function sessionRecord(sessionId, value) {
1715
+ const session = record(value) ?? {};
1716
+ const meta = record(session.meta);
1717
+ return {
1718
+ id: sessionId,
1719
+ metadata: meta ?? {},
1720
+ pending: Array.isArray(session.pending) ? session.pending : [],
1721
+ frames: Array.isArray(session.frames) ? session.frames : [],
1722
+ wakeTime: typeof session.wakeTime === "number" ? session.wakeTime : void 0
1723
+ };
1724
+ }
1614
1725
  var AgentManager = class {
1615
1726
  constructor(venue) {
1616
1727
  this.venue = venue;
@@ -1618,11 +1729,6 @@ var AgentManager = class {
1618
1729
  // pre-0.4 venues pay the probe once, not one failed GET per read (covia#180).
1619
1730
  this.agentsGetSupported = true;
1620
1731
  }
1621
- _headers() {
1622
- const headers = { "Content-Type": "application/json" };
1623
- this.venue.auth.apply(headers, this.venue.venueId);
1624
- return headers;
1625
- }
1626
1732
  /** Whether the venue serves `GET /api/v1/agents`: no if a probe already
1627
1733
  * 404'd, or if the venue's last known status identifies it as pre-0.4. */
1628
1734
  supportsAgentsGet() {
@@ -1633,25 +1739,25 @@ var AgentManager = class {
1633
1739
  }
1634
1740
  return this.agentsGetSupported;
1635
1741
  }
1636
- /** A job-free agents GET, falling back to the invoke path on pre-0.4 venues. */
1637
- async _agentsOr(path, params, fallback) {
1742
+ /** A job-free agents GET that rejects when the endpoint is unavailable. */
1743
+ async agentsGet(path, params) {
1638
1744
  if (this.supportsAgentsGet()) {
1639
1745
  try {
1640
1746
  const qs = new URLSearchParams();
1641
1747
  for (const [k, v] of Object.entries(params)) if (v !== void 0) qs.set(k, String(v));
1642
1748
  const q = qs.toString();
1643
- return await fetchWithError(
1644
- `${this.venue.baseUrl}/api/v1/agents${path}${q ? `?${q}` : ""}`,
1645
- { headers: this._headers() }
1749
+ return await venueJson(
1750
+ this.venue,
1751
+ `/api/v1/agents${path}${q ? `?${q}` : ""}`
1646
1752
  );
1647
1753
  } catch (e) {
1648
1754
  if (!(e instanceof NotFoundError)) throw e;
1649
- const routeMissing = path === "" || /\bEndpoint (GET|POST|PUT|DELETE|PATCH|HEAD) /.test(e.message);
1755
+ const routeMissing = path === "" || ROUTE_MISSING_404.test(e.message);
1650
1756
  if (!routeMissing) throw e;
1651
1757
  this.agentsGetSupported = false;
1652
1758
  }
1653
1759
  }
1654
- return fallback();
1760
+ throw new UnsupportedVenueFeatureError("agent reads");
1655
1761
  }
1656
1762
  async create(input) {
1657
1763
  return this.venue.operations.run("v/ops/agent/create", input);
@@ -1696,10 +1802,10 @@ var AgentManager = class {
1696
1802
  /**
1697
1803
  * List the caller's agents. **Job-free** on covia ≥ 0.4: goes through
1698
1804
  * `GET /api/v1/agents` (covia#180) — synchronous, no Job persisted. Older
1699
- * venues transparently fall back to the invoke path (one probe, remembered).
1805
+ * venues reject rather than silently creating a persisted Job.
1700
1806
  */
1701
1807
  async list(includeTerminated) {
1702
- const result = await this._agentsOr("", { includeTerminated }, () => this.venue.operations.run("v/ops/agent/list", { includeTerminated }));
1808
+ const result = await this.agentsGet("", { includeTerminated });
1703
1809
  const raw = result?.agents ?? [];
1704
1810
  const agents = raw.map((a) => typeof a === "string" ? { agentId: a } : a);
1705
1811
  return { agents };
@@ -1720,9 +1826,61 @@ var AgentManager = class {
1720
1826
  return this.venue.operations.run("v/ops/agent/cancel-task", { agentId, taskId });
1721
1827
  }
1722
1828
  /** Agent info. **Job-free** on covia ≥ 0.4 (`GET /api/v1/agents/{id}`,
1723
- * covia#180); older venues fall back to the invoke path. */
1829
+ * covia#180); older venues require an explicit compatibility opt-in. */
1724
1830
  async info(agentId) {
1725
- return this._agentsOr(`/${encodeURIComponent(agentId)}`, {}, () => this.venue.operations.run("v/ops/agent/info", { agentId }));
1831
+ return this.agentsGet(`/${encodeURIComponent(agentId)}`, {});
1832
+ }
1833
+ /**
1834
+ * Page an agent's sessions directly from `g/<agentId>/sessions`.
1835
+ * This is a job-free Values read; session adapter-specific fields remain
1836
+ * available on each record's `value` while common runtime fields are typed.
1837
+ */
1838
+ async listSessions(agentId, options = {}) {
1839
+ const result = await this.venue.workspace.slice(
1840
+ `g/${agentId}/sessions`,
1841
+ options.offset,
1842
+ options.limit
1843
+ );
1844
+ const sessions = (result.values ?? []).flatMap((entry) => {
1845
+ const pair = record(entry);
1846
+ if (!pair || typeof pair.key !== "string") return [];
1847
+ return [sessionRecord(pair.key, pair.value)];
1848
+ });
1849
+ return {
1850
+ items: sessions,
1851
+ total: result.count ?? sessions.length,
1852
+ offset: result.offset ?? options.offset ?? 0,
1853
+ limit: options.limit ?? sessions.length
1854
+ };
1855
+ }
1856
+ /** Read one agent session directly from the workspace Values surface. */
1857
+ async getSession(agentId, sessionId) {
1858
+ const path = `g/${agentId}/sessions/${sessionId}`;
1859
+ const result = await this.venue.workspace.read(path);
1860
+ if (!result.exists) {
1861
+ throw new AgentSessionNotFoundError(agentId, sessionId);
1862
+ }
1863
+ if (result.truncated) {
1864
+ return this._readLargeSession(path, sessionId);
1865
+ }
1866
+ if (result.value === void 0 || result.value === null) {
1867
+ throw new AgentSessionNotFoundError(agentId, sessionId);
1868
+ }
1869
+ return sessionRecord(sessionId, result.value);
1870
+ }
1871
+ async _readLargeSession(path, sessionId) {
1872
+ const [meta, wakeTime, pending, frames] = await Promise.all([
1873
+ this.venue.workspace.read(`${path}/meta`),
1874
+ this.venue.workspace.read(`${path}/wakeTime`),
1875
+ sliceAll(this.venue.workspace, `${path}/pending`),
1876
+ sliceAll(this.venue.workspace, `${path}/frames`)
1877
+ ]);
1878
+ return sessionRecord(sessionId, {
1879
+ meta: meta.exists ? meta.value : void 0,
1880
+ wakeTime: wakeTime.exists ? wakeTime.value : void 0,
1881
+ pending,
1882
+ frames
1883
+ });
1726
1884
  }
1727
1885
  async fork(input) {
1728
1886
  return this.venue.operations.run("v/ops/agent/fork", input);
@@ -1884,23 +2042,50 @@ var Job = class {
1884
2042
  };
1885
2043
 
1886
2044
  // src/JobManager.ts
2045
+ function jobPage(value) {
2046
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2047
+ throw new CoviaError("Venue returned an invalid jobs listing");
2048
+ }
2049
+ const page = value;
2050
+ if (!Array.isArray(page.items) || !page.items.every((item) => typeof item === "string") || !Number.isInteger(page.total) || (page.total ?? -1) < 0 || !Number.isInteger(page.offset) || (page.offset ?? -1) < 0 || !Number.isInteger(page.limit) || (page.limit ?? -1) < 0) {
2051
+ throw new CoviaError("Venue returned an invalid jobs page");
2052
+ }
2053
+ return page;
2054
+ }
1887
2055
  var JobManager = class {
1888
2056
  constructor(venue) {
1889
2057
  this.venue = venue;
1890
2058
  }
1891
2059
  async list() {
1892
- const body = await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs`, {
1893
- headers: this._buildHeaders()
1894
- });
1895
- if (Array.isArray(body)) return body;
1896
- const items = body?.items;
1897
- return Array.isArray(items) ? items : [];
2060
+ const items = [];
2061
+ let offset = 0;
2062
+ let targetTotal;
2063
+ while (targetTotal === void 0 || items.length < targetTotal) {
2064
+ const body = await venueJson(
2065
+ this.venue,
2066
+ `/api/v1/jobs?offset=${offset}&limit=1000`
2067
+ );
2068
+ if (Array.isArray(body)) {
2069
+ if (!body.every((item) => typeof item === "string")) {
2070
+ throw new CoviaError("Venue returned an invalid jobs listing");
2071
+ }
2072
+ return body;
2073
+ }
2074
+ const page = jobPage(body);
2075
+ targetTotal ?? (targetTotal = page.total);
2076
+ items.push(...page.items);
2077
+ if (page.items.length === 0) break;
2078
+ const nextOffset = page.offset + page.items.length;
2079
+ if (nextOffset <= offset) {
2080
+ throw new CoviaError("Venue returned a jobs page that did not advance");
2081
+ }
2082
+ offset = nextOffset;
2083
+ }
2084
+ return items;
1898
2085
  }
1899
2086
  async get(jobId) {
1900
2087
  try {
1901
- const data = await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}`, {
1902
- headers: this._buildHeaders()
1903
- });
2088
+ const data = await venueJson(this.venue, `/api/v1/jobs/${jobId}`);
1904
2089
  return new Job(jobId, this.venue, data);
1905
2090
  } catch (error) {
1906
2091
  if (error instanceof NotFoundError) {
@@ -1911,9 +2096,8 @@ var JobManager = class {
1911
2096
  }
1912
2097
  async cancel(jobId) {
1913
2098
  try {
1914
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/cancel`, {
1915
- method: "PUT",
1916
- headers: this._buildHeaders()
2099
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/cancel`, {
2100
+ method: "PUT"
1917
2101
  });
1918
2102
  } catch (error) {
1919
2103
  if (error instanceof NotFoundError) {
@@ -1924,9 +2108,8 @@ var JobManager = class {
1924
2108
  }
1925
2109
  async delete(jobId) {
1926
2110
  try {
1927
- await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/delete`, {
1928
- method: "PUT",
1929
- headers: this._buildHeaders()
2111
+ await venueStream(this.venue, `/api/v1/jobs/${jobId}/delete`, {
2112
+ method: "PUT"
1930
2113
  });
1931
2114
  } catch (error) {
1932
2115
  if (error instanceof NotFoundError) {
@@ -1937,9 +2120,8 @@ var JobManager = class {
1937
2120
  }
1938
2121
  async pause(jobId) {
1939
2122
  try {
1940
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/pause`, {
1941
- method: "PUT",
1942
- headers: this._buildHeaders()
2123
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/pause`, {
2124
+ method: "PUT"
1943
2125
  });
1944
2126
  } catch (error) {
1945
2127
  if (error instanceof NotFoundError) {
@@ -1950,9 +2132,8 @@ var JobManager = class {
1950
2132
  }
1951
2133
  async resume(jobId) {
1952
2134
  try {
1953
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/resume`, {
1954
- method: "PUT",
1955
- headers: this._buildHeaders()
2135
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/resume`, {
2136
+ method: "PUT"
1956
2137
  });
1957
2138
  } catch (error) {
1958
2139
  if (error instanceof NotFoundError) {
@@ -1963,9 +2144,8 @@ var JobManager = class {
1963
2144
  }
1964
2145
  async sendMessage(jobId, message) {
1965
2146
  try {
1966
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}`, {
2147
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}`, {
1967
2148
  method: "POST",
1968
- headers: this._buildHeaders(),
1969
2149
  body: JSON.stringify(message)
1970
2150
  });
1971
2151
  } catch (error) {
@@ -1976,16 +2156,11 @@ var JobManager = class {
1976
2156
  }
1977
2157
  }
1978
2158
  async *stream(jobId) {
1979
- const response = await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/sse`, {
1980
- headers: { ...this._buildHeaders(), "Accept": "text/event-stream" }
2159
+ const response = await venueStream(this.venue, `/api/v1/jobs/${jobId}/sse`, {
2160
+ headers: { "Accept": "text/event-stream" }
1981
2161
  });
1982
2162
  yield* parseSSEStream(response);
1983
2163
  }
1984
- _buildHeaders() {
1985
- const headers = { "Content-Type": "application/json" };
1986
- this.venue.auth.apply(headers, this.venue.venueId);
1987
- return headers;
1988
- }
1989
2164
  };
1990
2165
 
1991
2166
  // src/Asset.ts
@@ -2065,10 +2240,12 @@ var DataAsset = class extends Asset {
2065
2240
  };
2066
2241
 
2067
2242
  // src/AssetManager.ts
2068
- var cache = /* @__PURE__ */ new Map();
2069
2243
  var AssetManager = class {
2070
2244
  constructor(venue) {
2071
2245
  this.venue = venue;
2246
+ // Memory cache is manager/venue-local. Content-addressed metadata may still
2247
+ // be shared deliberately through the configured persistent metadata store.
2248
+ this.cache = /* @__PURE__ */ new Map();
2072
2249
  }
2073
2250
  /**
2074
2251
  * Get an asset by lattice address.
@@ -2081,23 +2258,25 @@ var AssetManager = class {
2081
2258
  * @returns Returns either an Operation or DataAsset based on the asset's metadata
2082
2259
  */
2083
2260
  async get(assetId) {
2084
- if (cache.has(assetId)) {
2085
- return this._wrap(assetId, cache.get(assetId));
2261
+ if (this.cache.has(assetId)) {
2262
+ return this._wrap(assetId, this.cache.get(assetId));
2086
2263
  }
2087
2264
  const hash = assetHash(assetId);
2088
2265
  if (hash) {
2089
2266
  const persisted = getAssetMetadataStore()?.get(normaliseHash(hash));
2090
2267
  if (persisted) {
2091
- cache.set(assetId, persisted);
2268
+ this.cache.set(assetId, persisted);
2092
2269
  return this._wrap(assetId, persisted);
2093
2270
  }
2094
2271
  }
2095
2272
  try {
2096
- const data = await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}`, {
2097
- headers: this._buildHeaders(null)
2098
- });
2273
+ const data = await venueJson(
2274
+ this.venue,
2275
+ `/api/v1/assets/${assetId}`,
2276
+ { contentType: null }
2277
+ );
2099
2278
  if (hash) {
2100
- cache.set(assetId, data);
2279
+ this.cache.set(assetId, data);
2101
2280
  getAssetMetadataStore()?.put(normaliseHash(hash), data);
2102
2281
  }
2103
2282
  return this._wrap(assetId, data);
@@ -2124,18 +2303,19 @@ var AssetManager = class {
2124
2303
  if (options.limit !== void 0) {
2125
2304
  params.set("limit", String(options.limit));
2126
2305
  }
2127
- return fetchWithError(`${this.venue.baseUrl}/api/v1/assets?${params.toString()}`, {
2128
- headers: this._buildHeaders(null)
2129
- });
2306
+ return venueJson(
2307
+ this.venue,
2308
+ `/api/v1/assets?${params.toString()}`,
2309
+ { contentType: null }
2310
+ );
2130
2311
  }
2131
2312
  /**
2132
2313
  * Register a new asset
2133
2314
  * @param assetData - Asset configuration
2134
2315
  */
2135
2316
  async register(assetData) {
2136
- return fetchWithError(`${this.venue.baseUrl}/api/v1/assets`, {
2317
+ return venueJson(this.venue, "/api/v1/assets", {
2137
2318
  method: "POST",
2138
- headers: this._buildHeaders(),
2139
2319
  body: JSON.stringify(assetData)
2140
2320
  }).then((response) => this.get(response));
2141
2321
  }
@@ -2145,9 +2325,11 @@ var AssetManager = class {
2145
2325
  */
2146
2326
  async getMetadata(assetId) {
2147
2327
  try {
2148
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}`, {
2149
- headers: this._buildHeaders(null)
2150
- });
2328
+ return await venueJson(
2329
+ this.venue,
2330
+ `/api/v1/assets/${assetId}`,
2331
+ { contentType: null }
2332
+ );
2151
2333
  } catch (error) {
2152
2334
  if (error instanceof NotFoundError) {
2153
2335
  throw new AssetNotFoundError(assetId);
@@ -2163,9 +2345,9 @@ var AssetManager = class {
2163
2345
  */
2164
2346
  async putContent(assetId, content) {
2165
2347
  try {
2166
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}/content`, {
2348
+ return await venueJson(this.venue, `/api/v1/assets/${assetId}/content`, {
2167
2349
  method: "PUT",
2168
- headers: this._buildHeaders(null),
2350
+ contentType: null,
2169
2351
  body: content
2170
2352
  });
2171
2353
  } catch (error) {
@@ -2181,9 +2363,11 @@ var AssetManager = class {
2181
2363
  */
2182
2364
  async getContent(assetId) {
2183
2365
  try {
2184
- const response = await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}/content`, {
2185
- headers: this._buildHeaders(null)
2186
- });
2366
+ const response = await venueStream(
2367
+ this.venue,
2368
+ `/api/v1/assets/${assetId}/content`,
2369
+ { contentType: null }
2370
+ );
2187
2371
  return response.body;
2188
2372
  } catch (error) {
2189
2373
  if (error instanceof NotFoundError) {
@@ -2204,17 +2388,11 @@ var AssetManager = class {
2204
2388
  * Clear the asset cache.
2205
2389
  */
2206
2390
  clearCache() {
2207
- cache.clear();
2208
- getAssetMetadataStore()?.clear();
2391
+ this.cache.clear();
2209
2392
  }
2210
- // `contentType` defaults to JSON for the metadata/register endpoints. Pass
2211
- // `undefined` for binary content upload so fetch infers the type from the
2212
- // body — forcing application/json would mislabel a Blob/ArrayBuffer/stream.
2213
- _buildHeaders(contentType = "application/json") {
2214
- const headers = {};
2215
- if (contentType) headers["Content-Type"] = contentType;
2216
- this.venue.auth.apply(headers, this.venue.venueId);
2217
- return headers;
2393
+ /** Clear the explicitly configured cross-session metadata store. */
2394
+ clearPersistentCache() {
2395
+ getAssetMetadataStore()?.clear();
2218
2396
  }
2219
2397
  };
2220
2398
 
@@ -2227,23 +2405,23 @@ var OperationManager = class {
2227
2405
  * List all named operations available on this venue
2228
2406
  */
2229
2407
  async list() {
2230
- return fetchWithError(`${this.venue.baseUrl}/api/v1/operations`);
2408
+ return venueJson(this.venue, "/api/v1/operations");
2231
2409
  }
2232
2410
  /**
2233
2411
  * Get details of a named operation
2234
2412
  * @param name - Operation name (e.g., "v/ops/schema/infer")
2235
2413
  */
2236
2414
  async get(name) {
2237
- return fetchWithError(`${this.venue.baseUrl}/api/v1/operations/${name}`);
2415
+ return venueJson(this.venue, `/api/v1/operations/${name}`);
2238
2416
  }
2239
2417
  /**
2240
2418
  * Execute an operation and wait for the result
2241
2419
  * @param assetId - Operation asset ID or named operation
2242
2420
  * @param input - Operation input parameters
2243
- * @param options - Invoke options (e.g., ucans)
2421
+ * @param options - Execution options (e.g., ucans or private execution)
2244
2422
  */
2245
2423
  async run(assetId, input, options) {
2246
- if (this.venue.privateJobs) {
2424
+ if (options?.private ?? this.venue.privateJobs) {
2247
2425
  return this._runPrivate(assetId, input, options);
2248
2426
  }
2249
2427
  const job = await this.invoke(assetId, input, options);
@@ -2265,9 +2443,8 @@ var OperationManager = class {
2265
2443
  wait: true
2266
2444
  };
2267
2445
  if (options?.ucans) payload.ucans = options.ucans;
2268
- const rec = await fetchWithError(`${this.venue.baseUrl}/api/v1/invoke`, {
2446
+ const rec = await venueJson(this.venue, "/api/v1/invoke", {
2269
2447
  method: "POST",
2270
- headers: this._buildHeaders(),
2271
2448
  body: JSON.stringify(payload)
2272
2449
  });
2273
2450
  const status = rec.status;
@@ -2279,6 +2456,7 @@ var OperationManager = class {
2279
2456
  const job = new Job(rec.id ?? "", this.venue, rec);
2280
2457
  return await job.result();
2281
2458
  } catch (e) {
2459
+ if (!(e instanceof NotFoundError)) throw e;
2282
2460
  throw new CoviaError(
2283
2461
  `Private job completed while unobserved \u2014 its record is already forgotten, so the result could not be collected. Private jobs that outlive the venue wait window cannot be reliably collected; consider a non-private run for long operations. (${e.message})`
2284
2462
  );
@@ -2291,7 +2469,7 @@ var OperationManager = class {
2291
2469
  * @param options - Invoke options (e.g., ucans)
2292
2470
  */
2293
2471
  async invoke(assetId, input, options) {
2294
- if (this.venue.privateJobs) {
2472
+ if (options?.private ?? this.venue.privateJobs) {
2295
2473
  throw new CoviaError(
2296
2474
  "Private-jobs mode requires run(): a completed private job is immediately forgotten by the venue, so a poll-style Job cannot collect its result."
2297
2475
  );
@@ -2303,27 +2481,15 @@ var OperationManager = class {
2303
2481
  if (options?.ucans) {
2304
2482
  payload.ucans = options.ucans;
2305
2483
  }
2306
- const response = await fetchWithError(`${this.venue.baseUrl}/api/v1/invoke`, {
2484
+ const response = await venueJson(this.venue, "/api/v1/invoke", {
2307
2485
  method: "POST",
2308
- headers: this._buildHeaders(),
2309
2486
  body: JSON.stringify(payload)
2310
2487
  });
2311
2488
  return new Job(response.id ?? "", this.venue, response);
2312
2489
  }
2313
- _buildHeaders() {
2314
- const headers = { "Content-Type": "application/json" };
2315
- this.venue.auth.apply(headers, this.venue.venueId);
2316
- return headers;
2317
- }
2318
2490
  };
2319
2491
 
2320
2492
  // src/WorkspaceManager.ts
2321
- function versionAtLeast2(version, major, minor) {
2322
- const m = version?.match(/^(\d+)\.(\d+)/);
2323
- if (!m) return true;
2324
- const [maj, min] = [Number(m[1]), Number(m[2])];
2325
- return maj > major || maj === major && min >= minor;
2326
- }
2327
2493
  var WorkspaceManager = class {
2328
2494
  constructor(venue) {
2329
2495
  this.venue = venue;
@@ -2331,16 +2497,11 @@ var WorkspaceManager = class {
2331
2497
  // so pre-0.3 venues pay the probe once, not one failed GET per read.
2332
2498
  this.valuesSupported = true;
2333
2499
  }
2334
- _headers() {
2335
- const headers = { "Content-Type": "application/json" };
2336
- this.venue.auth.apply(headers, this.venue.venueId);
2337
- return headers;
2338
- }
2339
2500
  /** GET a job-free `/api/v1/values/{op}` read; omit undefined params. */
2340
2501
  _values(op, params) {
2341
2502
  const qs = new URLSearchParams();
2342
2503
  for (const [k, v] of Object.entries(params)) if (v !== void 0) qs.set(k, String(v));
2343
- return fetchWithError(`${this.venue.baseUrl}/api/v1/values/${op}?${qs.toString()}`, { headers: this._headers() });
2504
+ return venueJson(this.venue, `/api/v1/values/${op}?${qs.toString()}`);
2344
2505
  }
2345
2506
  /**
2346
2507
  * Whether the venue serves `GET /api/v1/values/*`: no if a probe already
@@ -2351,40 +2512,46 @@ var WorkspaceManager = class {
2351
2512
  supportsValues() {
2352
2513
  if (!this.valuesSupported) return false;
2353
2514
  const status = this.venue.lastKnownStatus;
2354
- if (status && (!status.version || !versionAtLeast2(status.version, 0, 3))) {
2515
+ if (status && (!status.version || !versionAtLeast(status.version, 0, 3))) {
2355
2516
  this.valuesSupported = false;
2356
2517
  }
2357
2518
  return this.valuesSupported;
2358
2519
  }
2359
- /** A job-free values read, falling back to the invoke path on pre-0.3 venues. */
2360
- async _valuesOr(op, params, fallback) {
2520
+ unsupported(feature) {
2521
+ throw new UnsupportedVenueFeatureError(feature);
2522
+ }
2523
+ /** A job-free Values read that rejects when the endpoint is unavailable. */
2524
+ async valuesRead(op, params) {
2361
2525
  if (this.supportsValues()) {
2362
2526
  try {
2363
2527
  return await this._values(op, params);
2364
2528
  } catch (e) {
2365
2529
  if (!(e instanceof NotFoundError)) throw e;
2530
+ if (!ROUTE_MISSING_404.test(e.message)) throw e;
2366
2531
  this.valuesSupported = false;
2367
2532
  }
2368
2533
  }
2369
- return fallback();
2534
+ return this.unsupported(`workspace ${op} reads`);
2370
2535
  }
2371
2536
  // ── job-free reads (#177) ───────────────────────────────────────────────────
2372
2537
  async read(path, maxSize, ucans) {
2373
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/read", { path, maxSize }, { ucans });
2374
- return this._valuesOr("read", { path, maxSize }, () => this.venue.operations.run("v/ops/covia/read", { path, maxSize }));
2538
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace reads");
2539
+ return this.valuesRead("read", { path, maxSize });
2375
2540
  }
2376
2541
  async list(path, limit, offset, ucans) {
2377
2542
  path = path || "/";
2378
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/list", { path, limit, offset }, { ucans });
2379
- return this._valuesOr("list", { path, limit, offset }, () => this.venue.operations.run("v/ops/covia/list", { path, limit, offset }));
2543
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace listings");
2544
+ return this.valuesRead("list", { path, limit, offset });
2380
2545
  }
2381
2546
  async slice(path, offset, limit, ucans) {
2382
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/slice", { path, offset, limit }, { ucans });
2383
- return this._valuesOr("slice", { path, offset, limit }, () => this.venue.operations.run("v/ops/covia/slice", { path, offset, limit }));
2547
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace slices");
2548
+ return this.valuesRead("slice", { path, offset, limit });
2384
2549
  }
2385
2550
  async inspect(paths, budget, compact, ucans) {
2386
- if (ucans?.length || Array.isArray(paths)) return this.venue.operations.run("v/ops/covia/inspect", { paths, budget, compact }, { ucans });
2387
- return this._valuesOr("inspect", { path: paths, budget, compact }, () => this.venue.operations.run("v/ops/covia/inspect", { paths, budget, compact }));
2551
+ if (ucans?.length || Array.isArray(paths)) return this.unsupported(
2552
+ Array.isArray(paths) ? "multi-path workspace inspection" : "UCAN-authorised workspace inspection"
2553
+ );
2554
+ return this.valuesRead("inspect", { path: paths, budget, compact });
2388
2555
  }
2389
2556
  /**
2390
2557
  * Count entries at a depth below `path` — a job-free server-side tally, so the
@@ -2396,8 +2563,8 @@ var WorkspaceManager = class {
2396
2563
  */
2397
2564
  async count(path, opts = {}) {
2398
2565
  const { depth, ucans } = opts;
2399
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/aggregate", { path, depth }, { ucans });
2400
- return this._valuesOr("count", { path, depth }, () => this.venue.operations.run("v/ops/covia/aggregate", { path, depth }));
2566
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace counts");
2567
+ return this.valuesRead("count", { path, depth });
2401
2568
  }
2402
2569
  /**
2403
2570
  * Count entries at a depth below `path`, optionally partitioned by a field —
@@ -2409,8 +2576,8 @@ var WorkspaceManager = class {
2409
2576
  */
2410
2577
  async aggregate(path, opts = {}) {
2411
2578
  const { depth, groupBy, ucans } = opts;
2412
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/aggregate", { path, depth, groupBy }, { ucans });
2413
- return this._valuesOr("aggregate", { path, depth, groupBy }, () => this.venue.operations.run("v/ops/covia/aggregate", { path, depth, groupBy }));
2579
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace aggregation");
2580
+ return this.valuesRead("aggregate", { path, depth, groupBy });
2414
2581
  }
2415
2582
  // ── writes stay on the job path (they should leave an audit record) ─────────
2416
2583
  async write(path, value, ucans) {
@@ -2471,6 +2638,121 @@ var SecretManager = class {
2471
2638
  }
2472
2639
  };
2473
2640
 
2641
+ // src/SkillManager.ts
2642
+ var PAGE_SIZE = 100;
2643
+ var SkillManager = class {
2644
+ constructor(venue) {
2645
+ this.venue = venue;
2646
+ }
2647
+ /** Return every skill asset directly contained at `path`. */
2648
+ async list(path) {
2649
+ const location = path.replace(/\/+$/, "");
2650
+ const skills = [];
2651
+ let offset = 0;
2652
+ for (; ; ) {
2653
+ const page = await this.venue.workspace.list(location, PAGE_SIZE, offset);
2654
+ if (!page.exists) {
2655
+ if (skills.length === 0) return [];
2656
+ throw new CoviaError(`Skill listing at ${location} disappeared mid-listing`);
2657
+ }
2658
+ const keys = page.keys ?? [];
2659
+ const assets = await Promise.all(
2660
+ keys.map((key) => this.get(`${location}/${key}`))
2661
+ );
2662
+ skills.push(...assets);
2663
+ const nextOffset = (page.offset ?? offset) + keys.length;
2664
+ if (keys.length === 0 || nextOffset >= (page.count ?? nextOffset)) break;
2665
+ if (nextOffset <= offset) {
2666
+ throw new CoviaError("Venue returned a skills page that did not advance");
2667
+ }
2668
+ offset = nextOffset;
2669
+ }
2670
+ return skills;
2671
+ }
2672
+ /** Return the ordinary asset at exactly `path`, with typed skill metadata. */
2673
+ async get(path) {
2674
+ return this.venue.assets.get(path);
2675
+ }
2676
+ };
2677
+
2678
+ // src/MemoryManager.ts
2679
+ var DEFAULT_MEMORY_PATH = "w/memory";
2680
+ function memoryChange(value) {
2681
+ const result = record(value);
2682
+ if (typeof result?.n !== "number" || typeof result.count !== "number") {
2683
+ throw new CoviaError("Venue returned an invalid memory mutation result");
2684
+ }
2685
+ return { number: result.n, count: result.count };
2686
+ }
2687
+ var MemoryManager = class {
2688
+ constructor(venue) {
2689
+ this.venue = venue;
2690
+ }
2691
+ /**
2692
+ * Read memory directly from the workspace Values surface. This is job-free;
2693
+ * unlike `v/ops/memory {command:'recall'}`, it returns structured entries
2694
+ * rather than an LLM-oriented numbered text block.
2695
+ */
2696
+ async list(path = DEFAULT_MEMORY_PATH) {
2697
+ const result = await this.venue.workspace.read(path);
2698
+ if (!result.exists) {
2699
+ return [];
2700
+ }
2701
+ let raw;
2702
+ if (result.truncated) {
2703
+ raw = await sliceAll(this.venue.workspace, path);
2704
+ } else if (result.value === void 0 || result.value === null) {
2705
+ return [];
2706
+ } else if (Array.isArray(result.value)) {
2707
+ raw = result.value;
2708
+ } else {
2709
+ throw new CoviaError(`Memory at ${path} is not a flat list`);
2710
+ }
2711
+ const entries = raw.map((value, index) => {
2712
+ const item = record(value);
2713
+ const text = typeof value === "string" ? value : typeof item?.text === "string" ? item.text : void 0;
2714
+ if (text === void 0) {
2715
+ throw new CoviaError(`Memory entry ${index + 1} at ${path} has no text`);
2716
+ }
2717
+ return {
2718
+ number: index + 1,
2719
+ text,
2720
+ createdAt: typeof item?.ts === "number" ? item.ts : void 0,
2721
+ updatedAt: typeof item?.updated === "number" ? item.updated : void 0
2722
+ };
2723
+ });
2724
+ return entries;
2725
+ }
2726
+ /** Append a memory entry. Mutations intentionally remain audited Jobs. */
2727
+ async remember(text, path = DEFAULT_MEMORY_PATH) {
2728
+ const result = await this.venue.operations.run("v/ops/memory", {
2729
+ command: "remember",
2730
+ text,
2731
+ path
2732
+ });
2733
+ return memoryChange(result);
2734
+ }
2735
+ /** Replace a 1-based memory entry. Mutations intentionally remain audited Jobs. */
2736
+ async update(number, text, path = DEFAULT_MEMORY_PATH) {
2737
+ const result = await this.venue.operations.run("v/ops/memory", {
2738
+ command: "update",
2739
+ n: number,
2740
+ text,
2741
+ path
2742
+ });
2743
+ return memoryChange(result);
2744
+ }
2745
+ /** Remove a 1-based memory entry. Mutations intentionally remain audited Jobs. */
2746
+ async forget(number, path = DEFAULT_MEMORY_PATH) {
2747
+ const result = await this.venue.operations.run("v/ops/memory", {
2748
+ command: "forget",
2749
+ n: number,
2750
+ path
2751
+ });
2752
+ return memoryChange(result);
2753
+ }
2754
+ };
2755
+
2474
2756
  // src/Agent.ts
2475
2757
  var Agent = class _Agent {
2476
2758
  constructor(id, venue) {
@@ -2512,6 +2794,14 @@ var Agent = class _Agent {
2512
2794
  async info() {
2513
2795
  return this._agents.info(this.id);
2514
2796
  }
2797
+ /** Page this agent's sessions through the job-free workspace Values API. */
2798
+ async listSessions(options) {
2799
+ return this._agents.listSessions(this.id, options);
2800
+ }
2801
+ /** Read one session through the job-free workspace Values API. */
2802
+ async getSession(sessionId) {
2803
+ return this._agents.getSession(this.id, sessionId);
2804
+ }
2515
2805
  /**
2516
2806
  * Fork this agent into a new agent.
2517
2807
  * @param agentId - ID for the new forked agent
@@ -2596,7 +2886,11 @@ function connectionFailureMessage(venueId, attempts) {
2596
2886
  }
2597
2887
  var Venue = class _Venue {
2598
2888
  constructor(options = {}) {
2599
- /** Connection-level private-jobs mode — see {@link setPrivate}. */
2889
+ this._closed = false;
2890
+ /**
2891
+ * Connection-level private-jobs mode — see {@link setPrivate}.
2892
+ * @deprecated Pass `{ private: true }` to `operations.run()` instead.
2893
+ */
2600
2894
  this.privateJobs = false;
2601
2895
  this.baseUrl = options.baseUrl || "";
2602
2896
  this.venueId = options.venueId || "";
@@ -2607,6 +2901,10 @@ var Venue = class _Venue {
2607
2901
  description: options.description || ""
2608
2902
  };
2609
2903
  }
2904
+ /** Whether this handle has been closed and should not be reused by Grid. */
2905
+ get closed() {
2906
+ return this._closed;
2907
+ }
2610
2908
  /**
2611
2909
  * Puts this connection in **private-jobs mode**: every subsequent
2612
2910
  * `operations.run(...)` executes as a memory-only job (covia #192) — never
@@ -2615,7 +2913,10 @@ var Venue = class _Venue {
2615
2913
  *
2616
2914
  * Because a completed private job is immediately forgotten, results are
2617
2915
  * collected through the invoke `wait` window rather than polling — so
2618
- * private mode works with `run()`; a poll-style `invoke()` throws.
2916
+ * private mode works with `run()`; a poll-style `invoke()` throws.
2917
+ *
2918
+ * @deprecated Pass `{ private: true }` to `operations.run()` instead. The
2919
+ * per-call option avoids mutable connection-wide execution state.
2619
2920
  */
2620
2921
  setPrivate(enabled) {
2621
2922
  this.privateJobs = enabled;
@@ -2644,6 +2945,12 @@ var Venue = class _Venue {
2644
2945
  get secrets() {
2645
2946
  return this._secrets ?? (this._secrets = new SecretManager(this));
2646
2947
  }
2948
+ get skills() {
2949
+ return this._skills ?? (this._skills = new SkillManager(this));
2950
+ }
2951
+ get memory() {
2952
+ return this._memory ?? (this._memory = new MemoryManager(this));
2953
+ }
2647
2954
  /**
2648
2955
  * Connect to a venue, validating it with `GET {base}/api/v1/status`. If the
2649
2956
  * venue is auth-gated (status answers 401/403 — public access disabled), the
@@ -2673,7 +2980,7 @@ var Venue = class _Venue {
2673
2980
  baseUrl: venueId.baseUrl,
2674
2981
  venueId: venueId.venueId,
2675
2982
  name: venueId.metadata.name,
2676
- auth,
2983
+ auth: auth ?? venueId.auth,
2677
2984
  status: venueId.lastKnownStatus
2678
2985
  });
2679
2986
  await connected.status();
@@ -2692,7 +2999,7 @@ var Venue = class _Venue {
2692
2999
  if (typeof endpoint !== "string") {
2693
3000
  throw new CoviaError("No (string) endpoint found for DID");
2694
3001
  }
2695
- candidates = [endpoint.replace(/\/api\/v1/, "")];
3002
+ candidates = [stripTrailingSlash(endpoint).replace(/\/api\/v1$/, "")];
2696
3003
  } else {
2697
3004
  candidates = venueBaseUrlCandidates(venueId);
2698
3005
  }
@@ -2700,7 +3007,11 @@ var Venue = class _Venue {
2700
3007
  for (const baseUrl of candidates) {
2701
3008
  const statusUrl = baseUrl + "/api/v1/status";
2702
3009
  try {
2703
- const data = await fetchWithError(statusUrl);
3010
+ const data = await venueJson({
3011
+ baseUrl,
3012
+ venueId: expectedDid ?? "",
3013
+ auth: new NoAuth()
3014
+ }, "/api/v1/status");
2704
3015
  if (!data.did) {
2705
3016
  throw new CoviaError(`Venue status at ${statusUrl} did not include a DID`);
2706
3017
  }
@@ -2785,9 +3096,10 @@ var Venue = class _Venue {
2785
3096
  * @returns {Promise<string[]>}
2786
3097
  */
2787
3098
  async listSecrets() {
2788
- const result = await fetchWithError(`${this.baseUrl}/api/v1/secrets`, {
2789
- headers: this._buildHeaders()
2790
- });
3099
+ const result = await venueJson(
3100
+ this,
3101
+ "/api/v1/secrets"
3102
+ );
2791
3103
  return Array.isArray(result.items) ? result.items : [];
2792
3104
  }
2793
3105
  /**
@@ -2795,9 +3107,8 @@ var Venue = class _Venue {
2795
3107
  * @param name - Secret name
2796
3108
  */
2797
3109
  async deleteSecret(name) {
2798
- await fetchStreamWithError(`${this.baseUrl}/api/v1/secrets/${encodeURIComponent(name)}`, {
2799
- method: "DELETE",
2800
- headers: this._buildHeaders()
3110
+ await venueStream(this, `/api/v1/secrets/${encodeURIComponent(name)}`, {
3111
+ method: "DELETE"
2801
3112
  });
2802
3113
  }
2803
3114
  /**
@@ -2806,7 +3117,7 @@ var Venue = class _Venue {
2806
3117
  * @throws {VenueIdentityChangedError} If this address reports a different known DID.
2807
3118
  */
2808
3119
  async status() {
2809
- const data = await fetchWithError(`${this.baseUrl}/api/v1/status`);
3120
+ const data = await venueJson(this, "/api/v1/status");
2810
3121
  if (data.did) {
2811
3122
  if (this.venueId) {
2812
3123
  assertVenueIdentity(this.venueId, data.did, this.baseUrl);
@@ -2874,7 +3185,8 @@ var Venue = class _Venue {
2874
3185
  * Clears cached asset data for this venue.
2875
3186
  */
2876
3187
  close() {
2877
- this.assets.clearCache();
3188
+ this._closed = true;
3189
+ this._assets?.clearCache();
2878
3190
  }
2879
3191
  /**
2880
3192
  * Disposable support — allows `using venue = await Grid.connect(...)` in TS 5.2+.
@@ -2882,15 +3194,28 @@ var Venue = class _Venue {
2882
3194
  [Symbol.dispose]() {
2883
3195
  this.close();
2884
3196
  }
2885
- _buildHeaders() {
2886
- const headers = { "Content-Type": "application/json" };
2887
- this.auth.apply(headers, this.venueId);
2888
- return headers;
2889
- }
2890
3197
  };
2891
3198
 
2892
3199
  // src/Grid.ts
2893
- var cache2 = /* @__PURE__ */ new Map();
3200
+ var anonymousConnections = /* @__PURE__ */ new Map();
3201
+ var authenticatedConnections = /* @__PURE__ */ new WeakMap();
3202
+ function connectionsFor(auth) {
3203
+ if (!auth) return anonymousConnections;
3204
+ let connections = authenticatedConnections.get(auth);
3205
+ if (!connections) {
3206
+ connections = /* @__PURE__ */ new Map();
3207
+ authenticatedConnections.set(auth, connections);
3208
+ }
3209
+ return connections;
3210
+ }
3211
+ function normaliseKey(venueId) {
3212
+ return venueId.replace(/\/+$/, "");
3213
+ }
3214
+ function removeEntry(cache, entry) {
3215
+ for (const [key, candidate] of cache) {
3216
+ if (candidate === entry) cache.delete(key);
3217
+ }
3218
+ }
2894
3219
  var Grid = class {
2895
3220
  /**
2896
3221
  * Static method to connect to a venue
@@ -2899,12 +3224,27 @@ var Grid = class {
2899
3224
  * @returns {Promise<Venue>} A new Venue instance configured appropriately
2900
3225
  */
2901
3226
  static async connect(venueId, auth) {
2902
- const cached = cache2.get(venueId);
2903
- if (cached && cached.auth === auth)
2904
- return cached.venue;
2905
- const connectedVenue = await Venue.connect(venueId, auth);
2906
- cache2.set(venueId, { auth, venue: connectedVenue });
2907
- return connectedVenue;
3227
+ const cache = connectionsFor(auth);
3228
+ const key = normaliseKey(venueId);
3229
+ const cached = cache.get(key);
3230
+ if (cached) {
3231
+ if (!cached.venue?.closed) return cached.promise;
3232
+ removeEntry(cache, cached);
3233
+ }
3234
+ const entry = {};
3235
+ entry.promise = Venue.connect(venueId, auth).then((venue) => {
3236
+ entry.venue = venue;
3237
+ cache.set(normaliseKey(venue.baseUrl), entry);
3238
+ if (key.startsWith("did:")) {
3239
+ cache.set(normaliseKey(venue.venueId), entry);
3240
+ }
3241
+ return venue;
3242
+ }).catch((error) => {
3243
+ removeEntry(cache, entry);
3244
+ throw error;
3245
+ });
3246
+ cache.set(key, entry);
3247
+ return entry.promise;
2908
3248
  }
2909
3249
  };
2910
3250
 
@@ -2947,4 +3287,4 @@ function relayDelegation(privateKey, venueDID, lifetimeSeconds, caps = []) {
2947
3287
  (*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
2948
3288
  */
2949
3289
 
2950
- export { AdapterManager, Agent, AgentManager, AgentStatus, Asset, AssetManager, AssetNotFoundError, Auth, BasicAuth, BearerAuth, ChatSession, CoviaConnectionError, CoviaError, CoviaTimeoutError, CredentialsHTTP, DataAsset, Ed25519Auth, Grid, GridError, Job, JobFailedError, JobManager, JobNotFoundError, JobStatus, KeyPairAuth, Namespace, NoAuth, NotFoundError, Operation, OperationManager, RateLimitError, RunStatus, SecretManager, UCANManager, VENUE_RELAY, Venue, VenueIdentityChangedError, WorkspaceManager, assetHash, createSSEEvent, createUCANJWT, decodePublicKey, didFor, didFromPublicKey, didMethod, didUrl, encodePublicKey, fetchStreamWithError, fetchWithError, generateKeyPair, getAssetIdFromPath, getAssetIdFromVenueId, getAssetMetadataStore, getParsedAssetId, grant, hexToPrivateKey, identityToken, isDid, isJobComplete, isJobFinished, isJobPaused, logger, parseDidUrl, parseRetryAfterMs, parseSSEStream, privateKeyToHex, relayDelegation, retryDelayMs, setAssetMetadataStore, venueBaseUrlCandidates };
3290
+ export { AdapterManager, Agent, AgentManager, AgentSessionNotFoundError, AgentStatus, Asset, AssetManager, AssetNotFoundError, Auth, BasicAuth, BearerAuth, ChatSession, CoviaConnectionError, CoviaError, CoviaTimeoutError, CredentialsHTTP, DataAsset, Ed25519Auth, Grid, GridError, Job, JobFailedError, JobManager, JobNotFoundError, JobStatus, KeyPairAuth, MemoryManager, Namespace, NoAuth, NotFoundError, Operation, OperationManager, RateLimitError, RunStatus, SecretManager, SkillManager, UCANManager, UnsupportedVenueFeatureError, VENUE_RELAY, Venue, VenueIdentityChangedError, WorkspaceManager, assetHash, createSSEEvent, createUCANJWT, decodePublicKey, didFor, didFromPublicKey, didMethod, didUrl, encodePublicKey, fetchStreamWithError, fetchWithError, generateKeyPair, getAssetIdFromPath, getAssetIdFromVenueId, getAssetMetadataStore, getParsedAssetId, grant, hexToPrivateKey, identityToken, isDid, isJobComplete, isJobFinished, isJobPaused, logger, parseDidUrl, parseRetryAfterMs, parseSSEStream, privateKeyToHex, relayDelegation, retryDelayMs, setAssetMetadataStore, venueBaseUrlCandidates };