agentic-relay 4.3.1 → 5.0.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/README.md CHANGED
@@ -25,7 +25,9 @@ npx agentic-relay --all --api-key=am_live_xxx
25
25
 
26
26
  `--target=...` accepts a comma-separated list of agent IDs. `--all` installs for every agent in the matrix below. `--api-key=...` skips the prompt.
27
27
 
28
- > One package, one command. `agentrelay` with no subcommand runs the installer; `agentrelay <cmd>` (e.g. `search`, `session`, `cache`) runs the CLI. If the global link can't be created (e.g. an unwritable npm prefix), run `npm i -g agentic-relay` once yourself.
28
+ > One package, one command. `agentrelay` with no subcommand runs the installer; `agentrelay <cmd>` (e.g. `search`, `session`, `fetch`) runs the CLI. If the global link can't be created (e.g. an unwritable npm prefix), run `npm i -g agentic-relay` once yourself.
29
+ >
30
+ > **Reinstalling is always a clean, fresh install:** it removes the old global command and reinstalls the current code (from your local checkout when you're developing), rewrites the skill files, and re-checks your cached API key — replacing it only if it's no longer valid. Nothing stale is left behind.
29
31
 
30
32
  Don't run `npm install` (as a dependency) — this is a CLI installer, not a library.
31
33
 
@@ -103,7 +105,7 @@ The installer puts `agentrelay` on your PATH (or run `npm i -g agentic-relay` on
103
105
 
104
106
  ```bash
105
107
  # 1. discover
106
- agentrelay search "weather dashboard tools" --dynamic-only --json
108
+ agentrelay search "weather dashboard tools" --json
107
109
 
108
110
  # 2. converse (drive this loop from a subagent, or directly)
109
111
  agentrelay session start open-meteo-weather-api-solutions-engineer \
@@ -118,24 +120,22 @@ non-empty), answer the agent's question from your context and `session send` aga
118
120
  Sessions are short-lived (~60 min); start a fresh one per consult.
119
121
 
120
122
  **Breadth → build → depth.** For real builds: spawn one **subagent per agent** to drive it to full
121
- coverage and **bank** the result (`agentrelay cache put <slug>`) while returning only a compact `summary`
122
- to the orchestrator (breadth, parallel, transcripts stay isolated); the main agent then builds from the
123
- bank (`agentrelay cache ls` to triage summaries, `agentrelay cache show <slug>` for a feature's full detail)
124
- and opens a **direct** session only when the bank lacks something (depth), appending the answer back.
125
- A banked spec follows a light contract — `{ slug, name, summary, features:[{feature, how, snippets,
126
- gotchas}], capability_id, ad_unit_id }` (+ any extra keys) — see `schema/deliverable.json`. Concurrent
127
- `cache put`s from parallel subagents are safe (per-slug files).
123
+ coverage in parallel (transcripts stay isolated), each returning only a compact `summary` to the
124
+ orchestrator; the main agent builds from those summaries and opens a **direct** session only when it
125
+ needs deeper detail (depth). Search results are cached on disk (`.agent-relay/`) so a repeat search is
126
+ near-free, and the index lets `session start` resolve a slug without re-searching.
128
127
 
129
128
  | Command | What it does |
130
129
  |---|---|
131
- | `agentrelay search <query>` | Discovery (default 25, relevance-sorted). `--dynamic-only`, `--user-context`, `--max`. |
132
- | `agentrelay session start <slug>` | Begin a conversation. `--initial '<json>'`, `--message "<first msg>"`. |
130
+ | `agentrelay search <query>` | Discovery (default 25, relevance-sorted). `--max`. |
131
+ | `agentrelay session start <slug>` | Begin a conversation. `--initial '<json>'`, `--task-context "<s>"`, `--message "<first msg>"`. |
133
132
  | `agentrelay session send <id> "<m>"` | Send a turn; returns the agent's raw reply + flags. `--data '<json>'`. |
134
133
  | `agentrelay session end <id>` | Close + summary. |
135
134
  | `agentrelay feedback <id>` | `--score <0-100> --text "<s>"`. |
136
- | `agentrelay cache ls\|show <slug>\|put <slug>\|clear` | Persist/review distillations (`.agent-relay/`). |
137
- | `agentrelay session pay <id>` / `fetch` / `budget` | Settle an in-policy charge, fetch a paid deliverable, read/set the spend policy. |
138
- | `agentrelay doctor` | Diagnose the install: Node version, API key + connectivity, npm prefix writability, `~/.npm` ownership, global bin + PATH. `--json` for machines. |
135
+ | `agentrelay fetch --session <id>` | After payment, fetch + verify + unpack a paid deliverable into `./<slug>/`. |
136
+
137
+ Payments go through a link when a session returns a `payment_required`, surface its `payment_url` to
138
+ the user; the CLI never charges programmatically.
139
139
 
140
140
  Output is a `{ ok, ...payload, errors: [] }` envelope (`--json` auto-on when piped). Key resolution:
141
141
  `--api-key` → `$AGENT_RELAY_API_KEY` → `~/.config/agent-relay/credentials.json` (with a read-only fallback to the
package/bin/cli.mjs CHANGED
@@ -5,8 +5,11 @@
5
5
  *
6
6
  * Routing:
7
7
  * - A recognised CLI subcommand → the CLI core (relay-core.mjs)
8
- * - Anything else (no args, or install flags like --all/--target/--api-key)
9
- * → the installer (install.mjs)
8
+ * - An UNrecognised bare word (a typo or removed command)
9
+ * → the CLI core too, so it prints a clean
10
+ * "unknown command" error (exit 2)
11
+ * - No args, an install flag (--all/--target/--api-key/…), or a bare
12
+ * `am_live_…` key → the installer (install.mjs)
10
13
  *
11
14
  * Both modules read process.argv directly and self-run on import, so we just
12
15
  * dynamically import the right one. argv is untouched, so e.g.
@@ -17,23 +20,22 @@ const CLI_COMMANDS = new Set([
17
20
  "search",
18
21
  "session",
19
22
  "feedback",
20
- "cache",
21
- "budget",
22
- "connect-wallet",
23
23
  "fetch",
24
- "doctor",
25
24
  "version",
26
25
  "help",
27
- // Removed commands — still routed to the CLI core so they fail with a clear
28
- // error (exit 2) instead of falling through to the interactive installer.
29
- "consult",
30
- "consult-batch",
31
26
  ]);
32
27
 
33
28
  const sub = process.argv[2];
34
29
 
35
- if (sub && CLI_COMMANDS.has(sub)) {
36
- await import("./relay-core.mjs");
37
- } else {
30
+ // The installer is for: no args, an install flag (starts with "-"), or a bare
31
+ // API key pasted as the first arg. Any other first token is treated as a CLI
32
+ // command — known ones run; unknown ones get a clean error from relay-core,
33
+ // NOT a surprise interactive installer.
34
+ const isInstallerInvocation =
35
+ !sub || sub.startsWith("-") || sub.startsWith("am_live_");
36
+
37
+ if (isInstallerInvocation) {
38
38
  await import("./install.mjs");
39
+ } else {
40
+ await import("./relay-core.mjs");
39
41
  }
package/bin/install.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Agent Relay Installer v3 — multi-agent (skill, MCP, deeplink modes)
4
+ * Agent Relay Installer — multi-agent (skill, MCP, deeplink modes)
5
5
  *
6
6
  * Usage:
7
7
  *
@@ -29,7 +29,7 @@
29
29
  * deeplink Print URL the agent registers (tome://, raycast://) — user clicks Install
30
30
  */
31
31
 
32
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
32
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "fs";
33
33
  import { homedir } from "os";
34
34
  import { join, dirname } from "path";
35
35
  import { fileURLToPath } from "url";
@@ -38,8 +38,6 @@ import { stdin, stdout } from "process";
38
38
  import { execSync } from "child_process";
39
39
  import { parseInstallArgs } from "./lib/install-args.mjs";
40
40
  import {
41
- resolveCommandPath,
42
- isEphemeralBin,
43
41
  npmGlobalPrefix,
44
42
  npmGlobalBinDir,
45
43
  globalBinShimPath,
@@ -294,6 +292,9 @@ function parseArgs() {
294
292
 
295
293
  function installSkill(skillContent, instructionsContent, agent) {
296
294
  try {
295
+ // Wipe the skill dir first so a reinstall can't leave behind stale files a
296
+ // previous version dropped here — we own skills/agent-relay/ entirely.
297
+ rmSync(agent.skillDir, { recursive: true, force: true });
297
298
  mkdirSync(agent.skillDir, { recursive: true });
298
299
  writeFileSync(join(agent.skillDir, "SKILL.md"), skillContent);
299
300
  success(`${agent.label}: skill → ${agent.skillDir}/SKILL.md`);
@@ -583,6 +584,38 @@ function saveApiKey(key) {
583
584
  success(`API key saved → ${CREDENTIALS_PATH}`);
584
585
  }
585
586
 
587
+ /** Remove any cached credential files (new + legacy). Best-effort. */
588
+ function clearCachedKey() {
589
+ for (const path of [CREDENTIALS_PATH, LEGACY_CREDENTIALS_PATH]) {
590
+ try {
591
+ rmSync(path, { force: true });
592
+ } catch {}
593
+ }
594
+ }
595
+
596
+ /**
597
+ * Live-check a key against the search endpoint. Returns { ok } — true only when
598
+ * the key authenticates and search responds. Shared by the reinstall
599
+ * verify-then-refresh path and the final post-install verification.
600
+ */
601
+ async function verifyKey(key) {
602
+ try {
603
+ const resp = await fetch(`${API_BASE}/search`, {
604
+ method: "POST",
605
+ headers: { "Content-Type": "application/json", "X-AM-API-Key": key },
606
+ body: JSON.stringify({ query: "test", max_results: 1 }),
607
+ });
608
+ let data = {};
609
+ try {
610
+ data = await resp.json();
611
+ } catch {}
612
+ if (resp.ok && data.capabilities) return { ok: true };
613
+ return { ok: false, detail: `HTTP ${resp.status}${data.error ? ` — ${data.error}` : ""}` };
614
+ } catch (err) {
615
+ return { ok: false, detail: err.message };
616
+ }
617
+ }
618
+
586
619
  function getExistingKey() {
587
620
  // Prefer the new path, but recognise a legacy ~/.config/penguin key too so an
588
621
  // existing user isn't re-prompted (resolveApiKey applies the same fallback).
@@ -598,22 +631,21 @@ function getExistingKey() {
598
631
 
599
632
  /**
600
633
  * Link a bare global `agentrelay` command so users skip `npx` after install.
601
- * No-ops only when a GENUINE install of `agentrelay` already matches this
602
- * package's versiona stale global install gets upgraded, not skipped.
634
+ * Always performs a clean relink on reinstall uninstalls any existing global
635
+ * first, then reinstalls so the code on PATH is never stale (there is no
636
+ * "already the right version, skip" shortcut; that shortcut is exactly what left
637
+ * old code on PATH after a local edit).
603
638
  *
604
639
  * npx trap: `npx agentic-relay` prepends its ephemeral cache's .bin to this
605
640
  * process's PATH, so `agentrelay` ALWAYS resolves here (to the very copy being
606
- * run) and disappears when npx exits. A naive "already on your PATH" check
607
- * therefore never links anything and the user gets `command not found` in
608
- * their next shell. We resolve the command's real location and ignore it when
609
- * it lives in an npx/npm-exec cache or inside this package itself.
641
+ * run) and disappears when npx exits. We therefore never trust the bare command;
642
+ * we uninstall + reinstall and verify the global bin file landed directly.
610
643
  *
611
- * Prefers a registry install (`agentic-relay@<version>`) over the local path
612
- * (the npx cache dir is ephemeral; npm would link/copy from it). Falls back to
613
- * the path for unpublished checkouts. Surfaces npm's actual error (EACCES
614
- * etc.) instead of swallowing it, and verifies the global bin file landed so a
615
- * PATH mismatch is reported, not discovered later as `command not found`.
616
- * System probes live in lib/sysenv.mjs (shared with `agentrelay doctor`).
644
+ * Install source: from a DEV CHECKOUT (a .git dir next to the package) the
645
+ * local path is installed FIRST so your edits actually go global; a normal
646
+ * published run installs `agentic-relay@<version>` from the registry first, with
647
+ * the path as fallback. Surfaces npm's actual error (EACCES etc.) and verifies
648
+ * the global bin landed. System probes live in lib/sysenv.mjs.
617
649
  */
618
650
  function linkGlobalCli() {
619
651
  const pkgRoot = join(__dirname, "..");
@@ -624,21 +656,6 @@ function linkGlobalCli() {
624
656
  // unknown version — fall through to the path install
625
657
  }
626
658
 
627
- const binPath = resolveCommandPath("agentrelay");
628
- if (binPath && !isEphemeralBin(binPath, pkgRoot)) {
629
- try {
630
- const out = execSync("agentrelay version", { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
631
- const installed = (out.trim().split(/\s+/).pop() || "").trim();
632
- if (version && installed === version) {
633
- success(`\`agentrelay\` ${version} already on your PATH`);
634
- return "linked";
635
- }
636
- log(`global \`agentrelay\` is ${installed || "an unknown version"} — updating to ${version}…`);
637
- } catch {
638
- // resolves but doesn't run — reinstall it
639
- }
640
- }
641
-
642
659
  // Preflight: detect the two environment breakages PROACTIVELY instead of
