signafaide 0.2.1 → 0.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.
@@ -9,13 +9,16 @@ import {
9
9
  KeysRequired,
10
10
  MAX_CLOCK_SKEW_MS,
11
11
  SigningKey,
12
+ TOOLS,
12
13
  agentName,
13
14
  b64url,
14
15
  b64urlDecode,
16
+ byCodePoint,
15
17
  canonicalize,
16
18
  checkContent,
17
19
  clampTime,
18
20
  contentFromText,
21
+ declaredName,
19
22
  hashJson,
20
23
  instanceStartContent,
21
24
  instanceStopContent,
@@ -37,6 +40,7 @@ import {
37
40
  registration,
38
41
  signDraft,
39
42
  timestampHeights,
43
+ toolNoteName,
40
44
  useNativeCrypto,
41
45
  validProjectId,
42
46
  validTime,
@@ -45,7 +49,7 @@ import {
45
49
  verifySig,
46
50
  wireRecord,
47
51
  withWallTime
48
- } from "./chunk-MBXHRQ2J.js";
52
+ } from "./chunk-6UDL5LZL.js";
49
53
  import {
50
54
  ADAPTERS,
51
55
  Client,
@@ -57,7 +61,7 @@ import {
57
61
  detectAgent,
58
62
  fetchJson,
59
63
  telemetryEnabled
60
- } from "./chunk-S2RSID5F.js";
64
+ } from "./chunk-DRRRZNIH.js";
61
65
 
62
66
  // src/nodecrypto.ts
63
67
  import { createHash, createPublicKey, verify } from "crypto";
@@ -160,6 +164,8 @@ function fsError(e, home) {
160
164
  }
161
165
  return e;
162
166
  }
167
+ var TOOL_SOURCE_REFRESH_MS = 36e5;
168
+ var TOOL_SOURCE_MAX_AGE_MS = 864e5;
163
169
  var LOCK_STALE_MS = 1e4;
164
170
  var LOCK_WAIT_MS = 15e3;
165
171
  var PENDING = "_pending";
