@covia/covia-sdk 1.16.0 → 1.18.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
@@ -1741,16 +1741,46 @@ async function sliceAll(workspace, path, pageSize = SLICE_PAGE_SIZE) {
1741
1741
  }
1742
1742
  return items;
1743
1743
  }
1744
+ function descWindow(total, offset, limit) {
1745
+ const end = Math.max(0, total - offset);
1746
+ return [Math.max(0, end - limit), end];
1747
+ }
1744
1748
 
1745
1749
  // src/AgentManager.ts
1750
+ var SESSION_ROLES = ["system", "user", "assistant", "tool"];
1751
+ var MAX_COMPACTION_DEPTH = 32;
1752
+ function collectTurns(entries, out, depth = 0) {
1753
+ if (depth > MAX_COMPACTION_DEPTH) return;
1754
+ for (const entry of entries) {
1755
+ const e = record(entry);
1756
+ if (!e) continue;
1757
+ if (Array.isArray(e.items) && typeof e.summary === "string") {
1758
+ collectTurns(e.items, out, depth + 1);
1759
+ continue;
1760
+ }
1761
+ if (typeof e.role === "string" && SESSION_ROLES.includes(e.role)) {
1762
+ out.push({ ...e, role: e.role });
1763
+ }
1764
+ }
1765
+ }
1766
+ function sessionConversation(frames) {
1767
+ const turns = [];
1768
+ for (const frame of frames) {
1769
+ const entries = record(frame)?.conversation;
1770
+ if (Array.isArray(entries)) collectTurns(entries, turns);
1771
+ }
1772
+ return turns;
1773
+ }
1746
1774
  function sessionRecord(sessionId, value) {
1747
1775
  const session = record(value) ?? {};
1748
1776
  const meta = record(session.meta);
1777
+ const frames = Array.isArray(session.frames) ? session.frames : [];
1749
1778
  return {
1750
1779
  id: sessionId,
1751
1780
  metadata: meta ?? {},
1752
1781
  pending: Array.isArray(session.pending) ? session.pending : [],
1753
- frames: Array.isArray(session.frames) ? session.frames : [],
1782
+ frames,
1783
+ conversation: sessionConversation(frames),
1754
1784
  wakeTime: typeof session.wakeTime === "number" ? session.wakeTime : void 0
1755
1785
  };
1756
1786
  }
