@vincemakes/kiso-core 0.1.33 → 0.1.34

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 CHANGED
@@ -9,5 +9,4 @@ export * from "./kernel/permission.js";
9
9
  export * from "./kernel/loop.js";
10
10
  export * from "./kernel/compaction.js";
11
11
  export * from "./kernel/project.js";
12
- export * from "./kernel/ledger.js";
13
12
  export * from "./governance/delivery.js";
package/dist/index.js CHANGED
@@ -9,5 +9,4 @@ export * from "./kernel/permission.js";
9
9
  export * from "./kernel/loop.js";
10
10
  export * from "./kernel/compaction.js";
11
11
  export * from "./kernel/project.js";
12
- export * from "./kernel/ledger.js";
13
12
  export * from "./governance/delivery.js";
@@ -374,14 +374,17 @@ export async function* loop(config) {
374
374
  lastStop = ev.reason;
375
375
  stopCount += 1;
376
376
  }
377
- if (ev.type === "tool_call_end") {
378
- pending.push(ev);
377
+ // R-E 0.1.43: the append precedes the launch — the call's
378
+ // framework seq is assigned here; invocationSeq must never
379
+ // be the adapter's stream-local hint (ADR-0002).
380
+ const full = log.append(ev);
381
+ if (full.type === "tool_call_end") {
382
+ pending.push(full);
379
383
  // streaming execution: the call launches immediately — the decide
380
384
  // and the ledgered run proceed in parallel with the
381
385
  // model stream.
382
- launch(ev);
386
+ launch(full);
383
387
  }
384
- const full = log.append(ev);
385
388
  if (hooks.onEvent)
386
389
  await hooks.onEvent(full, {}).catch(() => { });
387
390
  yield full;
@@ -578,11 +581,13 @@ function terminalForStop(reason) {
578
581
  }
579
582
  }
580
583
  /** The tool_result event for a call — the shared shape (executionId rides
581
- * it as the durable correlation, round 5). */
584
+ * it as the durable correlation, round 5; invocationSeq = the framework
585
+ * identity, R-E 0.1.43). */
582
586
  function resultEvent(call, result, executionId) {
583
587
  return {
584
588
  type: "tool_result",
585
589
  callId: call.callId,
590
+ invocationSeq: call.seq,
586
591
  content: result.content,
587
592
  isError: result.isError,
588
593
  // P1-9: errorKind only exists on errors (the type now enforces it;
@@ -665,6 +670,7 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
665
670
  type: "permission_decided",
666
671
  decisionId: nextDecisionId(),
667
672
  callId: call.callId,
673
+ invocationSeq: call.seq,
668
674
  decision: chainVerdict.action === "allow" ? "approved" : "denied",
669
675
  ...(chainVerdict.action === "deny" && chainVerdict.reason !== undefined
670
676
  ? { reason: chainVerdict.reason }
@@ -733,6 +739,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
733
739
  type: "permission_requested",
734
740
  decisionId,
735
741
  callId: call.callId,
742
+ invocationSeq: call.seq,
736
743
  name: call.name,
737
744
  input: call.input ?? {},
738
745
  // W21: the ask verdict's speaker — the panel's why-asked line (a
@@ -759,6 +766,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
759
766
  type: "permission_decided",
760
767
  decisionId,
761
768
  callId: call.callId,
769
+ invocationSeq: call.seq,
762
770
  decision: verdict ? "approved" : "denied",
763
771
  ...(verdict ? {} : { reason: "denied by user" }),
764
772
  });
@@ -774,6 +782,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
774
782
  type: "permission_decided",
775
783
  decisionId,
776
784
  callId: call.callId, // binds the decision to the invocation (B group)
785
+ invocationSeq: call.seq,
777
786
  decision: finalDecision.action === "allow" ? "approved" : "denied",
778
787
  ...(finalDecision.action === "deny" && finalDecision.reason !== undefined
779
788
  ? { reason: finalDecision.reason }
@@ -806,6 +815,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
806
815
  push({
807
816
  type: "tool_execution_started",
808
817
  callId: call.callId,
818
+ invocationSeq: call.seq,
809
819
  name: call.name,
810
820
  input: call.input, // non-null: decideCall denied a null input before this ran
811
821
  }, (id) => res(id ?? ""));
@@ -851,6 +861,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
851
861
  type: "tool_execution_failed",
852
862
  executionId,
853
863
  callId: call.callId,
864
+ invocationSeq: call.seq,
854
865
  error: result.content,
855
866
  // P1-9: errorKind only exists on errors (the type now enforces it;
856
867
  // the runtime guard keeps a JS tool's illegal combination out of
@@ -865,6 +876,7 @@ async function runLedgered(call, registry, hooks, ctx, signal, push) {
865
876
  type: "tool_execution_succeeded",
866
877
  executionId,
867
878
  callId: call.callId,
879
+ invocationSeq: call.seq,
868
880
  result: { content: result.content, isError: false },
869
881
  ...(result.tags !== undefined ? { tags: result.tags } : {}),
870
882
  });
@@ -146,7 +146,17 @@ export function projectMessages(events) {
146
146
  }
147
147
  }