@@ -760,18 +766,97 @@ var State = class _State {
760
766
  * and put `wire(record)` in the outbox. Returns null when `stillValid()` is false once
761
767
  * the lock is held (the chain was just moved under its project; resolve it again). */
762
768
  append(chain, build, wire, stillValid = () => true) {
769
+ return this.appendRun(chain, [build], wire, stillValid)?.[0] ?? null;
770
+ }
771
+ /** `append` for several records that must follow each other with nothing between them (a tool
772
+ * declaration, §2.6): all of them are built and written under one hold of the chain lock, in
773
+ * one write, so no other writer's record lands inside the run. */
774
+ appendRun(chain, builds, wire, stillValid = () => true) {
763
775
  this.ensure();
764
776
  return this.lock(chain, () => {
765
777
  if (!stillValid()) return null;
766
- const [seq, prev, prevTime] = this.tail(chain);
767
- const rec = build(seq + 1, prev, prevTime);
778
+ let [seq, prev, prevTime] = this.tail(chain);
779
+ const recs = [];
780
+ for (const build of builds) {
781
+ const rec = build(seq + 1, prev, prevTime);
782
+ recs.push(rec);
783
+ [seq, prev, prevTime] = [rec.envelope.seq, recordHash(rec), rec.envelope.time];
784
+ }
785
+ try {
786
+ fs.appendFileSync(this.chainPath(chain), recs.map((r) => canonicalize(r) + "\n").join(""));
787
+ } catch (e) {
788
+ throw fsError(e, this.home);
789
+ }
790
+ for (const rec of recs) if (!rec.draft) this.writeOutbox(chain, wire(rec));
791
+ return recs;
792
+ });
793
+ }
794
+ // --- tool declarations --------------------------------------------------------
795
+ /** `tools/<agent>.json` (§8): the tools each source (the SDK in an application, each gateway)
796
+ * last declared for the agent. */
797
+ toolsPath(agent) {
798
+ return path.join(this.home, "tools", `${agent}.json`);
799
+ }
800
+ /** The tools every source last declared for `agent`: source -> {tool name: recorded}. */
801
+ toolSources(agent) {
802
+ return Object.fromEntries(Object.entries(this.toolEntries(agent)).map(([src, e]) => [src, e.tools]));
803
+ }
804
+ /** The entries of `tools/<agent>.json`: source -> when it last declared or refreshed, and its tools. */
805
+ toolEntries(agent) {
806
+ const doc = readJson(this.toolsPath(agent));
807
+ const out = {};
808
+ const sources = doc?.sources;
809
+ if (!sources || typeof sources !== "object" || Array.isArray(sources)) return out;
810
+ for (const [src, entry] of Object.entries(sources)) {
811
+ const tools = entry?.tools;
812
+ if (!tools || typeof tools !== "object" || Array.isArray(tools)) continue;
813
+ out[src] = { at: typeof entry.at === "string" ? entry.at : "", tools: Object.fromEntries(Object.entries(tools).filter(([, v]) => typeof v === "boolean")) };
814
+ }
815
+ return out;
816
+ }
817
+ /** A source that ends cleanly takes its own entry out of `tools/<agent>.json`, so the next
818
+ * declaration by any source no longer counts its tools (§8). Nothing is written to the chain. */
819
+ releaseToolSource(agent, source) {
820
+ const file = this.toolsPath(agent);
821
+ if (!fs.existsSync(file)) return;
822
+ this.lockAt(path.join(this.home, "tools", `${agent}.lock`), () => {
823
+ const entries = this.toolEntries(agent);
824
+ if (!(source in entries)) return;
825
+ delete entries[source];
826
+ writeJson(file, { sources: entries });
827
+ });
828
+ }
829
+ /** Set the tools `source` declares for `agent` and call `write` with the agent's whole tool
830
+ * list: the union over every source, a tool recorded when any source records it (§8). Held
831
+ * under `tools/<agent>.lock`, so two sources never write declarations that miss each other's
832
+ * tools. The source's own entry gets the time `nowMs`; another source's entry not refreshed for
833
+ * `TOOL_SOURCE_MAX_AGE_MS` is dropped (that source has stopped), and `expired` tells `write`
834
+ * whether dropping changed the agent's tool list. The file is updated only when `write` returns. */
835
+ declareTools(agent, source, tools, write, nowMs = Date.now()) {
836
+ this.ensure();
837
+ const file = this.toolsPath(agent);
838
+ return this.lockAt(path.join(this.home, "tools", `${agent}.lock`), () => {
839
+ const union = (entries) => {
840
+ const all2 = {};
841
+ for (const src of Object.keys(entries).sort()) for (const [name, recorded] of Object.entries(entries[src].tools)) all2[name] = all2[name] === true || recorded;
842
+ return all2;
843
+ };
844
+ const sorted = (all2) => JSON.stringify(Object.keys(all2).sort().map((n) => [n, all2[n]]));
845
+ const held = { ...this.toolEntries(agent), [source]: { at: new Date(nowMs).toISOString(), tools } };
846
+ const live = Object.fromEntries(
847
+ Object.entries(held).filter(([src, e]) => {
848
+ const at = Date.parse(e.at);
849
+ return src === source || Number.isFinite(at) && nowMs - at <= TOOL_SOURCE_MAX_AGE_MS;
850
+ })
851
+ );
852
+ const all = union(live);
853
+ const out = write(all, sorted(all) !== sorted(union(held)));
768
854
  try {
769
- fs.appendFileSync(this.chainPath(chain), canonicalize(rec) + "\n");
855
+ writeJson(file, { sources: live });
770
856
  } catch (e) {
771
857
  throw fsError(e, this.home);
772
858
  }
773
- if (!rec.draft) this.writeOutbox(chain, wire(rec));
774
- return rec;
859
+ return out;
775
860
  });
776
861
  }
777
862
  writeOutbox(chain, rec) {
@@ -1109,6 +1194,9 @@ var Recorder = class {
1109
1194
  /** Agents this process wrote records for since its last stop note. */
1110
1195
  wrote = /* @__PURE__ */ new Set();
1111
1196
  toldStrict = false;
1197
+ /** `<agent>\n<source>` -> the tool set this recorder last declared for it (§2.6), and the header's hash. */
1198
+ declared = /* @__PURE__ */ new Map();
1199
+ toolTimer;
1112
1200
  /** @internal */
1113
1201
  _url;
1114
1202
  /** @internal */
@@ -1330,32 +1418,39 @@ ${scope}`;
1330
1418
  /** Sign and chain one record locally (synchronous) and queue it for shipping.
1331
1419
  * The chain is this API key's `<project>/<env>/<agent>` (§8). */
1332
1420
  write(kind, name, content, opts = {}) {
1421
+ return this.writeRun([{ kind, name, content, ref: opts.ref }], opts)[0];
1422
+ }
1423
+ /** `write` for records that must follow each other with nothing between them (a tool
1424
+ * declaration, §2.6): they are signed and appended under one hold of the chain lock. */
1425
+ writeRun(items, opts = {}) {
1333
1426
  const why = noWrites.getStore();
1334
- if (why) throw new SignafaideError(`${why} must not write records: candidate functions must be side-effect free (it tried to record "${name}")`, { code: "no_writes" });
1427
+ if (why) throw new SignafaideError(`${why} must not write records: candidate functions must be side-effect free (it tried to record "${items[0]?.name}")`, { code: "no_writes" });
1335
1428
  const agent = opts.agent || this.defaultAgent;
1336
1429
  if (!AGENT_RE.test(agent)) throw new Error(`agent "${agent}" is invalid: use lowercase letters, digits, '.', '_' or '-' (max 64)`);
1337
1430
  const sc = opts.storeContent ?? this.storeContent;
1338
1431
  const apiKey = this.apiKey;
1339
- let rec = null;
1340
- while (!rec) {
1432
+ let recs = null;
1433
+ while (!recs) {
1341
1434
  const bucket = this.writeBucket(apiKey, agent);
1342
1435
  const scope = scopeOf(bucket);
1343
1436
  const key = this.key(agent, scope);
1344
1437
  const chain = `${bucket}/${agent}`;
1345
1438
  if (!fs2.existsSync(this.state.chainPath(chain))) for (const e of Object.values(this.adoptInto(bucket, apiKey))) this.report(`signafaide: ${e.message}`);
1346
1439
  const [project, env] = bucket.split("/");
1347
- const build = bucket.startsWith(PENDING + "/") ? (seq, prev, prevTime) => {
1348
- const [time, c] = this.timed(content, prevTime, agent, scope);
1349
- return makeDraft(key, { kind, agent, seq, prev, name, content: c, ref: opts.ref ?? null, time }, sc);
1350
- } : (seq, prev, prevTime) => {
1351
- const [time, c] = this.timed(content, prevTime, agent, scope);
1352
- return makeRecord(key, { kind, project, env, agent, seq, prev, name, content: c, ref: this.state.translateRef(chain, opts.ref), time });
1353
- };
1354
- rec = this.state.append(chain, build, (r) => wireRecord(r, sc), () => this.writeBucket(apiKey, agent) === bucket);
1440
+ const builds = items.map(
1441
+ ({ kind, name, content, ref }) => bucket.startsWith(PENDING + "/") ? (seq, prev, prevTime) => {
1442
+ const [time, c] = this.timed(content, prevTime, agent, scope);
1443
+ return makeDraft(key, { kind, agent, seq, prev, name, content: c, ref: ref ?? null, time }, sc);
1444
+ } : (seq, prev, prevTime) => {
1445
+ const [time, c] = this.timed(content, prevTime, agent, scope);
1446
+ return makeRecord(key, { kind, project, env, agent, seq, prev, name, content: c, ref: this.state.translateRef(chain, ref), time });
1447
+ }
1448
+ );
1449
+ recs = this.state.appendRun(chain, builds, (r) => wireRecord(r, sc), () => this.writeBucket(apiKey, agent) === bucket);
1355
1450
  }
1356
- this.pending++;
1451
+ this.pending += recs.length;
1357
1452
  this.exitFlushed = false;
1358
- if (name !== INSTANCE_STOP) this.wrote.add(agent);
1453
+ if (items.some((i) => i.name !== INSTANCE_STOP)) this.wrote.add(agent);
1359
1454
  if (!apiKey && !this.toldLocal && !this.quiet && !this.test) {
1360
1455
  this.toldLocal = true;
1361
1456
  console.error(
@@ -1364,7 +1459,76 @@ ${scope}`;
1364
1459
  }
1365
1460
  this.start();
1366
1461
  if (this.pending >= this.flushSize) void this.tick();
1367
- return rec;
1462
+ return recs;
1463
+ }
1464
+ /** Declare the tools one source knows for an agent (§2.6): a `signafaide.tools` note followed
1465
+ * by one note per tool, written as one run. `source` names who declares (the SDK in this
1466
+ * program, or one gateway); the notes list the agent's whole tool list, the union over every
1467
+ * source that declared on this machine (§8 `tools/<agent>.json`), a tool recorded when any
1468
+ * source records it. Written at most once per recorder for the same set: the first time, and
1469
+ * again when the set changes. Returns the header's record hash. */
1470
+ declareTools(tools, opts) {
1471
+ const agent = opts.agent || this.defaultAgent;
1472
+ if (!AGENT_RE.test(agent)) throw new Error(`agent "${agent}" is invalid: use lowercase letters, digits, '.', '_' or '-' (max 64)`);
1473
+ const names = Object.keys(tools).sort(byCodePoint);
1474
+ const bad = names.find((n) => !n || /[\u0000-\u001f\u007f]/.test(n));
1475
+ if (bad !== void 0) throw new TypeError(`a tool name must be a non-empty string without control characters (got ${JSON.stringify(bad)})`);
1476
+ const id = `${agent}
1477
+ ${opts.source}`;
1478
+ const sig = JSON.stringify(names.map((n) => [n, tools[n]]));
1479
+ const held = this.declared.get(id);
1480
+ const hash = this.state.declareTools(agent, opts.source, tools, (all, expired) => {
1481
+ if (held?.sig === sig && !expired) return held.hash;
1482
+ const byName = /* @__PURE__ */ new Map();
1483
+ for (const [tool, recorded2] of Object.entries(all)) {
1484
+ const d = declaredName(tool);
1485
+ const prev = byName.get(d);
1486
+ if (!prev || recorded2 && !prev.recorded) byName.set(d, { tool, recorded: recorded2 });
1487
+ }
1488
+ const list = [...byName.keys()].sort(byCodePoint).map((d) => byName.get(d));
1489
+ const recorded = list.filter((t) => t.recorded).length;
1490
+ const items = [
1491
+ { kind: "note", name: TOOLS, content: { source: opts.origin, tools: list.length, recorded, unrecorded: list.length - recorded } },
1492
+ ...list.map((t) => ({ kind: "note", name: toolNoteName(t.tool, t.recorded), content: { tool: t.tool, recorded: t.recorded } }))
1493
+ ];
1494
+ return recordHash(this.writeRun(items, { agent, storeContent: opts.storeContent })[0]);
1495
+ });
1496
+ this.declared.set(id, { sig, hash, agent, source: opts.source, origin: opts.origin, tools, storeContent: opts.storeContent });
1497
+ if (!this.toolTimer && !this.closed) {
1498
+ this.toolTimer = setInterval(() => this.refreshToolSources(), TOOL_SOURCE_REFRESH_MS);
1499
+ this.toolTimer.unref?.();
1500
+ }
1501
+ return hash;
1502
+ }
1503
+ /** Refresh this recorder's entries in `tools/<agent>.json` (§8): each gets the current time,
1504
+ * so other sources keep counting its tools, and entries of sources that stopped more than 24
1505
+ * hours ago drop out. Nothing is written to the chain unless that changed the agent's tool
1506
+ * list. Runs by itself once an hour while the process lives. Never throws. */
1507
+ refreshToolSources() {
1508
+ for (const d of [...this.declared.values()]) {
1509
+ try {
1510
+ this.declareTools(d.tools, { agent: d.agent, source: d.source, origin: d.origin, storeContent: d.storeContent });
1511
+ } catch (e) {
1512
+ this.report(`signafaide: could not refresh the tool list of ${d.agent}: ${e.message}`);
1513
+ }
1514
+ }
1515
+ }
1516
+ /** A clean end of this source (§8): its entries leave `tools/<agent>.json`, so the next
1517
+ * declaration by any source no longer counts its tools. Nothing is written to the chain. With
1518
+ * `source`, only that source ends (a gateway run inside another program). Never throws. */
1519
+ releaseToolSources(source) {
1520
+ for (const [id, d] of [...this.declared]) {
1521
+ if (source !== void 0 && d.source !== source) continue;
1522
+ try {
1523
+ this.state.releaseToolSource(d.agent, d.source);
1524
+ } catch {
1525
+ }
1526
+ this.declared.delete(id);
1527
+ }
1528
+ if (!this.declared.size && this.toolTimer) {
1529
+ clearInterval(this.toolTimer);
1530
+ this.toolTimer = void 0;
1531
+ }
1368
1532
  }
1369
1533
  /** Where `agent`'s next record goes. A known project whose chain for `agent` is not on this
1370
1534
  * disk (an empty `SIGNAFAIDE_HOME` on Lambda or Cloud Run) may already have that chain on the
@@ -1784,10 +1948,22 @@ ${k.keyId}`;
1784
1948
  /** The program has nothing left to do: one last attempt, held open at most `exitFlushMs`.
1785
1949
  * Background requests never keep the process alive, so they are cut off here. */
1786
1950
  onBeforeExit = () => {
1951
+ void this.exitAttempt();
1952
+ };
1953
+ /** The last attempt of a program that ends on its own schedule (the gateway, once its server
1954
+ * has exited): what happens by itself when a program runs out of work. Ships what is left and,
1955
+ * when stop notes are on, writes the signed stop note, all within `exitFlushMs`; then stops the
1956
+ * background timer. Never throws. */
1957
+ async shutdown() {
1958
+ await this.exitAttempt();
1959
+ this.releaseToolSources();
1960
+ this.close();
1961
+ }
1962
+ exitAttempt() {
1787
1963
  const stop2 = this.instanceStop && this.wrote.size > 0 && !this.test;
1788
- if (this.exitFlushed || !this.exitFlushMs || !this.apiKey || !this.waiting() && !stop2) return;
1964
+ if (this.exitFlushed || !this.exitFlushMs || !this.apiKey || !this.waiting() && !stop2) return void 0;
1789
1965
  this.exitFlushed = true;
1790
- if (this.halted && this.halted.fp === this.configFp()) return;
1966
+ if (this.halted && this.halted.fp === this.configFp()) return void 0;
1791
1967
  this.bgAbort.abort();
1792
1968
  this.bgAbort = new AbortController();
1793
1969
  const ctrl = new AbortController();
@@ -1797,15 +1973,16 @@ ${k.keyId}`;
1797
1973
  clearTimeout(timer);
1798
1974
  this.exitFlushed = true;
1799
1975
  };
1800
- if (!stop2) void this.flushLogged(this.exitFlushMs, "exit").finally(done);
1801
- else void this.stop(this.exitFlushMs, "exit").then((r) => {
1976
+ if (!stop2) return this.flushLogged(this.exitFlushMs, "exit").then(done, done);
1977
+ return this.stop(this.exitFlushMs, "exit").then((r) => {
1802
1978
  const errs = Object.entries(r.errors);
1803
1979
  if (errs.length && !ctrl.signal.aborted) this.report("signafaide: " + errs.map(([a, e]) => e.message.includes(a) ? e.message : `${a}: ${e.message}`).join("; "));
1804
1980
  }, (e) => {
1805
1981
  if (!ctrl.signal.aborted) this.report(`signafaide: ${e.message}`);
1806
- }).finally(done);
1807
- };
1982
+ }).then(done, done);
1983
+ }
1808
1984
  onExit = () => {
1985
+ this.releaseToolSources();
1809
1986
  const left = this.waiting();
1810
1987
  if (left && !this.quiet && this.apiKey) {
1811
1988
  console.error(`signafaide: ${plural(left, "record")} kept in ${path2.join(this.state.home, "outbox")}; they ship on the next run or with \`signafaide flush\``);
@@ -1814,6 +1991,8 @@ ${k.keyId}`;
1814
1991
  /** Stop the background timer and exit hooks (records stay in the outbox). */
1815
1992
  close() {
1816
1993
  this.closed = true;
1994
+ if (this.toolTimer) clearInterval(this.toolTimer);
1995
+ this.toolTimer = void 0;
1817
1996
  if (this.timer) clearInterval(this.timer);
1818
1997
  this.timer = void 0;
1819
1998
  this.bgAbort.abort();
@@ -1993,16 +2172,89 @@ function termsNotice(l) {
1993
2172
  var NotRecorded = class extends SignafaideError {
1994
2173
  name = "NotRecorded";
1995
2174
  };
2175
+ var WRAPPED = /* @__PURE__ */ new Map();
2176
+ var LISTED = /* @__PURE__ */ new Map();
2177
+ var EXPLICIT = /* @__PURE__ */ new Map();
2178
+ var toolsVersion = 0;
2179
+ var checked = /* @__PURE__ */ new WeakMap();
2180
+ var toldDeclare = false;
2181
+ function autoDeclareTools(env = process.env) {
2182
+ return !/^(0|false|no|off)$/i.test((env.SIGNAFAIDE_DECLARE_TOOLS ?? "").trim());
2183
+ }
2184
+ function remember(map, agent, name) {
2185
+ const set = map.get(agent ?? "") ?? /* @__PURE__ */ new Set();
2186
+ map.set(agent ?? "", set);
2187
+ if (!set.has(name)) {
2188
+ set.add(name);
2189
+ toolsVersion++;
2190
+ }
2191
+ }
2192
+ function namesFor(map, agent, isDefault) {
2193
+ return /* @__PURE__ */ new Set([...map.get(agent) ?? [], ...isDefault ? map.get("") ?? [] : []]);
2194
+ }
2195
+ function ownTools(rec, agent) {
2196
+ const isDefault = agent === rec.defaultAgent;
2197
+ const explicit = EXPLICIT.get(agent) ?? (isDefault ? EXPLICIT.get("") : void 0);
2198
+ if (explicit) {
2199
+ const wrapped = namesFor(WRAPPED, agent, isDefault);
2200
+ const tools = {};
2201
+ for (const n of explicit.names) tools[n] = wrapped.has(n) && !explicit.unrecorded.has(n);
2202
+ for (const n of explicit.unrecorded) tools[n] = false;
2203
+ return [tools, "declared"];
2204
+ }
2205
+ const listed = namesFor(LISTED, agent, isDefault);
2206
+ if (!listed.size || !autoDeclareTools()) return null;
2207
+ return [Object.fromEntries([...listed].map((n) => [n, true])), "wrap_tools"];
2208
+ }
2209
+ function declareOwn(rec, agentOpt, storeContent) {
2210
+ const agent = agentOpt || rec.defaultAgent;
2211
+ const own = ownTools(rec, agent);
2212
+ if (!own) return "";
2213
+ return rec.declareTools(own[0], { agent, source: "sdk", origin: own[1], storeContent });
2214
+ }
2215
+ function declareBeforeCall(rec, agentOpt) {
2216
+ const agent = agentOpt || rec.defaultAgent;
2217
+ const seen = checked.get(rec) ?? /* @__PURE__ */ new Map();
2218
+ checked.set(rec, seen);
2219
+ if (seen.get(agent) === toolsVersion || noWrites.getStore()) return;
2220
+ try {
2221
+ declareOwn(rec, agentOpt);
2222
+ seen.set(agent, toolsVersion);
2223
+ } catch (e) {
2224
+ seen.set(agent, toolsVersion);
2225
+ if (!toldDeclare) {
2226
+ toldDeclare = true;
2227
+ console.error(`signafaide: could not write the tool declaration for ${agent}: ${e.message}`);
2228
+ }
2229
+ }
2230
+ }
2231
+ function declareTools(names, opts = {}) {
2232
+ const list = [...names];
2233
+ const unrecorded = [...opts.unrecorded ?? []];
2234
+ for (const n of [...list, ...unrecorded]) if (typeof n !== "string" || !n) throw new TypeError("declareTools needs tool names: non-empty strings");
2235
+ EXPLICIT.set(opts.agent ?? "", { names: list, unrecorded: new Set(unrecorded) });
2236
+ toolsVersion++;
2237
+ if (recordingDisabled()) return "";
2238
+ const rec = getRecorder();
2239
+ const hash = declareOwn(rec, opts.agent);
2240
+ const seen = checked.get(rec) ?? /* @__PURE__ */ new Map();
2241
+ checked.set(rec, seen);
2242
+ seen.set(opts.agent || rec.defaultAgent, toolsVersion);
2243
+ return hash;
2244
+ }
1996
2245
  var Spec = class {
1997
- constructor(name, opts) {
2246
+ constructor(name, opts, listed = false) {
1998
2247
  this.name = name;
1999
2248
  this.opts = opts;
2000
2249
  if (!name) throw new TypeError('record(fn) needs a name: record(fn, { name: "approve_payout" })');
2250
+ remember(WRAPPED, opts.agent, name);
2251
+ if (listed) remember(LISTED, opts.agent, name);
2001
2252
  }
2002
2253
  name;
2003
2254
  opts;
2004
2255
  decide(input) {
2005
2256
  const rec = getRecorder();
2257
+ declareBeforeCall(rec, this.opts.agent);
2006
2258
  const meta = { sdk: `signafaide-js/${VERSION}`, ...this.opts.meta ?? {} };
2007
2259
  const content = { input: toJsonable(input) ?? null, meta: toJsonable(meta) };
2008
2260
  return [rec, rec.write("decision", this.name, content, { agent: this.opts.agent, storeContent: this.opts.storeContent })];
@@ -2056,7 +2308,9 @@ var Spec = class {
2056
2308
  }
2057
2309
  };
2058
2310
  function record(fn, opts = {}) {
2059
- const spec = new Spec(opts.name ?? fn.name, opts);
2311
+ return recordWith(new Spec(opts.name ?? fn.name, opts), fn);
2312
+ }
2313
+ function recordWith(spec, fn) {
2060
2314
  const wrapped = function(...args) {
2061
2315
  return spec.call(args.length === 1 ? args[0] : args, () => fn.apply(this, args));
2062
2316
  };
@@ -2079,7 +2333,8 @@ var isOpenAIAgentsTool = (t) => t?.type === "function" && typeof t.invoke === "f
2079
2333
  var isLangChainTool = (t) => Array.isArray(t?.lc_namespace) && typeof t._call === "function" && typeof t.invoke === "function";
2080
2334
  function wrapTool(tool, opts = {}) {
2081
2335
  const t = tool;
2082
- const spec = (name) => new Spec(opts.name ?? name, opts);
2336
+ const { declareTools: declare = true, ...ropts } = opts;
2337
+ const spec = (name) => new Spec(ropts.name ?? name, ropts, declare);
2083
2338
  if (isOpenAIAgentsTool(t)) {
2084
2339
  const s = spec(t.name);
2085
2340
  ADAPTERS.add("openai-agents");
@@ -2097,7 +2352,7 @@ function wrapTool(tool, opts = {}) {
2097
2352
  };
2098
2353
  return proxy;
2099
2354
  }
2100
- if (typeof t === "function") return record(t, { ...opts, name: opts.name ?? t.name });
2355
+ if (typeof t === "function") return recordWith(spec(t.name), t);
2101
2356
  if (typeof t?.execute === "function") {
2102
2357
  const s = spec(t.name ?? "");
2103
2358
  ADAPTERS.add("vercel-ai");
@@ -2610,7 +2865,7 @@ async function readInput(src, flag) {
2610
2865
  }
2611
2866
  console.error(`WARNING: ${flag} ${src} uses plain http:// on this machine; the keys do not count as independent`);
2612
2867
  }
2613
- return parseJson(JSON.stringify(await (await import("./http-YWG535HP.js")).fetchJson(src)));
2868
+ return parseJson(JSON.stringify(await (await import("./http-BZI6VZM3.js")).fetchJson(src)));
2614
2869
  }
2615
2870
  return parseJson(fs4.readFileSync(src, "utf8"));
2616
2871
  }
@@ -2731,6 +2986,8 @@ async function verify2(pack, opts = {}) {
2731
2986
  }
2732
2987
 
2733
2988
  export {
2989
+ TOOL_SOURCE_REFRESH_MS,
2990
+ TOOL_SOURCE_MAX_AGE_MS,
2734
2991
  StateError,
2735
2992
  findRoot,
2736
2993
  State,
@@ -2742,6 +2999,8 @@ export {
2742
2999
  configure,
2743
3000
  termsNotice,
2744
3001
  NotRecorded,
3002
+ autoDeclareTools,
3003
+ declareTools,
2745
3004
  record,
2746
3005
  note,
2747
3006
  wrapTools,