@vincemakes/kiso-runtime 0.1.34 → 0.1.35
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ledger.d.ts +45 -0
- package/dist/ledger.js +90 -0
- package/dist/run.js +196 -10
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/ledger.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 — the execution ledger: exactly-once side effects from the event log.
|
|
3
|
+
*
|
|
4
|
+
* Every tool execution writes `tool_execution_started` before the handler
|
|
5
|
+
* and `tool_execution_succeeded` / `tool_execution_failed` after
|
|
6
|
+
* (kernel/loop.ts). From those events alone — no second store — this module
|
|
7
|
+
* answers the recovery questions:
|
|
8
|
+
*
|
|
9
|
+
* 1. What is the durable status of execution X? (`executionLedger`)
|
|
10
|
+
* 2. What is the latest execution of call Y? (`executionForCallId`)
|
|
11
|
+
*
|
|
12
|
+
* IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
|
|
13
|
+
* framework-generated id unique per log (one per started event). The
|
|
14
|
+
* provider's `callId` is correlation only and may repeat; two logical calls
|
|
15
|
+
* with identical (name, input) are two executions.
|
|
16
|
+
*
|
|
17
|
+
* Status derivation:
|
|
18
|
+
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
|
+
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
+
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
+
* the outcome — ruling #12 / ADR-0038;
|
|
22
|
+
* safeToRetry stays on the event for
|
|
23
|
+
* history, it no longer feeds status)
|
|
24
|
+
* resolved "rerun" → "rerun" (human cleared it)
|
|
25
|
+
* resolved "abandoned" → "abandoned" (human killed it)
|
|
26
|
+
*/
|
|
27
|
+
import type { Event } from "@vincemakes/kiso-core";
|
|
28
|
+
export type ExecutionStatus = "uncertain" | "succeeded" | "failed" | "rerun" | "abandoned";
|
|
29
|
+
export interface ExecutionRecord {
|
|
30
|
+
readonly executionId: string;
|
|
31
|
+
readonly callId: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly input: Readonly<Record<string, unknown>>;
|
|
34
|
+
readonly status: ExecutionStatus;
|
|
35
|
+
/** Present when `status` is "succeeded" — the durable result to replay. */
|
|
36
|
+
readonly result?: {
|
|
37
|
+
readonly content: string;
|
|
38
|
+
readonly isError: false;
|
|
39
|
+
};
|
|
40
|
+
readonly error?: string;
|
|
41
|
+
}
|
|
42
|
+
/** executionId → durable status, rebuilt purely from events (ADR-0002). */
|
|
43
|
+
export declare function executionLedger(events: readonly Event[]): Map<string, ExecutionRecord>;
|
|
44
|
+
/** The LATEST execution record for a provider call id (correlation only). */
|
|
45
|
+
export declare function executionForCallId(events: readonly Event[], callId: string): ExecutionRecord | undefined;
|
package/dist/ledger.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2 — the execution ledger: exactly-once side effects from the event log.
|
|
3
|
+
*
|
|
4
|
+
* Every tool execution writes `tool_execution_started` before the handler
|
|
5
|
+
* and `tool_execution_succeeded` / `tool_execution_failed` after
|
|
6
|
+
* (kernel/loop.ts). From those events alone — no second store — this module
|
|
7
|
+
* answers the recovery questions:
|
|
8
|
+
*
|
|
9
|
+
* 1. What is the durable status of execution X? (`executionLedger`)
|
|
10
|
+
* 2. What is the latest execution of call Y? (`executionForCallId`)
|
|
11
|
+
*
|
|
12
|
+
* IDENTITY (Area 3): the ledger is keyed by `executionId` — a persistent,
|
|
13
|
+
* framework-generated id unique per log (one per started event). The
|
|
14
|
+
* provider's `callId` is correlation only and may repeat; two logical calls
|
|
15
|
+
* with identical (name, input) are two executions.
|
|
16
|
+
*
|
|
17
|
+
* Status derivation:
|
|
18
|
+
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
|
+
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
+
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
+
* the outcome — ruling #12 / ADR-0038;
|
|
22
|
+
* safeToRetry stays on the event for
|
|
23
|
+
* history, it no longer feeds status)
|
|
24
|
+
* resolved "rerun" → "rerun" (human cleared it)
|
|
25
|
+
* resolved "abandoned" → "abandoned" (human killed it)
|
|
26
|
+
*/
|
|
27
|
+
/** executionId → durable status, rebuilt purely from events (ADR-0002). */
|
|
28
|
+
export function executionLedger(events) {
|
|
29
|
+
const ledger = new Map();
|
|
30
|
+
for (const ev of events) {
|
|
31
|
+
switch (ev.type) {
|
|
32
|
+
case "tool_execution_started":
|
|
33
|
+
ledger.set(ev.executionId, {
|
|
34
|
+
executionId: ev.executionId,
|
|
35
|
+
callId: ev.callId,
|
|
36
|
+
name: ev.name,
|
|
37
|
+
input: ev.input,
|
|
38
|
+
status: "uncertain",
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
case "tool_execution_succeeded": {
|
|
42
|
+
const prior = ledger.get(ev.executionId);
|
|
43
|
+
if (prior) {
|
|
44
|
+
ledger.set(ev.executionId, { ...prior, status: "succeeded", result: ev.result });
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case "tool_execution_failed": {
|
|
49
|
+
const prior = ledger.get(ev.executionId);
|
|
50
|
+
if (prior) {
|
|
51
|
+
ledger.set(ev.executionId, {
|
|
52
|
+
...prior,
|
|
53
|
+
// ruling #12 (ADR-0038): a complete receipt IS the outcome —
|
|
54
|
+
// failed is "failed", never "uncertain"; uncertainty
|
|
55
|
+
// belongs to the crash window alone (started, no receipt).
|
|
56
|
+
status: "failed",
|
|
57
|
+
...(ev.error !== undefined ? { error: ev.error } : {}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "tool_execution_resolved": {
|
|
63
|
+
const prior = ledger.get(ev.executionId);
|
|
64
|
+
if (prior) {
|
|
65
|
+
ledger.set(ev.executionId, {
|
|
66
|
+
...prior,
|
|
67
|
+
status: ev.resolution === "rerun" ? "rerun" : "abandoned",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
default:
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return ledger;
|
|
77
|
+
}
|
|
78
|
+
/** The LATEST execution record for a provider call id (correlation only). */
|
|
79
|
+
export function executionForCallId(events, callId) {
|
|
80
|
+
const ledger = executionLedger(events);
|
|
81
|
+
let found;
|
|
82
|
+
for (const ev of events) {
|
|
83
|
+
if (ev.type !== "tool_execution_started")
|
|
84
|
+
continue;
|
|
85
|
+
if (ev.callId !== callId)
|
|
86
|
+
continue;
|
|
87
|
+
found = ledger.get(ev.executionId);
|
|
88
|
+
}
|
|
89
|
+
return found;
|
|
90
|
+
}
|
package/dist/run.js
CHANGED
|
@@ -176,7 +176,7 @@ export class Run {
|
|
|
176
176
|
if (ev.seq > baseSeq)
|
|
177
177
|
await this.#session.persist(this.runId, ev);
|
|
178
178
|
};
|
|
179
|
-
for await (const ev of this.#recover(log, signal, lastOpen.events)) {
|
|
179
|
+
for await (const ev of this.#recover(log, signal, lastOpen.events, approvalChain, this.#config.hooks)) {
|
|
180
180
|
await persist(ev);
|
|
181
181
|
yield ev;
|
|
182
182
|
}
|
|
@@ -242,10 +242,185 @@ export class Run {
|
|
|
242
242
|
* re-approved); a denial writes its tool result; a succeeded/failed
|
|
243
243
|
* execution whose tool_result never landed is completed from the
|
|
244
244
|
* receipt. Undecided requests pause and await approve().
|
|
245
|
+
*
|
|
246
|
+
* R-E 0.1.43 (Gap A): a committed turn's tool_call_end with no durable
|
|
247
|
+
* decision and no execution is UNDECIDED — recovery re-enters it into
|
|
248
|
+
* the approval pipeline before anything else (only a durable
|
|
249
|
+
* permission_decided authorizes an effect).
|
|
245
250
|
*/
|
|
246
|
-
async *#recover(log, signal, scope) {
|
|
247
|
-
|
|
251
|
+
async *#recover(log, signal, scope, approvalChain, hooks) {
|
|
252
|
+
// ── Gap B: the tail draft is abandoned — never committed history ──
|
|
253
|
+
// "A model output suffix without a committed stop is an incomplete
|
|
254
|
+
// draft and must never become committed provider history." The
|
|
255
|
+
// resume appends the abandon marker FIRST: it voids the range after
|
|
256
|
+
// the last committed boundary (stop / user_input / terminal /
|
|
257
|
+
// compaction / summarized — or an earlier marker), so the projection
|
|
258
|
+
// excludes the draft and a call inside it is never executed (the
|
|
259
|
+
// boundary clause: the two Gaps divide at the stop). The audit bytes
|
|
260
|
+
// stay; a marker is kernel-exclusive (the AdapterEvent whitelist).
|
|
261
|
+
// Idempotent: an already-voided draft has a marker as its last
|
|
262
|
+
// boundary — the detection finds no output after it, and the
|
|
263
|
+
// recovered events (decided/started/result) are no draft.
|
|
264
|
+
const boundary = [...scope].reverse().find((e) => e.type === "stop" ||
|
|
265
|
+
e.type === "user_input" ||
|
|
266
|
+
e.type === "terminal" ||
|
|
267
|
+
e.type === "microcompacted" ||
|
|
268
|
+
e.type === "compacted" ||
|
|
269
|
+
e.type === "summarized" ||
|
|
270
|
+
e.type === "model_output_abandoned");
|
|
271
|
+
if (boundary !== undefined) {
|
|
272
|
+
const draft = scope.some((e) => (e.type === "text_delta" || e.type === "thinking") && e.seq > boundary.seq);
|
|
273
|
+
if (draft) {
|
|
274
|
+
yield log.append({
|
|
275
|
+
type: "model_output_abandoned",
|
|
276
|
+
voidFromSeq: boundary.seq,
|
|
277
|
+
reason: "a model output suffix without a committed stop — abandoned on resume",
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// The invocation's framework identity (R-E 0.1.43): the
|
|
282
|
+
// tool_call_end's seq — carried by new logs, derived by
|
|
283
|
+
// callId+proximity for old ones (the last such call before the seq).
|
|
284
|
+
const callSeqOf = (callId, before) => {
|
|
285
|
+
let seq;
|
|
286
|
+
for (const e of scope) {
|
|
287
|
+
if (e.type === "tool_call_end" && e.callId === callId && e.seq < before)
|
|
288
|
+
seq = e.seq;
|
|
289
|
+
}
|
|
290
|
+
return seq;
|
|
291
|
+
};
|
|
292
|
+
// ── Gap A: a committed turn's UNDECIDED invocation ────────────────
|
|
293
|
+
// A durable stop with a bare tool_call_end (no decision, no
|
|
294
|
+
// execution) re-enters the approval pipeline: the composed chain
|
|
295
|
+
// decides — allow → durable permission_decided (decidedBy
|
|
296
|
+
// faithfully) + the persisted execution; deny → decided + the
|
|
297
|
+
// denial result; ask/all-abstain → permission_requested (the
|
|
298
|
+
// requests pass below announces it and waits for the human). No
|
|
299
|
+
// guessing, no inheriting, no retro-authorization — re-decide.
|
|
300
|
+
// A durable POLICY verdict (E1: decidedBy set) newer than the call
|
|
301
|
+
// binds it — the chain never re-runs for a decided invocation.
|
|
302
|
+
const gapAsks = [];
|
|
303
|
+
for (const call of scope) {
|
|
304
|
+
if (call.type !== "tool_call_end")
|
|
305
|
+
continue;
|
|
306
|
+
// The boundary clause (the directive): a call whose turn has no
|
|
307
|
+
// legal stop is a DRAFT's call — Gap B voids it (never executed,
|
|
308
|
+
// never in the provider projection); this stage never touches it.
|
|
309
|
+
// The two Gaps divide at the stop; no mixing.
|
|
310
|
+
const turnEnd = scope.find((e) => e.type === "user_input" && e.seq > call.seq)?.seq ?? Number.POSITIVE_INFINITY;
|
|
311
|
+
const turnStop = scope.some((e) => e.type === "stop" && e.seq > call.seq && e.seq < turnEnd);
|
|
312
|
+
if (!turnStop)
|
|
313
|
+
continue;
|
|
314
|
+
// The requests pass below owns request-tracked invocations (it
|
|
315
|
+
// binds the stored request by decisionId, or pauses for the
|
|
316
|
+
// human) — Gap A must never re-decide over a stored
|
|
317
|
+
// permission_requested. "Only a durable permission_decided
|
|
318
|
+
// authorizes an effect": a pending request is not a decision.
|
|
319
|
+
const hasRequest = log.all.some((e) => e.type === "permission_requested" && e.callId === call.callId && e.seq > call.seq);
|
|
320
|
+
if (hasRequest)
|
|
321
|
+
continue;
|
|
322
|
+
const decided = log.all.find((e) => e.type === "permission_decided" && e.callId === call.callId && e.seq > call.seq && e.decidedBy !== undefined);
|
|
323
|
+
const hasExecution = log.all.some((e) => e.type === "tool_execution_started" && e.callId === call.callId && e.seq > call.seq);
|
|
324
|
+
const hasResult = log.all.some((e) => e.type === "tool_result" && e.callId === call.callId && e.seq > call.seq);
|
|
325
|
+
if (hasResult)
|
|
326
|
+
continue; // closed — nothing to fill
|
|
327
|
+
if (decided !== undefined) {
|
|
328
|
+
// E1: the durable verdict speaks for the call — apply it
|
|
329
|
+
// without re-running the chain.
|
|
330
|
+
if (signal.aborted)
|
|
331
|
+
return;
|
|
332
|
+
if (decided.decision === "approved" && !hasExecution) {
|
|
333
|
+
yield* this.#executePersisted(call.callId, call.name, call.input ?? {}, call.seq, signal);
|
|
334
|
+
}
|
|
335
|
+
else if (!hasResult) {
|
|
336
|
+
yield* this.#denialResult(call.callId, decided.reason ?? "denied by user", call.seq);
|
|
337
|
+
}
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
// UNDECIDED — re-enter the approval pipeline with the LIVE
|
|
341
|
+
// decision order (loop.ts decideCall): the composed chain
|
|
342
|
+
// first; only when no chain exists do the hooks' onPreTool
|
|
343
|
+
// speak (defer → ask, deny → deny, allow → allow); no policies
|
|
344
|
+
// at all → the kernel's default allow. Same semantics as the
|
|
345
|
+
// live path — a defer policy must not collapse into an
|
|
346
|
+
// auto-allow on resume. A throwing chain counts as ask: it
|
|
347
|
+
// speaks, never silently (the live parity).
|
|
348
|
+
const payload = { callId: call.callId, name: call.name, input: call.input ?? {} };
|
|
349
|
+
// The chain's PolicyCall carries name+input only — callId is the
|
|
350
|
+
// framework's, the hook's is the provider-facing ToolCallPayload.
|
|
351
|
+
const policyCall = { name: payload.name, input: payload.input };
|
|
352
|
+
let verdict;
|
|
353
|
+
try {
|
|
354
|
+
if (approvalChain !== undefined) {
|
|
355
|
+
const chainVerdict = await abortable(Promise.resolve(approvalChain.decide(policyCall, { signal, sessionId: this.#session.id })), signal);
|
|
356
|
+
if (chainVerdict === ABORTED)
|
|
357
|
+
return;
|
|
358
|
+
verdict = chainVerdict;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
catch {
|
|
362
|
+
verdict = { action: "ask" };
|
|
363
|
+
}
|
|
364
|
+
if (verdict === undefined && hooks?.onPreTool !== undefined) {
|
|
365
|
+
const decision = await abortable(Promise.resolve(hooks.onPreTool(payload, { sessionId: this.#session.id })), signal);
|
|
366
|
+
if (decision === ABORTED)
|
|
367
|
+
return;
|
|
368
|
+
if (decision.action === "defer")
|
|
369
|
+
verdict = { action: "ask" };
|
|
370
|
+
else if (decision.action !== "allow")
|
|
371
|
+
verdict = { action: "deny", reason: decision.reason ?? "denied" };
|
|
372
|
+
else
|
|
373
|
+
verdict = { action: "allow" };
|
|
374
|
+
}
|
|
375
|
+
if (verdict === undefined)
|
|
376
|
+
verdict = { action: "allow" }; // no policies — the kernel's default allow
|
|
377
|
+
const decisionId = `d-${log.all.length + 1}`;
|
|
378
|
+
if (verdict.action === "allow") {
|
|
379
|
+
yield log.append({
|
|
380
|
+
type: "permission_decided",
|
|
381
|
+
decisionId,
|
|
382
|
+
callId: call.callId,
|
|
383
|
+
invocationSeq: call.seq,
|
|
384
|
+
decision: "approved",
|
|
385
|
+
...("decidedBy" in verdict ? { decidedBy: verdict.decidedBy } : {}),
|
|
386
|
+
});
|
|
387
|
+
if (signal.aborted)
|
|
388
|
+
return;
|
|
389
|
+
yield* this.#executePersisted(call.callId, call.name, call.input ?? {}, call.seq, signal);
|
|
390
|
+
}
|
|
391
|
+
else if (verdict.action === "deny") {
|
|
392
|
+
yield log.append({
|
|
393
|
+
type: "permission_decided",
|
|
394
|
+
decisionId,
|
|
395
|
+
callId: call.callId,
|
|
396
|
+
invocationSeq: call.seq,
|
|
397
|
+
decision: "denied",
|
|
398
|
+
...("reason" in verdict && verdict.reason !== undefined ? { reason: verdict.reason } : {}),
|
|
399
|
+
...("decidedBy" in verdict ? { decidedBy: verdict.decidedBy } : {}),
|
|
400
|
+
});
|
|
401
|
+
yield* this.#denialResult(call.callId, ("reason" in verdict && verdict.reason) || "denied", call.seq);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
// ask / all-abstain — the requests pass below announces the
|
|
405
|
+
// stored request and waits for the human.
|
|
406
|
+
const appended = log.append({
|
|
407
|
+
type: "permission_requested",
|
|
408
|
+
decisionId,
|
|
409
|
+
callId: call.callId,
|
|
410
|
+
invocationSeq: call.seq,
|
|
411
|
+
name: call.name,
|
|
412
|
+
input: call.input ?? {},
|
|
413
|
+
});
|
|
414
|
+
gapAsks.push(appended);
|
|
415
|
+
yield appended;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const requests = [...scope.filter((e) => e.type === "permission_requested"), ...gapAsks];
|
|
248
419
|
for (const pending of requests) {
|
|
420
|
+
// R-E 0.1.43: the writes below carry the invocation's framework
|
|
421
|
+
// identity — the request's own, or the callId+proximity
|
|
422
|
+
// fallback for old logs (the compat contract).
|
|
423
|
+
const invocationSeq = pending.invocationSeq ?? callSeqOf(pending.callId, pending.seq);
|
|
249
424
|
const decided = log.all.find((e) => e.type === "permission_decided" && e.decisionId === pending.decisionId);
|
|
250
425
|
// round 4: paired by events NEWER than the request — a historical
|
|
251
426
|
// same-callId execution from an earlier run must not count as THIS
|
|
@@ -303,10 +478,10 @@ export class Run {
|
|
|
303
478
|
});
|
|
304
479
|
if (final.action === "allow") {
|
|
305
480
|
if (!hasExecution)
|
|
306
|
-
yield* this.#executePersisted(pending.callId, pending.name, pending.input, signal);
|
|
481
|
+
yield* this.#executePersisted(pending.callId, pending.name, pending.input, invocationSeq, signal);
|
|
307
482
|
}
|
|
308
483
|
else if (!hasResult) {
|
|
309
|
-
yield* this.#denialResult(pending.callId, final.reason ?? "denied by user");
|
|
484
|
+
yield* this.#denialResult(pending.callId, final.reason ?? "denied by user", invocationSeq);
|
|
310
485
|
}
|
|
311
486
|
}
|
|
312
487
|
else if (decided.decision === "approved") {
|
|
@@ -316,10 +491,10 @@ export class Run {
|
|
|
316
491
|
if (signal.aborted)
|
|
317
492
|
return;
|
|
318
493
|
if (!hasExecution)
|
|
319
|
-
yield* this.#executePersisted(pending.callId, pending.name, pending.input, signal);
|
|
494
|
+
yield* this.#executePersisted(pending.callId, pending.name, pending.input, invocationSeq, signal);
|
|
320
495
|
}
|
|
321
496
|
else if (!hasResult) {
|
|
322
|
-
yield* this.#denialResult(pending.callId, decided.reason ?? "denied by user");
|
|
497
|
+
yield* this.#denialResult(pending.callId, decided.reason ?? "denied by user", invocationSeq);
|
|
323
498
|
}
|
|
324
499
|
}
|
|
325
500
|
// Receipt repair: an execution that reached a terminal state but
|
|
@@ -385,7 +560,7 @@ export class Run {
|
|
|
385
560
|
* the permission hook (it was decided) and the model (it was never
|
|
386
561
|
* asked to re-issue). Full ledgered lifecycle.
|
|
387
562
|
*/
|
|
388
|
-
async *#executePersisted(callId, name, input, signal) {
|
|
563
|
+
async *#executePersisted(callId, name, input, invocationSeq, signal) {
|
|
389
564
|
const log = this.#session.log;
|
|
390
565
|
const tool = this.#config.registry.get(name);
|
|
391
566
|
const executionId = `ex-${log.lastSeq + 1}`;
|
|
@@ -393,7 +568,14 @@ export class Run {
|
|
|
393
568
|
// not start the side effect (finding 3).
|
|
394
569
|
if (signal.aborted)
|
|
395
570
|
return;
|
|
396
|
-
yield log.append({
|
|
571
|
+
yield log.append({
|
|
572
|
+
type: "tool_execution_started",
|
|
573
|
+
executionId,
|
|
574
|
+
callId,
|
|
575
|
+
...(invocationSeq !== undefined ? { invocationSeq } : {}),
|
|
576
|
+
name,
|
|
577
|
+
input,
|
|
578
|
+
});
|
|
397
579
|
let result;
|
|
398
580
|
if (tool === undefined) {
|
|
399
581
|
result = { content: `Unknown tool: ${name}`, isError: true, errorKind: "invalid_input" };
|
|
@@ -432,6 +614,7 @@ export class Run {
|
|
|
432
614
|
type: "tool_execution_failed",
|
|
433
615
|
executionId,
|
|
434
616
|
callId,
|
|
617
|
+
...(invocationSeq !== undefined ? { invocationSeq } : {}),
|
|
435
618
|
error: result.content,
|
|
436
619
|
// P1-9: errorKind only exists on errors — runtime-guarded too.
|
|
437
620
|
...(result.isError && result.errorKind !== undefined ? { errorKind: result.errorKind } : {}),
|
|
@@ -444,6 +627,7 @@ export class Run {
|
|
|
444
627
|
type: "tool_execution_succeeded",
|
|
445
628
|
executionId,
|
|
446
629
|
callId,
|
|
630
|
+
...(invocationSeq !== undefined ? { invocationSeq } : {}),
|
|
447
631
|
result: { content: result.content, isError: false },
|
|
448
632
|
...(result.tags !== undefined ? { tags: result.tags } : {}),
|
|
449
633
|
});
|
|
@@ -451,6 +635,7 @@ export class Run {
|
|
|
451
635
|
yield log.append({
|
|
452
636
|
type: "tool_result",
|
|
453
637
|
callId,
|
|
638
|
+
...(invocationSeq !== undefined ? { invocationSeq } : {}),
|
|
454
639
|
content: result.content,
|
|
455
640
|
isError: result.isError,
|
|
456
641
|
// P1-9: errorKind only exists on errors — runtime-guarded too.
|
|
@@ -465,11 +650,12 @@ export class Run {
|
|
|
465
650
|
// alone. A retry passes the approval chain again.
|
|
466
651
|
}
|
|
467
652
|
/** The model-facing result of a durable denial — no execution happened. */
|
|
468
|
-
async *#denialResult(callId, reason) {
|
|
653
|
+
async *#denialResult(callId, reason, invocationSeq) {
|
|
469
654
|
const denial = denialResult(reason);
|
|
470
655
|
yield this.#session.log.append({
|
|
471
656
|
type: "tool_result",
|
|
472
657
|
callId,
|
|
658
|
+
...(invocationSeq !== undefined ? { invocationSeq } : {}),
|
|
473
659
|
content: denial.content,
|
|
474
660
|
isError: true,
|
|
475
661
|
errorKind: denial.errorKind,
|
package/dist/session.d.ts
CHANGED
|
@@ -145,7 +145,7 @@ export declare class AgentSession {
|
|
|
145
145
|
*/
|
|
146
146
|
approve(decisionId: string, allow: boolean, reason?: string): Promise<void>;
|
|
147
147
|
/** Executions that started but never reported a result (crash window). */
|
|
148
|
-
uncertainExecutions(): import("
|
|
148
|
+
uncertainExecutions(): import("./ledger.js").ExecutionRecord[];
|
|
149
149
|
/**
|
|
150
150
|
* The human's verdict on an interrupted execution, keyed by EXECUTION ID
|
|
151
151
|
* (B group): "rerun" (the human says the side effect did NOT happen — the
|
package/dist/session.js
CHANGED
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
* support in recovery.ts, the E1/E2 composition helpers in compose.ts —
|
|
28
28
|
* same package, same exports (index.ts re-exports all four).
|
|
29
29
|
*/
|
|
30
|
-
import { EventLog,
|
|
30
|
+
import { EventLog, projectMessages, } from "@vincemakes/kiso-core";
|
|
31
|
+
import { executionLedger } from "./ledger.js";
|
|
31
32
|
import { denialResult } from "@vincemakes/kiso-core";
|
|
32
33
|
import { estimateSummarySavings, KEEP_RECENT_ROUNDS, lastSummaryPoint, summarizeConversation, summaryBoundarySeq, } from "./summarize.js";
|
|
33
34
|
import { estimateTokens } from "@vincemakes/kiso-core";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
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.34"
|
|
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.35",
|
|
28
|
+
"@vincemakes/kiso-provider-openai": "0.1.35"
|
|
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.35",
|
|
40
40
|
"@types/node": "^26.1.2",
|
|
41
41
|
"typescript": "^5.7.2",
|
|
42
42
|
"vitest": "^3.0.0"
|