@vincemakes/kiso-runtime 0.1.31 → 0.1.32
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 +3 -1
- package/dist/compose.js +32 -0
- package/dist/run.js +15 -3
- package/dist/session.d.ts +11 -0
- package/dist/session.js +22 -0
- package/dist/summarize.d.ts +2 -2
- package/dist/summarize.js +2 -2
- package/dist/truncation-guard.d.ts +27 -0
- package/dist/truncation-guard.js +54 -0
- package/package.json +5 -5
package/dist/compose.d.ts
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
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 } from "@vincemakes/kiso-core";
|
|
6
|
+
import type { HookHost, KisoExtension, ToolRegistry } from "@vincemakes/kiso-core";
|
|
7
7
|
import type { SessionConfig } from "./session.js";
|
|
8
|
+
/** The table, or "" when the registry is empty (no vocabulary, no tools). */
|
|
9
|
+
export declare function composeToolTable(registry: ToolRegistry): string;
|
|
8
10
|
/**
|
|
9
11
|
* E2: the session's systemPrompt plus every extension's append, in LOAD
|
|
10
12
|
* order, \n\n-joined — deterministic (same extension list → same prompt).
|
package/dist/compose.js
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* 0.1.40 (R-C item 1) — the tool substitution table: the fixed vocabulary
|
|
8
|
+
* (the CC content in kiso's voice, each line bound to the tool that makes
|
|
9
|
+
* it true) filtered to the ACTIVE tool set + each active tool's ONE-line
|
|
10
|
+
* snippet + its guideline bullets. The full descriptions NEVER enter the
|
|
11
|
+
* system prompt — the provider transmits them in the JSON schema anyway
|
|
12
|
+
* (never pay twice). Deterministic: same registry → same table.
|
|
13
|
+
*/
|
|
14
|
+
const TOOL_RULES = [
|
|
15
|
+
{ tool: "read_file", line: "read files with read_file, never shell cat/head/tail" },
|
|
16
|
+
{ tool: "search_text", line: "search with search_text, never shell grep/rg" },
|
|
17
|
+
{ tool: "list_dir", line: "list with list_dir, never ls" },
|
|
18
|
+
{ tool: "shell", line: "reserve shell for real system commands" },
|
|
19
|
+
];
|
|
20
|
+
/** The table, or "" when the registry is empty (no vocabulary, no tools). */
|
|
21
|
+
export function composeToolTable(registry) {
|
|
22
|
+
const tools = registry.list();
|
|
23
|
+
if (tools.length === 0)
|
|
24
|
+
return "";
|
|
25
|
+
const active = new Set(tools.map((t) => t.name));
|
|
26
|
+
const lines = [
|
|
27
|
+
"Tool use:",
|
|
28
|
+
...TOOL_RULES.filter((r) => active.has(r.tool)).map((r) => `- ${r.line}`),
|
|
29
|
+
// the parallel directive: the window applies to every active turn.
|
|
30
|
+
"- batch independent tool calls into one reply — they run in parallel",
|
|
31
|
+
...tools.flatMap((t) => (t.promptSnippet === undefined ? [] : [`- ${t.promptSnippet}`])),
|
|
32
|
+
];
|
|
33
|
+
const guidelines = tools.flatMap((t) => (t.promptGuidelines ?? []).map((g) => `- ${t.name}: ${g}`));
|
|
34
|
+
if (guidelines.length > 0)
|
|
35
|
+
lines.push("Active tool guidelines:", ...guidelines);
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
6
38
|
/**
|
|
7
39
|
* E2: the session's systemPrompt plus every extension's append, in LOAD
|
|
8
40
|
* order, \n\n-joined — deterministic (same extension list → same prompt).
|
package/dist/run.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { denialResult, loop } from "@vincemakes/kiso-core";
|
|
7
7
|
import { ABORTED, MergedSignal, abortable, openRunId } from "./recovery.js";
|
|
8
|
-
import { composeSystemPrompt, microcompactFor } from "./compose.js";
|
|
8
|
+
import { composeSystemPrompt, composeToolTable, microcompactFor } from "./compose.js";
|
|
9
|
+
import { truncationGuard } from "./truncation-guard.js";
|
|
9
10
|
import { ResumeBlockedError } from "./session.js";
|
|
10
11
|
/**
|
|
11
12
|
* A single turn. Async-iterable, so `for await (const ev of session.run(x))`
|
|
@@ -56,12 +57,23 @@ export class Run {
|
|
|
56
57
|
// E2: the session's own microcompact wins; otherwise the FIRST
|
|
57
58
|
// extension providing a compaction config supplies it.
|
|
58
59
|
const microcompact = microcompactFor(this.#config);
|
|
60
|
+
// 0.1.40 (R-C item 1): the tool substitution table — the ACTIVE tool
|
|
61
|
+
// set's vocabulary, snippets, and guidelines — sits BETWEEN the
|
|
62
|
+
// session's base prompt and the extension appends: generated
|
|
63
|
+
// machinery never outranks the deliberate extension text (the E2
|
|
64
|
+
// "append lands at the END" contract holds). "" when empty.
|
|
65
|
+
const toolTable = composeToolTable(this.#config.registry);
|
|
66
|
+
const basePrompt = toolTable === "" ? this.#config.systemPrompt
|
|
67
|
+
: this.#config.systemPrompt === undefined ? toolTable
|
|
68
|
+
: `${this.#config.systemPrompt}\n\n${toolTable}`;
|
|
59
69
|
// E2: the session's own systemPrompt first, then every extension
|
|
60
70
|
// append in LOAD order — deterministic (same extensions → same
|
|
61
71
|
// prompt); no appends → byte-identical to the extension-less run.
|
|
62
|
-
const systemPrompt = composeSystemPrompt(
|
|
72
|
+
const systemPrompt = composeSystemPrompt(basePrompt, this.#config.extensions ?? []);
|
|
63
73
|
const loopConfig = () => ({
|
|
64
|
-
|
|
74
|
+
// 0.1.40 (R-C item 3): the truncation guard gates the model
|
|
75
|
+
// stream — a truncated turn's tool batch never executes.
|
|
76
|
+
adapter: truncationGuard(this.#adapter),
|
|
65
77
|
model: this.#config.model,
|
|
66
78
|
sessionId: this.#session.id, // P3: tools see their session (ToolContext.sessionId)
|
|
67
79
|
...(systemPrompt !== undefined ? { systemPrompt } : {}),
|
package/dist/session.d.ts
CHANGED
|
@@ -61,6 +61,16 @@ export interface SummarizeResult {
|
|
|
61
61
|
/** The estimated tokens the compression saved (chars/4 proxy). */
|
|
62
62
|
readonly savedTokens: number;
|
|
63
63
|
}
|
|
64
|
+
/** W18: the knowable pre-call data, surfaced through onStart — everything
|
|
65
|
+
* the indicator's indeterminate row shows (rounds, the token estimate)
|
|
66
|
+
* is computed locally BEFORE the one adapter call; no fraction exists. */
|
|
67
|
+
export interface CompactInfo {
|
|
68
|
+
readonly coversToSeq: number;
|
|
69
|
+
/** The covered user rounds — the inputs in (previous summary point, boundary]. */
|
|
70
|
+
readonly rounds: number;
|
|
71
|
+
/** The covered content's estimated tokens (the chars/4 proxy). */
|
|
72
|
+
readonly tokens: number;
|
|
73
|
+
}
|
|
64
74
|
export declare class AgentSession {
|
|
65
75
|
#private;
|
|
66
76
|
readonly id: string;
|
|
@@ -112,6 +122,7 @@ export declare class AgentSession {
|
|
|
112
122
|
summarize(options?: {
|
|
113
123
|
keepRounds?: number;
|
|
114
124
|
signal?: AbortSignalLike;
|
|
125
|
+
onStart?: (info: CompactInfo) => void;
|
|
115
126
|
}): Promise<SummarizeResult | null>;
|
|
116
127
|
/**
|
|
117
128
|
* Pauses that still await a human decision (durable, survives restart).
|
package/dist/session.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import { EventLog, executionLedger, projectMessages, } from "@vincemakes/kiso-core";
|
|
31
31
|
import { denialResult } from "@vincemakes/kiso-core";
|
|
32
32
|
import { estimateSummarySavings, KEEP_RECENT_ROUNDS, lastSummaryPoint, summarizeConversation, summaryBoundarySeq, } from "./summarize.js";
|
|
33
|
+
import { estimateTokens } from "@vincemakes/kiso-core";
|
|
33
34
|
import { StaleWriterError } from "./store.js";
|
|
34
35
|
import { composeHooks } from "./compose.js";
|
|
35
36
|
import { Run } from "./run.js";
|
|
@@ -181,18 +182,39 @@ export class AgentSession {
|
|
|
181
182
|
async summarize(options = {}) {
|
|
182
183
|
this.ensureHealthy();
|
|
183
184
|
const keepRounds = options.keepRounds ?? KEEP_RECENT_ROUNDS;
|
|
185
|
+
// W18: the signal is observed at EVERY phase boundary — the cancel
|
|
186
|
+
// affordance works for the whole call (local work included), never
|
|
187
|
+
// just the adapter's wait. The abort error is the honest "nothing
|
|
188
|
+
// happened" outcome (ADR-0044 crash semantics).
|
|
189
|
+
const cancelled = () => new Error("the compaction was cancelled");
|
|
190
|
+
if (options.signal !== undefined && options.signal.aborted)
|
|
191
|
+
throw cancelled();
|
|
184
192
|
const events = this.log.all;
|
|
185
193
|
const boundary = summaryBoundarySeq(events, keepRounds);
|
|
186
194
|
if (boundary === undefined)
|
|
187
195
|
return null;
|
|
188
196
|
const prevPoint = lastSummaryPoint(events);
|
|
189
197
|
const covered = projectMessages(events.filter((e) => e.seq > prevPoint && e.seq <= boundary && e.type !== "summarized"));
|
|
198
|
+
// W18: the indicator's pre-call data — rounds + the token estimate
|
|
199
|
+
// are knowable BEFORE the adapter call; the summary itself is ONE
|
|
200
|
+
// call with no fraction (kiso never invents a percentage here).
|
|
201
|
+
if (options.signal !== undefined && options.signal.aborted)
|
|
202
|
+
throw cancelled();
|
|
203
|
+
options.onStart?.({
|
|
204
|
+
coversToSeq: boundary,
|
|
205
|
+
rounds: events.filter((e) => e.type === "user_input" && e.seq > prevPoint && e.seq <= boundary).length,
|
|
206
|
+
tokens: estimateTokens(covered),
|
|
207
|
+
});
|
|
190
208
|
const summary = await summarizeConversation({
|
|
191
209
|
adapter: this.#adapter,
|
|
192
210
|
model: this.#config.model,
|
|
193
211
|
messages: covered,
|
|
194
212
|
...(options.signal !== undefined ? { signal: options.signal } : {}),
|
|
195
213
|
});
|
|
214
|
+
// The post-call boundary check: an abort that landed while the
|
|
215
|
+
// adapter returned must NOT persist — "nothing happened".
|
|
216
|
+
if (options.signal !== undefined && options.signal.aborted)
|
|
217
|
+
throw cancelled();
|
|
196
218
|
const full = this.log.append({ type: "summarized", coversToSeq: boundary, summary });
|
|
197
219
|
// The record rides the LAST recorded run's id — a summarized fact
|
|
198
220
|
// must never open a run of its own: the open-run gate keys on
|
package/dist/summarize.d.ts
CHANGED
|
@@ -51,8 +51,8 @@ export declare function lastSummaryPoint(events: readonly Event[]): number;
|
|
|
51
51
|
* a message. Returns undefined when fewer than keepRounds+1 uncovered
|
|
52
52
|
* rounds exist (nothing worth covering yet).
|
|
53
53
|
*
|
|
54
|
-
* ⑥ (
|
|
55
|
-
* memory (the
|
|
54
|
+
* ⑥ (task round): a tool result tagged do-not-compact is DURABLE work
|
|
55
|
+
* memory (the task_set echo) — the summary must never cover its round,
|
|
56
56
|
* or the model loses the current list. When the base boundary would
|
|
57
57
|
* cover such a result, the boundary pulls back to just before the round
|
|
58
58
|
* containing the LATEST one (still a turn boundary). A protected round
|
package/dist/summarize.js
CHANGED
|
@@ -85,8 +85,8 @@ export function lastSummaryPoint(events) {
|
|
|
85
85
|
* a message. Returns undefined when fewer than keepRounds+1 uncovered
|
|
86
86
|
* rounds exist (nothing worth covering yet).
|
|
87
87
|
*
|
|
88
|
-
* ⑥ (
|
|
89
|
-
* memory (the
|
|
88
|
+
* ⑥ (task round): a tool result tagged do-not-compact is DURABLE work
|
|
89
|
+
* memory (the task_set echo) — the summary must never cover its round,
|
|
90
90
|
* or the model loses the current list. When the base boundary would
|
|
91
91
|
* cover such a result, the boundary pulls back to just before the round
|
|
92
92
|
* containing the LATEST one (still a turn boundary). A protected round
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.1.40 (R-C item 3) — the truncation guard: a runtime adapter wrapper.
|
|
3
|
+
*
|
|
4
|
+
* The pi protection: a truncated stream (stopReason max_tokens/length) can
|
|
5
|
+
* yield tool args that parse and validate but are silently incomplete —
|
|
6
|
+
* executing them is the destructive-bug class. The provider adapters already
|
|
7
|
+
* see the stop reason; the RUNTIME vetoes execution of the whole batch:
|
|
8
|
+
*
|
|
9
|
+
* - tool_call_end events are held per turn (the deltas pass through live —
|
|
10
|
+
* the UI still shows the calls building, only the COMPLETION is gated);
|
|
11
|
+
* - a compatible stop flushes the held calls in order, then the stop — the
|
|
12
|
+
* kernel launches them exactly as before (the parallel window survives;
|
|
13
|
+
* only the 0.1.26 mid-stream launch timing is gone — the cost of the
|
|
14
|
+
* guarantee: the turn's truncated intent is never half-executed);
|
|
15
|
+
* - a truncation stop flushes the held calls with input: null — the
|
|
16
|
+
* kernel's EXISTING invalid-input denial fails the whole batch without
|
|
17
|
+
* executing anything (the same honest null the adapters already emit
|
|
18
|
+
* for unparseable partials — zero new protocol surface), and the turn
|
|
19
|
+
* still ends with the max_tokens terminal (the loop's voided settle).
|
|
20
|
+
*
|
|
21
|
+
* The kernel machinery (the streaming launch, the window, the voided
|
|
22
|
+
* settle) is untouched — the gate lives at the adapter boundary, where the
|
|
23
|
+
* stop reason is already known.
|
|
24
|
+
*/
|
|
25
|
+
import type { Adapter } from "@vincemakes/kiso-core";
|
|
26
|
+
/** Wrap the adapter so a truncated turn's tool batch can never execute. */
|
|
27
|
+
export declare function truncationGuard(adapter: Adapter): Adapter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.1.40 (R-C item 3) — the truncation guard: a runtime adapter wrapper.
|
|
3
|
+
*
|
|
4
|
+
* The pi protection: a truncated stream (stopReason max_tokens/length) can
|
|
5
|
+
* yield tool args that parse and validate but are silently incomplete —
|
|
6
|
+
* executing them is the destructive-bug class. The provider adapters already
|
|
7
|
+
* see the stop reason; the RUNTIME vetoes execution of the whole batch:
|
|
8
|
+
*
|
|
9
|
+
* - tool_call_end events are held per turn (the deltas pass through live —
|
|
10
|
+
* the UI still shows the calls building, only the COMPLETION is gated);
|
|
11
|
+
* - a compatible stop flushes the held calls in order, then the stop — the
|
|
12
|
+
* kernel launches them exactly as before (the parallel window survives;
|
|
13
|
+
* only the 0.1.26 mid-stream launch timing is gone — the cost of the
|
|
14
|
+
* guarantee: the turn's truncated intent is never half-executed);
|
|
15
|
+
* - a truncation stop flushes the held calls with input: null — the
|
|
16
|
+
* kernel's EXISTING invalid-input denial fails the whole batch without
|
|
17
|
+
* executing anything (the same honest null the adapters already emit
|
|
18
|
+
* for unparseable partials — zero new protocol surface), and the turn
|
|
19
|
+
* still ends with the max_tokens terminal (the loop's voided settle).
|
|
20
|
+
*
|
|
21
|
+
* The kernel machinery (the streaming launch, the window, the voided
|
|
22
|
+
* settle) is untouched — the gate lives at the adapter boundary, where the
|
|
23
|
+
* stop reason is already known.
|
|
24
|
+
*/
|
|
25
|
+
/** Wrap the adapter so a truncated turn's tool batch can never execute. */
|
|
26
|
+
export function truncationGuard(adapter) {
|
|
27
|
+
return {
|
|
28
|
+
stream(options) {
|
|
29
|
+
return guardStream(adapter.stream(options));
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async function* guardStream(stream) {
|
|
34
|
+
const held = [];
|
|
35
|
+
for await (const ev of stream) {
|
|
36
|
+
if (ev.type === "tool_call_end") {
|
|
37
|
+
held.push(ev);
|
|
38
|
+
continue; // held — emitted at the stop, complete or nulled
|
|
39
|
+
}
|
|
40
|
+
if (ev.type === "stop") {
|
|
41
|
+
const truncated = ev.reason === "max_tokens";
|
|
42
|
+
for (const call of held) {
|
|
43
|
+
// truncation: null input — the kernel's denial path fails the
|
|
44
|
+
// call without executing it; otherwise: untouched, in order.
|
|
45
|
+
yield truncated ? { ...call, input: null } : call;
|
|
46
|
+
}
|
|
47
|
+
held.length = 0;
|
|
48
|
+
}
|
|
49
|
+
yield ev;
|
|
50
|
+
}
|
|
51
|
+
// A stream that ends WITHOUT a stop drops the held ends — the kernel
|
|
52
|
+
// already voids the malformed turn (invalid_request); the dangling
|
|
53
|
+
// deltas are the pre-existing malformed-stream shape.
|
|
54
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.32",
|
|
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.31"
|
|
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.32",
|
|
28
|
+
"@vincemakes/kiso-provider-openai": "0.1.32"
|
|
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.32",
|
|
40
40
|
"@types/node": "^26.1.2",
|
|
41
41
|
"typescript": "^5.7.2",
|
|
42
42
|
"vitest": "^3.0.0"
|