@naulon/wayfarer 0.1.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.
Files changed (86) hide show
  1. package/README.md +39 -0
  2. package/dist/agent.d.ts +77 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +235 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/allocation.d.ts +36 -0
  7. package/dist/allocation.d.ts.map +1 -0
  8. package/dist/allocation.js +45 -0
  9. package/dist/allocation.js.map +1 -0
  10. package/dist/appraise.d.ts +3 -0
  11. package/dist/appraise.d.ts.map +1 -0
  12. package/dist/appraise.js +60 -0
  13. package/dist/appraise.js.map +1 -0
  14. package/dist/buyer.d.ts +195 -0
  15. package/dist/buyer.d.ts.map +1 -0
  16. package/dist/buyer.js +254 -0
  17. package/dist/buyer.js.map +1 -0
  18. package/dist/decide.d.ts +115 -0
  19. package/dist/decide.d.ts.map +1 -0
  20. package/dist/decide.js +206 -0
  21. package/dist/decide.js.map +1 -0
  22. package/dist/discover.d.ts +10 -0
  23. package/dist/discover.d.ts.map +1 -0
  24. package/dist/discover.js +5 -0
  25. package/dist/discover.js.map +1 -0
  26. package/dist/discovery.d.ts +26 -0
  27. package/dist/discovery.d.ts.map +1 -0
  28. package/dist/discovery.js +93 -0
  29. package/dist/discovery.js.map +1 -0
  30. package/dist/gateway.d.ts +117 -0
  31. package/dist/gateway.d.ts.map +1 -0
  32. package/dist/gateway.js +187 -0
  33. package/dist/gateway.js.map +1 -0
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +19 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/lib.d.ts +40 -0
  39. package/dist/lib.d.ts.map +1 -0
  40. package/dist/lib.js +44 -0
  41. package/dist/lib.js.map +1 -0
  42. package/dist/licenseStore.d.ts +56 -0
  43. package/dist/licenseStore.d.ts.map +1 -0
  44. package/dist/licenseStore.js +79 -0
  45. package/dist/licenseStore.js.map +1 -0
  46. package/dist/memo.d.ts +43 -0
  47. package/dist/memo.d.ts.map +1 -0
  48. package/dist/memo.js +102 -0
  49. package/dist/memo.js.map +1 -0
  50. package/dist/origin-policy.d.ts +74 -0
  51. package/dist/origin-policy.d.ts.map +1 -0
  52. package/dist/origin-policy.js +88 -0
  53. package/dist/origin-policy.js.map +1 -0
  54. package/dist/paidFetch.d.ts +14 -0
  55. package/dist/paidFetch.d.ts.map +1 -0
  56. package/dist/paidFetch.js +93 -0
  57. package/dist/paidFetch.js.map +1 -0
  58. package/dist/pay.d.ts +3 -0
  59. package/dist/pay.d.ts.map +1 -0
  60. package/dist/pay.js +82 -0
  61. package/dist/pay.js.map +1 -0
  62. package/dist/pop.d.ts +8 -0
  63. package/dist/pop.d.ts.map +1 -0
  64. package/dist/pop.js +25 -0
  65. package/dist/pop.js.map +1 -0
  66. package/dist/rail.d.ts +18 -0
  67. package/dist/rail.d.ts.map +1 -0
  68. package/dist/rail.js +73 -0
  69. package/dist/rail.js.map +1 -0
  70. package/dist/rss.d.ts +38 -0
  71. package/dist/rss.d.ts.map +1 -0
  72. package/dist/rss.js +110 -0
  73. package/dist/rss.js.map +1 -0
  74. package/dist/sign.d.ts +13 -0
  75. package/dist/sign.d.ts.map +1 -0
  76. package/dist/sign.js +52 -0
  77. package/dist/sign.js.map +1 -0
  78. package/dist/types.d.ts +75 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +2 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/wallet.d.ts +12 -0
  83. package/dist/wallet.d.ts.map +1 -0
  84. package/dist/wallet.js +46 -0
  85. package/dist/wallet.js.map +1 -0
  86. package/package.json +42 -0
