@vincemakes/kiso-core 0.42.1 → 0.42.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.
@@ -2,7 +2,7 @@
2
2
  * L2 — context-economy primitives, the MECHANICAL half.
3
3
  *
4
4
  * The kernel's compaction policy is identity preservation, not summary
5
- * (mauri ADR-0007): keep the message SHELL (id, role, position), replace
5
+ * (the predecessor's ADR-0007): keep the message SHELL (id, role, position), replace
6
6
  * the content with a marker, zero LLM calls. A summary is a NEW message; it
7
7
  * never rewrites an old one. Messages are immutable (ADR-0002) — "clearing"
8
8
  * is append, not mutation.
@@ -21,7 +21,7 @@ import type { Message } from "../protocol/messages.js";
21
21
  /**
22
22
  * Rough token estimate (chars/4 + structural overhead). Calibration-free on
23
23
  * purpose: context economy only needs a stable MONOTONE proxy, not an exact
24
- * count — the threshold absorbs the error (mauri ADR-0007). The proxy's
24
+ * count — the threshold absorbs the error (the predecessor's ADR-0007). The proxy's
25
25
  * three-word contract, and the pins that hold it, are
26
26
  * packages/core/tests/sc1b-estimator.test.ts.
27
27
  */
@@ -2,7 +2,7 @@
2
2
  * L2 — context-economy primitives, the MECHANICAL half.
3
3
  *
4
4
  * The kernel's compaction policy is identity preservation, not summary
5
- * (mauri ADR-0007): keep the message SHELL (id, role, position), replace
5
+ * (the predecessor's ADR-0007): keep the message SHELL (id, role, position), replace
6
6
  * the content with a marker, zero LLM calls. A summary is a NEW message; it
7
7
  * never rewrites an old one. Messages are immutable (ADR-0002) — "clearing"
8
8
  * is append, not mutation.
@@ -64,7 +64,7 @@ function textTokens(text) {
64
64
  /**
65
65
  * Rough token estimate (chars/4 + structural overhead). Calibration-free on
66
66
  * purpose: context economy only needs a stable MONOTONE proxy, not an exact
67
- * count — the threshold absorbs the error (mauri ADR-0007). The proxy's
67
+ * count — the threshold absorbs the error (the predecessor's ADR-0007). The proxy's
68
68
  * three-word contract, and the pins that hold it, are
69
69
  * packages/core/tests/sc1b-estimator.test.ts.
70
70
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * L2 — the hook vocabulary: 3 phases + lifecycle (mauri ADR-0006).
2
+ * L2 — the hook vocabulary: 3 phases + lifecycle (the predecessor's ADR-0006).
3
3
  *
4
4
  * ReAct's loop has exactly three model↔world interfaces — assemble (feed the
5
5
  * model), model (the model speaks), execute (the model makes the world work)
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Payload types are SHARED between fire-site and read-site: a hook author
11
11
  * cannot drift a key name, because the keys do not exist — the payload is a
12
- * typed object (the mauri payload-contract discipline, ADR-0017, as a
12
+ * typed object (the predecessor's payload-contract discipline, ADR-0017, as a
13
13
  * compile-time fact instead of a written rule).
14
14
  */
15
15
  import type { Event } from "../protocol/events.js";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * L2 — the hook vocabulary: 3 phases + lifecycle (mauri ADR-0006).
2
+ * L2 — the hook vocabulary: 3 phases + lifecycle (the predecessor's ADR-0006).
3
3
  *
4
4
  * ReAct's loop has exactly three model↔world interfaces — assemble (feed the
5
5
  * model), model (the model speaks), execute (the model makes the world work)
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * Payload types are SHARED between fire-site and read-site: a hook author
11
11
  * cannot drift a key name, because the keys do not exist — the payload is a
12
- * typed object (the mauri payload-contract discipline, ADR-0017, as a
12
+ * typed object (the predecessor's payload-contract discipline, ADR-0017, as a
13
13
  * compile-time fact instead of a written rule).
14
14
  */
15
15
  /** Every hook optional; unset = pass-through. The kernel runs with this. */
@@ -112,6 +112,15 @@ export async function* loop(config) {
112
112
  return full;
113
113
  };
114
114
  const aborted = () => signal?.aborted === true;
115
+ /** append → observe → yield, for a producer with NOTHING between the
116
+ * three (never the execution queue's ack, the stream's launch, or a
117
+ * producer without an observer). */
118
+ async function* emit(e) {
119
+ const full = log.append(e);
120
+ if (hooks.onEvent)
121
+ await hooks.onEvent(full, {}).catch(() => { });
122
+ yield full;
123
+ }
115
124
  // Assemble: the incoming user message may be rewritten or vetoed.
116
125
  // C group: the outcome is PERSISTED as a user_input_replaced event — the
117
126
  // projection renders the final replacement AT THE INPUT'S POSITION (or
@@ -502,12 +511,8 @@ export async function* loop(config) {
502
511
  // ── ADR-0055 (A1b): the compaction point, before every request ──
503
512
  if (config.compact !== undefined) {
504
513
  const added = await config.compact(log.all, messages, overflow === null ? "request" : "overflow");
505
- for (const e of added) {
506
- const full = log.append(e);
507
- if (hooks.onEvent)
508
- await hooks.onEvent(full, {}).catch(() => { });
509
- yield full;
510
- }
514
+ for (const e of added)
515
+ yield* emit(e);
511
516
  if (overflow !== null && added.length === 0) {
512
517
  yield await terminal({ kind: "error", error: overflow });
513
518
  return;
@@ -573,12 +578,8 @@ export async function* loop(config) {
573
578
  yield* drainSettled();
574
579
  if (launchError !== null)
575
580
  throw launchError;
576
- if (log.lastSeq > turnStart && !unsafeStartedInDraft()) {
577
- const marker = log.append({ type: "model_output_abandoned", voidFromSeq: turnStart, reason });
578
- if (hooks.onEvent)
579
- await hooks.onEvent(marker, {}).catch(() => { });
580
- yield marker;
581
- }
581
+ if (log.lastSeq > turnStart && !unsafeStartedInDraft())
582
+ yield* emit({ type: "model_output_abandoned", voidFromSeq: turnStart, reason });
582
583
  };
583
584
  while (true) {
584
585
  // Area 4: the backoff is abortable — a cancel landing during a
@@ -867,11 +868,8 @@ export async function* loop(config) {
867
868
  return;
868
869
  }
869
870
  if (voided === null && heldStop !== null) {
870
- const full = log.append(heldStop);
871
- if (hooks.onEvent)
872
- await hooks.onEvent(full, {}).catch(() => { });
873
- committed = true;
874
- yield full;
871
+ committed = true; // a local flag; set before the emit, read only after it (the observer cannot throw)
872
+ yield* emit(heldStop);
875
873
  }
876
874
  settleTurn();
877
875
  // A turn with no tool calls ends on its OWN stop reason (Phase B,
@@ -969,6 +967,20 @@ function terminalForStop(reason) {
969
967
  };
970
968
  }
971
969
  }
970
+ /** The permission_decided event for an invocation — the fields in the
971
+ * order the log has always written them. The CALLER decides: approved or
972
+ * not, the reason, the speaker; this builds, never classifies. */
973
+ function decidedEvent(call, decisionId, approved, reason, decidedBy) {
974
+ return {
975
+ type: "permission_decided",
976
+ decisionId,
977
+ callId: call.callId,
978
+ invocationSeq: call.seq,
979
+ decision: approved ? "approved" : "denied",
980
+ ...(reason !== undefined ? { reason } : {}),
981
+ ...(decidedBy !== undefined ? { decidedBy } : {}),
982
+ };
983
+ }
972
984
  /** The tool_result event for a call — the shared shape (executionId rides
973
985
  * it as the durable correlation, round 5; invocationSeq = the framework
974
986
  * identity, R-E 0.1.43). */
@@ -1101,17 +1113,7 @@ async function decideCall(call, registry, hooks, ctx, log, resolveApproval, reso
1101
1113
  // allow/deny are PERSISTED FACTS (decidedBy = a SPEAKING
1102
1114
  // extension — never the chain head on behalf of a non-speaker)
1103
1115
  // — never a human pause.
1104
- push({
1105
- type: "permission_decided",
1106
- decisionId: nextDecisionId(),
1107
- callId: call.callId,
1108
- invocationSeq: call.seq,
1109
- decision: chainVerdict.action === "allow" ? "approved" : "denied",
1110
- ...(chainVerdict.action === "deny" && chainVerdict.reason !== undefined
1111
- ? { reason: chainVerdict.reason }
1112
- : {}),
1113
- decidedBy: chainVerdict.decidedBy,
1114
- });
1116
+ push(decidedEvent(call, nextDecisionId(), chainVerdict.action === "allow", chainVerdict.action === "deny" ? chainVerdict.reason : undefined, chainVerdict.decidedBy));
1115
1117
  }
1116
1118
  }
1117
1119
  if (chainVerdict?.action === "deny") {
@@ -1197,14 +1199,7 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
1197
1199
  // its honest aborted terminal.
1198
1200
  const verdict = resolveApprovalVerdict?.(decisionId);
1199
1201
  if (verdict !== undefined) {
1200
- push({
1201
- type: "permission_decided",
1202
- decisionId,
1203
- callId: call.callId,
1204
- invocationSeq: call.seq,
1205
- decision: verdict ? "approved" : "denied",
1206
- ...(verdict ? {} : { reason: "denied by user" }),
1207
- });
1202
+ push(decidedEvent(call, decisionId, verdict, verdict ? undefined : "denied by user"));
1208
1203
  }
1209
1204
  }
1210
1205
  throw err;
@@ -1213,16 +1208,8 @@ async function humanPause(call, decisionId, hooks, log, resolveApproval, resolve
1213
1208
  // write-ahead BEFORE waking the resolver (Area 2): if it already
1214
1209
  // landed in the log, this is the same decision, not a duplicate.
1215
1210
  if (log.all.find((e) => e.type === "permission_decided" && e.decisionId === decisionId) === undefined) {
1216
- push({
1217
- type: "permission_decided",
1218
- decisionId,
1219
- callId: call.callId, // binds the decision to the invocation (B group)
1220
- invocationSeq: call.seq,
1221
- decision: finalDecision.action === "allow" ? "approved" : "denied",
1222
- ...(finalDecision.action === "deny" && finalDecision.reason !== undefined
1223
- ? { reason: finalDecision.reason }
1224
- : {}),
1225
- });
1211
+ // callId binds the decision to the invocation (B group)
1212
+ push(decidedEvent(call, decisionId, finalDecision.action === "allow", finalDecision.action === "deny" ? finalDecision.reason : undefined));
1226
1213
  }
1227
1214
  return finalDecision;
1228
1215
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * L2 — permission as a negotiation, not a gate (mauri ADR-0002).
2
+ * L2 — permission as a negotiation, not a gate (the predecessor's ADR-0002).
3
3
  *
4
4
  * A permission decision is a dialog step with state and an upgrade path:
5
5
  * allow one call, deny with a reason fed back to the model, or defer to a
@@ -1,5 +1,5 @@
1
1
  /**
2
- * L2 — permission as a negotiation, not a gate (mauri ADR-0002).
2
+ * L2 — permission as a negotiation, not a gate (the predecessor's ADR-0002).
3
3
  *
4
4
  * A permission decision is a dialog step with state and an upgrade path:
5
5
  * allow one call, deny with a reason fed back to the model, or defer to a
@@ -10,7 +10,7 @@
10
10
  * carries images. Forcing every caller into the array shape to serve the
11
11
  * 5% that need vision is a tax paid on every line of product code.
12
12
  *
13
- * See ADR-0003 (sum type). The tool contract's record is mauri ADR-0008 —
13
+ * See ADR-0003 (sum type). The tool contract's record is the predecessor's ADR-0008 —
14
14
  * the PREDECESSOR series, cited the same way in kernel/hooks.ts and
15
15
  * kernel/mode.ts: kiso's own numbers 0006-0019 were NEVER assigned
16
16
  * (docs/adrs/README.md), so an unprefixed "ADR-0008" sends a reader to a
@@ -10,7 +10,7 @@
10
10
  * carries images. Forcing every caller into the array shape to serve the
11
11
  * 5% that need vision is a tax paid on every line of product code.
12
12
  *
13
- * See ADR-0003 (sum type). The tool contract's record is mauri ADR-0008 —
13
+ * See ADR-0003 (sum type). The tool contract's record is the predecessor's ADR-0008 —
14
14
  * the PREDECESSOR series, cited the same way in kernel/hooks.ts and
15
15
  * kernel/mode.ts: kiso's own numbers 0006-0019 were NEVER assigned
16
16
  * (docs/adrs/README.md), so an unprefixed "ADR-0008" sends a reader to a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincemakes/kiso-core",
3
- "version": "0.42.1",
3
+ "version": "0.42.3",
4
4
  "description": "kiso core \u2014 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.42.1",
36
+ "@vincemakes/kiso-evals": "0.42.3",
37
37
  "@types/node": "^26.1.2",
38
38
  "typescript": "^5.7.2",
39
39
  "vitest": "^3.0.0"