@vincemakes/kiso-runtime 0.1.32 → 0.1.33
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/compose.d.ts +21 -1
- package/dist/compose.js +83 -1
- package/dist/run.js +5 -2
- package/dist/session.d.ts +4 -1
- package/dist/session.js +9 -3
- package/dist/truncation-guard.d.ts +2 -1
- package/dist/truncation-guard.js +2 -1
- package/package.json +5 -5
package/dist/compose.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* from session.ts: the extension system-prompt appends, the extension
|
|
4
4
|
* hook composition (the existing come first), and the loop's microcompact config lookup.
|
|
5
5
|
*/
|
|
6
|
-
import type { HookHost, KisoExtension, ToolRegistry } from "@vincemakes/kiso-core";
|
|
6
|
+
import type { ApprovalChain, HookHost, KisoExtension, ToolRegistry } from "@vincemakes/kiso-core";
|
|
7
7
|
import type { SessionConfig } from "./session.js";
|
|
8
8
|
/** The table, or "" when the registry is empty (no vocabulary, no tools). */
|
|
9
9
|
export declare function composeToolTable(registry: ToolRegistry): string;
|
|
@@ -32,3 +32,23 @@ export declare function microcompactFor(config: SessionConfig): {
|
|
|
32
32
|
readonly thresholdTokens: number;
|
|
33
33
|
readonly keepResults?: number;
|
|
34
34
|
} | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* E1 (W21/R3 — moved out of the kernel by the 2026-08-09 corrective
|
|
37
|
+
* action; the composition is extension wiring, the same category as
|
|
38
|
+
* composeHooks): compose the extensions' approval policies into ONE
|
|
39
|
+
* chain — the kernel's gate. deny > allow > ask: any deny wins (the
|
|
40
|
+
* FIRST denial's reason), then ANY allow (a LATER allow beats an
|
|
41
|
+
* EARLIER ask: the allow-only dont-ask-again extension must override a
|
|
42
|
+
* mode tier's ask — the old ask-wins chain left it structurally dead),
|
|
43
|
+
* and an ask falls into the kernel's human flow (its speaker = the
|
|
44
|
+
* first non-abstain — the panel's why-asked line). Only an ask-free
|
|
45
|
+
* chain auto-approves: decidedBy = the deciding extension (the FIRST
|
|
46
|
+
* allow — the symmetry of the first denial; an every-speaker-allows
|
|
47
|
+
* chain records the first speaker). An all-abstain chain (ADR-0042)
|
|
48
|
+
* ASKS — no opinion is never a silent allow; absent a channel the
|
|
49
|
+
* kernel's honest denial. A policy that throws counts as ask — it
|
|
50
|
+
* speaks, never silently. No policies → undefined (no chain: the
|
|
51
|
+
* kernel's plain flow — the all-abstain ask exists only where a chain
|
|
52
|
+
* does).
|
|
53
|
+
*/
|
|
54
|
+
export declare function composeApprovalChain(extensions: readonly KisoExtension[]): ApprovalChain | undefined;
|
package/dist/compose.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
7
|
* 0.1.40 (R-C item 1) — the tool substitution table: the fixed vocabulary
|
|
8
|
-
* (the
|
|
8
|
+
* (the reference implementation's content in kiso's voice, each line bound
|
|
9
|
+
* to the tool that makes
|
|
9
10
|
* it true) filtered to the ACTIVE tool set + each active tool's ONE-line
|
|
10
11
|
* snippet + its guideline bullets. The full descriptions NEVER enter the
|
|
11
12
|
* system prompt — the provider transmits them in the JSON schema anyway
|
|
@@ -28,6 +29,9 @@ export function composeToolTable(registry) {
|
|
|
28
29
|
...TOOL_RULES.filter((r) => active.has(r.tool)).map((r) => `- ${r.line}`),
|
|
29
30
|
// the parallel directive: the window applies to every active turn.
|
|
30
31
|
"- batch independent tool calls into one reply — they run in parallel",
|
|
32
|
+
// D1: a tool result is evidence, not an answer — the turn ends with
|
|
33
|
+
// the findings that make the evidence useful to the human.
|
|
34
|
+
"- end your turn with your findings — never end on a bare tool result",
|
|
31
35
|
...tools.flatMap((t) => (t.promptSnippet === undefined ? [] : [`- ${t.promptSnippet}`])),
|
|
32
36
|
];
|
|
33
37
|
const guidelines = tools.flatMap((t) => (t.promptGuidelines ?? []).map((g) => `- ${t.name}: ${g}`));
|
|
@@ -147,3 +151,81 @@ export function microcompactFor(config) {
|
|
|
147
151
|
}
|
|
148
152
|
return undefined;
|
|
149
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* E1 (W21/R3 — moved out of the kernel by the 2026-08-09 corrective
|
|
156
|
+
* action; the composition is extension wiring, the same category as
|
|
157
|
+
* composeHooks): compose the extensions' approval policies into ONE
|
|
158
|
+
* chain — the kernel's gate. deny > allow > ask: any deny wins (the
|
|
159
|
+
* FIRST denial's reason), then ANY allow (a LATER allow beats an
|
|
160
|
+
* EARLIER ask: the allow-only dont-ask-again extension must override a
|
|
161
|
+
* mode tier's ask — the old ask-wins chain left it structurally dead),
|
|
162
|
+
* and an ask falls into the kernel's human flow (its speaker = the
|
|
163
|
+
* first non-abstain — the panel's why-asked line). Only an ask-free
|
|
164
|
+
* chain auto-approves: decidedBy = the deciding extension (the FIRST
|
|
165
|
+
* allow — the symmetry of the first denial; an every-speaker-allows
|
|
166
|
+
* chain records the first speaker). An all-abstain chain (ADR-0042)
|
|
167
|
+
* ASKS — no opinion is never a silent allow; absent a channel the
|
|
168
|
+
* kernel's honest denial. A policy that throws counts as ask — it
|
|
169
|
+
* speaks, never silently. No policies → undefined (no chain: the
|
|
170
|
+
* kernel's plain flow — the all-abstain ask exists only where a chain
|
|
171
|
+
* does).
|
|
172
|
+
*/
|
|
173
|
+
export function composeApprovalChain(extensions) {
|
|
174
|
+
const policies = extensions.flatMap((e) => (e.approvals ?? []).map((policy) => ({ extension: e.name, policy })));
|
|
175
|
+
if (policies.length === 0)
|
|
176
|
+
return undefined;
|
|
177
|
+
return {
|
|
178
|
+
async decide(payload, ctx) {
|
|
179
|
+
let chainVerdict;
|
|
180
|
+
let deniedReason;
|
|
181
|
+
let deniedBy;
|
|
182
|
+
let allowedBy; // the FIRST allowing extension (the composition's decider for an allow)
|
|
183
|
+
let firstSpeaker; // the first non-abstain verdict's extension
|
|
184
|
+
let anySpoke = false;
|
|
185
|
+
for (const { extension, policy } of policies) {
|
|
186
|
+
let v;
|
|
187
|
+
try {
|
|
188
|
+
v = await Promise.resolve(policy.decide(payload, ctx));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
v = { action: "ask" }; // a throwing policy counts as ask — it speaks, never silently
|
|
192
|
+
}
|
|
193
|
+
if (v.action === "abstain")
|
|
194
|
+
continue; // no opinion — not a verdict
|
|
195
|
+
anySpoke = true;
|
|
196
|
+
firstSpeaker ??= extension;
|
|
197
|
+
if (v.action === "deny") {
|
|
198
|
+
deniedBy ??= extension;
|
|
199
|
+
deniedReason ??= v.reason; // the FIRST denial's reason
|
|
200
|
+
}
|
|
201
|
+
else if (v.action === "allow") {
|
|
202
|
+
// deny > allow > ask — the allow overrides any EARLIER
|
|
203
|
+
// ask in the chain (the allow-only dont-ask-again
|
|
204
|
+
// extension after a mode tier that asked). The first
|
|
205
|
+
// allow is the deciding one — the symmetry of deniedBy.
|
|
206
|
+
allowedBy ??= extension;
|
|
207
|
+
chainVerdict = { action: "allow" };
|
|
208
|
+
}
|
|
209
|
+
else if (chainVerdict === undefined) {
|
|
210
|
+
chainVerdict = { action: "ask" }; // recorded — a later allow overrides it
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (deniedBy !== undefined) {
|
|
214
|
+
return { action: "deny", reason: deniedReason ?? "denied", decidedBy: deniedBy };
|
|
215
|
+
}
|
|
216
|
+
if (allowedBy !== undefined) {
|
|
217
|
+
return { action: "allow", decidedBy: allowedBy }; // an allow beat any earlier ask
|
|
218
|
+
}
|
|
219
|
+
if (chainVerdict === undefined && anySpoke) {
|
|
220
|
+
return { action: "allow", decidedBy: firstSpeaker }; // every speaker allows
|
|
221
|
+
}
|
|
222
|
+
if (chainVerdict === undefined) {
|
|
223
|
+
// an all-abstain (ADR-0042): NO policy speaks — the call falls
|
|
224
|
+
// to the ask flow, never to a silent auto-approve. The human
|
|
225
|
+
// decides; absent a channel, the kernel's honest denial.
|
|
226
|
+
return { action: "ask" };
|
|
227
|
+
}
|
|
228
|
+
return { action: "ask", ...(firstSpeaker !== undefined ? { speaker: firstSpeaker } : {}) };
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
package/dist/run.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { denialResult, loop } from "@vincemakes/kiso-core";
|
|
7
7
|
import { ABORTED, MergedSignal, abortable, openRunId } from "./recovery.js";
|
|
8
|
-
import { composeSystemPrompt, composeToolTable, microcompactFor } from "./compose.js";
|
|
8
|
+
import { composeApprovalChain, composeSystemPrompt, composeToolTable, microcompactFor } from "./compose.js";
|
|
9
9
|
import { truncationGuard } from "./truncation-guard.js";
|
|
10
10
|
import { ResumeBlockedError } from "./session.js";
|
|
11
11
|
/**
|
|
@@ -70,6 +70,7 @@ export class Run {
|
|
|
70
70
|
// append in LOAD order — deterministic (same extensions → same
|
|
71
71
|
// prompt); no appends → byte-identical to the extension-less run.
|
|
72
72
|
const systemPrompt = composeSystemPrompt(basePrompt, this.#config.extensions ?? []);
|
|
73
|
+
const approvalChain = composeApprovalChain(this.#config.extensions ?? []);
|
|
73
74
|
const loopConfig = () => ({
|
|
74
75
|
// 0.1.40 (R-C item 3): the truncation guard gates the model
|
|
75
76
|
// stream — a truncated turn's tool batch never executes.
|
|
@@ -85,7 +86,9 @@ export class Run {
|
|
|
85
86
|
...(this.#config.compaction !== undefined ? { compaction: this.#config.compaction } : {}),
|
|
86
87
|
...(microcompact !== undefined ? { microcompact } : {}),
|
|
87
88
|
...(this.#config.maxRetries !== undefined ? { maxRetries: this.#config.maxRetries } : {}),
|
|
88
|
-
|
|
89
|
+
// E1: the composed approval chain — the extensions'
|
|
90
|
+
// policies composed into ONE gate (deny > allow > ask).
|
|
91
|
+
...(approvalChain !== undefined ? { approvalPolicy: approvalChain } : {}),
|
|
89
92
|
log,
|
|
90
93
|
signal,
|
|
91
94
|
resolveApproval: (decisionId) => new Promise((resolve) => {
|
package/dist/session.d.ts
CHANGED
|
@@ -139,8 +139,11 @@ export declare class AgentSession {
|
|
|
139
139
|
* next resume applies it without re-asking. The crash window between a
|
|
140
140
|
* resolve and the run's write is benign: nothing has executed yet, so a
|
|
141
141
|
* lost decision only re-presents the request.
|
|
142
|
+
* W21: an optional reason rides a DENIAL (the panel's feedback — the
|
|
143
|
+
* tool_result carries `[Permission denied] <the words>`); allow reasons
|
|
144
|
+
* are never persisted (the words ride the next user turn instead).
|
|
142
145
|
*/
|
|
143
|
-
approve(decisionId: string, allow: boolean): Promise<void>;
|
|
146
|
+
approve(decisionId: string, allow: boolean, reason?: string): Promise<void>;
|
|
144
147
|
/** Executions that started but never reported a result (crash window). */
|
|
145
148
|
uncertainExecutions(): import("@vincemakes/kiso-core").ExecutionRecord[];
|
|
146
149
|
/**
|
package/dist/session.js
CHANGED
|
@@ -270,8 +270,11 @@ export class AgentSession {
|
|
|
270
270
|
* next resume applies it without re-asking. The crash window between a
|
|
271
271
|
* resolve and the run's write is benign: nothing has executed yet, so a
|
|
272
272
|
* lost decision only re-presents the request.
|
|
273
|
+
* W21: an optional reason rides a DENIAL (the panel's feedback — the
|
|
274
|
+
* tool_result carries `[Permission denied] <the words>`); allow reasons
|
|
275
|
+
* are never persisted (the words ride the next user turn instead).
|
|
273
276
|
*/
|
|
274
|
-
async approve(decisionId, allow) {
|
|
277
|
+
async approve(decisionId, allow, reason) {
|
|
275
278
|
// round 4: a poisoned session may not mutate the log — checked before
|
|
276
279
|
// anything is recorded.
|
|
277
280
|
this.ensureHealthy();
|
|
@@ -306,7 +309,10 @@ export class AgentSession {
|
|
|
306
309
|
// cannot issue while awaiting approve().)
|
|
307
310
|
this.#pendingDurableApprovals.set(decisionId, allow);
|
|
308
311
|
this.#pendingResolvers.delete(decisionId);
|
|
309
|
-
|
|
312
|
+
// W21: the panel's feedback rides the denial — the tool_result
|
|
313
|
+
// carries `[Permission denied] <the words>` (the rejection
|
|
314
|
+
// asymmetry: words keep the run alive).
|
|
315
|
+
resolver(allow ? { action: "allow" } : { action: "deny", reason: reason ?? "denied by user" });
|
|
310
316
|
return;
|
|
311
317
|
}
|
|
312
318
|
const runId = request?.runId ?? "approval";
|
|
@@ -315,7 +321,7 @@ export class AgentSession {
|
|
|
315
321
|
decisionId,
|
|
316
322
|
...(request !== undefined ? { callId: request.event.callId } : {}),
|
|
317
323
|
decision: allow ? "approved" : "denied",
|
|
318
|
-
...(allow ? {} : { reason: "denied by user" }),
|
|
324
|
+
...(allow ? {} : { reason: reason ?? "denied by user" }),
|
|
319
325
|
});
|
|
320
326
|
await this.persist(runId, decided);
|
|
321
327
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 0.1.40 (R-C item 3) — the truncation guard: a runtime adapter wrapper.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The reference implementation's protection: a truncated stream (stopReason
|
|
5
|
+
* max_tokens/length) can
|
|
5
6
|
* yield tool args that parse and validate but are silently incomplete —
|
|
6
7
|
* executing them is the destructive-bug class. The provider adapters already
|
|
7
8
|
* see the stop reason; the RUNTIME vetoes execution of the whole batch:
|
package/dist/truncation-guard.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 0.1.40 (R-C item 3) — the truncation guard: a runtime adapter wrapper.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The reference implementation's protection: a truncated stream (stopReason
|
|
5
|
+
* max_tokens/length) can
|
|
5
6
|
* yield tool args that parse and validate but are silently incomplete —
|
|
6
7
|
* executing them is the destructive-bug class. The provider adapters already
|
|
7
8
|
* see the stop reason; the RUNTIME vetoes execution of the whole batch:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.33",
|
|
4
4
|
"description": "kiso runtime — durable multi-turn agent sessions: AgentDefinition, AgentRuntime, AgentSession, Run, append-only JSONL store.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"test": "vitest run"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@vincemakes/kiso-core": "0.1.
|
|
24
|
+
"@vincemakes/kiso-core": "0.1.32"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@vincemakes/kiso-provider-anthropic": "0.1.
|
|
28
|
-
"@vincemakes/kiso-provider-openai": "0.1.
|
|
27
|
+
"@vincemakes/kiso-provider-anthropic": "0.1.33",
|
|
28
|
+
"@vincemakes/kiso-provider-openai": "0.1.33"
|
|
29
29
|
},
|
|
30
30
|
"peerDependenciesMeta": {
|
|
31
31
|
"@vincemakes/kiso-provider-anthropic": {
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@vincemakes/kiso-evals": "0.1.
|
|
39
|
+
"@vincemakes/kiso-evals": "0.1.33",
|
|
40
40
|
"@types/node": "^26.1.2",
|
|
41
41
|
"typescript": "^5.7.2",
|
|
42
42
|
"vitest": "^3.0.0"
|