package/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # @naulon/wayfarer
2
+
3
+ The paying agent. Give it a topic and a budget; it discovers tolled articles,
4
+ decides which are worth citing, pays only those, and answers from what it bought.
5
+
6
+ The point isn't automation for its own sake — it's that the agent makes a real
7
+ budget decision at each step instead of paying for everything. It ranks candidates
8
+ by relevance-per-dollar, buys greedily under the budget down to a relevance floor,
9
+ reuses anything already paid for, and logs *why* for every candidate. The
10
+ reasoning is the artifact.
11
+
12
+ ## Run
13
+
14
+ ```bash
15
+ npm run -w @naulon/tollgate dev & # the gate to pay
16
+ npm run -w @naulon/wayfarer start -- "payment and passage" # the agent
17
+ ```
18
+
19
+ ```text
20
+ decisions:
21
+ [PAY] the-naulon — relevance 1.00 @ $0.005; $0.095 left
22
+ [SKIP] on-stillness — relevance 0.00 below floor 0.35
23
+ ✓ paid $0.005000 for the-naulon
24
+ ```
25
+
26
+ It runs offline against mock settlement. Set `OPENAI_API_KEY` for LLM appraisal
27
+ and answer synthesis instead of the keyword heuristic; set `PAYMENT_MODE=gateway`
28
+ with a funded buyer wallet to pay for real over Circle Gateway on Arc Network.
29
+
30
+ ## What's inside
31
+
32
+ - **`discover.ts` / `discovery.ts`** — find tollable candidates from a gate or feed.
33
+ - **`appraise.ts` / `decide.ts` / `allocation.ts`** — relevance, the pay/skip call, budget allocation.
34
+ - **`buyer.ts` / `gateway.ts`** — the x402 payment loop (mock and Circle Gateway).
35
+ - **`agent.ts` / `lib.ts`** — the end-to-end research run, also importable as a library.
36
+
37
+ Exposed as an MCP server by [`@naulon/wayfarer-mcp`](../wayfarer-mcp).
38
+
39
+ MIT.
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The Wayfarer pipeline: discover → price → appraise → decide → pay → ground.
3
+ *
4
+ * Each stage is explicit and logged, so the agent's reasoning is auditable — the
5
+ * decision log is the artifact, not a black box. Paying runs against the
6
+ * tollgate over the real x402 contract; the Buyer is mock (offline) or Circle
7
+ * Gateway (PAYMENT_MODE=gateway), chosen at startup.
8
+ */
9
+ import { type JwkSet } from "@naulon/shared";
10
+ import { type GatewaySigner } from "./gateway.ts";
11
+ import { type MemoSigner } from "./memo.ts";
12
+ import { type RailSigners } from "./rail.ts";
13
+ import type { DecideContext, DecisionPolicy } from "./decide.ts";
14
+ import type { HeldStore } from "./licenseStore.ts";
15
+ import type { RunResult } from "./types.ts";
16
+ import type { AgentWallet } from "./wallet.ts";
17
+ export type Logger = (line: string) => void;
18
+ export declare function tollgateBase(): string;
19
+ export declare function articleUrl(base: string, slug: string): string;
20
+ /** Fetch the gate's public key set so the agent can verify the receipts it holds. */
21
+ export declare function fetchJwks(base: string): Promise<JwkSet | null>;
22
+ /** Verify a captured license against the gate's JWKS. null when JWKS unavailable. */
23
+ export declare function verifyAgainst(jws: string, jwks: JwkSet): boolean;
24
+ /** Optional overrides for a single run. `budgetUsdc` lets a caller spend LESS than
25
+ * the configured ceiling for this run (e.g. the MCP clamping the model's requested
26
+ * budget to what remains in the session envelope) — it can only narrow, never widen,
27
+ * because the caller is expected to pass `min(requested, configCeiling)`. */
28
+ export interface RunOptions {
29
+ budgetUsdc?: number;
30
+ /**
31
+ * Operator policy for this run (allow/deny domains, per-domain cap, approval
32
+ * threshold, kill-switch). Server/config-supplied, never LLM-controlled — same
33
+ * principle as `budgetUsdc`. Omitted ⇒ `DEFAULT_POLICY` (the original behavior).
34
+ */
35
+ policy?: DecisionPolicy;
36
+ /** Window state for per-domain rate caps across runs (prior pays per host). */
37
+ decideContext?: DecideContext;
38
+ /**
39
+ * Gate this run settles into (base URL). Server/config-supplied, never
40
+ * LLM-controlled — same principle as `budgetUsdc`/`policy`. Lets a hosted caller
41
+ * point one run at a specific fleet tenant's gate. Omitted ⇒ `tollgateBase()`
42
+ * (the env default), so the OSS single-gate path is unchanged.
43
+ */
44
+ tollgateUrl?: string;
45
+ /**
46
+ * This run's custody-free session signer (BUY-4 hosted path). When present, tolls
47
+ * are signed by the injected `MemoSigner` (naulon's grant-checked `/sign-memo` BFF
48
+ * over an encrypted session key) instead of the env `BUYER_PRIVATE_KEY` — so the
49
+ * hosted `naulon_research` pays from the buyer's managed session wallet, mirroring
50
+ * `naulon_pay_and_read`. Omitted ⇒ `selectBuyer()` picks the env buyer (the OSS
51
+ * self-host path). Server/config-supplied, never LLM-controlled. A `MemoSigner`
52
+ * (memo/Arc rail) or a `GatewaySigner` (memo-less Circle rails); `run` routes it to
53
+ * the matching buyer via `supportsMemo(activeNetwork())`, like `selectBuyer()`.
54
+ */
55
+ signer?: MemoSigner | GatewaySigner;
56
+ /**
57
+ * BOTH rail signers over the same sealed session key (RAS-B mixed fleet). When present, the run
58
+ * pays through `railBuyer`, which picks memo vs gateway PER-402 from each tenant's advertised
59
+ * network — exactly like `naulon_pay_and_read` — instead of the fleet-global `supportsMemo`
60
+ * routing a single `signer` gets. Wins over `signer`. Server/config-supplied, never LLM-controlled.
61
+ */
62
+ railSigners?: RailSigners;
63
+ /**
64
+ * This run's held-license backend (BUY-4 hosted path). The cloud injects a
65
+ * per-session store so the composite loop's free re-reads never cross buyer
66
+ * boundaries in a shared process. Omitted ⇒ the process-global file (OSS path).
67
+ */
68
+ heldStore?: HeldStore;
69
+ /**
70
+ * The wallet that signs proof-of-possession for this run's cnf-bound held
71
+ * re-reads. Hosted path injects a signer over the session key (`/sign-pop`);
72
+ * omitted ⇒ `getWallet()` (the env/dev wallet, unchanged OSS path).
73
+ */
74
+ popWallet?: AgentWallet;
75
+ }
76
+ export declare function run(topic: string, log?: Logger, opts?: RunOptions): Promise<RunResult>;
77
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA+D,KAAK,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG1G,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,KAAK,EAAuC,SAAS,EAAU,MAAM,YAAY,CAAC;AAEzF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;AAE5C,wBAAgB,YAAY,IAAI,MAAM,CAQrC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,qFAAqF;AACrF,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASpE;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAgBhE;AAED;;;8EAG8E;AAC9E,MAAM,WAAW,UAAU;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,aAAa,CAAC;IACpC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,WAAW,CAAC;CACzB;AAED,wBAAsB,GAAG,CACvB,KAAK,EAAE,MAAM,EACb,GAAG,GAAE,MAAiB,EACtB,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,SAAS,CAAC,CAwJpB"}
package/dist/agent.js ADDED
@@ -0,0 +1,235 @@
1
+ /**
2
+ * The Wayfarer pipeline: discover → price → appraise → decide → pay → ground.
3
+ *
4
+ * Each stage is explicit and logged, so the agent's reasoning is auditable — the
5
+ * decision log is the artifact, not a black box. Paying runs against the
6
+ * tollgate over the real x402 contract; the Buyer is mock (offline) or Circle
7
+ * Gateway (PAYMENT_MODE=gateway), chosen at startup.
8
+ */
9
+ import { activeNetwork, getConfig, supportsMemo, usdc, verifyLicense } from "@naulon/shared";
10
+ import { appraise } from "./appraise.js";
11
+ import { quotedTotalAtomic, rereadWithLicense, selectBuyer } from "./buyer.js";
12
+ import { gatewayBuyer } from "./gateway.js";
13
+ import { memoBuyer } from "./memo.js";
14
+ import { railBuyer } from "./rail.js";
15
+ import { decide, DEFAULT_POLICY } from "./decide.js";
16
+ import { discover } from "./discover.js";
17
+ import { decodeHeld, fileHeldStore, isLive } from "./licenseStore.js";
18
+ import { buildPopProof } from "./pop.js";
19
+ import { agentFetch } from "./sign.js";
20
+ import { getWallet } from "./wallet.js";
21
+ export function tollgateBase() {
22
+ const url = getConfig().TOLLGATE_URL;
23
+ // No fail-open: an unset gate is a config error surfaced here, not a fabricated
24
+ // http://localhost target that refuses later with a confusing "unreachable host".
25
+ // Callers prefer an injected per-session `opts.tollgateUrl`; this is only reached
26
+ // when neither is set.
27
+ if (!url)
28
+ throw new Error("TOLLGATE_URL is not configured");
29
+ return url;
30
+ }
31
+ export function articleUrl(base, slug) {
32
+ return `${base.replace(/\/$/, "")}/essays/${encodeURIComponent(slug)}`;
33
+ }
34
+ /** Fetch the gate's public key set so the agent can verify the receipts it holds. */
35
+ export async function fetchJwks(base) {
36
+ try {
37
+ const res = await agentFetch(`${base.replace(/\/$/, "")}/.well-known/naulon-jwks.json`);
38
+ if (!res.ok)
39
+ return null;
40
+ const set = (await res.json());
41
+ return set.keys?.length ? set : null;
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ /** Verify a captured license against the gate's JWKS. null when JWKS unavailable. */
48
+ export function verifyAgainst(jws, jwks) {
49
+ try {
50
+ const claims = JSON.parse(Buffer.from(jws.split(".")[1] ?? "", "base64url").toString("utf8"));
51
+ const r = verifyLicense(jws, {
52
+ now: Date.now(),
53
+ expectedIssuer: claims.iss ?? "",
54
+ expectedAudience: claims.aud ?? "",
55
+ jwks,
56
+ });
57
+ return r.ok;
58
+ }
59
+ catch {
60
+ return false;
61
+ }
62
+ }
63
+ export async function run(topic, log = () => { }, opts = {}) {
64
+ const cfg = getConfig();
65
+ const budget = opts.budgetUsdc ?? cfg.WAYFARER_BUDGET_USDC;
66
+ const base = opts.tollgateUrl ?? tollgateBase();
67
+ // Hosted path: sign each leg via the injected cloud session key (custody-free); the
68
+ // env BUYER_PRIVATE_KEY is never read. Route the injected signer to the active network's
69
+ // rail — memo-LESS networks (Base + every Gateway chain) settle via gatewayBuyer (the Circle
70
+ // envelope), memo networks via memoBuyer — mirroring selectBuyer() and naulon_pay_and_read so
71
+ // hosted research pays from the same wallet on whatever rail the tenant settles on. The cloud
72
+ // injects the signer matching the network's rail. Default: the BYO-key buyer selectBuyer() picks.
73
+ const buyer = opts.railSigners
74
+ ? railBuyer(opts.railSigners)
75
+ : opts.signer
76
+ ? supportsMemo(activeNetwork())
77
+ ? memoBuyer(opts.signer)
78
+ : gatewayBuyer(opts.signer)
79
+ : await selectBuyer();
80
+ // Per-session held store + PoP signer (BUY-4): the injected pair keeps the composite loop's
81
+ // free re-reads isolated per buyer and signed by the paying session EOA. Omitted ⇒ OSS defaults.
82
+ const heldStore = opts.heldStore ?? fileHeldStore;
83
+ const popWallet = opts.popWallet ?? getWallet();
84
+ log(`topic: "${topic}"`);
85
+ log(`budget: $${budget} · mode ${cfg.PAYMENT_MODE} · wallet ${buyer.address} · gate ${base}`);
86
+ await buyer.init();
87
+ // 1. discover (free teasers)
88
+ const candidates = await discover(topic);
89
+ log(`\ndiscovered ${candidates.length} candidate essays`);
90
+ // 2. price (free x402 probes — no payment yet)
91
+ const priced = [];
92
+ // The TRUE total (all legs, atomic micro-USDC) each candidate was quoted at here. The pay step
93
+ // uses it as the toll-moved ceiling, so a gate that quotes low at appraisal and charges more at
94
+ // pay time is aborted — the BUY-1.4 guarantee. Budget-remaining alone cannot detect that.
95
+ const quotedTotals = new Map();
96
+ for (const c of candidates) {
97
+ const quoted = await buyer.price(c.url ?? articleUrl(base, c.slug), "citation");
98
+ if (quoted) {
99
+ priced.push({ ...c, price: usdc(quoted.priceUsdc) });
100
+ quotedTotals.set(c.slug, quotedTotalAtomic(quoted));
101
+ }
102
+ else
103
+ log(` · ${c.slug}: not gated — skipping`);
104
+ }
105
+ log(`priced ${priced.length} gated essays`);
106
+ // 3. appraise
107
+ const appraised = await appraise(topic, priced);
108
+ log(`\nappraisal:`);
109
+ for (const a of appraised)
110
+ log(` · ${a.slug}: relevance ${a.relevance.toFixed(2)} — ${a.rationale}`);
111
+ // 4. decide (the agency). A live license already held for an essay makes it a
112
+ // zero-cost "cache" — pay once, re-read free.
113
+ const held = await heldStore.load();
114
+ const nowSec = Math.floor(Date.now() / 1000);
115
+ const licensed = new Set([...held.values()].filter((h) => isLive(h, nowSec)).map((h) => h.slug));
116
+ if (licensed.size)
117
+ log(`\nholding ${licensed.size} live license(s) — those re-read free`);
118
+ // gateBase lets decide() resolve a slug-only candidate to the SAME url the pay step below uses,
119
+ // so domain policy is evaluated against the host that actually gets paid (never Candidate.host).
120
+ const decisions = decide(appraised, budget, licensed, opts.policy ?? DEFAULT_POLICY, {
121
+ ...opts.decideContext,
122
+ gateBase: opts.decideContext?.gateBase ?? base,
123
+ });
124
+ log(`\ndecisions:`);
125
+ for (const d of decisions)
126
+ log(` [${d.action.toUpperCase()}] ${d.slug} — ${d.reason}`);
127
+ // 5. obtain: pay for new sources (capturing + verifying the license), or re-read
128
+ // already-licensed ones for free.
129
+ const jwks = await fetchJwks(base);
130
+ const sources = [];
131
+ let spent = 0;
132
+ for (const d of decisions) {
133
+ const url = d.url ?? articleUrl(base, d.slug);
134
+ if (d.action === "cache") {
135
+ const h = held.get(d.slug);
136
+ if (!h)
137
+ continue;
138
+ // Holder-of-key license: sign a fresh proof-of-possession so the gate knows
139
+ // we still hold the payer wallet, not just a captured token.
140
+ let proof;
141
+ if (h.pop) {
142
+ proof = (await buildPopProof(h, popWallet, Date.now())) ?? undefined;
143
+ if (!proof) {
144
+ log(` ✗ re-read ${d.slug}: license is holder-of-key but wallet can't sign — paying instead`);
145
+ continue;
146
+ }
147
+ }
148
+ const reread = await rereadWithLicense(url, "citation", h.jws, buyer.address, proof);
149
+ if (reread.ok) {
150
+ sources.push({ slug: d.slug, title: d.title, content: reread.content ?? "", paidUsdc: 0, licenseId: h.jti });
151
+ log(` 🎫 re-read ${d.slug} FREE with held license (${h.jti.slice(0, 8)})${h.pop ? " 🔑 proof-of-possession" : ""}`);
152
+ }
153
+ else {
154
+ log(` ✗ re-read failed for ${d.slug}: ${reread.error}`);
155
+ }
156
+ continue;
157
+ }
158
+ if (d.action !== "pay")
159
+ continue;
160
+ // The pay-time ceiling is the TIGHTER of two independent bounds:
161
+ // · toll-moved — the total this candidate was quoted at in step 2, plus the configured
162
+ // tolerance. Catches a gate that quotes low at appraisal and charges more at pay time
163
+ // (a budget check alone cannot: the inflated price still "fits").
164
+ // · budget — what is actually left. decide() plans against the AUTHOR-leg price, which
165
+ // under-counts a fee'd toll, so its plan can exceed budget.
166
+ // Whichever is smaller wins; exceeding it aborts the pay having spent nothing.
167
+ const remainingAtomic = BigInt(Math.max(0, Math.round((budget - spent) * 1_000_000)));
168
+ const quotedAtomic = quotedTotals.get(d.slug);
169
+ const bps = BigInt(cfg.WAYFARER_TOLL_TOLERANCE_BPS);
170
+ const tollCeiling = quotedAtomic === undefined ? undefined : quotedAtomic + (quotedAtomic * bps) / 10000n;
171
+ const ceiling = tollCeiling === undefined ? remainingAtomic : (tollCeiling < remainingAtomic ? tollCeiling : remainingAtomic);
172
+ const result = await buyer.fetch(url, "citation", { maxTotalAtomic: ceiling.toString() });
173
+ if (!result.ok) {
174
+ log(` ✗ payment failed for ${d.slug}: ${result.error}`);
175
+ continue;
176
+ }
177
+ // Debit the TRUE total across all legs (costUsdc), not the author leg (paidUsdc) — else a
178
+ // fee'd toll under-counts spend and the loop keeps buying past budget.
179
+ spent += result.costUsdc ?? result.paidUsdc ?? d.price;
180
+ let licenseId;
181
+ if (result.license) {
182
+ const decoded = decodeHeld(result.license);
183
+ const verified = jwks ? verifyAgainst(result.license, jwks) : null;
184
+ if (decoded) {
185
+ // Persist the url actually paid alongside the license, so it travels with the
186
+ // held record (a slug-only re-read can then target the real link, not a template).
187
+ held.set(d.slug, { ...decoded, jws: result.license, url });
188
+ licenseId = decoded.jti;
189
+ }
190
+ const mark = verified === true ? " → 🎫 license verified" : verified === false ? " → ⚠ license UNVERIFIED" : " → 🎫 license";
191
+ log(` ✓ paid $${(result.paidUsdc ?? d.price).toFixed(6)} for ${d.slug} (ref ${result.settlementRef})${mark} ${licenseId?.slice(0, 8) ?? ""}`);
192
+ }
193
+ else {
194
+ log(` ✓ paid $${(result.paidUsdc ?? d.price).toFixed(6)} for ${d.slug} (ref ${result.settlementRef})`);
195
+ }
196
+ sources.push({
197
+ slug: d.slug,
198
+ title: d.title,
199
+ content: result.content ?? "",
200
+ paidUsdc: result.paidUsdc ?? d.price,
201
+ settlementRef: result.settlementRef,
202
+ licenseId,
203
+ });
204
+ }
205
+ await heldStore.save(held);
206
+ // 6. ground
207
+ const answer = await ground(topic, sources);
208
+ log(`\nspent $${spent.toFixed(6)} of $${budget} across ${sources.length} cited source(s)`);
209
+ return { topic, budget, spent: usdc(spent), decisions, sources, answer };
210
+ }
211
+ async function ground(topic, sources) {
212
+ if (sources.length === 0) {
213
+ return `No sources were worth paying for under budget for "${topic}".`;
214
+ }
215
+ const citations = sources
216
+ .map((s, i) => `[${i + 1}] ${s.title} (${s.slug})${s.licenseId ? ` · licensed 🎫 ${s.licenseId.slice(0, 8)}` : ""}`)
217
+ .join("\n");
218
+ if (getConfig().OPENAI_API_KEY) {
219
+ try {
220
+ const { ChatOpenAI } = await import("@langchain/openai");
221
+ const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0.2 });
222
+ const corpus = sources
223
+ .map((s, i) => `[${i + 1}] ${s.title}\n${s.content.slice(0, 4000)}`)
224
+ .join("\n\n");
225
+ const res = await model.invoke(`Answer the topic "${topic}" grounded ONLY in these sources, citing with [n].\n\n${corpus}`);
226
+ return `${String(res.content)}\n\nSources:\n${citations}`;
227
+ }
228
+ catch {
229
+ // fall through to template
230
+ }
231
+ }
232
+ return (`Grounded answer for "${topic}" (template; set OPENAI_API_KEY for synthesis):\n` +
233
+ `Paid for and cited ${sources.length} source(s).\n\nSources:\n${citations}`);
234
+ }
235
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Cross-source proportional allocation (BUY-3.2) — a BUYER-side policy.
3
+ *
4
+ * Keryx splits a citation-reward pool across the sources it actually cited,
5
+ * weighted by each source's contribution. Wayfarer pays each source a flat
6
+ * per-article toll; this closes the parity gap as a *policy* on top of those
7
+ * tolls: given a fixed answer-budget, decide how much of it each cited source
8
+ * earned, proportional to its appraised contribution.
9
+ *
10
+ * It computes numbers — it does NOT pay. There is no new settlement primitive;
11
+ * a caller that wants to act on the split reuses the existing per-article toll
12
+ * rail. Money is integer micro-USDC and the split is dust-free (largest-
13
+ * remainder apportionment, same money-truth discipline as shared `splitMicro`,
14
+ * here keyed by source slug rather than payee wallet).
15
+ */
16
+ /** One source's share of the answer-budget. */
17
+ export interface SourceAllocation {
18
+ slug: string;
19
+ /** Integer micro-USDC of the pool attributed to this source. */
20
+ micro: number;
21
+ /** Normalized 0..1 contribution weight (for display / a citation-reward call). */
22
+ weight: number;
23
+ }
24
+ /**
25
+ * Allocate `poolMicro` (integer micro-USDC) across `sources` proportional to each
26
+ * source's `contribution`. Negative contributions clamp to zero. When the total
27
+ * contribution is positive, `Σ micro === floor(poolMicro)` exactly — no dust
28
+ * created or lost; the rounding remainder goes to the largest fractional shares.
29
+ * When every contribution is zero (or the list is empty), nothing is allocated
30
+ * (all `micro === 0`) — there is no basis to weight the pool.
31
+ */
32
+ export declare function allocateByContribution(sources: readonly {
33
+ slug: string;
34
+ contribution: number;
35
+ }[], poolMicro: number): SourceAllocation[];
36
+ //# sourceMappingURL=allocation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocation.d.ts","sourceRoot":"","sources":["../src/allocation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,+CAA+C;AAC/C,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1D,SAAS,EAAE,MAAM,GAChB,gBAAgB,EAAE,CAsBpB"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Cross-source proportional allocation (BUY-3.2) — a BUYER-side policy.
3
+ *
4
+ * Keryx splits a citation-reward pool across the sources it actually cited,
5
+ * weighted by each source's contribution. Wayfarer pays each source a flat
6
+ * per-article toll; this closes the parity gap as a *policy* on top of those
7
+ * tolls: given a fixed answer-budget, decide how much of it each cited source
8
+ * earned, proportional to its appraised contribution.
9
+ *
10
+ * It computes numbers — it does NOT pay. There is no new settlement primitive;
11
+ * a caller that wants to act on the split reuses the existing per-article toll
12
+ * rail. Money is integer micro-USDC and the split is dust-free (largest-
13
+ * remainder apportionment, same money-truth discipline as shared `splitMicro`,
14
+ * here keyed by source slug rather than payee wallet).
15
+ */
16
+ /**
17
+ * Allocate `poolMicro` (integer micro-USDC) across `sources` proportional to each
18
+ * source's `contribution`. Negative contributions clamp to zero. When the total
19
+ * contribution is positive, `Σ micro === floor(poolMicro)` exactly — no dust
20
+ * created or lost; the rounding remainder goes to the largest fractional shares.
21
+ * When every contribution is zero (or the list is empty), nothing is allocated
22
+ * (all `micro === 0`) — there is no basis to weight the pool.
23
+ */
24
+ export function allocateByContribution(sources, poolMicro) {
25
+ const weights = sources.map((s) => (s.contribution > 0 ? s.contribution : 0));
26
+ const total = weights.reduce((a, b) => a + b, 0);
27
+ if (total <= 0)
28
+ return sources.map((s) => ({ slug: s.slug, micro: 0, weight: 0 }));
29
+ const pool = Math.max(0, Math.floor(poolMicro));
30
+ const exact = weights.map((w) => (pool * w) / total);
31
+ const micro = exact.map((e) => Math.floor(e));
32
+ let remainder = pool - micro.reduce((a, b) => a + b, 0);
33
+ // Largest-remainder apportionment: hand each leftover micro-unit to the source
34
+ // with the biggest fractional part (tie → larger weight, then lower index, for
35
+ // determinism). remainder is an integer in [0, sources.length], so this closes
36
+ // the gap exactly without ever over-distributing.
37
+ const byFraction = exact
38
+ .map((e, i) => ({ i, frac: e - Math.floor(e), weight: weights[i] }))
39
+ .sort((a, b) => b.frac - a.frac || b.weight - a.weight || a.i - b.i);
40
+ for (let k = 0; remainder > 0 && k < byFraction.length; k++, remainder--) {
41
+ micro[byFraction[k].i]++;
42
+ }
43
+ return sources.map((s, i) => ({ slug: s.slug, micro: micro[i], weight: weights[i] / total }));
44
+ }
45
+ //# sourceMappingURL=allocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allocation.js","sourceRoot":"","sources":["../src/allocation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CACpC,OAA0D,EAC1D,SAAiB;IAEjB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9E,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IACjD,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAEnF,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,SAAS,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAExD,+EAA+E;IAC/E,+EAA+E;IAC/E,+EAA+E;IAC/E,kDAAkD;IAClD,MAAM,UAAU,GAAG,KAAK;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC;SACpE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,SAAS,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,CAAC;QACzE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC,CAAE,EAAE,CAAC;IAC7B,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAE,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;AAClG,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PricedCandidate, AppraisedCandidate } from "./types.ts";
2
+ export declare function appraise(topic: string, candidates: PricedCandidate[]): Promise<AppraisedCandidate[]>;
3
+ //# sourceMappingURL=appraise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appraise.d.ts","sourceRoot":"","sources":["../src/appraise.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAa,eAAe,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAqDjF,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,eAAe,EAAE,GAC5B,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAQ/B"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Appraisal — estimate how relevant each candidate is to the research topic,
3
+ * from its free teaser alone. Returns a 0..1 score plus a one-line rationale
4
+ * that surfaces in the decision log.
5
+ *
6
+ * With OPENAI_API_KEY set, an LLM judges relevance. Without it, a keyword-overlap
7
+ * heuristic stands in, so the agent still makes a real (if blunter) judgement
8
+ * offline — no network, no key, still decides.
9
+ */
10
+ import { getConfig } from "@naulon/shared";
11
+ const STOP = new Set([
12
+ "the", "a", "an", "and", "or", "of", "to", "in", "on", "for", "with", "is",
13
+ "are", "what", "how", "why", "about", "into", "we", "i", "it", "its", "that",
14
+ ]);
15
+ function tokens(s) {
16
+ return new Set(s
17
+ .toLowerCase()
18
+ .replace(/[^a-z0-9\s]/g, " ")
19
+ .split(/\s+/)
20
+ .filter((w) => w.length > 2 && !STOP.has(w)));
21
+ }
22
+ /** Keyword-overlap relevance in [0,1]: matched topic terms / topic terms. */
23
+ function heuristicScore(topic, c) {
24
+ const topicTerms = tokens(topic);
25
+ const docTerms = tokens(`${c.title} ${c.summary}`);
26
+ if (topicTerms.size === 0)
27
+ return { relevance: 0, rationale: "empty topic" };
28
+ const hits = [...topicTerms].filter((t) => docTerms.has(t));
29
+ const relevance = hits.length / topicTerms.size;
30
+ const rationale = hits.length
31
+ ? `shares ${hits.length}/${topicTerms.size} topic terms (${hits.join(", ")})`
32
+ : "no topic-term overlap in the teaser";
33
+ return { relevance, rationale };
34
+ }
35
+ async function llmScore(topic, c) {
36
+ try {
37
+ const { ChatOpenAI } = await import("@langchain/openai");
38
+ const model = new ChatOpenAI({ model: "gpt-4o-mini", temperature: 0 });
39
+ const res = await model.invoke(`Topic: "${topic}"\nEssay: "${c.title}" — ${c.summary}\n` +
40
+ `Rate 0-100 how useful this essay is for the topic, then one short reason. ` +
41
+ `Reply exactly as: SCORE|reason`);
42
+ const text = String(res.content);
43
+ const [scoreStr, ...rest] = text.split("|");
44
+ const score = Number(scoreStr?.trim());
45
+ if (!Number.isFinite(score))
46
+ return null;
47
+ return { relevance: Math.max(0, Math.min(1, score / 100)), rationale: rest.join("|").trim() };
48
+ }
49
+ catch {
50
+ return null; // any failure → caller falls back to heuristic
51
+ }
52
+ }
53
+ export async function appraise(topic, candidates) {
54
+ const useLlm = Boolean(getConfig().OPENAI_API_KEY);
55
+ return Promise.all(candidates.map(async (c) => {
56
+ const scored = (useLlm ? await llmScore(topic, c) : null) ?? heuristicScore(topic, c);
57
+ return { ...c, relevance: scored.relevance, rationale: scored.rationale };
58
+ }));
59
+ }
60
+ //# sourceMappingURL=appraise.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appraise.js","sourceRoot":"","sources":["../src/appraise.ts"],"names":[],"mappings":"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"}