@covia/covia-sdk 1.8.0 → 1.9.1

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.js CHANGED
@@ -45,6 +45,15 @@ var GridError = class extends CoviaError {
45
45
  this.responseBody = responseBody;
46
46
  }
47
47
  };
48
+ var UnsupportedVenueFeatureError = class extends CoviaError {
49
+ constructor(feature) {
50
+ super(
51
+ `The connected venue cannot serve ${feature} without creating a job. Invoke the corresponding operation explicitly if creating a persisted job is intended.`
52
+ );
53
+ this.feature = feature;
54
+ this.name = "UnsupportedVenueFeatureError";
55
+ }
56
+ };
48
57
  var CoviaConnectionError = class extends CoviaError {
49
58
  constructor(message, options = {}) {
50
59
  super(message);
@@ -114,6 +123,14 @@ var JobNotFoundError = class extends NotFoundError {
114
123
  this.jobId = jobId;
115
124
  }
116
125
  };
126
+ var AgentSessionNotFoundError = class extends CoviaError {
127
+ constructor(agentId, sessionId) {
128
+ super(`Agent session not found: ${agentId}/${sessionId}`, 404);
129
+ this.agentId = agentId;
130
+ this.sessionId = sessionId;
131
+ this.name = "AgentSessionNotFoundError";
132
+ }
133
+ };
117
134
 
118
135
  // node_modules/.pnpm/@noble+ed25519@3.1.0/node_modules/@noble/ed25519/index.js
119
136
  var ed25519_CURVE = Object.freeze({
@@ -1033,9 +1050,13 @@ function privateKeyToHex(key) {
1033
1050
  return Array.from(key).map((b) => b.toString(16).padStart(2, "0")).join("");
1034
1051
  }
1035
1052
  function hexToPrivateKey(hex) {
1036
- const bytes = new Uint8Array(hex.length / 2);
1053
+ const cleaned = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
1054
+ if (!/^[0-9a-fA-F]{64}$/.test(cleaned)) {
1055
+ throw new Error("Invalid Ed25519 private key: expected 64 hex characters (32 bytes)");
1056
+ }
1057
+ const bytes = new Uint8Array(32);
1037
1058
  for (let i = 0; i < bytes.length; i++) {
1038
- bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
1059
+ bytes[i] = parseInt(cleaned.substring(i * 2, i * 2 + 2), 16);
1039
1060
  }
1040
1061
  return bytes;
1041
1062
  }
@@ -1149,6 +1170,14 @@ function createEdDSAJWT(privateKey, lifetimeSeconds = 300, audience) {
1149
1170
  }
1150
1171
 
1151
1172
  // src/Credentials.ts
1173
+ function base64Encode(text) {
1174
+ const bytes = new TextEncoder().encode(text);
1175
+ let binary = "";
1176
+ for (let i = 0; i < bytes.length; i++) {
1177
+ binary += String.fromCharCode(bytes[i]);
1178
+ }
1179
+ return btoa(binary);
1180
+ }
1152
1181
  var Auth = class {
1153
1182
  };
1154
1183
  var NoAuth = class extends Auth {
@@ -1171,7 +1200,7 @@ var BasicAuth = class extends Auth {
1171
1200
  this._password = password;
1172
1201
  }
1173
1202
  apply(headers, _audience) {
1174
- const credentials = Buffer.from(`${this._username}:${this._password}`, "utf-8").toString("base64");
1203
+ const credentials = base64Encode(`${this._username}:${this._password}`);
1175
1204
  headers["Authorization"] = `Basic ${credentials}`;
1176
1205
  }
1177
1206
  };
@@ -1188,8 +1217,30 @@ var Ed25519Auth = class _Ed25519Auth extends Auth {
1188
1217
  this._lifetime = tokenLifetimeSeconds;
1189
1218
  }
1190
1219
  apply(headers, audience) {
1191
- const jwt = createEdDSAJWT(this._privateKey, this._lifetime, this._audience ?? audience);
1192
- headers["Authorization"] = `Bearer ${jwt}`;
1220
+ headers["Authorization"] = `Bearer ${this.identityToken(audience)}`;
1221
+ }
1222
+ /**
1223
+ * Mint a bearer-usable identity JWT for this key — the same token
1224
+ * {@link apply} attaches to every request, exposed for callers that need
1225
+ * to present this identity elsewhere (CLI tools, curl, another client:
1226
+ * `Authorization: Bearer <token>`).
1227
+ *
1228
+ * An explicitly-pinned {@link audience} wins; otherwise the JWT `aud` is
1229
+ * bound to the venue DID given here. A token with no `aud` is replayable
1230
+ * at any venue that accepts the caller's DID, so minting one is refused
1231
+ * rather than silently weakened.
1232
+ *
1233
+ * @param audience - The venue DID the token is bound to (`aud` claim).
1234
+ * @param lifetimeSeconds - Token lifetime; defaults to this instance's.
1235
+ */
1236
+ identityToken(audience, lifetimeSeconds) {
1237
+ const aud = this._audience ?? (audience || void 0);
1238
+ if (!aud) {
1239
+ throw new CoviaError(
1240
+ "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."
1241
+ );
1242
+ }
1243
+ return createEdDSAJWT(this._privateKey, lifetimeSeconds ?? this._lifetime, aud);
1193
1244
  }
1194
1245
  /** The caller's DID derived from the public key. */
1195
1246
  getDID() {
@@ -1302,17 +1353,24 @@ async function parseErrorBody(response) {
1302
1353
  let body = null;
1303
1354
  let message = `Request failed with status ${response.status}`;
1304
1355
  try {
1305
- body = await response.json();
1356
+ if (typeof response.text === "function") {
1357
+ const text = await response.text();
1358
+ if (text) {
1359
+ try {
1360
+ body = JSON.parse(text);
1361
+ } catch {
1362
+ body = text;
1363
+ message = text;
1364
+ }
1365
+ }
1366
+ } else {
1367
+ body = await response.json();
1368
+ }
1306
1369
  if (body && typeof body === "object" && "error" in body) {
1307
1370
  const err2 = body.error;
1308
1371
  if (typeof err2 === "string") message = err2;
1309
1372
  }
1310
1373
  } catch {
1311
- try {
1312
- const text = await response.text();
1313
- if (text) message = text;
1314
- } catch {
1315
- }
1316
1374
  }
1317
1375
  return { message, body };
1318
1376
  }
@@ -1606,13 +1664,82 @@ var AdapterManager = class {
1606
1664
  }
1607
1665
  };
1608
1666
 
1609
- // src/AgentManager.ts
1667
+ // src/VenueTransport.ts
1668
+ function requestOptions(venue, options) {
1669
+ const {
1670
+ contentType,
1671
+ headers: suppliedHeaders = {},
1672
+ ...request
1673
+ } = options;
1674
+ const headers = { ...suppliedHeaders };
1675
+ const effectiveContentType = contentType !== void 0 ? contentType : request.body != null ? "application/json" : null;
1676
+ if (effectiveContentType && headers["Content-Type"] === void 0) {
1677
+ headers["Content-Type"] = effectiveContentType;
1678
+ }
1679
+ venue.auth.apply(headers, venue.venueId);
1680
+ return { ...request, headers };
1681
+ }
1682
+ function venueJson(venue, path, options = {}) {
1683
+ return fetchWithError(
1684
+ `${venue.baseUrl}${path}`,
1685
+ requestOptions(venue, options)
1686
+ );
1687
+ }
1688
+ function venueStream(venue, path, options = {}) {
1689
+ return fetchStreamWithError(
1690
+ `${venue.baseUrl}${path}`,
1691
+ requestOptions(venue, options)
1692
+ );
1693
+ }
1694
+
1695
+ // src/venue-features.ts
1610
1696
  function versionAtLeast(version, major, minor) {
1611
1697
  const m = version?.match(/^(\d+)\.(\d+)/);
1612
1698
  if (!m) return true;
1613
1699
  const [maj, min] = [Number(m[1]), Number(m[2])];
1614
1700
  return maj > major || maj === major && min >= minor;
1615
1701
  }
1702
+ var ROUTE_MISSING_404 = /\bEndpoint (GET|POST|PUT|DELETE|PATCH|HEAD) /;
1703
+
1704
+ // src/values-util.ts
1705
+ function record(value) {
1706
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1707
+ }
1708
+ var SLICE_PAGE_SIZE = 200;
1709
+ async function sliceAll(workspace, path, pageSize = SLICE_PAGE_SIZE) {
1710
+ const items = [];
1711
+ let offset = 0;
1712
+ for (; ; ) {
1713
+ const page = await workspace.slice(path, offset, pageSize);
1714
+ if (!page.exists) {
1715
+ if (items.length === 0) return items;
1716
+ throw new CoviaError(`Sliceable value at ${path} disappeared mid-read`);
1717
+ }
1718
+ const values = page.values ?? [];
1719
+ items.push(...values);
1720
+ const nextOffset = (page.offset ?? offset) + values.length;
1721
+ const done = page.count !== void 0 ? nextOffset >= page.count : values.length < pageSize;
1722
+ if (values.length === 0 || done) break;
1723
+ if (nextOffset <= offset) {
1724
+ throw new CoviaError(`Venue returned a slice of ${path} that did not advance`);
1725
+ }
1726
+ offset = nextOffset;
1727
+ }
1728
+ return items;
1729
+ }
1730
+
1731
+ // src/AgentManager.ts
1732
+ function sessionRecord(sessionId, value) {
1733
+ const session = record(value) ?? {};
1734
+ const meta = record(session.meta);
1735
+ return {
1736
+ id: sessionId,
1737
+ metadata: meta ?? {},
1738
+ pending: Array.isArray(session.pending) ? session.pending : [],
1739
+ frames: Array.isArray(session.frames) ? session.frames : [],
1740
+ wakeTime: typeof session.wakeTime === "number" ? session.wakeTime : void 0
1741
+ };
1742
+ }
1616
1743
  var AgentManager = class {
1617
1744
  constructor(venue) {
1618
1745
  this.venue = venue;
@@ -1620,11 +1747,6 @@ var AgentManager = class {
1620
1747
  // pre-0.4 venues pay the probe once, not one failed GET per read (covia#180).
1621
1748
  this.agentsGetSupported = true;
1622
1749
  }
1623
- _headers() {
1624
- const headers = { "Content-Type": "application/json" };
1625
- this.venue.auth.apply(headers, this.venue.venueId);
1626
- return headers;
1627
- }
1628
1750
  /** Whether the venue serves `GET /api/v1/agents`: no if a probe already
1629
1751
  * 404'd, or if the venue's last known status identifies it as pre-0.4. */
1630
1752
  supportsAgentsGet() {
@@ -1635,25 +1757,25 @@ var AgentManager = class {
1635
1757
  }
1636
1758
  return this.agentsGetSupported;
1637
1759
  }
1638
- /** A job-free agents GET, falling back to the invoke path on pre-0.4 venues. */
1639
- async _agentsOr(path, params, fallback) {
1760
+ /** A job-free agents GET that rejects when the endpoint is unavailable. */
1761
+ async agentsGet(path, params) {
1640
1762
  if (this.supportsAgentsGet()) {
1641
1763
  try {
1642
1764
  const qs = new URLSearchParams();
1643
1765
  for (const [k, v] of Object.entries(params)) if (v !== void 0) qs.set(k, String(v));
1644
1766
  const q = qs.toString();
1645
- return await fetchWithError(
1646
- `${this.venue.baseUrl}/api/v1/agents${path}${q ? `?${q}` : ""}`,
1647
- { headers: this._headers() }
1767
+ return await venueJson(
1768
+ this.venue,
1769
+ `/api/v1/agents${path}${q ? `?${q}` : ""}`
1648
1770
  );
1649
1771
  } catch (e) {
1650
1772
  if (!(e instanceof NotFoundError)) throw e;
1651
- const routeMissing = path === "" || /\bEndpoint (GET|POST|PUT|DELETE|PATCH|HEAD) /.test(e.message);
1773
+ const routeMissing = path === "" || ROUTE_MISSING_404.test(e.message);
1652
1774
  if (!routeMissing) throw e;
1653
1775
  this.agentsGetSupported = false;
1654
1776
  }
1655
1777
  }
1656
- return fallback();
1778
+ throw new UnsupportedVenueFeatureError("agent reads");
1657
1779
  }
1658
1780
  async create(input) {
1659
1781
  return this.venue.operations.run("v/ops/agent/create", input);
@@ -1698,10 +1820,10 @@ var AgentManager = class {
1698
1820
  /**
1699
1821
  * List the caller's agents. **Job-free** on covia ≥ 0.4: goes through
1700
1822
  * `GET /api/v1/agents` (covia#180) — synchronous, no Job persisted. Older
1701
- * venues transparently fall back to the invoke path (one probe, remembered).
1823
+ * venues reject rather than silently creating a persisted Job.
1702
1824
  */
1703
1825
  async list(includeTerminated) {
1704
- const result = await this._agentsOr("", { includeTerminated }, () => this.venue.operations.run("v/ops/agent/list", { includeTerminated }));
1826
+ const result = await this.agentsGet("", { includeTerminated });
1705
1827
  const raw = result?.agents ?? [];
1706
1828
  const agents = raw.map((a) => typeof a === "string" ? { agentId: a } : a);
1707
1829
  return { agents };
@@ -1722,9 +1844,61 @@ var AgentManager = class {
1722
1844
  return this.venue.operations.run("v/ops/agent/cancel-task", { agentId, taskId });
1723
1845
  }
1724
1846
  /** Agent info. **Job-free** on covia ≥ 0.4 (`GET /api/v1/agents/{id}`,
1725
- * covia#180); older venues fall back to the invoke path. */
1847
+ * covia#180); older venues require an explicit compatibility opt-in. */
1726
1848
  async info(agentId) {
1727
- return this._agentsOr(`/${encodeURIComponent(agentId)}`, {}, () => this.venue.operations.run("v/ops/agent/info", { agentId }));
1849
+ return this.agentsGet(`/${encodeURIComponent(agentId)}`, {});
1850
+ }
1851
+ /**
1852
+ * Page an agent's sessions directly from `g/<agentId>/sessions`.
1853
+ * This is a job-free Values read; session adapter-specific fields remain
1854
+ * available on each record's `value` while common runtime fields are typed.
1855
+ */
1856
+ async listSessions(agentId, options = {}) {
1857
+ const result = await this.venue.workspace.slice(
1858
+ `g/${agentId}/sessions`,
1859
+ options.offset,
1860
+ options.limit
1861
+ );
1862
+ const sessions = (result.values ?? []).flatMap((entry) => {
1863
+ const pair = record(entry);
1864
+ if (!pair || typeof pair.key !== "string") return [];
1865
+ return [sessionRecord(pair.key, pair.value)];
1866
+ });
1867
+ return {
1868
+ items: sessions,
1869
+ total: result.count ?? sessions.length,
1870
+ offset: result.offset ?? options.offset ?? 0,
1871
+ limit: options.limit ?? sessions.length
1872
+ };
1873
+ }
1874
+ /** Read one agent session directly from the workspace Values surface. */
1875
+ async getSession(agentId, sessionId) {
1876
+ const path = `g/${agentId}/sessions/${sessionId}`;
1877
+ const result = await this.venue.workspace.read(path);
1878
+ if (!result.exists) {
1879
+ throw new AgentSessionNotFoundError(agentId, sessionId);
1880
+ }
1881
+ if (result.truncated) {
1882
+ return this._readLargeSession(path, sessionId);
1883
+ }
1884
+ if (result.value === void 0 || result.value === null) {
1885
+ throw new AgentSessionNotFoundError(agentId, sessionId);
1886
+ }
1887
+ return sessionRecord(sessionId, result.value);
1888
+ }
1889
+ async _readLargeSession(path, sessionId) {
1890
+ const [meta, wakeTime, pending, frames] = await Promise.all([
1891
+ this.venue.workspace.read(`${path}/meta`),
1892
+ this.venue.workspace.read(`${path}/wakeTime`),
1893
+ sliceAll(this.venue.workspace, `${path}/pending`),
1894
+ sliceAll(this.venue.workspace, `${path}/frames`)
1895
+ ]);
1896
+ return sessionRecord(sessionId, {
1897
+ meta: meta.exists ? meta.value : void 0,
1898
+ wakeTime: wakeTime.exists ? wakeTime.value : void 0,
1899
+ pending,
1900
+ frames
1901
+ });
1728
1902
  }
1729
1903
  async fork(input) {
1730
1904
  return this.venue.operations.run("v/ops/agent/fork", input);
@@ -1886,23 +2060,50 @@ var Job = class {
1886
2060
  };
1887
2061
 
1888
2062
  // src/JobManager.ts
2063
+ function jobPage(value) {
2064
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2065
+ throw new CoviaError("Venue returned an invalid jobs listing");
2066
+ }
2067
+ const page = value;
2068
+ 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) {
2069
+ throw new CoviaError("Venue returned an invalid jobs page");
2070
+ }
2071
+ return page;
2072
+ }
1889
2073
  var JobManager = class {
1890
2074
  constructor(venue) {
1891
2075
  this.venue = venue;
1892
2076
  }
1893
2077
  async list() {
1894
- const body = await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs`, {
1895
- headers: this._buildHeaders()
1896
- });
1897
- if (Array.isArray(body)) return body;
1898
- const items = body?.items;
1899
- return Array.isArray(items) ? items : [];
2078
+ const items = [];
2079
+ let offset = 0;
2080
+ let targetTotal;
2081
+ while (targetTotal === void 0 || items.length < targetTotal) {
2082
+ const body = await venueJson(
2083
+ this.venue,
2084
+ `/api/v1/jobs?offset=${offset}&limit=1000`
2085
+ );
2086
+ if (Array.isArray(body)) {
2087
+ if (!body.every((item) => typeof item === "string")) {
2088
+ throw new CoviaError("Venue returned an invalid jobs listing");
2089
+ }
2090
+ return body;
2091
+ }
2092
+ const page = jobPage(body);
2093
+ targetTotal ?? (targetTotal = page.total);
2094
+ items.push(...page.items);
2095
+ if (page.items.length === 0) break;
2096
+ const nextOffset = page.offset + page.items.length;
2097
+ if (nextOffset <= offset) {
2098
+ throw new CoviaError("Venue returned a jobs page that did not advance");
2099
+ }
2100
+ offset = nextOffset;
2101
+ }
2102
+ return items;
1900
2103
  }
1901
2104
  async get(jobId) {
1902
2105
  try {
1903
- const data = await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}`, {
1904
- headers: this._buildHeaders()
1905
- });
2106
+ const data = await venueJson(this.venue, `/api/v1/jobs/${jobId}`);
1906
2107
  return new Job(jobId, this.venue, data);
1907
2108
  } catch (error) {
1908
2109
  if (error instanceof NotFoundError) {
@@ -1913,9 +2114,8 @@ var JobManager = class {
1913
2114
  }
1914
2115
  async cancel(jobId) {
1915
2116
  try {
1916
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/cancel`, {
1917
- method: "PUT",
1918
- headers: this._buildHeaders()
2117
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/cancel`, {
2118
+ method: "PUT"
1919
2119
  });
1920
2120
  } catch (error) {
1921
2121
  if (error instanceof NotFoundError) {
@@ -1926,9 +2126,8 @@ var JobManager = class {
1926
2126
  }
1927
2127
  async delete(jobId) {
1928
2128
  try {
1929
- await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/delete`, {
1930
- method: "PUT",
1931
- headers: this._buildHeaders()
2129
+ await venueStream(this.venue, `/api/v1/jobs/${jobId}/delete`, {
2130
+ method: "PUT"
1932
2131
  });
1933
2132
  } catch (error) {
1934
2133
  if (error instanceof NotFoundError) {
@@ -1939,9 +2138,8 @@ var JobManager = class {
1939
2138
  }
1940
2139
  async pause(jobId) {
1941
2140
  try {
1942
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/pause`, {
1943
- method: "PUT",
1944
- headers: this._buildHeaders()
2141
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/pause`, {
2142
+ method: "PUT"
1945
2143
  });
1946
2144
  } catch (error) {
1947
2145
  if (error instanceof NotFoundError) {
@@ -1952,9 +2150,8 @@ var JobManager = class {
1952
2150
  }
1953
2151
  async resume(jobId) {
1954
2152
  try {
1955
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/resume`, {
1956
- method: "PUT",
1957
- headers: this._buildHeaders()
2153
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}/resume`, {
2154
+ method: "PUT"
1958
2155
  });
1959
2156
  } catch (error) {
1960
2157
  if (error instanceof NotFoundError) {
@@ -1965,9 +2162,8 @@ var JobManager = class {
1965
2162
  }
1966
2163
  async sendMessage(jobId, message) {
1967
2164
  try {
1968
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}`, {
2165
+ return await venueJson(this.venue, `/api/v1/jobs/${jobId}`, {
1969
2166
  method: "POST",
1970
- headers: this._buildHeaders(),
1971
2167
  body: JSON.stringify(message)
1972
2168
  });
1973
2169
  } catch (error) {
@@ -1978,16 +2174,11 @@ var JobManager = class {
1978
2174
  }
1979
2175
  }
1980
2176
  async *stream(jobId) {
1981
- const response = await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/jobs/${jobId}/sse`, {
1982
- headers: { ...this._buildHeaders(), "Accept": "text/event-stream" }
2177
+ const response = await venueStream(this.venue, `/api/v1/jobs/${jobId}/sse`, {
2178
+ headers: { "Accept": "text/event-stream" }
1983
2179
  });
1984
2180
  yield* parseSSEStream(response);
1985
2181
  }
1986
- _buildHeaders() {
1987
- const headers = { "Content-Type": "application/json" };
1988
- this.venue.auth.apply(headers, this.venue.venueId);
1989
- return headers;
1990
- }
1991
2182
  };
1992
2183
 
1993
2184
  // src/Asset.ts
@@ -2067,10 +2258,12 @@ var DataAsset = class extends Asset {
2067
2258
  };
2068
2259
 
2069
2260
  // src/AssetManager.ts
2070
- var cache = /* @__PURE__ */ new Map();
2071
2261
  var AssetManager = class {
2072
2262
  constructor(venue) {
2073
2263
  this.venue = venue;
2264
+ // Memory cache is manager/venue-local. Content-addressed metadata may still
2265
+ // be shared deliberately through the configured persistent metadata store.
2266
+ this.cache = /* @__PURE__ */ new Map();
2074
2267
  }
2075
2268
  /**
2076
2269
  * Get an asset by lattice address.
@@ -2083,23 +2276,25 @@ var AssetManager = class {
2083
2276
  * @returns Returns either an Operation or DataAsset based on the asset's metadata
2084
2277
  */
2085
2278
  async get(assetId) {
2086
- if (cache.has(assetId)) {
2087
- return this._wrap(assetId, cache.get(assetId));
2279
+ if (this.cache.has(assetId)) {
2280
+ return this._wrap(assetId, this.cache.get(assetId));
2088
2281
  }
2089
2282
  const hash = assetHash(assetId);
2090
2283
  if (hash) {
2091
2284
  const persisted = getAssetMetadataStore()?.get(normaliseHash(hash));
2092
2285
  if (persisted) {
2093
- cache.set(assetId, persisted);
2286
+ this.cache.set(assetId, persisted);
2094
2287
  return this._wrap(assetId, persisted);
2095
2288
  }
2096
2289
  }
2097
2290
  try {
2098
- const data = await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}`, {
2099
- headers: this._buildHeaders(null)
2100
- });
2291
+ const data = await venueJson(
2292
+ this.venue,
2293
+ `/api/v1/assets/${assetId}`,
2294
+ { contentType: null }
2295
+ );
2101
2296
  if (hash) {
2102
- cache.set(assetId, data);
2297
+ this.cache.set(assetId, data);
2103
2298
  getAssetMetadataStore()?.put(normaliseHash(hash), data);
2104
2299
  }
2105
2300
  return this._wrap(assetId, data);
@@ -2126,18 +2321,19 @@ var AssetManager = class {
2126
2321
  if (options.limit !== void 0) {
2127
2322
  params.set("limit", String(options.limit));
2128
2323
  }
2129
- return fetchWithError(`${this.venue.baseUrl}/api/v1/assets?${params.toString()}`, {
2130
- headers: this._buildHeaders(null)
2131
- });
2324
+ return venueJson(
2325
+ this.venue,
2326
+ `/api/v1/assets?${params.toString()}`,
2327
+ { contentType: null }
2328
+ );
2132
2329
  }
2133
2330
  /**
2134
2331
  * Register a new asset
2135
2332
  * @param assetData - Asset configuration
2136
2333
  */
2137
2334
  async register(assetData) {
2138
- return fetchWithError(`${this.venue.baseUrl}/api/v1/assets`, {
2335
+ return venueJson(this.venue, "/api/v1/assets", {
2139
2336
  method: "POST",
2140
- headers: this._buildHeaders(),
2141
2337
  body: JSON.stringify(assetData)
2142
2338
  }).then((response) => this.get(response));
2143
2339
  }
@@ -2147,9 +2343,11 @@ var AssetManager = class {
2147
2343
  */
2148
2344
  async getMetadata(assetId) {
2149
2345
  try {
2150
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}`, {
2151
- headers: this._buildHeaders(null)
2152
- });
2346
+ return await venueJson(
2347
+ this.venue,
2348
+ `/api/v1/assets/${assetId}`,
2349
+ { contentType: null }
2350
+ );
2153
2351
  } catch (error) {
2154
2352
  if (error instanceof NotFoundError) {
2155
2353
  throw new AssetNotFoundError(assetId);
@@ -2165,9 +2363,9 @@ var AssetManager = class {
2165
2363
  */
2166
2364
  async putContent(assetId, content) {
2167
2365
  try {
2168
- return await fetchWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}/content`, {
2366
+ return await venueJson(this.venue, `/api/v1/assets/${assetId}/content`, {
2169
2367
  method: "PUT",
2170
- headers: this._buildHeaders(null),
2368
+ contentType: null,
2171
2369
  body: content
2172
2370
  });
2173
2371
  } catch (error) {
@@ -2183,9 +2381,11 @@ var AssetManager = class {
2183
2381
  */
2184
2382
  async getContent(assetId) {
2185
2383
  try {
2186
- const response = await fetchStreamWithError(`${this.venue.baseUrl}/api/v1/assets/${assetId}/content`, {
2187
- headers: this._buildHeaders(null)
2188
- });
2384
+ const response = await venueStream(
2385
+ this.venue,
2386
+ `/api/v1/assets/${assetId}/content`,
2387
+ { contentType: null }
2388
+ );
2189
2389
  return response.body;
2190
2390
  } catch (error) {
2191
2391
  if (error instanceof NotFoundError) {
@@ -2206,17 +2406,11 @@ var AssetManager = class {
2206
2406
  * Clear the asset cache.
2207
2407
  */
2208
2408
  clearCache() {
2209
- cache.clear();
2210
- getAssetMetadataStore()?.clear();
2409
+ this.cache.clear();
2211
2410
  }
2212
- // `contentType` defaults to JSON for the metadata/register endpoints. Pass
2213
- // `undefined` for binary content upload so fetch infers the type from the
2214
- // body — forcing application/json would mislabel a Blob/ArrayBuffer/stream.
2215
- _buildHeaders(contentType = "application/json") {
2216
- const headers = {};
2217
- if (contentType) headers["Content-Type"] = contentType;
2218
- this.venue.auth.apply(headers, this.venue.venueId);
2219
- return headers;
2411
+ /** Clear the explicitly configured cross-session metadata store. */
2412
+ clearPersistentCache() {
2413
+ getAssetMetadataStore()?.clear();
2220
2414
  }
2221
2415
  };
2222
2416
 
@@ -2229,23 +2423,23 @@ var OperationManager = class {
2229
2423
  * List all named operations available on this venue
2230
2424
  */
2231
2425
  async list() {
2232
- return fetchWithError(`${this.venue.baseUrl}/api/v1/operations`);
2426
+ return venueJson(this.venue, "/api/v1/operations");
2233
2427
  }
2234
2428
  /**
2235
2429
  * Get details of a named operation
2236
2430
  * @param name - Operation name (e.g., "v/ops/schema/infer")
2237
2431
  */
2238
2432
  async get(name) {
2239
- return fetchWithError(`${this.venue.baseUrl}/api/v1/operations/${name}`);
2433
+ return venueJson(this.venue, `/api/v1/operations/${name}`);
2240
2434
  }
2241
2435
  /**
2242
2436
  * Execute an operation and wait for the result
2243
2437
  * @param assetId - Operation asset ID or named operation
2244
2438
  * @param input - Operation input parameters
2245
- * @param options - Invoke options (e.g., ucans)
2439
+ * @param options - Execution options (e.g., ucans or private execution)
2246
2440
  */
2247
2441
  async run(assetId, input, options) {
2248
- if (this.venue.privateJobs) {
2442
+ if (options?.private ?? this.venue.privateJobs) {
2249
2443
  return this._runPrivate(assetId, input, options);
2250
2444
  }
2251
2445
  const job = await this.invoke(assetId, input, options);
@@ -2267,9 +2461,8 @@ var OperationManager = class {
2267
2461
  wait: true
2268
2462
  };
2269
2463
  if (options?.ucans) payload.ucans = options.ucans;
2270
- const rec = await fetchWithError(`${this.venue.baseUrl}/api/v1/invoke`, {
2464
+ const rec = await venueJson(this.venue, "/api/v1/invoke", {
2271
2465
  method: "POST",
2272
- headers: this._buildHeaders(),
2273
2466
  body: JSON.stringify(payload)
2274
2467
  });
2275
2468
  const status = rec.status;
@@ -2281,6 +2474,7 @@ var OperationManager = class {
2281
2474
  const job = new Job(rec.id ?? "", this.venue, rec);
2282
2475
  return await job.result();
2283
2476
  } catch (e) {
2477
+ if (!(e instanceof NotFoundError)) throw e;
2284
2478
  throw new CoviaError(
2285
2479
  `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})`
2286
2480
  );
@@ -2293,7 +2487,7 @@ var OperationManager = class {
2293
2487
  * @param options - Invoke options (e.g., ucans)
2294
2488
  */
2295
2489
  async invoke(assetId, input, options) {
2296
- if (this.venue.privateJobs) {
2490
+ if (options?.private ?? this.venue.privateJobs) {
2297
2491
  throw new CoviaError(
2298
2492
  "Private-jobs mode requires run(): a completed private job is immediately forgotten by the venue, so a poll-style Job cannot collect its result."
2299
2493
  );
@@ -2305,27 +2499,15 @@ var OperationManager = class {
2305
2499
  if (options?.ucans) {
2306
2500
  payload.ucans = options.ucans;
2307
2501
  }
2308
- const response = await fetchWithError(`${this.venue.baseUrl}/api/v1/invoke`, {
2502
+ const response = await venueJson(this.venue, "/api/v1/invoke", {
2309
2503
  method: "POST",
2310
- headers: this._buildHeaders(),
2311
2504
  body: JSON.stringify(payload)
2312
2505
  });
2313
2506
  return new Job(response.id ?? "", this.venue, response);
2314
2507
  }
2315
- _buildHeaders() {
2316
- const headers = { "Content-Type": "application/json" };
2317
- this.venue.auth.apply(headers, this.venue.venueId);
2318
- return headers;
2319
- }
2320
2508
  };
2321
2509
 
2322
2510
  // src/WorkspaceManager.ts
2323
- function versionAtLeast2(version, major, minor) {
2324
- const m = version?.match(/^(\d+)\.(\d+)/);
2325
- if (!m) return true;
2326
- const [maj, min] = [Number(m[1]), Number(m[2])];
2327
- return maj > major || maj === major && min >= minor;
2328
- }
2329
2511
  var WorkspaceManager = class {
2330
2512
  constructor(venue) {
2331
2513
  this.venue = venue;
@@ -2333,16 +2515,11 @@ var WorkspaceManager = class {
2333
2515
  // so pre-0.3 venues pay the probe once, not one failed GET per read.
2334
2516
  this.valuesSupported = true;
2335
2517
  }
2336
- _headers() {
2337
- const headers = { "Content-Type": "application/json" };
2338
- this.venue.auth.apply(headers, this.venue.venueId);
2339
- return headers;
2340
- }
2341
2518
  /** GET a job-free `/api/v1/values/{op}` read; omit undefined params. */
2342
2519
  _values(op, params) {
2343
2520
  const qs = new URLSearchParams();
2344
2521
  for (const [k, v] of Object.entries(params)) if (v !== void 0) qs.set(k, String(v));
2345
- return fetchWithError(`${this.venue.baseUrl}/api/v1/values/${op}?${qs.toString()}`, { headers: this._headers() });
2522
+ return venueJson(this.venue, `/api/v1/values/${op}?${qs.toString()}`);
2346
2523
  }
2347
2524
  /**
2348
2525
  * Whether the venue serves `GET /api/v1/values/*`: no if a probe already
@@ -2353,40 +2530,46 @@ var WorkspaceManager = class {
2353
2530
  supportsValues() {
2354
2531
  if (!this.valuesSupported) return false;
2355
2532
  const status = this.venue.lastKnownStatus;
2356
- if (status && (!status.version || !versionAtLeast2(status.version, 0, 3))) {
2533
+ if (status && (!status.version || !versionAtLeast(status.version, 0, 3))) {
2357
2534
  this.valuesSupported = false;
2358
2535
  }
2359
2536
  return this.valuesSupported;
2360
2537
  }
2361
- /** A job-free values read, falling back to the invoke path on pre-0.3 venues. */
2362
- async _valuesOr(op, params, fallback) {
2538
+ unsupported(feature) {
2539
+ throw new UnsupportedVenueFeatureError(feature);
2540
+ }
2541
+ /** A job-free Values read that rejects when the endpoint is unavailable. */
2542
+ async valuesRead(op, params) {
2363
2543
  if (this.supportsValues()) {
2364
2544
  try {
2365
2545
  return await this._values(op, params);
2366
2546
  } catch (e) {
2367
2547
  if (!(e instanceof NotFoundError)) throw e;
2548
+ if (!ROUTE_MISSING_404.test(e.message)) throw e;
2368
2549
  this.valuesSupported = false;
2369
2550
  }
2370
2551
  }
2371
- return fallback();
2552
+ return this.unsupported(`workspace ${op} reads`);
2372
2553
  }
2373
2554
  // ── job-free reads (#177) ───────────────────────────────────────────────────
2374
2555
  async read(path, maxSize, ucans) {
2375
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/read", { path, maxSize }, { ucans });
2376
- return this._valuesOr("read", { path, maxSize }, () => this.venue.operations.run("v/ops/covia/read", { path, maxSize }));
2556
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace reads");
2557
+ return this.valuesRead("read", { path, maxSize });
2377
2558
  }
2378
2559
  async list(path, limit, offset, ucans) {
2379
2560
  path = path || "/";
2380
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/list", { path, limit, offset }, { ucans });
2381
- return this._valuesOr("list", { path, limit, offset }, () => this.venue.operations.run("v/ops/covia/list", { path, limit, offset }));
2561
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace listings");
2562
+ return this.valuesRead("list", { path, limit, offset });
2382
2563
  }
2383
2564
  async slice(path, offset, limit, ucans) {
2384
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/slice", { path, offset, limit }, { ucans });
2385
- return this._valuesOr("slice", { path, offset, limit }, () => this.venue.operations.run("v/ops/covia/slice", { path, offset, limit }));
2565
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace slices");
2566
+ return this.valuesRead("slice", { path, offset, limit });
2386
2567
  }
2387
2568
  async inspect(paths, budget, compact, ucans) {
2388
- if (ucans?.length || Array.isArray(paths)) return this.venue.operations.run("v/ops/covia/inspect", { paths, budget, compact }, { ucans });
2389
- return this._valuesOr("inspect", { path: paths, budget, compact }, () => this.venue.operations.run("v/ops/covia/inspect", { paths, budget, compact }));
2569
+ if (ucans?.length || Array.isArray(paths)) return this.unsupported(
2570
+ Array.isArray(paths) ? "multi-path workspace inspection" : "UCAN-authorised workspace inspection"
2571
+ );
2572
+ return this.valuesRead("inspect", { path: paths, budget, compact });
2390
2573
  }
2391
2574
  /**
2392
2575
  * Count entries at a depth below `path` — a job-free server-side tally, so the
@@ -2398,8 +2581,8 @@ var WorkspaceManager = class {
2398
2581
  */
2399
2582
  async count(path, opts = {}) {
2400
2583
  const { depth, ucans } = opts;
2401
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/aggregate", { path, depth }, { ucans });
2402
- return this._valuesOr("count", { path, depth }, () => this.venue.operations.run("v/ops/covia/aggregate", { path, depth }));
2584
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace counts");
2585
+ return this.valuesRead("count", { path, depth });
2403
2586
  }
2404
2587
  /**
2405
2588
  * Count entries at a depth below `path`, optionally partitioned by a field —
@@ -2411,8 +2594,8 @@ var WorkspaceManager = class {
2411
2594
  */
2412
2595
  async aggregate(path, opts = {}) {
2413
2596
  const { depth, groupBy, ucans } = opts;
2414
- if (ucans?.length) return this.venue.operations.run("v/ops/covia/aggregate", { path, depth, groupBy }, { ucans });
2415
- return this._valuesOr("aggregate", { path, depth, groupBy }, () => this.venue.operations.run("v/ops/covia/aggregate", { path, depth, groupBy }));
2597
+ if (ucans?.length) return this.unsupported("UCAN-authorised workspace aggregation");
2598
+ return this.valuesRead("aggregate", { path, depth, groupBy });
2416
2599
  }
2417
2600
  // ── writes stay on the job path (they should leave an audit record) ─────────
2418
2601
  async write(path, value, ucans) {
@@ -2473,6 +2656,121 @@ var SecretManager = class {
2473
2656
  }
2474
2657
  };
2475
2658
 
2659
+ // src/SkillManager.ts
2660
+ var PAGE_SIZE = 100;
2661
+ var SkillManager = class {
2662
+ constructor(venue) {
2663
+ this.venue = venue;
2664
+ }
2665
+ /** Return every skill asset directly contained at `path`. */
2666
+ async list(path) {
2667
+ const location = path.replace(/\/+$/, "");
2668
+ const skills = [];
2669
+ let offset = 0;
2670
+ for (; ; ) {
2671
+ const page = await this.venue.workspace.list(location, PAGE_SIZE, offset);
2672
+ if (!page.exists) {
2673
+ if (skills.length === 0) return [];
2674
+ throw new CoviaError(`Skill listing at ${location} disappeared mid-listing`);
2675
+ }
2676
+ const keys = page.keys ?? [];
2677
+ const assets = await Promise.all(
2678
+ keys.map((key) => this.get(`${location}/${key}`))
2679
+ );
2680
+ skills.push(...assets);
2681
+ const nextOffset = (page.offset ?? offset) + keys.length;
2682
+ if (keys.length === 0 || nextOffset >= (page.count ?? nextOffset)) break;
2683
+ if (nextOffset <= offset) {
2684
+ throw new CoviaError("Venue returned a skills page that did not advance");
2685
+ }
2686
+ offset = nextOffset;
2687
+ }
2688
+ return skills;
2689
+ }
2690
+ /** Return the ordinary asset at exactly `path`, with typed skill metadata. */
2691
+ async get(path) {
2692
+ return this.venue.assets.get(path);
2693
+ }
2694
+ };
2695
+
2696
+ // src/MemoryManager.ts
2697
+ var DEFAULT_MEMORY_PATH = "w/memory";
2698
+ function memoryChange(value) {
2699
+ const result = record(value);
2700
+ if (typeof result?.n !== "number" || typeof result.count !== "number") {
2701
+ throw new CoviaError("Venue returned an invalid memory mutation result");
2702
+ }
2703
+ return { number: result.n, count: result.count };
2704
+ }
2705
+ var MemoryManager = class {
2706
+ constructor(venue) {
2707
+ this.venue = venue;
2708
+ }
2709
+ /**
2710
+ * Read memory directly from the workspace Values surface. This is job-free;
2711
+ * unlike `v/ops/memory {command:'recall'}`, it returns structured entries
2712
+ * rather than an LLM-oriented numbered text block.
2713
+ */
2714
+ async list(path = DEFAULT_MEMORY_PATH) {
2715
+ const result = await this.venue.workspace.read(path);
2716
+ if (!result.exists) {
2717
+ return [];
2718
+ }
2719
+ let raw;
2720
+ if (result.truncated) {
2721
+ raw = await sliceAll(this.venue.workspace, path);
2722
+ } else if (result.value === void 0 || result.value === null) {
2723
+ return [];
2724
+ } else if (Array.isArray(result.value)) {
2725
+ raw = result.value;
2726
+ } else {
2727
+ throw new CoviaError(`Memory at ${path} is not a flat list`);
2728
+ }
2729
+ const entries = raw.map((value, index) => {
2730
+ const item = record(value);
2731
+ const text = typeof value === "string" ? value : typeof item?.text === "string" ? item.text : void 0;
2732
+ if (text === void 0) {
2733
+ throw new CoviaError(`Memory entry ${index + 1} at ${path} has no text`);
2734
+ }
2735
+ return {
2736
+ number: index + 1,
2737
+ text,
2738
+ createdAt: typeof item?.ts === "number" ? item.ts : void 0,
2739
+ updatedAt: typeof item?.updated === "number" ? item.updated : void 0
2740
+ };
2741
+ });
2742
+ return entries;
2743
+ }
2744
+ /** Append a memory entry. Mutations intentionally remain audited Jobs. */
2745
+ async remember(text, path = DEFAULT_MEMORY_PATH) {
2746
+ const result = await this.venue.operations.run("v/ops/memory", {
2747
+ command: "remember",
2748
+ text,
2749
+ path
2750
+ });
2751
+ return memoryChange(result);
2752
+ }
2753
+ /** Replace a 1-based memory entry. Mutations intentionally remain audited Jobs. */
2754
+ async update(number, text, path = DEFAULT_MEMORY_PATH) {
2755
+ const result = await this.venue.operations.run("v/ops/memory", {
2756
+ command: "update",
2757
+ n: number,
2758
+ text,
2759
+ path
2760
+ });
2761
+ return memoryChange(result);
2762
+ }
2763
+ /** Remove a 1-based memory entry. Mutations intentionally remain audited Jobs. */
2764
+ async forget(number, path = DEFAULT_MEMORY_PATH) {
2765
+ const result = await this.venue.operations.run("v/ops/memory", {
2766
+ command: "forget",
2767
+ n: number,
2768
+ path
2769
+ });
2770
+ return memoryChange(result);
2771
+ }
2772
+ };
2773
+
2476
2774
  // src/Agent.ts
2477
2775
  var Agent = class _Agent {
2478
2776
  constructor(id, venue) {
@@ -2514,6 +2812,14 @@ var Agent = class _Agent {
2514
2812
  async info() {
2515
2813
  return this._agents.info(this.id);
2516
2814
  }
2815
+ /** Page this agent's sessions through the job-free workspace Values API. */
2816
+ async listSessions(options) {
2817
+ return this._agents.listSessions(this.id, options);
2818
+ }
2819
+ /** Read one session through the job-free workspace Values API. */
2820
+ async getSession(sessionId) {
2821
+ return this._agents.getSession(this.id, sessionId);
2822
+ }
2517
2823
  /**
2518
2824
  * Fork this agent into a new agent.
2519
2825
  * @param agentId - ID for the new forked agent
@@ -2598,7 +2904,11 @@ function connectionFailureMessage(venueId, attempts) {
2598
2904
  }
2599
2905
  var Venue = class _Venue {
2600
2906
  constructor(options = {}) {
2601
- /** Connection-level private-jobs mode — see {@link setPrivate}. */
2907
+ this._closed = false;
2908
+ /**
2909
+ * Connection-level private-jobs mode — see {@link setPrivate}.
2910
+ * @deprecated Pass `{ private: true }` to `operations.run()` instead.
2911
+ */
2602
2912
  this.privateJobs = false;
2603
2913
  this.baseUrl = options.baseUrl || "";
2604
2914
  this.venueId = options.venueId || "";
@@ -2609,6 +2919,10 @@ var Venue = class _Venue {
2609
2919
  description: options.description || ""
2610
2920
  };
2611
2921
  }
2922
+ /** Whether this handle has been closed and should not be reused by Grid. */
2923
+ get closed() {
2924
+ return this._closed;
2925
+ }
2612
2926
  /**
2613
2927
  * Puts this connection in **private-jobs mode**: every subsequent
2614
2928
  * `operations.run(...)` executes as a memory-only job (covia #192) — never
@@ -2617,7 +2931,10 @@ var Venue = class _Venue {
2617
2931
  *
2618
2932
  * Because a completed private job is immediately forgotten, results are
2619
2933
  * collected through the invoke `wait` window rather than polling — so
2620
- * private mode works with `run()`; a poll-style `invoke()` throws.
2934
+ * private mode works with `run()`; a poll-style `invoke()` throws.
2935
+ *
2936
+ * @deprecated Pass `{ private: true }` to `operations.run()` instead. The
2937
+ * per-call option avoids mutable connection-wide execution state.
2621
2938
  */
2622
2939
  setPrivate(enabled) {
2623
2940
  this.privateJobs = enabled;
@@ -2646,6 +2963,12 @@ var Venue = class _Venue {
2646
2963
  get secrets() {
2647
2964
  return this._secrets ?? (this._secrets = new SecretManager(this));
2648
2965
  }
2966
+ get skills() {
2967
+ return this._skills ?? (this._skills = new SkillManager(this));
2968
+ }
2969
+ get memory() {
2970
+ return this._memory ?? (this._memory = new MemoryManager(this));
2971
+ }
2649
2972
  /**
2650
2973
  * Connect to a venue, validating it with `GET {base}/api/v1/status`. If the
2651
2974
  * venue is auth-gated (status answers 401/403 — public access disabled), the
@@ -2675,7 +2998,7 @@ var Venue = class _Venue {
2675
2998
  baseUrl: venueId.baseUrl,
2676
2999
  venueId: venueId.venueId,
2677
3000
  name: venueId.metadata.name,
2678
- auth,
3001
+ auth: auth ?? venueId.auth,
2679
3002
  status: venueId.lastKnownStatus
2680
3003
  });
2681
3004
  await connected.status();
@@ -2694,7 +3017,7 @@ var Venue = class _Venue {
2694
3017
  if (typeof endpoint !== "string") {
2695
3018
  throw new CoviaError("No (string) endpoint found for DID");
2696
3019
  }
2697
- candidates = [endpoint.replace(/\/api\/v1/, "")];
3020
+ candidates = [stripTrailingSlash(endpoint).replace(/\/api\/v1$/, "")];
2698
3021
  } else {
2699
3022
  candidates = venueBaseUrlCandidates(venueId);
2700
3023
  }
@@ -2702,7 +3025,11 @@ var Venue = class _Venue {
2702
3025
  for (const baseUrl of candidates) {
2703
3026
  const statusUrl = baseUrl + "/api/v1/status";
2704
3027
  try {
2705
- const data = await fetchWithError(statusUrl);
3028
+ const data = await venueJson({
3029
+ baseUrl,
3030
+ venueId: expectedDid ?? "",
3031
+ auth: new NoAuth()
3032
+ }, "/api/v1/status");
2706
3033
  if (!data.did) {
2707
3034
  throw new CoviaError(`Venue status at ${statusUrl} did not include a DID`);
2708
3035
  }
@@ -2787,9 +3114,10 @@ var Venue = class _Venue {
2787
3114
  * @returns {Promise<string[]>}
2788
3115
  */
2789
3116
  async listSecrets() {
2790
- const result = await fetchWithError(`${this.baseUrl}/api/v1/secrets`, {
2791
- headers: this._buildHeaders()
2792
- });
3117
+ const result = await venueJson(
3118
+ this,
3119
+ "/api/v1/secrets"
3120
+ );
2793
3121
  return Array.isArray(result.items) ? result.items : [];
2794
3122
  }
2795
3123
  /**
@@ -2797,9 +3125,8 @@ var Venue = class _Venue {
2797
3125
  * @param name - Secret name
2798
3126
  */
2799
3127
  async deleteSecret(name) {
2800
- await fetchStreamWithError(`${this.baseUrl}/api/v1/secrets/${encodeURIComponent(name)}`, {
2801
- method: "DELETE",
2802
- headers: this._buildHeaders()
3128
+ await venueStream(this, `/api/v1/secrets/${encodeURIComponent(name)}`, {
3129
+ method: "DELETE"
2803
3130
  });
2804
3131
  }
2805
3132
  /**
@@ -2808,7 +3135,7 @@ var Venue = class _Venue {
2808
3135
  * @throws {VenueIdentityChangedError} If this address reports a different known DID.
2809
3136
  */
2810
3137
  async status() {
2811
- const data = await fetchWithError(`${this.baseUrl}/api/v1/status`);
3138
+ const data = await venueJson(this, "/api/v1/status");
2812
3139
  if (data.did) {
2813
3140
  if (this.venueId) {
2814
3141
  assertVenueIdentity(this.venueId, data.did, this.baseUrl);
@@ -2876,7 +3203,8 @@ var Venue = class _Venue {
2876
3203
  * Clears cached asset data for this venue.
2877
3204
  */
2878
3205
  close() {
2879
- this.assets.clearCache();
3206
+ this._closed = true;
3207
+ this._assets?.clearCache();
2880
3208
  }
2881
3209
  /**
2882
3210
  * Disposable support — allows `using venue = await Grid.connect(...)` in TS 5.2+.
@@ -2884,15 +3212,28 @@ var Venue = class _Venue {
2884
3212
  [Symbol.dispose]() {
2885
3213
  this.close();
2886
3214
  }
2887
- _buildHeaders() {
2888
- const headers = { "Content-Type": "application/json" };
2889
- this.auth.apply(headers, this.venueId);
2890
- return headers;
2891
- }
2892
3215
  };
2893
3216
 
2894
3217
  // src/Grid.ts
2895
- var cache2 = /* @__PURE__ */ new Map();
3218
+ var anonymousConnections = /* @__PURE__ */ new Map();
3219
+ var authenticatedConnections = /* @__PURE__ */ new WeakMap();
3220
+ function connectionsFor(auth) {
3221
+ if (!auth) return anonymousConnections;
3222
+ let connections = authenticatedConnections.get(auth);
3223
+ if (!connections) {
3224
+ connections = /* @__PURE__ */ new Map();
3225
+ authenticatedConnections.set(auth, connections);
3226
+ }
3227
+ return connections;
3228
+ }
3229
+ function normaliseKey(venueId) {
3230
+ return venueId.replace(/\/+$/, "");
3231
+ }
3232
+ function removeEntry(cache, entry) {
3233
+ for (const [key, candidate] of cache) {
3234
+ if (candidate === entry) cache.delete(key);
3235
+ }
3236
+ }
2896
3237
  var Grid = class {
2897
3238
  /**
2898
3239
  * Static method to connect to a venue
@@ -2901,12 +3242,27 @@ var Grid = class {
2901
3242
  * @returns {Promise<Venue>} A new Venue instance configured appropriately
2902
3243
  */
2903
3244
  static async connect(venueId, auth) {
2904
- const cached = cache2.get(venueId);
2905
- if (cached && cached.auth === auth)
2906
- return cached.venue;
2907
- const connectedVenue = await Venue.connect(venueId, auth);
2908
- cache2.set(venueId, { auth, venue: connectedVenue });
2909
- return connectedVenue;
3245
+ const cache = connectionsFor(auth);
3246
+ const key = normaliseKey(venueId);
3247
+ const cached = cache.get(key);
3248
+ if (cached) {
3249
+ if (!cached.venue?.closed) return cached.promise;
3250
+ removeEntry(cache, cached);
3251
+ }
3252
+ const entry = {};
3253
+ entry.promise = Venue.connect(venueId, auth).then((venue) => {
3254
+ entry.venue = venue;
3255
+ cache.set(normaliseKey(venue.baseUrl), entry);
3256
+ if (key.startsWith("did:")) {
3257
+ cache.set(normaliseKey(venue.venueId), entry);
3258
+ }
3259
+ return venue;
3260
+ }).catch((error) => {
3261
+ removeEntry(cache, entry);
3262
+ throw error;
3263
+ });
3264
+ cache.set(key, entry);
3265
+ return entry.promise;
2910
3266
  }
2911
3267
  };
2912
3268
 
@@ -2952,6 +3308,7 @@ function relayDelegation(privateKey, venueDID, lifetimeSeconds, caps = []) {
2952
3308
  exports.AdapterManager = AdapterManager;
2953
3309
  exports.Agent = Agent;
2954
3310
  exports.AgentManager = AgentManager;
3311
+ exports.AgentSessionNotFoundError = AgentSessionNotFoundError;
2955
3312
  exports.AgentStatus = AgentStatus;
2956
3313
  exports.Asset = Asset;
2957
3314
  exports.AssetManager = AssetManager;
@@ -2974,6 +3331,7 @@ exports.JobManager = JobManager;
2974
3331
  exports.JobNotFoundError = JobNotFoundError;
2975
3332
  exports.JobStatus = JobStatus;
2976
3333
  exports.KeyPairAuth = KeyPairAuth;
3334
+ exports.MemoryManager = MemoryManager;
2977
3335
  exports.Namespace = Namespace;
2978
3336
  exports.NoAuth = NoAuth;
2979
3337
  exports.NotFoundError = NotFoundError;
@@ -2982,7 +3340,9 @@ exports.OperationManager = OperationManager;
2982
3340
  exports.RateLimitError = RateLimitError;
2983
3341
  exports.RunStatus = RunStatus;
2984
3342
  exports.SecretManager = SecretManager;
3343
+ exports.SkillManager = SkillManager;
2985
3344
  exports.UCANManager = UCANManager;
3345
+ exports.UnsupportedVenueFeatureError = UnsupportedVenueFeatureError;
2986
3346
  exports.VENUE_RELAY = VENUE_RELAY;
2987
3347
  exports.Venue = Venue;
2988
3348
  exports.VenueIdentityChangedError = VenueIdentityChangedError;