148
148
  }
149
- const isCovered = (seq) => summaryRanges.some((r) => seq > r.from && seq <= r.to);
149
+ // R-E 0.1.43 (Gap B): a model_output_abandoned marker voids the range
150
+ // (voidFromSeq, its seq] — the abandoned draft's events. The ranges are
151
+ // disjoint and in seq order; the skip below treats them exactly like the
152
+ // summary ranges (the marker itself renders nothing and skips itself).
153
+ const voidRanges = [];
154
+ for (const ev of events) {
155
+ if (ev.type === "model_output_abandoned")
156
+ voidRanges.push({ from: ev.voidFromSeq, to: ev.seq ?? -1 });
157
+ }
158
+ const isCovered = (seq) => summaryRanges.some((r) => seq > r.from && seq <= r.to) ||
159
+ voidRanges.some((r) => seq > r.from && seq <= r.to);
150
160
  // Summaries render in range order as the pass crosses their boundaries.
151
161
  let renderedSummaries = 0;
152
162
  // 0.1.26 (ADR-0024 Amd, parallel execution): the tool results of ONE turn
@@ -459,6 +469,10 @@ export function projectMessages(events) {
459
469
  case "permission_decided":
460
470
  case "permission_expired":
461
471
  case "uncertain_pending":
472
+ case "model_output_abandoned":
473
+ // R-E 0.1.43: renders nothing — the marker is always skipped by
474
+ // its own range; the case documents the intent (and no flush:
475
+ // it must never split a message).
462
476
  break;
463
477
  case "microcompacted":
464
478
  // handled above (the replacement pass) — no open message.
@@ -134,6 +134,7 @@ export interface ToolResultEvent {
134
134
  readonly seq: number;
135
135
  readonly type: "tool_result";
136
136
  readonly callId: string;
137
+ readonly invocationSeq?: number;
137
138
  /** Full content — blocks preserved losslessly (D group). */
138
139
  readonly content: string | readonly import("./messages.js").ContentBlock[];
139
140
  readonly isError: boolean;
@@ -199,6 +200,7 @@ export interface ToolExecutionStarted {
199
200
  readonly type: "tool_execution_started";
200
201
  readonly executionId: string;
201
202
  readonly callId: string;
203
+ readonly invocationSeq?: number;
202
204
  readonly name: string;
203
205
  readonly input: Readonly<Record<string, unknown>>;
204
206
  }
@@ -208,6 +210,7 @@ export interface ToolExecutionSucceeded {
208
210
  readonly type: "tool_execution_succeeded";
209
211
  readonly executionId: string;
210
212
  readonly callId: string;
213
+ readonly invocationSeq?: number;
211
214
  readonly result: {
212
215
  readonly content: string;
213
216
  readonly isError: false;
@@ -227,6 +230,7 @@ export interface ToolExecutionFailed {
227
230
  readonly type: "tool_execution_failed";
228
231
  readonly executionId: string;
229
232
  readonly callId: string;
233
+ readonly invocationSeq?: number;
230
234
  readonly error: string;
231
235
  readonly errorKind?: ToolErrorKind;
232
236
  readonly safeToRetry: boolean;
@@ -243,6 +247,7 @@ export interface ToolExecutionResolved {
243
247
  readonly type: "tool_execution_resolved";
244
248
  readonly executionId: string;
245
249
  readonly callId: string;
250
+ readonly invocationSeq?: number;
246
251
  readonly resolution: "rerun" | "abandoned";
247
252
  }
248
253
  /**
@@ -256,6 +261,7 @@ export interface PermissionRequested {
256
261
  readonly type: "permission_requested";
257
262
  readonly decisionId: string;
258
263
  readonly callId: string;
264
+ readonly invocationSeq?: number;
259
265
  readonly name: string;
260
266
  readonly input: Readonly<Record<string, unknown>>;
261
267
  /** W21: the first non-abstain extension's name — the panel's why-asked
@@ -269,6 +275,7 @@ export interface PermissionDecided {
269
275
  readonly decisionId: string;
270
276
  /** The invocation this decision binds to (B group). */
271
277
  readonly callId?: string;
278
+ readonly invocationSeq?: number;
272
279
  readonly decision: "approved" | "denied";
273
280
  readonly reason?: string;
274
281
  /** E1: the deciding extension's name — present ONLY on policy decisions;
@@ -353,6 +360,22 @@ export interface SummarizedEvent {
353
360
  /** The model's compression — replaces the covered range in the projection. */
354
361
  readonly summary: string;
355
362
  }
363
+ /**
364
+ * R-E 0.1.43 / ADR-0047 (Gap B) — a model output suffix WITHOUT a committed
365
+ * stop was abandoned on resume. `voidFromSeq` is the last committed-boundary
366
+ * event's seq (stop / user_input / terminal / compaction / summarized); the
367
+ * voided range is (voidFromSeq, this.seq]. The projection skips the range and
368
+ * the marker itself renders nothing — "a model output suffix without a
369
+ * committed stop is an incomplete draft and must never become committed
370
+ * provider history." Kernel-owned: the AdapterEvent whitelist excludes it, so
371
+ * adapter/extension forgery is invalid_request by construction.
372
+ */
373
+ export interface ModelOutputAbandoned {
374
+ readonly seq: number;
375
+ readonly type: "model_output_abandoned";
376
+ readonly voidFromSeq: number;
377
+ readonly reason: string;
378
+ }
356
379
  /** Extended-thinking content. Providers without it emit nothing here. */
357
380
  export interface Thinking {
358
381
  readonly seq: number;
@@ -440,10 +463,17 @@ export interface TerminalEvent {
440
463
  readonly outcome: Terminal;
441
464
  }
442
465
  /**
466
+ * IDENTITY (the three-identity model, R-E 0.1.43 / ADR-0047): an invocation
467
+ * carries THREE ids — `callId` (provider correlation only, may repeat across
468
+ * runs), `invocationSeq` (the framework identity = the call's tool_call_end
469
+ * .seq; OPTIONAL — absent on old logs, present on everything written from
470
+ * 0.1.43), and `executionId` (a specific execution of that invocation, one
471
+ * per started event). Old logs keep the callId + seq-proximity fallback.
472
+ *
443
473
  * The union. Consume it with a `switch (event.type)`; with
444
474
  * `strictNullChecks` on, an unhandled variant is a compile error.
445
475
  */
446
- export type Event = AssistantStart | AssistantEnd | TextStart | TextDelta | TextEnd | ToolCallStart | ToolCallInputDelta | ToolCallEnd | ToolResultEvent | Thinking | Usage | Stop | UserInputEvent | CompactedEvent | ToolExecutionStarted | ToolExecutionSucceeded | ToolExecutionFailed | ToolExecutionResolved | PermissionRequested | PermissionDecided | PermissionExpired | UncertainPending | UserInputReplaced | MicroCompactEvent | SummarizedEvent | TerminalEvent;
476
+ export type Event = AssistantStart | AssistantEnd | TextStart | TextDelta | TextEnd | ToolCallStart | ToolCallInputDelta | ToolCallEnd | ToolResultEvent | Thinking | Usage | Stop | UserInputEvent | CompactedEvent | ToolExecutionStarted | ToolExecutionSucceeded | ToolExecutionFailed | ToolExecutionResolved | PermissionRequested | PermissionDecided | PermissionExpired | UncertainPending | UserInputReplaced | MicroCompactEvent | SummarizedEvent | ModelOutputAbandoned | TerminalEvent;
447
477
  /**
448
478
  * Runtime type guard for the union. The store validates every JSONL record
449
479
  * with it: valid JSON that is not a kiso event is corruption, not history.
@@ -102,6 +102,9 @@ function isErrorKind(v) {
102
102
  function isExecutionId(v) {
103
103
  return v.executionId === undefined || typeof v.executionId === "string";
104
104
  }
105
+ /** R-E 0.1.43: the optional framework invocation identity — absent on old
106
+ * logs, otherwise a non-negative safe integer (the tool_call_end.seq). */
107
+ const isInvocationSeq = (v) => v.invocationSeq === undefined || isNonNegativeInt(v.invocationSeq);
105
108
  /** A StructuredError — the shape the `error` terminal carries. round 9: a
106
109
  * status, when present, is a non-negative safe integer (no negatives,
107
110
  * NaN, Infinity, or fractions). */
@@ -170,7 +173,7 @@ const EVENT_VALIDATORS = {
170
173
  isErrorKind(v) &&
171
174
  isExecutionId(v) &&
172
175
  isSource(v) &&
173
- isTags(v),
176
+ isTags(v) && isInvocationSeq(v),
174
177
  thinking: (v) => typeof v.text === "string",
175
178
  usage: isUsage,
176
179
  stop: (v) => STOP_REASONS.has(v.reason),
@@ -186,32 +189,32 @@ const EVENT_VALIDATORS = {
186
189
  tool_execution_started: (v) => typeof v.executionId === "string" &&
187
190
  typeof v.callId === "string" &&
188
191
  typeof v.name === "string" &&
189
- isPlainObject(v.input),
192
+ isPlainObject(v.input) && isInvocationSeq(v),
190
193
  tool_execution_succeeded: (v) => typeof v.executionId === "string" &&
191
194
  typeof v.callId === "string" &&
192
195
  isPlainObject(v.result) &&
193
196
  typeof v.result.content === "string" &&
194
197
  v.result.isError === false &&
195
- isTags(v),
198
+ isTags(v) && isInvocationSeq(v),
196
199
  tool_execution_failed: (v) => typeof v.executionId === "string" &&
197
200
  typeof v.callId === "string" &&
198
201
  typeof v.error === "string" &&
199
202
  typeof v.safeToRetry === "boolean" &&
200
203
  isErrorKind(v) &&
201
- isTags(v),
204
+ isTags(v) && isInvocationSeq(v),
202
205
  tool_execution_resolved: (v) => typeof v.executionId === "string" &&
203
206
  typeof v.callId === "string" &&
204
- (v.resolution === "rerun" || v.resolution === "abandoned"),
207
+ (v.resolution === "rerun" || v.resolution === "abandoned") && isInvocationSeq(v),
205
208
  permission_requested: (v) => typeof v.decisionId === "string" &&
206
209
  typeof v.callId === "string" &&
207
210
  typeof v.name === "string" &&
208
211
  isPlainObject(v.input) &&
209
- (v.speaker === undefined || typeof v.speaker === "string"),
212
+ (v.speaker === undefined || typeof v.speaker === "string") && isInvocationSeq(v),
210
213
  permission_decided: (v) => typeof v.decisionId === "string" &&
211
214
  (v.decision === "approved" || v.decision === "denied") &&
212
215
  (v.callId === undefined || typeof v.callId === "string") &&
213
216
  (v.reason === undefined || typeof v.reason === "string") &&
214
- (v.decidedBy === undefined || typeof v.decidedBy === "string"),
217
+ (v.decidedBy === undefined || typeof v.decidedBy === "string") && isInvocationSeq(v),
215
218
  permission_expired: (v) => typeof v.decisionId === "string" && typeof v.reason === "string",
216
219
  uncertain_pending: (v) => typeof v.executionId === "string" && typeof v.callId === "string" && typeof v.name === "string" && typeof v.error === "string",
217
220
  microcompacted: (v) => isNonNegativeInt(v.beforeSeq),
@@ -221,6 +224,9 @@ const EVENT_VALIDATORS = {
221
224
  typeof v.summary === "string" &&
222
225
  v.summary.length > 0 &&
223
226
  v.coversToSeq < v.seq,
227
+ // R-E 0.1.43: the void starts at a committed boundary BEFORE the marker —
228
+ // a marker that voids nothing (or itself) is corruption, not history.
229
+ model_output_abandoned: (v) => isNonNegativeInt(v.voidFromSeq) && typeof v.reason === "string" && v.voidFromSeq < v.seq,
224
230
  user_input_replaced: (v) => isNonNegativeInt(v.replaces) && (v.content === null || isContent(v.content)) && isSource(v),
225
231
  terminal: (v) => isTerminal(v.outcome),
226
232
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.1.33",
3
+ "version": "0.1.34",
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.1.33",
36
+ "@vincemakes/kiso-evals": "0.1.35",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"