agentic-relay 4.2.1 → 4.3.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/bin/lib/api.mjs +2 -4
- package/bin/lib/cache.mjs +2 -2
- package/bin/lib/commands.mjs +30 -63
- package/bin/relay-core.mjs +7 -14
- package/bin/test/cache.test.mjs +5 -7
- package/package.json +1 -1
- package/schema/deliverable.json +1 -3
package/bin/lib/api.mjs
CHANGED
|
@@ -73,15 +73,13 @@ export function search(query, { apiKey, userContext, maxResults = 25, timeoutMs
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export function sessionStart(
|
|
76
|
-
{
|
|
76
|
+
{ agentId, initialData },
|
|
77
77
|
{ apiKey, timeoutMs },
|
|
78
78
|
) {
|
|
79
79
|
return apiPost(
|
|
80
80
|
"/session/start",
|
|
81
81
|
{
|
|
82
|
-
|
|
83
|
-
ad_unit_id: adUnitId,
|
|
84
|
-
clearing_price_cents: clearingPriceCents,
|
|
82
|
+
agent_id: agentId,
|
|
85
83
|
...(initialData ? { initial_data: initialData } : {}),
|
|
86
84
|
},
|
|
87
85
|
{ apiKey, timeoutMs },
|
package/bin/lib/cache.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* On-disk cache (spec §9). Layout under --cache-dir (default ./.agent-relay):
|
|
3
3
|
*
|
|
4
|
-
* index.json { slug: { ts, expires,
|
|
4
|
+
* index.json { slug: { ts, expires, agent_id, last_session_id } }
|
|
5
5
|
* specs/<slug>.json a Deliverable
|
|
6
6
|
* search/<hash>.json { ts, expires, query, capabilities }
|
|
7
7
|
*
|
|
@@ -143,7 +143,7 @@ export class Cache {
|
|
|
143
143
|
name: spec.name ?? null,
|
|
144
144
|
summary: spec.summary ?? null,
|
|
145
145
|
features: Array.isArray(spec.features) ? spec.features.length : null,
|
|
146
|
-
|
|
146
|
+
agent_id: spec.agent_id ?? null,
|
|
147
147
|
ts: spec.ts ?? null,
|
|
148
148
|
};
|
|
149
149
|
});
|
package/bin/lib/commands.mjs
CHANGED
|
@@ -23,36 +23,25 @@ import { slugify } from "./config.mjs";
|
|
|
23
23
|
import { Cache } from "./cache.mjs";
|
|
24
24
|
import { cmdFetch } from "./fetch.mjs";
|
|
25
25
|
|
|
26
|
-
/** Normalize one upstream capability into the
|
|
26
|
+
/** Normalize one upstream capability into the lean menu shape. `agent_id` is the only id —
|
|
27
|
+
* session start needs nothing else (ad_unit + price are resolved server-side). */
|
|
27
28
|
function normalizeCapability(c) {
|
|
28
29
|
const name = c.name || "";
|
|
29
30
|
return {
|
|
30
31
|
slug: slugify(name),
|
|
31
32
|
name,
|
|
32
|
-
type:
|
|
33
|
+
type: "dynamic_capability", // single type now; kept for the --dynamic-only filter
|
|
34
|
+
agent_id: c.agent_id || null,
|
|
33
35
|
relevance: typeof c.relevance_score === "number" ? c.relevance_score : null,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
ad_unit_id: c.ad_unit_id || null,
|
|
36
|
+
reputation: typeof c.reputation_score === "number" ? c.reputation_score : null,
|
|
37
|
+
total_agent_ratings: typeof c.total_agent_ratings === "number" ? c.total_agent_ratings : 0,
|
|
38
|
+
sessions_count: typeof c.sessions_count === "number" ? c.sessions_count : 0,
|
|
38
39
|
description: c.description || "",
|
|
39
|
-
click_url: c.click_url || null,
|
|
40
|
-
// Track-record evidence card (server-side from capability_profile.fit). The
|
|
41
|
-
// backend already ranks/trims and strips confidence; pass these through as-is
|
|
42
|
-
// so --json carries the exact same shape the API and MCP return. Empty arrays
|
|
43
|
-
// when a capability has no roller profile yet.
|
|
44
|
-
summary: c.summary || "",
|
|
45
|
-
feedback_payout:
|
|
46
|
-
typeof c.feedback_payout === "number"
|
|
47
|
-
? c.feedback_payout
|
|
48
|
-
: typeof c.clearing_price_cents === "number"
|
|
49
|
-
? c.clearing_price_cents
|
|
50
|
-
: 0,
|
|
51
40
|
offers_paid_deliverable: Boolean(c.offers_paid_deliverable),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
deliverable_price_cents: typeof c.deliverable_price_cents === "number" ? c.deliverable_price_cents : null,
|
|
42
|
+
required_inputs: Array.isArray(c.required_inputs) ? c.required_inputs : [],
|
|
43
|
+
// Track-record cards are disabled server-side for now; the API returns a note string.
|
|
44
|
+
fit: typeof c.fit === "string" ? c.fit : "",
|
|
56
45
|
};
|
|
57
46
|
}
|
|
58
47
|
|
|
@@ -92,13 +81,11 @@ export async function cmdSearch(query, opts) {
|
|
|
92
81
|
paymentContext = data.payment_context ?? null;
|
|
93
82
|
capabilities = dedupeSlugs((data.capabilities || []).map(normalizeCapability));
|
|
94
83
|
cache.writeSearch(query, capabilities, paymentContext);
|
|
95
|
-
// Seed the index so session start can resolve
|
|
84
|
+
// Seed the index so session start can resolve agent_id without re-searching.
|
|
96
85
|
for (const c of capabilities) {
|
|
97
|
-
if (c.
|
|
86
|
+
if (c.agent_id) {
|
|
98
87
|
cache.updateIndex(c.slug, {
|
|
99
|
-
|
|
100
|
-
ad_unit_id: c.ad_unit_id,
|
|
101
|
-
clearing_price_cents: c.clearing_price_cents,
|
|
88
|
+
agent_id: c.agent_id,
|
|
102
89
|
name: c.name,
|
|
103
90
|
ts: new Date().toISOString(),
|
|
104
91
|
});
|
|
@@ -122,38 +109,26 @@ export async function cmdSearch(query, opts) {
|
|
|
122
109
|
}
|
|
123
110
|
|
|
124
111
|
/**
|
|
125
|
-
* Resolve a slug (or
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
112
|
+
* Resolve a slug (or agent_id) to { agentId, slug, name }. Order: cache index →
|
|
113
|
+
* banked spec file → fresh search keyed on the de-kebabed slug. The spec-file
|
|
114
|
+
* fallback lets a depth session resolve even when the cache was filled only by
|
|
115
|
+
* `cache put` during the breadth pass (which doesn't write the index).
|
|
116
|
+
* agent_id is the only id needed — the ad_unit + price are resolved server-side.
|
|
130
117
|
*/
|
|
131
118
|
export async function resolveCapability(slugOrId, opts) {
|
|
132
119
|
const cache = new Cache(opts.cacheDir, { enabled: !opts.noCache });
|
|
133
120
|
const slug = slugify(slugOrId);
|
|
134
121
|
|
|
135
122
|
const entry = cache.getIndexEntry(slug) || cache.getIndexEntry(slugOrId);
|
|
136
|
-
if (entry && entry.
|
|
137
|
-
return {
|
|
138
|
-
capabilityId: entry.capability_id,
|
|
139
|
-
adUnitId: entry.ad_unit_id,
|
|
140
|
-
clearingPriceCents: entry.clearing_price_cents || 0,
|
|
141
|
-
slug,
|
|
142
|
-
name: entry.name || slug,
|
|
143
|
-
};
|
|
123
|
+
if (entry && entry.agent_id && !opts.refresh) {
|
|
124
|
+
return { agentId: entry.agent_id, slug, name: entry.name || slug };
|
|
144
125
|
}
|
|
145
126
|
|
|
146
|
-
// Banked spec fallback — a breadth subagent stored
|
|
127
|
+
// Banked spec fallback — a breadth subagent stored the agent_id here via `cache put`.
|
|
147
128
|
if (!opts.refresh) {
|
|
148
129
|
const spec = cache.readSpec(slug, { ttlMs: Infinity });
|
|
149
|
-
if (spec && spec.
|
|
150
|
-
return {
|
|
151
|
-
capabilityId: spec.capability_id,
|
|
152
|
-
adUnitId: spec.ad_unit_id,
|
|
153
|
-
clearingPriceCents: spec.clearing_price_cents || 0,
|
|
154
|
-
slug,
|
|
155
|
-
name: spec.name || slug,
|
|
156
|
-
};
|
|
130
|
+
if (spec && spec.agent_id) {
|
|
131
|
+
return { agentId: spec.agent_id, slug, name: spec.name || slug };
|
|
157
132
|
}
|
|
158
133
|
}
|
|
159
134
|
|
|
@@ -162,20 +137,14 @@ export async function resolveCapability(slugOrId, opts) {
|
|
|
162
137
|
const found = await cmdSearch(queryGuess, { ...opts, refresh: true, max: 25 });
|
|
163
138
|
const match =
|
|
164
139
|
found.capabilities.find((c) => c.slug === slug) ||
|
|
165
|
-
found.capabilities.find((c) => c.
|
|
140
|
+
found.capabilities.find((c) => c.agent_id === slugOrId) ||
|
|
166
141
|
found.capabilities.find((c) => c.slug.startsWith(slug));
|
|
167
|
-
if (!match || !match.
|
|
142
|
+
if (!match || !match.agent_id) {
|
|
168
143
|
throw new Error(
|
|
169
|
-
`could not resolve "${slugOrId}" — run \`agentrelay search\` first or pass a known
|
|
144
|
+
`could not resolve "${slugOrId}" — run \`agentrelay search\` first or pass a known agent_id`,
|
|
170
145
|
);
|
|
171
146
|
}
|
|
172
|
-
return {
|
|
173
|
-
capabilityId: match.capability_id,
|
|
174
|
-
adUnitId: match.ad_unit_id,
|
|
175
|
-
clearingPriceCents: match.clearing_price_cents || 0,
|
|
176
|
-
slug: match.slug,
|
|
177
|
-
name: match.name,
|
|
178
|
-
};
|
|
147
|
+
return { agentId: match.agent_id, slug: match.slug, name: match.name };
|
|
179
148
|
}
|
|
180
149
|
|
|
181
150
|
// ── session primitives (thin api_v1 wrappers — the LLM-driven path) ─────────
|
|
@@ -234,16 +203,14 @@ export async function cmdSession(sub, positionals, opts) {
|
|
|
234
203
|
if (sub === "start") {
|
|
235
204
|
const slugOrId = positionals[0];
|
|
236
205
|
if (!slugOrId) {
|
|
237
|
-
const e = new Error("usage: agentrelay session start <slug|
|
|
206
|
+
const e = new Error("usage: agentrelay session start <slug|agent_id> [--initial json] [--message m]");
|
|
238
207
|
e.usage = true;
|
|
239
208
|
throw e;
|
|
240
209
|
}
|
|
241
210
|
const resolved = await resolveCapability(slugOrId, opts);
|
|
242
211
|
const start = await sessionStart(
|
|
243
212
|
{
|
|
244
|
-
|
|
245
|
-
adUnitId: resolved.adUnitId,
|
|
246
|
-
clearingPriceCents: resolved.clearingPriceCents,
|
|
213
|
+
agentId: resolved.agentId,
|
|
247
214
|
initialData: parseJsonFlag(opts.initial, "initial"),
|
|
248
215
|
},
|
|
249
216
|
net,
|
package/bin/relay-core.mjs
CHANGED
|
@@ -141,25 +141,18 @@ function fail(message, exitCode, json) {
|
|
|
141
141
|
const fmtSearch = (e) => {
|
|
142
142
|
const lines = [`${e.count} capabilities for "${e.query}"${e.from_cache ? ` (cached ${e.cached_at})` : ""}`];
|
|
143
143
|
for (const c of e.capabilities) {
|
|
144
|
-
const price = c.clearing_price_cents ? `$${(c.clearing_price_cents / 100).toFixed(2)}` : "free";
|
|
145
144
|
const rel = c.relevance != null ? c.relevance.toFixed(2) : "—";
|
|
146
|
-
const
|
|
145
|
+
const rep = c.reputation != null ? c.reputation.toFixed(2) : "—";
|
|
147
146
|
const desc = c.description.length > 90 ? c.description.slice(0, 90) + "…" : c.description;
|
|
148
|
-
lines.push(`
|
|
147
|
+
lines.push(` [agent] ${c.slug} (rel ${rel}, rep ${rep}, ${c.sessions_count ?? 0} sessions/${c.total_agent_ratings ?? 0} ratings) ${c.name}`);
|
|
149
148
|
if (desc) lines.push(` ${desc}`);
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (Array.isArray(c.proven_for) && c.proven_for.length) {
|
|
153
|
-
lines.push(` proven: ${c.proven_for.map((p) => p.use_case).join("; ")}`);
|
|
149
|
+
if (Array.isArray(c.required_inputs) && c.required_inputs.length) {
|
|
150
|
+
lines.push(` needs: ${c.required_inputs.map((i) => i.field).join(", ")}`);
|
|
154
151
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
152
|
+
if (c.offers_paid_deliverable) {
|
|
153
|
+
const dp = typeof c.deliverable_price_cents === "number" ? ` ($${(c.deliverable_price_cents / 100).toFixed(2)})` : "";
|
|
154
|
+
lines.push(` offers paid deliverable${dp}`);
|
|
157
155
|
}
|
|
158
|
-
if (Array.isArray(c.works_with) && c.works_with.length) {
|
|
159
|
-
const flag = (r) => (r === "conflicts" || r === "requires" ? `${r}!` : r);
|
|
160
|
-
lines.push(` works-with: ${c.works_with.map((w) => `${w.companion_tool} (${flag(w.relationship)})`).join(", ")}`);
|
|
161
|
-
}
|
|
162
|
-
if (c.offers_paid_deliverable) lines.push(` offers paid deliverable`);
|
|
163
156
|
}
|
|
164
157
|
// Surface the spend policy in human output too — agents reading --json get
|
|
165
158
|
// the full payment_context object; humans shouldn't be blind to it.
|
package/bin/test/cache.test.mjs
CHANGED
|
@@ -27,7 +27,7 @@ test("listSpecHeadlines: returns slug+name+summary+feature-count, no index.json
|
|
|
27
27
|
withCache((dir, cache) => {
|
|
28
28
|
cache.writeSpec("a-tool", {
|
|
29
29
|
slug: "a-tool", name: "A", summary: "does A",
|
|
30
|
-
features: [{ feature: "x", how: "y" }],
|
|
30
|
+
features: [{ feature: "x", how: "y" }], agent_id: "agent-a", ts: ISO,
|
|
31
31
|
});
|
|
32
32
|
cache.writeSpec("b-tool", { slug: "b-tool", name: "B", summary: "does B", features: [], ts: ISO });
|
|
33
33
|
|
|
@@ -37,7 +37,7 @@ test("listSpecHeadlines: returns slug+name+summary+feature-count, no index.json
|
|
|
37
37
|
assert.equal(a.name, "A");
|
|
38
38
|
assert.equal(a.summary, "does A");
|
|
39
39
|
assert.equal(a.features, 1);
|
|
40
|
-
assert.equal(a.
|
|
40
|
+
assert.equal(a.agent_id, "agent-a");
|
|
41
41
|
// Race-safe: the put path never wrote a shared index.json.
|
|
42
42
|
assert.equal(existsSync(join(dir, "index.json")), false);
|
|
43
43
|
});
|
|
@@ -54,17 +54,15 @@ test("listSpecHeadlines: tolerates a spec missing the optional fields", () => {
|
|
|
54
54
|
});
|
|
55
55
|
});
|
|
56
56
|
|
|
57
|
-
test("resolveCapability: falls back to the banked spec's
|
|
57
|
+
test("resolveCapability: falls back to the banked spec's agent_id (no network)", async () => {
|
|
58
58
|
await withCache(async (dir, cache) => {
|
|
59
59
|
cache.writeSpec("a-tool", {
|
|
60
60
|
slug: "a-tool", name: "A Tool", summary: "s", features: [],
|
|
61
|
-
|
|
61
|
+
agent_id: "agent-a", ts: ISO,
|
|
62
62
|
});
|
|
63
63
|
// No index.json, refresh falsy → must resolve from the spec file alone.
|
|
64
64
|
const r = await resolveCapability("a-tool", { cacheDir: dir, noCache: false });
|
|
65
|
-
assert.equal(r.
|
|
66
|
-
assert.equal(r.adUnitId, "unit-a");
|
|
67
|
-
assert.equal(r.clearingPriceCents, 130);
|
|
65
|
+
assert.equal(r.agentId, "agent-a");
|
|
68
66
|
assert.equal(r.slug, "a-tool");
|
|
69
67
|
assert.equal(r.name, "A Tool");
|
|
70
68
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-relay",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.0",
|
|
4
4
|
"description": "Install Agent Relay AI search across 17+ agents — Claude Code, Codex, Cursor, Gemini CLI, Goose, Windsurf, Cline, BoltAI, Claude Desktop, VS Code, Amazon Q, Roo Code, Witsy, LibreChat, OpenClaw, Tome, Raycast — plus the `agentrelay` CLI for live agent sessions, payments, and deliverable fetch",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agentrelay": "bin/cli.mjs"
|
package/schema/deliverable.json
CHANGED
|
@@ -39,9 +39,7 @@
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
|
-
"
|
|
43
|
-
"ad_unit_id": { "type": ["string", "null"] },
|
|
44
|
-
"clearing_price_cents": { "type": "integer" },
|
|
42
|
+
"agent_id": { "type": ["string", "null"], "description": "The only id needed to open a session (ad_unit + price resolved server-side)." },
|
|
45
43
|
"ts": { "type": "string", "format": "date-time" }
|
|
46
44
|
}
|
|
47
45
|
}
|