@@ -1903,11 +1933,31 @@ var AgentManager = class {
1903
1933
  * available on each record's `value` while common runtime fields are typed.
1904
1934
  */
1905
1935
  async listSessions(agentId, options = {}) {
1906
- const result = await this.venue.workspace.slice(
1907
- `g/${agentId}/sessions`,
1908
- options.offset,
1909
- options.limit
1910
- );
1936
+ const path = `g/${agentId}/sessions`;
1937
+ if ((options.order ?? "asc") === "asc") {
1938
+ return this._sessionPage(path, await this.venue.workspace.slice(
1939
+ path,
1940
+ options.offset,
1941
+ options.limit
1942
+ ), options.offset ?? 0, options.limit);
1943
+ }
1944
+ const offset = Math.max(0, options.offset ?? 0);
1945
+ const limit = options.limit;
1946
+ const guess = (await this.venue.workspace.count(path)).count ?? 0;
1947
+ if (limit === 0) return { items: [], total: guess, offset, limit: 0 };
1948
+ const read = async (total2) => {
1949
+ const [start, end] = descWindow(total2, offset, limit ?? total2);
1950
+ return end > start ? this.venue.workspace.slice(path, start, end - start) : { exists: true, values: [], count: total2, offset: start };
1951
+ };
1952
+ let result = await read(guess);
1953
+ const total = result.count ?? guess;
1954
+ if (total !== guess) result = await read(total);
1955
+ const page = this._sessionPage(path, result, offset, limit);
1956
+ page.items.reverse();
1957
+ return page;
1958
+ }
1959
+ /** Shape a `{key, value}` session slice into a page. */
1960
+ _sessionPage(_path, result, offset, limit) {
1911
1961
  const sessions = (result.values ?? []).flatMap((entry) => {
1912
1962
  const pair = record(entry);
1913
1963
  if (!pair || typeof pair.key !== "string") return [];
@@ -1916,8 +1966,8 @@ var AgentManager = class {
1916
1966
  return {
1917
1967
  items: sessions,
1918
1968
  total: result.count ?? sessions.length,
1919
- offset: result.offset ?? options.offset ?? 0,
1920
- limit: options.limit ?? sessions.length
1969
+ offset,
1970
+ limit: limit ?? sessions.length
1921
1971
  };
1922
1972
  }
1923
1973
  /** Read one agent session directly from the workspace Values surface. */
@@ -2109,6 +2159,24 @@ var Job = class {
2109
2159
  };
2110
2160
 
2111
2161
  // src/JobManager.ts
2162
+ var JOB_INDEX = "j";
2163
+ var DEFAULT_HISTORY_LIMIT = 50;
2164
+ var MAX_SLICE_LIMIT = 100;
2165
+ function isSliceCapError(error) {
2166
+ return error instanceof Error && error.message.includes("exceeds maxSize");
2167
+ }
2168
+ function jobRecords(values) {
2169
+ const records = [];
2170
+ for (const entry of values) {
2171
+ const pair = record(entry);
2172
+ const value = record(pair?.value);
2173
+ if (!value) continue;
2174
+ const meta = value;
2175
+ const key = typeof pair?.key === "string" ? pair.key : "";
2176
+ records.push({ ...meta, id: meta.id ?? `0x${key}` });
2177
+ }
2178
+ return records;
2179
+ }
2112
2180
  function jobPage(value) {
2113
2181
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
2114
2182
  throw new CoviaError("Venue returned an invalid jobs listing");
@@ -2150,6 +2218,70 @@ var JobManager = class {
2150
2218
  }
2151
2219
  return items;
2152
2220
  }
2221
+ /**
2222
+ * A page of job history: `JobMetadata` rows, newest-first by default, with an
2223
+ * authoritative total.
2224
+ *
2225
+ * `list()` answers "which jobs exist" as ids; a history view needs a bounded
2226
+ * window of *records* and a count to page against. This reads the job index
2227
+ * directly, so each row arrives with its metadata already attached — the
2228
+ * alternative, listing ids and then fetching each one, is an N+1 fan-out per
2229
+ * page. Reads stay job-free (covia#177): paging job history must not itself
2230
+ * write a job record per page.
2231
+ *
2232
+ * The total is read before a `desc` window can be placed, so the index may
2233
+ * grow in between; the page self-corrects once against the count the slice
2234
+ * itself reports, which is authoritative for the rows returned with it.
2235
+ */
2236
+ async history(options = {}) {
2237
+ const offset = Math.max(0, Math.trunc(options.offset ?? 0));
2238
+ const limit = Math.max(0, Math.trunc(options.limit ?? DEFAULT_HISTORY_LIMIT));
2239
+ const descending = (options.order ?? "desc") === "desc";
2240
+ if (limit === 0) {
2241
+ const { count } = await this.venue.workspace.count(JOB_INDEX);
2242
+ return { items: [], total: count ?? 0, offset, limit };
2243
+ }
2244
+ if (!descending) {
2245
+ const asc = await this._readJobRange(offset, offset + limit);
2246
+ return { items: jobRecords(asc.values), total: asc.count ?? offset + asc.values.length, offset, limit };
2247
+ }
2248
+ const guess = (await this.venue.workspace.count(JOB_INDEX)).count ?? 0;
2249
+ let read = await this._readJobRange(...descWindow(guess, offset, limit));
2250
+ const total = read.count ?? guess;
2251
+ if (total !== guess) {
2252
+ read = await this._readJobRange(...descWindow(total, offset, limit));
2253
+ }
2254
+ return { items: jobRecords(read.values).reverse(), total: read.count ?? total, offset, limit };
2255
+ }
2256
+ /**
2257
+ * Read index ranks `[start, end)` in cap-aware chunks, halving on an oversize
2258
+ * response and skipping a record that is individually oversize.
2259
+ */
2260
+ async _readJobRange(start, end) {
2261
+ const values = [];
2262
+ let count;
2263
+ let pos = start;
2264
+ let chunk = MAX_SLICE_LIMIT;
2265
+ while (pos < end) {
2266
+ const want = Math.min(chunk, end - pos);
2267
+ try {
2268
+ const page = await this.venue.workspace.slice(JOB_INDEX, pos, want);
2269
+ count = page.count ?? count;
2270
+ const got = page.values ?? [];
2271
+ values.push(...got);
2272
+ if (got.length < want) break;
2273
+ pos += got.length;
2274
+ } catch (error) {
2275
+ if (!isSliceCapError(error)) throw error;
2276
+ if (want > 1) {
2277
+ chunk = Math.max(1, Math.floor(want / 2));
2278
+ continue;
2279
+ }
2280
+ pos += 1;
2281
+ }
2282
+ }
2283
+ return { count, values };
2284
+ }
2153
2285
  async get(jobId) {
2154
2286
  try {
2155
2287
  const data = await venueJson(this.venue, `/api/v1/jobs/${jobId}`);
@@ -2581,12 +2713,52 @@ var OperationManager = class {
2581
2713
  };
2582
2714
 
2583
2715
  // src/WorkspaceManager.ts
2716
+ var SCOPED_NAMESPACES = ["t", "n", "c"];
2717
+ var SCOPE_UNEXPANDED = /Cannot use '[tnc]\/' prefix outside/;
2718
+ function scopedNamespaceOf(path) {
2719
+ const head = path.split("/", 1)[0];
2720
+ return SCOPED_NAMESPACES.includes(head) ? head : void 0;
2721
+ }
2722
+ function scopeParamsFor(ns, scope) {
2723
+ const { agent, task, session } = scope;
2724
+ switch (ns) {
2725
+ case "n":
2726
+ return { agent };
2727
+ case "c":
2728
+ if (!session) throw new CoviaError("A 'c/' scratch read needs a session in its scope");
2729
+ return { agent, session };
2730
+ case "t":
2731
+ if (!task) throw new CoviaError("A 't/' scratch read needs a task in its scope");
2732
+ return { agent, task };
2733
+ }
2734
+ }
2735
+ function expandScopedPath(path, ns, scope) {
2736
+ if (scope.agent.startsWith("did:")) {
2737
+ throw new CoviaError(
2738
+ "This venue cannot expand scoped scratch paths and the SDK cannot do it for a DID-qualified agent \u2014 pass a bare agent id, or upgrade the venue (covia#230)"
2739
+ );
2740
+ }
2741
+ const suffix = path.slice(ns.length);
2742
+ switch (ns) {
2743
+ case "n":
2744
+ return `g/${scope.agent}/n${suffix}`;
2745
+ case "c":
2746
+ return `g/${scope.agent}/sessions/${scope.session}/c${suffix}`;
2747
+ case "t":
2748
+ return `j/${scope.task}/temp${suffix}`;
2749
+ }
2750
+ }
2584
2751
  var WorkspaceManager = class {
2585
2752
  constructor(venue) {
2586
2753
  this.venue = venue;
2587
2754
  // Whether this venue serves GET /api/v1/values/* — flipped on the first 404
2588
2755
  // so pre-0.3 venues pay the probe once, not one failed GET per read.
2589
2756
  this.valuesSupported = true;
2757
+ // Whether this venue expands t/, n/ and c/ from explicit agent/task/session
2758
+ // selectors (covia#230) — flipped off the first time a venue answers a
2759
+ // selector-bearing read by complaining the shorthand has no execution scope,
2760
+ // after which scoped reads expand client-side instead.
2761
+ this.scopeExpansionSupported = true;
2590
2762
  }
2591
2763
  /** GET a job-free `/api/v1/values/{op}` read; omit undefined params. */
2592
2764
  _values(op, params) {
@@ -2620,6 +2792,52 @@ var WorkspaceManager = class {
2620
2792
  }
2621
2793
  return this.unsupported(`workspace ${op} reads`);
2622
2794
  }
2795
+ /**
2796
+ * A job-free Values read of an execution-scoped path, preferring the venue's
2797
+ * own selector expansion and falling back to the client-side one.
2798
+ *
2799
+ * The venue is preferred because it expands *before* the capability check, so
2800
+ * authorisation applies to the same canonical resource that is ultimately
2801
+ * read — a caller cannot substitute a sibling or foreign agent's scratch.
2802
+ * The fallback reaches the same resource but re-derives the layout here.
2803
+ */
2804
+ async scopedRead(op, params, scope) {
2805
+ const path = String(params.path ?? "");
2806
+ const ns = scopedNamespaceOf(path);
2807
+ if (!ns) return this.valuesRead(op, params);
2808
+ if (this.scopeExpansionSupported) {
2809
+ try {
2810
+ return await this.valuesRead(op, { ...params, ...scopeParamsFor(ns, scope) });
2811
+ } catch (e) {
2812
+ if (!(e instanceof Error) || !SCOPE_UNEXPANDED.test(e.message)) throw e;
2813
+ this.scopeExpansionSupported = false;
2814
+ }
2815
+ }
2816
+ scopeParamsFor(ns, scope);
2817
+ return this.valuesRead(op, { ...params, path: expandScopedPath(path, ns, scope) });
2818
+ }
2819
+ /**
2820
+ * Bind an execution scope, giving job-free reads of the `t/`, `n/` and `c/`
2821
+ * scratch shorthands that a bare GET cannot otherwise resolve (covia#177
2822
+ * forbids minting a Job for a read; the shorthands need context a GET lacks).
2823
+ *
2824
+ * ```ts
2825
+ * const scratch = venue.workspace.scoped({ agent: 'alice', task: taskId });
2826
+ * await scratch.read('t/snapshot');
2827
+ * await scratch.list('t/');
2828
+ * ```
2829
+ *
2830
+ * Ordinary paths still work through the handle, so a caller rendering a task
2831
+ * inspector can use one reader for both scratch and workspace. Capability
2832
+ * behaviour is unchanged: the caller needs read caps on the agent's subtree.
2833
+ */
2834
+ scoped(scope) {
2835
+ return new ScopedWorkspace(this, scope);
2836
+ }
2837
+ /** @internal — the scoped read used by {@link ScopedWorkspace}. */
2838
+ _scopedRead(op, params, scope) {
2839
+ return this.scopedRead(op, params, scope);
2840
+ }
2623
2841
  // ── job-free reads (#177) ───────────────────────────────────────────────────
2624
2842
  async read(path, maxSize, ucans) {
2625
2843
  if (ucans?.length) return this.unsupported("UCAN-authorised workspace reads");
@@ -2680,6 +2898,30 @@ var WorkspaceManager = class {
2680
2898
  return this.venue.operations.run("v/ops/covia/copy", { from, to }, { ucans });
2681
2899
  }
2682
2900
  };
2901
+ var ScopedWorkspace = class {
2902
+ constructor(workspace, scope) {
2903
+ this.workspace = workspace;
2904
+ this.scope = scope;
2905
+ }
2906
+ read(path, maxSize) {
2907
+ return this.workspace._scopedRead("read", { path, maxSize }, this.scope);
2908
+ }
2909
+ list(path, limit, offset) {
2910
+ return this.workspace._scopedRead("list", { path, limit, offset }, this.scope);
2911
+ }
2912
+ slice(path, offset, limit) {
2913
+ return this.workspace._scopedRead("slice", { path, offset, limit }, this.scope);
2914
+ }
2915
+ inspect(path, budget, compact) {
2916
+ return this.workspace._scopedRead("inspect", { path, budget, compact }, this.scope);
2917
+ }
2918
+ count(path, opts = {}) {
2919
+ return this.workspace._scopedRead("count", { path, depth: opts.depth }, this.scope);
2920
+ }
2921
+ aggregate(path, opts = {}) {
2922
+ return this.workspace._scopedRead("aggregate", { path, depth: opts.depth, groupBy: opts.groupBy }, this.scope);
2923
+ }
2924
+ };
2683
2925
 
2684
2926
  // src/UCANManager.ts
2685
2927
  var UCANManager = class {
@@ -3051,6 +3293,15 @@ var Agent = class _Agent {
3051
3293
  async getSession(sessionId) {
3052
3294
  return this._agents.getSession(this.id, sessionId);
3053
3295
  }
3296
+ /**
3297
+ * One session's transcript — its turns in order, with compacted segments
3298
+ * expanded back into the turns they archived.
3299
+ *
3300
+ * Shorthand for `getSession(sessionId)` when only the conversation is wanted.
3301
+ */
3302
+ async transcript(sessionId) {
3303
+ return (await this._agents.getSession(this.id, sessionId)).conversation;
3304
+ }
3054
3305
  /** Stream this agent's run-loop events. See `AgentManager.events`. */
3055
3306
  async *events(options) {
3056
3307
  yield* this._agents.events(this.id, options);
@@ -3549,4 +3800,4 @@ function relayDelegation(privateKey, venueDID, lifetimeSeconds, caps = []) {
3549
3800
  (*! noble-ed25519 - MIT License (c) 2019 Paul Miller (paulmillr.com) *)
3550
3801
  */
3551
3802
 
3552
- export { A2AManager, AdapterManager, Agent, AgentManager, AgentSessionNotFoundError, AgentStatus, Asset, AssetManager, AssetNotFoundError, Auth, BasicAuth, BearerAuth, ChatSession, CoviaConnectionError, CoviaError, CoviaTimeoutError, CredentialsHTTP, DLFSManager, DataAsset, Ed25519Auth, Grid, GridError, Job, JobFailedError, JobManager, JobNotFoundError, JobStatus, KeyPairAuth, MemoryManager, Namespace, NoAuth, NotFoundError, Operation, OperationManager, RateLimitError, RunStatus, SecretManager, SkillManager, UCANManager, UnsupportedVenueFeatureError, UserManager, 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 };
3803
+ export { A2AManager, AdapterManager, Agent, AgentManager, AgentSessionNotFoundError, AgentStatus, Asset, AssetManager, AssetNotFoundError, Auth, BasicAuth, BearerAuth, ChatSession, CoviaConnectionError, CoviaError, CoviaTimeoutError, CredentialsHTTP, DLFSManager, DataAsset, Ed25519Auth, Grid, GridError, Job, JobFailedError, JobManager, JobNotFoundError, JobStatus, KeyPairAuth, MemoryManager, Namespace, NoAuth, NotFoundError, Operation, OperationManager, RateLimitError, RunStatus, ScopedWorkspace, SecretManager, SkillManager, UCANManager, UnsupportedVenueFeatureError, UserManager, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@covia/covia-sdk",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "Typescript library for covia-ai",
5
5
  "packageManager": "pnpm@11.9.0",
6
6
  "main": "./dist/index.js",