@vincemakes/kiso-core 0.1.11 → 0.1.12
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/kernel/ledger.d.ts +4 -2
- package/dist/kernel/ledger.js +8 -6
- package/dist/kernel/loop.js +20 -87
- package/package.json +2 -2
package/dist/kernel/ledger.d.ts
CHANGED
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
* Status derivation:
|
|
18
18
|
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
19
|
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
-
* failed
|
|
21
|
-
*
|
|
20
|
+
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
+
* the outcome — 裁决 #12 / ADR-0038;
|
|
22
|
+
* safeToRetry stays on the event for
|
|
23
|
+
* history, it no longer feeds status)
|
|
22
24
|
* resolved "rerun" → "rerun" (human cleared it)
|
|
23
25
|
* resolved "abandoned" → "abandoned" (human killed it)
|
|
24
26
|
*/
|
package/dist/kernel/ledger.js
CHANGED
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
* Status derivation:
|
|
18
18
|
* started, no terminal event yet → "uncertain" (interrupted: human)
|
|
19
19
|
* succeeded → "succeeded" (confirmed, never re-run)
|
|
20
|
-
* failed
|
|
21
|
-
*
|
|
20
|
+
* failed (any) → "failed" (a complete receipt IS
|
|
21
|
+
* the outcome — 裁决 #12 / ADR-0038;
|
|
22
|
+
* safeToRetry stays on the event for
|
|
23
|
+
* history, it no longer feeds status)
|
|
22
24
|
* resolved "rerun" → "rerun" (human cleared it)
|
|
23
25
|
* resolved "abandoned" → "abandoned" (human killed it)
|
|
24
26
|
*/
|
|
@@ -48,10 +50,10 @@ export function executionLedger(events) {
|
|
|
48
50
|
if (prior) {
|
|
49
51
|
ledger.set(ev.executionId, {
|
|
50
52
|
...prior,
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
status:
|
|
53
|
+
// 裁决 #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",
|
|
55
57
|
...(ev.error !== undefined ? { error: ev.error } : {}),
|
|
56
58
|
});
|
|
57
59
|
}
|
package/dist/kernel/loop.js
CHANGED
|
@@ -361,14 +361,8 @@ export async function* loop(config) {
|
|
|
361
361
|
yield await terminal({ kind: "aborted", by: "user" });
|
|
362
362
|
return;
|
|
363
363
|
}
|
|
364
|
-
let currentExecutionId;
|
|
365
364
|
try {
|
|
366
365
|
for await (const ev of executeOne(call, registry, hooks, { signal: signal ?? NEVER_ABORT, ...(config.sessionId !== undefined ? { sessionId: config.sessionId } : {}) }, log, config.resolveApproval, config.approvalVerdict, signal, config.approvalPolicies)) {
|
|
367
|
-
// 四: the identity of THIS execution comes from the stream —
|
|
368
|
-
// a historical same-callId execution must never be mistaken
|
|
369
|
-
// for this call's (the provider callId may repeat across runs).
|
|
370
|
-
if (ev.type === "tool_execution_started")
|
|
371
|
-
currentExecutionId = ev.executionId;
|
|
372
366
|
if (hooks.onEvent)
|
|
373
367
|
await hooks.onEvent(ev, {}).catch(() => { });
|
|
374
368
|
yield ev;
|
|
@@ -383,87 +377,14 @@ export async function* loop(config) {
|
|
|
383
377
|
}
|
|
384
378
|
throw err;
|
|
385
379
|
}
|
|
386
|
-
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
: [...log.all]
|
|
395
|
-
.reverse()
|
|
396
|
-
.find((e) => e.type === "tool_execution_failed" && e.executionId === currentExecutionId);
|
|
397
|
-
if (failed !== undefined && !failed.safeToRetry) {
|
|
398
|
-
// Register the human channel BEFORE announcing the pause —
|
|
399
|
-
// a consumer that answers the moment it sees the event must
|
|
400
|
-
// find the resolver already waiting (no deadlock between
|
|
401
|
-
// yield and await, mirroring the approval pause).
|
|
402
|
-
const pendingResolution = config.resolveUncertainty !== undefined ? config.resolveUncertainty(failed.executionId) : undefined;
|
|
403
|
-
const pendingUncertain = log.append({
|
|
404
|
-
type: "uncertain_pending",
|
|
405
|
-
executionId: failed.executionId,
|
|
406
|
-
callId: call.callId,
|
|
407
|
-
name: call.name,
|
|
408
|
-
error: failed.error,
|
|
409
|
-
});
|
|
410
|
-
if (hooks.onEvent)
|
|
411
|
-
await hooks.onEvent(pendingUncertain, {}).catch(() => { });
|
|
412
|
-
yield pendingUncertain;
|
|
413
|
-
let resolution;
|
|
414
|
-
if (pendingResolution !== undefined) {
|
|
415
|
-
try {
|
|
416
|
-
resolution = await raceAbort(pendingResolution, signal);
|
|
417
|
-
}
|
|
418
|
-
catch (err) {
|
|
419
|
-
if (err === ABORTED) {
|
|
420
|
-
// 第四轮(对抗): the human may have answered in the
|
|
421
|
-
// same instant the abort landed — a CONSUMED verdict
|
|
422
|
-
// must be recorded (exactly once), never lost. It is
|
|
423
|
-
// appended here, then the run ends with its honest
|
|
424
|
-
// aborted terminal; the execution is resolved, not
|
|
425
|
-
// bricked.
|
|
426
|
-
const verdict = config.uncertaintyVerdict?.(failed.executionId);
|
|
427
|
-
if (verdict !== undefined) {
|
|
428
|
-
const verdictEvent = log.append({
|
|
429
|
-
type: "tool_execution_resolved",
|
|
430
|
-
executionId: failed.executionId,
|
|
431
|
-
callId: call.callId,
|
|
432
|
-
resolution: verdict,
|
|
433
|
-
});
|
|
434
|
-
if (hooks.onEvent)
|
|
435
|
-
await hooks.onEvent(verdictEvent, {}).catch(() => { });
|
|
436
|
-
yield verdictEvent;
|
|
437
|
-
}
|
|
438
|
-
yield await terminal({ kind: "aborted", by: "user" });
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
throw err;
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
// No channel: record the conservative verdict — the
|
|
446
|
-
// failure is NEVER auto-retried, and the ledger stays
|
|
447
|
-
// consistent for future resumes.
|
|
448
|
-
resolution = "abandoned";
|
|
449
|
-
}
|
|
450
|
-
// 七: the LOOP owns the resolution event — appended and
|
|
451
|
-
// yielded on EVERY verdict path (channel or not), so the Run
|
|
452
|
-
// persists it and the consumer's stream has no hidden gap.
|
|
453
|
-
// A live resolveUncertain() only passed the verdict; the
|
|
454
|
-
// event itself is created here, exactly once.
|
|
455
|
-
const resolvedEvent = log.append({
|
|
456
|
-
type: "tool_execution_resolved",
|
|
457
|
-
executionId: failed.executionId,
|
|
458
|
-
callId: call.callId,
|
|
459
|
-
resolution,
|
|
460
|
-
});
|
|
461
|
-
if (hooks.onEvent)
|
|
462
|
-
await hooks.onEvent(resolvedEvent, {}).catch(() => { });
|
|
463
|
-
yield resolvedEvent;
|
|
464
|
-
// Either verdict ends the pending list: siblings never run.
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
380
|
+
// 裁决 #12 (ADR-0038): the failed-receipt uncertain PAUSE is
|
|
381
|
+
// REMOVED — with a complete receipt (succeeded or failed) the
|
|
382
|
+
// outcome is KNOWN, and uncertainty belongs to the crash window
|
|
383
|
+
// alone (started, no receipt; surfaced through the ledger's
|
|
384
|
+
// uncertainExecutions and resolved offline). A retry is a NEW
|
|
385
|
+
// call — it passes the approval chain again, which is the correct
|
|
386
|
+
// guard for partial side effects; the honest note on
|
|
387
|
+
// non-idempotent failures rides the result (修正一).
|
|
467
388
|
}
|
|
468
389
|
// ── Advance history: the log grew; re-derive for the next turn ─────
|
|
469
390
|
messages = derive();
|
|
@@ -794,6 +715,18 @@ async function* executeOne(call, registry, hooks, ctx, log, resolveApproval, res
|
|
|
794
715
|
if (hooks.onPostTool) {
|
|
795
716
|
result = await hooks.onPostTool(payload, result, ctx);
|
|
796
717
|
}
|
|
718
|
+
// 裁决 #12 修正一: a non-idempotent failure's side effects may have
|
|
719
|
+
// partially applied — an honest note rides the RESULT (and the failed
|
|
720
|
+
// receipt below, losslessly — a crash-window repair of the tool_result
|
|
721
|
+
// reproduces the normal path). Idempotent failures carry no note; the
|
|
722
|
+
// MCP bridge maps tools without declaring idempotency, so unknown
|
|
723
|
+
// idempotency = the note applies (honest).
|
|
724
|
+
if (result.isError && tool.idempotent !== true) {
|
|
725
|
+
result = {
|
|
726
|
+
...result,
|
|
727
|
+
content: `${result.content}\n[non-idempotent tool failed — its side effects may have partially applied; verify before retrying]`,
|
|
728
|
+
};
|
|
729
|
+
}
|
|
797
730
|
if (result.isError) {
|
|
798
731
|
// Area 3: only a tool that PROVED safe-to-retry (idempotent) gets a
|
|
799
732
|
// clean failure; a non-idempotent failure may have produced a side
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vincemakes/kiso-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "kiso(基礎) 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.
|
|
36
|
+
"@vincemakes/kiso-evals": "0.1.12",
|
|
37
37
|
"@types/node": "^26.1.2",
|
|
38
38
|
"typescript": "^5.7.2",
|
|
39
39
|
"vitest": "^3.0.0"
|