@vincemakes/kiso-core 0.26.0 → 0.26.2
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/dist/errors.d.ts +7 -1
- package/dist/errors.js +27 -2
- package/dist/kernel/loop.d.ts +3 -0
- package/dist/kernel/loop.js +104 -13
- package/dist/protocol/events.d.ts +4 -0
- package/dist/tools/registry.d.ts +18 -1
- package/dist/tools/registry.js +36 -7
- package/package.json +2 -2
package/dist/errors.d.ts
CHANGED
|
@@ -6,4 +6,10 @@
|
|
|
6
6
|
* providers proxy raw.
|
|
7
7
|
*/
|
|
8
8
|
import type { StructuredError } from "./protocol/events.js";
|
|
9
|
-
|
|
9
|
+
/** CX-1 F8: `Retry-After` → milliseconds. Integer seconds or an HTTP-date;
|
|
10
|
+
* anything else — negative, non-finite, a date already past — is
|
|
11
|
+
* undefined (the kernel falls back to its own backoff). Never shortened:
|
|
12
|
+
* a wait above the kernel's cap is reported as-is and the kernel stops
|
|
13
|
+
* rather than retrying early. */
|
|
14
|
+
export declare function parseRetryAfter(value: string | null | undefined, now?: number): number | undefined;
|
|
15
|
+
export declare function mapApiError(status: number | undefined, message: string, retryAfterMs?: number): StructuredError;
|
package/dist/errors.js
CHANGED
|
@@ -5,8 +5,33 @@
|
|
|
5
5
|
* (ADR-0005). 529 is the Cloudflare overload code most OpenAI-compat
|
|
6
6
|
* providers proxy raw.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** CX-1 F8: `Retry-After` → milliseconds. Integer seconds or an HTTP-date;
|
|
9
|
+
* anything else — negative, non-finite, a date already past — is
|
|
10
|
+
* undefined (the kernel falls back to its own backoff). Never shortened:
|
|
11
|
+
* a wait above the kernel's cap is reported as-is and the kernel stops
|
|
12
|
+
* rather than retrying early. */
|
|
13
|
+
export function parseRetryAfter(value, now = Date.now()) {
|
|
14
|
+
if (value === null || value === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const v = value.trim();
|
|
17
|
+
if (v === "")
|
|
18
|
+
return undefined;
|
|
19
|
+
if (/^\d+$/.test(v)) {
|
|
20
|
+
const ms = Number(v) * 1000;
|
|
21
|
+
return Number.isFinite(ms) ? ms : undefined;
|
|
22
|
+
}
|
|
23
|
+
const at = Date.parse(v);
|
|
24
|
+
if (!Number.isFinite(at))
|
|
25
|
+
return undefined;
|
|
26
|
+
const ms = at - now;
|
|
27
|
+
return ms >= 0 ? ms : undefined;
|
|
28
|
+
}
|
|
29
|
+
export function mapApiError(status, message, retryAfterMs) {
|
|
30
|
+
const withStatus = (e) => ({
|
|
31
|
+
...e,
|
|
32
|
+
...(status !== undefined ? { status } : {}),
|
|
33
|
+
...(retryAfterMs !== undefined && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? { retryAfterMs } : {}),
|
|
34
|
+
});
|
|
10
35
|
switch (status) {
|
|
11
36
|
case 401:
|
|
12
37
|
case 403:
|
package/dist/kernel/loop.d.ts
CHANGED
|
@@ -168,6 +168,9 @@ export interface LoopConfig {
|
|
|
168
168
|
*/
|
|
169
169
|
export declare const DEFAULT_MAX_TURNS: number;
|
|
170
170
|
export declare const DEFAULT_MAX_RETRIES = 2;
|
|
171
|
+
/** CX-1 F8: the longest Retry-After the kernel will honor; beyond it the
|
|
172
|
+
* run stops with an explicit error rather than waiting or retrying early. */
|
|
173
|
+
export declare const RETRY_AFTER_MAX_MS = 60000;
|
|
171
174
|
export declare function loop(config: LoopConfig): AsyncGenerator<Event>;
|
|
172
175
|
/**
|
|
173
176
|
* Adapter exceptions → StructuredError. Anything already shaped like one
|
package/dist/kernel/loop.js
CHANGED
|
@@ -63,6 +63,9 @@ import { messagesToEvents, MICROCOMPACTABLE, DO_NOT_COMPACT, projectMessages } f
|
|
|
63
63
|
*/
|
|
64
64
|
export const DEFAULT_MAX_TURNS = Number.POSITIVE_INFINITY;
|
|
65
65
|
export const DEFAULT_MAX_RETRIES = 2;
|
|
66
|
+
/** CX-1 F8: the longest Retry-After the kernel will honor; beyond it the
|
|
67
|
+
* run stops with an explicit error rather than waiting or retrying early. */
|
|
68
|
+
export const RETRY_AFTER_MAX_MS = 60_000;
|
|
66
69
|
export async function* loop(config) {
|
|
67
70
|
const log = config.log ?? new EventLog();
|
|
68
71
|
const hooks = config.hooks ?? NoOpHooks;
|
|
@@ -73,6 +76,13 @@ export async function* loop(config) {
|
|
|
73
76
|
const registry = mode?.visibleToolNames !== undefined
|
|
74
77
|
? config.registry.subset(mode.visibleToolNames)
|
|
75
78
|
: config.registry;
|
|
79
|
+
// CX-1 F7b (audit F7): ONE tool table per request — captured at
|
|
80
|
+
// assembly, dispatched from until the next assembly. The definition
|
|
81
|
+
// the model saw, the schema the arguments are validated against, the
|
|
82
|
+
// execute that runs and the effects the scheduler reads are the same
|
|
83
|
+
// captured values; a live source that changes mid-request reaches the
|
|
84
|
+
// NEXT request.
|
|
85
|
+
let table = registry.snapshot();
|
|
76
86
|
// Seed: a fresh log encodes the seed history as events so the projection
|
|
77
87
|
// (and any later replay) contains it. A non-empty log (session resume)
|
|
78
88
|
// is never re-seeded — the log already holds everything.
|
|
@@ -315,7 +325,7 @@ export async function* loop(config) {
|
|
|
315
325
|
};
|
|
316
326
|
const launch = (call) => {
|
|
317
327
|
execActive += 1;
|
|
318
|
-
const tool =
|
|
328
|
+
const tool = table.get(call.name);
|
|
319
329
|
const slot = reserve(tool);
|
|
320
330
|
// EC-1 ③ — this call's place in the ASK order, filled in by the
|
|
321
331
|
// decide chain (call order). `ahead` is the gate of an ask ACCEPTED
|
|
@@ -326,7 +336,7 @@ export async function* loop(config) {
|
|
|
326
336
|
launches.push((async () => {
|
|
327
337
|
try {
|
|
328
338
|
decideChain = decideChain.then(async () => {
|
|
329
|
-
const v = await decideCall(call,
|
|
339
|
+
const v = await decideCall(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, log, config.resolveApproval, config.approvalVerdict, signal, config.approvalPolicy, nextDecisionId, pushExec);
|
|
330
340
|
askOrder.ahead = askGate;
|
|
331
341
|
if (v.action === "ask") {
|
|
332
342
|
askGate = new Promise((res) => {
|
|
@@ -425,7 +435,7 @@ export async function* loop(config) {
|
|
|
425
435
|
return;
|
|
426
436
|
await acquireWindow();
|
|
427
437
|
try {
|
|
428
|
-
await runLedgered(call,
|
|
438
|
+
await runLedgered(call, table, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, signal, pushExec);
|
|
429
439
|
}
|
|
430
440
|
finally {
|
|
431
441
|
releaseWindow();
|
|
@@ -521,7 +531,7 @@ export async function* loop(config) {
|
|
|
521
531
|
// commit-required execution in the draft (impossible under
|
|
522
532
|
// invariant 3) suppresses the marker — no void over a started
|
|
523
533
|
// fact, the pre-F4 abandon exactly.
|
|
524
|
-
const unsafeStartedInDraft = () => log.all.some((e) => e.type === "tool_execution_started" && e.seq > turnStart &&
|
|
534
|
+
const unsafeStartedInDraft = () => log.all.some((e) => e.type === "tool_execution_started" && e.seq > turnStart && table.get(e.name)?.effects?.precommitSafe !== true);
|
|
525
535
|
const abandonDraft = async function* (reason) {
|
|
526
536
|
settleTurn();
|
|
527
537
|
violated = true;
|
|
@@ -549,7 +559,7 @@ export async function* loop(config) {
|
|
|
549
559
|
messages,
|
|
550
560
|
...(config.reasoning !== undefined ? { reasoning: config.reasoning } : {}),
|
|
551
561
|
...(config.systemPrompt !== undefined ? { systemPrompt: config.systemPrompt } : {}),
|
|
552
|
-
tools: registry.
|
|
562
|
+
tools: (table = registry.snapshot()).specs,
|
|
553
563
|
...(config.maxTokens !== undefined ? { maxTokens: config.maxTokens } : {}),
|
|
554
564
|
...(config.temperature !== undefined ? { temperature: config.temperature } : {}),
|
|
555
565
|
...(signal !== undefined ? { signal } : {}),
|
|
@@ -630,13 +640,28 @@ export async function* loop(config) {
|
|
|
630
640
|
yield await terminal({ kind: "aborted", by: "user" });
|
|
631
641
|
return;
|
|
632
642
|
}
|
|
633
|
-
|
|
643
|
+
let structured = toStructuredError(err);
|
|
644
|
+
// CX-1 F8 (ruled: the kernel owns retries): the wait is
|
|
645
|
+
// max(attempts × 250 ms, the provider's Retry-After), abortable.
|
|
646
|
+
// A wait above the cap is never shortened — the run stops with
|
|
647
|
+
// an explicit error instead of retrying early.
|
|
648
|
+
const retryWait = () => {
|
|
649
|
+
const ms = Math.max((attempts + 1) * 250, structured.retryAfterMs ?? 0);
|
|
650
|
+
return ms > RETRY_AFTER_MAX_MS ? null : ms;
|
|
651
|
+
};
|
|
652
|
+
if (structured.retryable && attempts < maxRetries && retryWait() === null) {
|
|
653
|
+
structured = {
|
|
654
|
+
...structured,
|
|
655
|
+
retryable: false,
|
|
656
|
+
message: `${structured.message} (the provider asked to wait ${structured.retryAfterMs}ms — beyond the ${RETRY_AFTER_MAX_MS}ms cap; not retried)`,
|
|
657
|
+
};
|
|
658
|
+
}
|
|
634
659
|
// Phase B: never SILENTLY re-stream a turn that already emitted
|
|
635
660
|
// content — duplicates are worse than failures. Nothing streamed
|
|
636
661
|
// yet: the cheap in-place retry (no draft exists to void).
|
|
637
662
|
if (structured.retryable && !streamed && attempts < maxRetries) {
|
|
638
663
|
attempts += 1;
|
|
639
|
-
await sleep(attempts * 250, signal); // abortable backoff
|
|
664
|
+
await sleep(retryWait() ?? attempts * 250, signal); // abortable backoff, Retry-After honored
|
|
640
665
|
continue;
|
|
641
666
|
}
|
|
642
667
|
// F4 — ABANDON HYGIENE, every streamed exit: settle, drain,
|
|
@@ -651,7 +676,7 @@ export async function* loop(config) {
|
|
|
651
676
|
// budget (ADR-0005 Amendment 1: frame state, per-process).
|
|
652
677
|
if (structured.retryable && attempts < maxRetries && !unsafeStartedInDraft()) {
|
|
653
678
|
attempts += 1;
|
|
654
|
-
await sleep(attempts * 250, signal); // the same abortable backoff
|
|
679
|
+
await sleep(retryWait() ?? attempts * 250, signal); // the same abortable backoff, Retry-After honored
|
|
655
680
|
// Fresh per-attempt state — the marker is the boundary now.
|
|
656
681
|
pending.length = 0;
|
|
657
682
|
lastStop = undefined;
|
|
@@ -959,12 +984,76 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
|
|
|
959
984
|
// its verdict. A throwing chain counts as ask. Allow/deny are recorded
|
|
960
985
|
// durably with decidedBy = the deciding extension, never pausing for a
|
|
961
986
|
// human.
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
987
|
+
// CX-1 F1 (audit F1): a durable decision is consumable ONCE — by the
|
|
988
|
+
// invocation it was recorded for, or by that invocation's RE-ISSUE on
|
|
989
|
+
// resume (same callId, same tool, identical input, and the original
|
|
990
|
+
// never produced a result). A provider callId alone is correlation,
|
|
991
|
+
// never identity: the old lookup found the current call itself
|
|
992
|
+
// (already appended), compared its input to its own, and took the
|
|
993
|
+
// FIRST decision with that callId in the whole log — so a reused
|
|
994
|
+
// callId inherited an old allow for a new call.
|
|
995
|
+
//
|
|
996
|
+
// The decision's invocationSeq names the original call (every
|
|
997
|
+
// producer stamps it, round 5). A legacy decision (no invocationSeq,
|
|
998
|
+
// pre-round-5 logs) binds to the nearest PRECEDING same-callId call
|
|
999
|
+
// before it — call first, decision after — with no run boundary
|
|
1000
|
+
// between; one that binds to nothing is ambiguous, and the call goes
|
|
1001
|
+
// to ask, never to the chain's allow.
|
|
1002
|
+
const all = log.all;
|
|
1003
|
+
const legacyBinding = (d) => {
|
|
1004
|
+
for (let i = all.length - 1; i >= 0; i -= 1) {
|
|
1005
|
+
const e = all[i];
|
|
1006
|
+
if (e.seq >= d.seq)
|
|
1007
|
+
continue;
|
|
1008
|
+
if (e.type === "terminal")
|
|
1009
|
+
return undefined;
|
|
1010
|
+
if (e.type === "tool_call_end" && e.callId === call.callId)
|
|
1011
|
+
return e.seq;
|
|
1012
|
+
}
|
|
1013
|
+
return undefined;
|
|
1014
|
+
};
|
|
1015
|
+
/** The re-issue rule: a resume re-issues the pending call under the SAME
|
|
1016
|
+
* provider id — same callId, same tool, identical input — and the
|
|
1017
|
+
* original never completed: no result and no other call under that id
|
|
1018
|
+
* stands between the original and this call. A different callId with
|
|
1019
|
+
* the same arguments is a new invocation (the /mode-switch shape: a
|
|
1020
|
+
* write denied under plan is asked afresh under default). */
|
|
1021
|
+
const reissues = (origSeq) => {
|
|
1022
|
+
const orig = all.find((e) => e.seq === origSeq);
|
|
1023
|
+
if (orig === undefined || orig.type !== "tool_call_end")
|
|
1024
|
+
return false;
|
|
1025
|
+
if (orig.callId !== call.callId || orig.name !== call.name)
|
|
1026
|
+
return false;
|
|
1027
|
+
if (JSON.stringify(orig.input) !== JSON.stringify(call.input))
|
|
1028
|
+
return false;
|
|
1029
|
+
for (const e of all) {
|
|
1030
|
+
if (e.seq <= origSeq || e.seq >= call.seq)
|
|
1031
|
+
continue;
|
|
1032
|
+
if ((e.type === "tool_result" || e.type === "tool_call_end") && e.callId === orig.callId)
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
return true;
|
|
1036
|
+
};
|
|
1037
|
+
let durable;
|
|
1038
|
+
let legacyAmbiguous = false;
|
|
1039
|
+
for (const e of all) {
|
|
1040
|
+
if (e.type !== "permission_decided" || e.decidedBy === undefined)
|
|
1041
|
+
continue;
|
|
1042
|
+
if (e.invocationSeq === undefined && e.callId !== call.callId)
|
|
1043
|
+
continue;
|
|
1044
|
+
const origSeq = e.invocationSeq ?? legacyBinding(e);
|
|
1045
|
+
if (origSeq === undefined) {
|
|
1046
|
+
legacyAmbiguous = true;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (origSeq === call.seq || reissues(origSeq))
|
|
1050
|
+
durable = e;
|
|
1051
|
+
}
|
|
966
1052
|
let chainVerdict;
|
|
967
|
-
if (durable === undefined &&
|
|
1053
|
+
if (durable === undefined && legacyAmbiguous) {
|
|
1054
|
+
chainVerdict = { action: "ask" }; // the conservative reading of an old log
|
|
1055
|
+
}
|
|
1056
|
+
else if (durable === undefined && approvalPolicy !== undefined) {
|
|
968
1057
|
try {
|
|
969
1058
|
chainVerdict = await raceAbort(Promise.resolve(approvalPolicy.decide(payload, ctx)), signal);
|
|
970
1059
|
}
|
|
@@ -1237,6 +1326,8 @@ export function toStructuredError(err) {
|
|
|
1237
1326
|
...(e.status !== undefined ? { status: e.status } : {}),
|
|
1238
1327
|
retryable: e.retryable,
|
|
1239
1328
|
message: typeof e.message === "string" ? e.message : String(err),
|
|
1329
|
+
// CX-1 F8: the provider's Retry-After rides through to the kernel
|
|
1330
|
+
...(typeof e.retryAfterMs === "number" && Number.isFinite(e.retryAfterMs) && e.retryAfterMs >= 0 ? { retryAfterMs: e.retryAfterMs } : {}),
|
|
1240
1331
|
};
|
|
1241
1332
|
}
|
|
1242
1333
|
}
|
|
@@ -471,6 +471,10 @@ export interface StructuredError {
|
|
|
471
471
|
readonly status?: number;
|
|
472
472
|
readonly retryable: boolean;
|
|
473
473
|
readonly message: string;
|
|
474
|
+
/** CX-1 F8: the provider's `Retry-After`, normalized to milliseconds
|
|
475
|
+
* (finite, >= 0) — the kernel owns retries and honors it; absent when
|
|
476
|
+
* the header was missing, unparseable, or a date already past. */
|
|
477
|
+
readonly retryAfterMs?: number;
|
|
474
478
|
}
|
|
475
479
|
/**
|
|
476
480
|
* Why the whole run ended. The ONE terminal shape every run converges to.
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -24,12 +24,29 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { ToolSpec } from "../protocol/messages.js";
|
|
26
26
|
import type { Tool } from "./tool.js";
|
|
27
|
+
/** CX-1 F7: a captured tool table — what ONE request advertises and
|
|
28
|
+
* dispatches from. */
|
|
29
|
+
export interface ToolTable {
|
|
30
|
+
readonly specs: readonly ToolSpec[];
|
|
31
|
+
get(name: string): Tool<any> | undefined;
|
|
32
|
+
}
|
|
27
33
|
export declare class ToolRegistry {
|
|
28
34
|
#private;
|
|
29
35
|
register(tool: Tool<any>): void;
|
|
30
36
|
/** 0.1.26: a live tool source — consulted on every lookup, never
|
|
31
37
|
* snapshotted. The source returns the CURRENT array (it may grow). */
|
|
32
|
-
registerLive(source: () => readonly Tool[]): void;
|
|
38
|
+
registerLive(source: () => readonly Tool[], owner?: string): void;
|
|
39
|
+
/** CX-1 F7 (audit F7) — ONE table per request. The loop takes a
|
|
40
|
+
* snapshot at request assembly and dispatches from it: the definition
|
|
41
|
+
* sent to the model, the schema validated against, the `execute` that
|
|
42
|
+
* runs and the `effects` the scheduler reads are the SAME captured
|
|
43
|
+
* values (a shallow copy of each Tool — extensions replace their Tool
|
|
44
|
+
* objects rather than mutating them, and the copy makes that a
|
|
45
|
+
* contract). Two sources publishing one name is an error the moment
|
|
46
|
+
* it is observed, naming both owners — never a traversal-order pick;
|
|
47
|
+
* the one exception is the SAME Tool object reached twice (the 0.1.27
|
|
48
|
+
* dedup), which is one tool. */
|
|
49
|
+
snapshot(): ToolTable;
|
|
33
50
|
get(name: string): Tool<any> | undefined;
|
|
34
51
|
list(): readonly Tool[];
|
|
35
52
|
has(name: string): boolean;
|
package/dist/tools/registry.js
CHANGED
|
@@ -33,15 +33,44 @@ export class ToolRegistry {
|
|
|
33
33
|
}
|
|
34
34
|
/** 0.1.26: a live tool source — consulted on every lookup, never
|
|
35
35
|
* snapshotted. The source returns the CURRENT array (it may grow). */
|
|
36
|
-
registerLive(source) {
|
|
37
|
-
this.#live.push(source);
|
|
36
|
+
registerLive(source, owner = `live#${this.#live.length + 1}`) {
|
|
37
|
+
this.#live.push({ source, owner });
|
|
38
|
+
}
|
|
39
|
+
/** CX-1 F7 (audit F7) — ONE table per request. The loop takes a
|
|
40
|
+
* snapshot at request assembly and dispatches from it: the definition
|
|
41
|
+
* sent to the model, the schema validated against, the `execute` that
|
|
42
|
+
* runs and the `effects` the scheduler reads are the SAME captured
|
|
43
|
+
* values (a shallow copy of each Tool — extensions replace their Tool
|
|
44
|
+
* objects rather than mutating them, and the copy makes that a
|
|
45
|
+
* contract). Two sources publishing one name is an error the moment
|
|
46
|
+
* it is observed, naming both owners — never a traversal-order pick;
|
|
47
|
+
* the one exception is the SAME Tool object reached twice (the 0.1.27
|
|
48
|
+
* dedup), which is one tool. */
|
|
49
|
+
snapshot() {
|
|
50
|
+
const captured = new Map();
|
|
51
|
+
const take = (t, owner) => {
|
|
52
|
+
const prior = captured.get(t.name);
|
|
53
|
+
if (prior !== undefined) {
|
|
54
|
+
if (prior.original === t)
|
|
55
|
+
return; // the same object reached twice — one tool
|
|
56
|
+
throw new Error(`tool table: '${t.name}' is published by two sources (${prior.owner}, ${owner})`);
|
|
57
|
+
}
|
|
58
|
+
captured.set(t.name, { tool: Object.freeze({ ...t }), original: t, owner });
|
|
59
|
+
};
|
|
60
|
+
for (const t of this.#tools.values())
|
|
61
|
+
take(t, "registered");
|
|
62
|
+
for (const { source, owner } of this.#live)
|
|
63
|
+
for (const t of source())
|
|
64
|
+
take(t, owner);
|
|
65
|
+
const specs = [...captured.values()].map(({ tool: t }) => ({ name: t.name, description: t.description, inputSchema: t.parameters }));
|
|
66
|
+
return { specs, get: (name) => captured.get(name)?.tool };
|
|
38
67
|
}
|
|
39
68
|
get(name) {
|
|
40
69
|
const t = this.#tools.get(name);
|
|
41
70
|
if (t !== undefined)
|
|
42
71
|
return t;
|
|
43
|
-
for (const
|
|
44
|
-
const found =
|
|
72
|
+
for (const { source } of this.#live) {
|
|
73
|
+
const found = source().find((x) => x.name === name);
|
|
45
74
|
if (found !== undefined)
|
|
46
75
|
return found;
|
|
47
76
|
}
|
|
@@ -61,8 +90,8 @@ export class ToolRegistry {
|
|
|
61
90
|
seen.add(t.name);
|
|
62
91
|
out.push(t);
|
|
63
92
|
}
|
|
64
|
-
for (const
|
|
65
|
-
for (const t of
|
|
93
|
+
for (const { source } of this.#live) {
|
|
94
|
+
for (const t of source()) {
|
|
66
95
|
if (!seen.has(t.name)) {
|
|
67
96
|
seen.add(t.name);
|
|
68
97
|
out.push(t);
|
|
@@ -74,7 +103,7 @@ export class ToolRegistry {
|
|
|
74
103
|
has(name) {
|
|
75
104
|
if (this.#tools.has(name))
|
|
76
105
|
return true;
|
|
77
|
-
return this.#live.some((
|
|
106
|
+
return this.#live.some(({ source }) => source().some((x) => x.name === name));
|
|
78
107
|
}
|
|
79
108
|
/** A registry restricted to the named tools. Unknown names are dropped
|
|
80
109
|
* loudly (the kernel never silently shrinks a tool set). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.2",
|
|
4
4
|
"description": "kiso (foundation) core — protocol, event log, loop, hooks, modes, permissions, compaction, delivery truth. The 2,000-line kernel at the bottom of the kiso framework.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"openai"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@vincemakes/kiso-evals": "0.26.
|
|
36
|
+
"@vincemakes/kiso-evals": "0.26.2",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|