@vincemakes/kiso-core 0.26.2 → 0.26.3
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/kernel/loop.js +22 -42
- package/dist/tools/registry.d.ts +5 -3
- package/dist/tools/registry.js +7 -4
- package/package.json +2 -2
package/dist/kernel/loop.js
CHANGED
|
@@ -641,15 +641,13 @@ export async function* loop(config) {
|
|
|
641
641
|
return;
|
|
642
642
|
}
|
|
643
643
|
let structured = toStructuredError(err);
|
|
644
|
-
// CX-1 F8 (ruled: the kernel owns retries): the
|
|
645
|
-
// max(
|
|
646
|
-
//
|
|
647
|
-
//
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
};
|
|
652
|
-
if (structured.retryable && attempts < maxRetries && retryWait() === null) {
|
|
644
|
+
// CX-1 F8 (ruled: the kernel owns retries): the n-th retry waits
|
|
645
|
+
// max(n × 250 ms, the provider's Retry-After), abortable — computed
|
|
646
|
+
// BEFORE `attempts` advances (the 2026-09-07 review's P3: computed
|
|
647
|
+
// after it, the first wait was 500 ms). A wait above the cap is
|
|
648
|
+
// never shortened — the run stops with an explicit error instead.
|
|
649
|
+
const retryWait = () => Math.max((attempts + 1) * 250, structured.retryAfterMs ?? 0);
|
|
650
|
+
if (structured.retryable && attempts < maxRetries && retryWait() > RETRY_AFTER_MAX_MS) {
|
|
653
651
|
structured = {
|
|
654
652
|
...structured,
|
|
655
653
|
retryable: false,
|
|
@@ -660,8 +658,8 @@ export async function* loop(config) {
|
|
|
660
658
|
// content — duplicates are worse than failures. Nothing streamed
|
|
661
659
|
// yet: the cheap in-place retry (no draft exists to void).
|
|
662
660
|
if (structured.retryable && !streamed && attempts < maxRetries) {
|
|
661
|
+
await sleep(retryWait(), signal); // abortable backoff, Retry-After honored
|
|
663
662
|
attempts += 1;
|
|
664
|
-
await sleep(retryWait() ?? attempts * 250, signal); // abortable backoff, Retry-After honored
|
|
665
663
|
continue;
|
|
666
664
|
}
|
|
667
665
|
// F4 — ABANDON HYGIENE, every streamed exit: settle, drain,
|
|
@@ -675,8 +673,8 @@ export async function* loop(config) {
|
|
|
675
673
|
// F4 — the mid-stream retry: same classification, same per-turn
|
|
676
674
|
// budget (ADR-0005 Amendment 1: frame state, per-process).
|
|
677
675
|
if (structured.retryable && attempts < maxRetries && !unsafeStartedInDraft()) {
|
|
676
|
+
await sleep(retryWait(), signal); // the same abortable backoff, Retry-After honored
|
|
678
677
|
attempts += 1;
|
|
679
|
-
await sleep(retryWait() ?? attempts * 250, signal); // the same abortable backoff, Retry-After honored
|
|
680
678
|
// Fresh per-attempt state — the marker is the boundary now.
|
|
681
679
|
pending.length = 0;
|
|
682
680
|
lastStop = undefined;
|
|
@@ -984,14 +982,18 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
|
|
|
984
982
|
// its verdict. A throwing chain counts as ask. Allow/deny are recorded
|
|
985
983
|
// durably with decidedBy = the deciding extension, never pausing for a
|
|
986
984
|
// human.
|
|
987
|
-
// CX-1 F1 (audit F1): a durable decision
|
|
988
|
-
// invocation it was recorded for
|
|
989
|
-
//
|
|
990
|
-
//
|
|
991
|
-
//
|
|
992
|
-
// (
|
|
993
|
-
//
|
|
994
|
-
//
|
|
985
|
+
// CX-1 F1 (audit F1): a durable decision binds to EXACTLY the
|
|
986
|
+
// invocation it was recorded for — `invocationSeq === call.seq`. A
|
|
987
|
+
// provider callId alone is correlation, never identity: the old lookup
|
|
988
|
+
// took the FIRST decision with that callId in the whole log, so a
|
|
989
|
+
// reused callId inherited an old allow for a new call. The 0.26.1
|
|
990
|
+
// "re-issue" exception (same callId, same input, no result yet) is
|
|
991
|
+
// RETRACTED (the 2026-09-07 review's P1): it let a same-turn duplicate
|
|
992
|
+
// and a voided draft's retry inherit a verdict, and nothing needed it —
|
|
993
|
+
// a committed turn's pending call is executed by the recovery driver
|
|
994
|
+
// under its ORIGINAL seq, and a re-streamed call after a void is a new
|
|
995
|
+
// invocation the chain decides again (a policy is a function; running
|
|
996
|
+
// it twice costs nothing and gives a stateful one its say).
|
|
995
997
|
//
|
|
996
998
|
// The decision's invocationSeq names the original call (every
|
|
997
999
|
// producer stamps it, round 5). A legacy decision (no invocationSeq,
|
|
@@ -1012,28 +1014,6 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
|
|
|
1012
1014
|
}
|
|
1013
1015
|
return undefined;
|
|
1014
1016
|
};
|
|
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
1017
|
let durable;
|
|
1038
1018
|
let legacyAmbiguous = false;
|
|
1039
1019
|
for (const e of all) {
|
|
@@ -1046,7 +1026,7 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
|
|
|
1046
1026
|
legacyAmbiguous = true;
|
|
1047
1027
|
continue;
|
|
1048
1028
|
}
|
|
1049
|
-
if (origSeq === call.seq
|
|
1029
|
+
if (origSeq === call.seq)
|
|
1050
1030
|
durable = e;
|
|
1051
1031
|
}
|
|
1052
1032
|
let chainVerdict;
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -40,9 +40,11 @@ export declare class ToolRegistry {
|
|
|
40
40
|
* snapshot at request assembly and dispatches from it: the definition
|
|
41
41
|
* sent to the model, the schema validated against, the `execute` that
|
|
42
42
|
* runs and the `effects` the scheduler reads are the SAME captured
|
|
43
|
-
* values
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* values: the declaration data (schema, effects) is COPIED into the
|
|
44
|
+
* table, the handler stays by reference — an in-place edit to the
|
|
45
|
+
* source after the snapshot cannot reach a request already under way
|
|
46
|
+
* (the 2026-09-07 review's P2: a shallow copy shared the nested
|
|
47
|
+
* objects). Two sources publishing one name is an error the moment
|
|
46
48
|
* it is observed, naming both owners — never a traversal-order pick;
|
|
47
49
|
* the one exception is the SAME Tool object reached twice (the 0.1.27
|
|
48
50
|
* dedup), which is one tool. */
|
package/dist/tools/registry.js
CHANGED
|
@@ -40,9 +40,11 @@ export class ToolRegistry {
|
|
|
40
40
|
* snapshot at request assembly and dispatches from it: the definition
|
|
41
41
|
* sent to the model, the schema validated against, the `execute` that
|
|
42
42
|
* runs and the `effects` the scheduler reads are the SAME captured
|
|
43
|
-
* values
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* values: the declaration data (schema, effects) is COPIED into the
|
|
44
|
+
* table, the handler stays by reference — an in-place edit to the
|
|
45
|
+
* source after the snapshot cannot reach a request already under way
|
|
46
|
+
* (the 2026-09-07 review's P2: a shallow copy shared the nested
|
|
47
|
+
* objects). Two sources publishing one name is an error the moment
|
|
46
48
|
* it is observed, naming both owners — never a traversal-order pick;
|
|
47
49
|
* the one exception is the SAME Tool object reached twice (the 0.1.27
|
|
48
50
|
* dedup), which is one tool. */
|
|
@@ -55,7 +57,8 @@ export class ToolRegistry {
|
|
|
55
57
|
return; // the same object reached twice — one tool
|
|
56
58
|
throw new Error(`tool table: '${t.name}' is published by two sources (${prior.owner}, ${owner})`);
|
|
57
59
|
}
|
|
58
|
-
|
|
60
|
+
const copy = { ...t, parameters: structuredClone(t.parameters), ...(t.effects === undefined ? {} : { effects: Object.freeze(structuredClone(t.effects)) }) };
|
|
61
|
+
captured.set(t.name, { tool: Object.freeze(copy), original: t, owner });
|
|
59
62
|
};
|
|
60
63
|
for (const t of this.#tools.values())
|
|
61
64
|
take(t, "registered");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.3",
|
|
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.3",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|