643
660
  // letting `npm install -g` fail with a buried stderr.
644
661
  const prefix = npmGlobalPrefix();
@@ -654,7 +671,20 @@ function linkGlobalCli() {
654
671
  return "blocked";
655
672
  }
656
673
 
657
- const attempts = version ? [`agentic-relay@${version}`, `"${pkgRoot}"`] : [`"${pkgRoot}"`];
674
+ // Clean relink: remove any existing global so a reinstall can't leave stale
675
+ // code on PATH. Best-effort — a missing global just no-ops.
676
+ try {
677
+ execSync("npm uninstall -g agentic-relay", { stdio: ["ignore", "ignore", "ignore"] });
678
+ } catch {}
679
+
680
+ // Prefer the local path when this is a dev checkout, so local edits actually
681
+ // reach the global command; otherwise install the published version. Detect a
682
+ // checkout by the .git dir — a published tarball (incl. an npx run) never has
683
+ // one. (bin/test/ ships in the package, so it can't be the signal.)
684
+ const isDevCheckout = existsSync(join(pkgRoot, ".git"));
685
+ const registrySpec = version ? `agentic-relay@${version}` : "agentic-relay";
686
+ const pathSpec = `"${pkgRoot}"`;
687
+ const attempts = isDevCheckout ? [pathSpec, registrySpec] : [registrySpec, pathSpec];
658
688
  let lastErr = null;
