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.
- package/README.md +32 -1
- package/dist/{chunk-MBXHRQ2J.js → chunk-6UDL5LZL.js} +96 -6
- package/dist/{chunk-S2RSID5F.js → chunk-DRRRZNIH.js} +1 -1
- package/dist/{chunk-4C2JEYAU.js → chunk-P3GJDXSQ.js} +291 -32
- package/dist/cli.js +403 -6
- package/dist/{core-BMfko_ex.d.ts → core-BTJGPYVW.d.cts} +64 -2
- package/dist/{core-BMfko_ex.d.cts → core-BTJGPYVW.d.ts} +64 -2
- package/dist/core.cjs +104 -6
- package/dist/core.d.cts +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +20 -2
- package/dist/{http-YWG535HP.js → http-BZI6VZM3.js} +1 -1
- package/dist/index.cjs +391 -30
- package/dist/index.d.cts +98 -5
- package/dist/index.d.ts +98 -5
- package/dist/index.js +29 -3
- package/package.json +1 -1
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
767
|
-
const
|
|
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
|
-
|
|
855
|
+
writeJson(file, { sources: live });
|
|
770
856
|
} catch (e) {
|
|
771
857
|
throw fsError(e, this.home);
|
|
772
858
|
}
|
|
773
|
-
|
|
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
|
|
1340
|
-
while (!
|
|
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
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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
|
|
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)
|
|
1801
|
-
|
|
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
|
-
}).
|
|
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
|
-
|
|
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
|
|
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
|
|
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-
|
|
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,
|