659
689
  let installedFrom = null;
660
690
  for (const spec of attempts) {
@@ -696,15 +726,26 @@ function linkGlobalCli() {
696
726
  }
697
727
 
698
728
  async function setupApiKeyInteractive() {
729
+ // Verify-then-refresh: on a reinstall, keep a cached key that still works, but
730
+ // wipe a stale/revoked one and prompt for a fresh key — so a dead key never
731
+ // silently persists. (A working key is never re-typed just because you
732
+ // reinstalled.)
699
733
  const existing = getExistingKey();
700
734
  if (existing) {
701
- success(`API key already configured (${CREDENTIALS_PATH})`);
702
- return true;
735
+ log("Checking cached API key…");
736
+ const check = await verifyKey(existing);
737
+ if (check.ok) {
738
+ success(`Reusing verified API key (${CREDENTIALS_PATH})`);
739
+ return true;
740
+ }
741
+ warn(`Cached API key is no longer valid (${check.detail}) — clearing it.`);
742
+ clearCachedKey();
743
+ // fall through and prompt for a fresh one
703
744
  }
704
745
 
705
746
  // Scripted/CI runs have no TTY — don't hang on a prompt nobody will answer.
706
747
  if (!stdin.isTTY) {
707
- warn("no API key configured and stdin is not a terminal — skipping the prompt.");
748
+ warn("no valid API key configured and stdin is not a terminal — skipping the prompt.");
708
749
  warn("Pass --api-key=<am_live_...> or write ~/.config/agent-relay/credentials.json");
709
750
  return false;
710
751
  }
@@ -748,23 +789,11 @@ async function verifyInstall() {
748
789
  if (!key) return;
749
790
 
750
791
  log("Verifying API key…");
751
- try {
752
- const resp = await fetch(`${API_BASE}/search`, {
753
- method: "POST",
754
- headers: {
755
- "Content-Type": "application/json",
756
- "X-AM-API-Key": key,
757
- },
758
- body: JSON.stringify({ query: "test", max_results: 1 }),
759
- });
760
- const data = await resp.json();
761
- if (resp.ok && data.capabilities) {
762
- success("API key verified — search is working");
763
- } else {
764
- warn(`API returned ${resp.status}: ${data.error || "unknown error"}`);
765
- }
766
- } catch (err) {
767
- warn(`Verification failed: ${err.message}`);
792
+ const check = await verifyKey(key);
793
+ if (check.ok) {
794
+ success("API key verified — search is working");
795
+ } else {
796
+ warn(`API key verification failed: ${check.detail}`);
768
797
  }
769
798
  }
770
799
 
package/bin/lib/api.mjs CHANGED
@@ -66,10 +66,8 @@ export async function apiPost(path, body, { apiKey, timeoutMs = 30000 } = {}) {
66
66
  return data;
67
67
  }
68
68
 
69
- export function search(query, { apiKey, userContext, maxResults = 25, timeoutMs }) {
70
- const body = { query, max_results: maxResults };
71
- if (userContext) body.user_context = userContext;
72
- return apiPost("/search", body, { apiKey, timeoutMs });
69
+ export function search(query, { apiKey, maxResults = 25, timeoutMs }) {
70
+ return apiPost("/search", { query, max_results: maxResults }, { apiKey, timeoutMs });
73
71
  }
74
72
 
75
73
  export function sessionStart(
@@ -107,30 +105,6 @@ export function sessionEnd({ sessionId }, { apiKey, timeoutMs }) {
107
105
  return apiPost("/session/end", { session_id: sessionId }, { apiKey, timeoutMs });
108
106
  }
109
107
 
110
- /**
111
- * Settle a payment request for a session. Omit `paymentCredential` to pay from the
112
- * account's server-side wallet grant; pass it for a client-side MPP/x402 wallet.
113
- * Returns the structured SettleResult ({ ok, error_code, ... }); does not throw on a
114
- * 402 policy/charge rejection — the caller inspects `ok`.
115
- */
116
- export async function sessionPay({ sessionId, paymentCredential }, { apiKey, timeoutMs }) {
117
- try {
118
- return await apiPost(
119
- "/session/pay",
120
- {
121
- ...(sessionId ? { session_id: sessionId } : {}),
122
- ...(paymentCredential ? { payment_credential: paymentCredential } : {}),
123
- },
124
- { apiKey, timeoutMs },
125
- );
126
- } catch (err) {
127
- // A 402 is an expected "policy/charge rejected" outcome — return its structured body
128
- // so the agent can fall back to bubbling the payment_url, not crash.
129
- if (err instanceof ApiError && err.body && typeof err.body === "object") return err.body;
130
- throw err;
131
- }
132
- }
133
-
134
108
  export function feedback({ sessionId, feedbackText, score }, { apiKey, timeoutMs }) {
135
109
  return apiPost(
136
110
  "/feedback",
@@ -138,50 +112,3 @@ export function feedback({ sessionId, feedbackText, score }, { apiKey, timeoutMs
138
112
  { apiKey, timeoutMs },
139
113
  );
140
114
  }
141
-
142
- /**
143
- * Generic request used by the GET/PATCH payment-policy endpoints. Same timeout +
144
- * lenient-parse + error handling as apiPost; omits the body for GET.
145
- */
146
- async function apiRequest(method, path, body, { apiKey, timeoutMs = 30000 } = {}) {
147
- const controller = new AbortController();
148
- const timer = setTimeout(() => controller.abort(), timeoutMs);
149
- let resp;
150
- try {
151
- resp = await fetch(`${API_BASE}${path}`, {
152
- method,
153
- headers: { "Content-Type": "application/json", "X-AM-API-Key": apiKey },
154
- ...(body !== undefined ? { body: JSON.stringify(body) } : {}),
155
- signal: controller.signal,
156
- });
157
- } catch (err) {
158
- if (err && err.name === "AbortError") {
159
- throw new ApiError(`timeout after ${Math.round(timeoutMs / 1000)}s`, { status: 0 });
160
- }
161
- throw new ApiError(`network error: ${err.message}`, { status: 0 });
162
- } finally {
163
- clearTimeout(timer);
164
- }
165
-
166
- const text = await resp.text();
167
- const parsed = lenientParse(text);
168
- const data = parsed.ok ? parsed.value : null;
169
- if (!resp.ok) {
170
- const msg = (data && (data.message || data.error)) || `HTTP ${resp.status}`;
171
- throw new ApiError(String(msg), { status: resp.status, body: data });
172
- }
173
- if (!parsed.ok) {
174
- throw new ApiError("unparseable response from upstream", { status: resp.status, body: text.slice(0, 500) });
175
- }
176
- return data;
177
- }
178
-
179
- /** Read the account's autonomous-spend policy. */
180
- export function getPaymentPolicy({ apiKey, timeoutMs }) {
181
- return apiRequest("GET", "/account/payment-policy", undefined, { apiKey, timeoutMs });
182
- }
183
-
184
- /** Update the account's autonomous-spend policy (partial patch; cents). */
185
- export function patchPaymentPolicy(patch, { apiKey, timeoutMs }) {
186
- return apiRequest("PATCH", "/account/payment-policy", patch, { apiKey, timeoutMs });
187
- }
package/bin/lib/cache.mjs CHANGED
@@ -1,26 +1,17 @@
1
1
  /**
2
- * On-disk cache (spec §9). Layout under --cache-dir (default ./.agent-relay):
2
+ * On-disk search cache. Layout under --cache-dir (default ./.agent-relay):
3
3
  *
4
- * index.json { slug: { ts, expires, agent_id, last_session_id } }
5
- * specs/<slug>.json a Deliverable
4
+ * index.json { slug: { ts, agent_id, name } }
6
5
  * search/<hash>.json { ts, expires, query, capabilities }
7
6
  *
8
- * This is what makes just-in-time, project-long consulting near-free: a repeat
9
- * consult reads specs/<slug>.json instead of re-engaging the agent.
7
+ * A repeat search inside the TTL is served from disk, and the index lets
8
+ * `session start` resolve a slug agent_id without re-searching.
10
9
  */
11
10
 
12
- import {
13
- readFileSync,
14
- writeFileSync,
15
- existsSync,
16
- mkdirSync,
17
- readdirSync,
18
- rmSync,
19
- } from "fs";
11
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
20
12
  import { join } from "path";
21
13
  import { createHash } from "crypto";
22
14
 
23
- const SPEC_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
24
15
  const SEARCH_TTL_MS = 60 * 60 * 1000; // 1 hour
25
16
 
26
17
  export class Cache {
@@ -28,12 +19,10 @@ export class Cache {
28
19
  this.dir = dir;
29
20
  this.enabled = enabled;
30
21
  this.indexPath = join(dir, "index.json");
31
- this.specsDir = join(dir, "specs");
32
22
  this.searchDir = join(dir, "search");
33
23
  }
34
24
 
35
25
  _ensure() {
36
- mkdirSync(this.specsDir, { recursive: true });
37
26
  mkdirSync(this.searchDir, { recursive: true });
38
27
  }
39
28
 
@@ -69,11 +58,7 @@ export class Cache {
69
58
  return join(this.searchDir, `${h}.json`);
70
59
  }
71
60
 
72
- /**
73
- * Whole unexpired search entry — `{ts, capabilities, payment_context}` — or
74
- * null. Returning the entry (not just capabilities) lets the caller surface
75
- * how stale the cached payment_context snapshot is (see SKILL.md Pay).
76
- */
61
+ /** Whole unexpired search entry — `{ts, query, capabilities}` — or null. */
77
62
  readSearchEntry(query) {
78
63
  if (!this.enabled) return null;
79
64
  const entry = this._readJson(this._searchPath(query));
@@ -83,7 +68,7 @@ export class Cache {
83
68
  return entry;
84
69
  }
85
70
 
86
- writeSearch(query, capabilities, paymentContext = null) {
71
+ writeSearch(query, capabilities) {
87
72
  if (!this.enabled) return;
88
73
  this._ensure();
89
74
  writeFileSync(
@@ -94,62 +79,10 @@ export class Cache {
94
79
  expires: Date.now() + SEARCH_TTL_MS,
95
80
  query,
96
81
  capabilities,
97
- payment_context: paymentContext,
98
82
  },
99
83
  null,
100
84
  2,
101
85
  ) + "\n",
102
86
  );
103
87
  }
104
-
105
- // ── spec (Deliverable) cache ─────────────────────────────────────────
106
- _specPath(slug) {
107
- return join(this.specsDir, `${slug}.json`);
108
- }
109
-
110
- readSpec(slug, { ttlMs = SPEC_TTL_MS } = {}) {
111
- if (!this.enabled) return null;
112
- const spec = this._readJson(this._specPath(slug));
113
- if (!spec) return null;
114
- if (spec.ts && Date.now() - new Date(spec.ts).getTime() > ttlMs) return null;
115
- return spec;
116
- }
117
-
118
- writeSpec(slug, deliverable) {
119
- if (!this.enabled) return;
120
- this._ensure();
121
- writeFileSync(this._specPath(slug), JSON.stringify(deliverable, null, 2) + "\n");
122
- }
123
-
124
- listSpecs() {
125
- if (!existsSync(this.specsDir)) return [];
126
- return readdirSync(this.specsDir)
127
- .filter((f) => f.endsWith(".json"))
128
- .map((f) => f.replace(/\.json$/, ""));
129
- }
130
-
131
- /**
132
- * Triage view of the banked specs — read straight from each spec file (no
133
- * shared index.json), so it's safe under the parallel breadth pass where N
134
- * subagents each `cache put` their own slug file concurrently. Lets the main
135
- * agent see what's available + each summary without opening every file.
136
- */
137
- listSpecHeadlines() {
138
- if (!existsSync(this.specsDir)) return [];
139
- return this.listSpecs().map((slug) => {
140
- const spec = this._readJson(this._specPath(slug)) || {};
141
- return {
142
- slug,
143
- name: spec.name ?? null,
144
- summary: spec.summary ?? null,
145
- features: Array.isArray(spec.features) ? spec.features.length : null,
146
- agent_id: spec.agent_id ?? null,
147
- ts: spec.ts ?? null,
148
- };
149
- });
150
- }
151
-
152
- clear() {
153
- if (existsSync(this.dir)) rmSync(this.dir, { recursive: true, force: true });
154
- }
155
88
  }