@ouro.bot/cli 0.1.0-alpha.776 → 0.1.0-alpha.777
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/changelog.json +6 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/agent-entry.js +69 -6
- package/dist/heart/daemon/daemon.js +54 -9
- package/dist/heart/daemon/process-manager.js +113 -4
- package/dist/heart/external-events/router.js +14 -0
- package/dist/heart/tool-loop.js +1 -1
- package/dist/repertoire/tools-continuity.js +199 -146
- package/dist/senses/private-runtime-worker.js +287 -208
- package/dist/senses/private-runtime.js +4 -1
- package/dist/senses/sanctuary-runtime.js +43 -0
- package/npm-shrinkwrap.json +40 -25
- package/package.json +1 -1
|
@@ -57,6 +57,15 @@ const spend_ledger_1 = require("../rsvp/spend-ledger");
|
|
|
57
57
|
const run_ledger_1 = require("../heart/run-ledger");
|
|
58
58
|
const autonomy_budget_1 = require("../heart/autonomy-budget");
|
|
59
59
|
const habit_lifecycle_guard_1 = require("./habit-lifecycle-guard");
|
|
60
|
+
function createQueueCompletion(rejectOnError) {
|
|
61
|
+
let resolve;
|
|
62
|
+
let reject;
|
|
63
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
64
|
+
resolve = resolvePromise;
|
|
65
|
+
reject = rejectPromise;
|
|
66
|
+
});
|
|
67
|
+
return { promise, resolve, reject, errors: [], rejectOnError };
|
|
68
|
+
}
|
|
60
69
|
function isUsageData(value) {
|
|
61
70
|
const usage = value;
|
|
62
71
|
return !!usage
|
|
@@ -492,226 +501,287 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
492
501
|
timer.unref?.();
|
|
493
502
|
return () => clearInterval(timer);
|
|
494
503
|
};
|
|
495
|
-
async function run(reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent) {
|
|
504
|
+
async function run(reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent, dispatchId) {
|
|
505
|
+
const completion = createQueueCompletion(dispatchId !== undefined);
|
|
506
|
+
let activeCompletion = completion;
|
|
496
507
|
if (running) {
|
|
497
|
-
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent, ...(externalEvent ? { stopExternalEventHeartbeat: startExternalEventHeartbeat(externalEvent) } : {}) });
|
|
498
|
-
return;
|
|
508
|
+
queue.push({ reason, taskId, habitName, awaitName, trigger, privateTurnDecision, noSend, externalEvent, dispatchId, completion, ...(externalEvent ? { stopExternalEventHeartbeat: startExternalEventHeartbeat(externalEvent) } : {}) });
|
|
509
|
+
return completion.promise;
|
|
499
510
|
}
|
|
500
511
|
running = true;
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
return;
|
|
531
|
-
recordHabitCompletion(currentHabitRun, new Date(nowSource()).toISOString());
|
|
532
|
-
currentHabitRunFinalized = true;
|
|
533
|
-
};
|
|
534
|
-
const turnErrors = [];
|
|
535
|
-
if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
|
|
536
|
-
clearHeartbeatRestShield();
|
|
537
|
-
}
|
|
538
|
-
let turnResult;
|
|
539
|
-
let blockedAutonomyTurn = false;
|
|
540
|
-
try {
|
|
541
|
-
const turnStartedAt = new Date(nowSource()).toISOString();
|
|
542
|
-
const autonomyDecision = currentHabitRun && !currentHabitRun.blockedReason
|
|
543
|
-
? reserveHabitAutonomyBudget(currentHabitRun, turnStartedAt)
|
|
512
|
+
void (async () => {
|
|
513
|
+
try {
|
|
514
|
+
let nextReason = reason;
|
|
515
|
+
let nextTaskId = taskId;
|
|
516
|
+
let nextHabitName = habitName;
|
|
517
|
+
let nextAwaitName = awaitName;
|
|
518
|
+
let nextTrigger = trigger;
|
|
519
|
+
let nextPrivateTurnDecision = privateTurnDecision;
|
|
520
|
+
let nextExternalEvent = externalEvent;
|
|
521
|
+
let nextExternalEventHeartbeat = externalEvent ? startExternalEventHeartbeat(externalEvent) : undefined;
|
|
522
|
+
let nextCompletion = completion;
|
|
523
|
+
let nextNoSend = reason === "habit" ? noSend : undefined;
|
|
524
|
+
let nextHabitRun = null;
|
|
525
|
+
let consecutiveInstinctTurns = reason === "instinct" ? 1 : 0;
|
|
526
|
+
runLoop: do {
|
|
527
|
+
const currentReason = nextReason;
|
|
528
|
+
const currentHabitName = nextHabitName;
|
|
529
|
+
const currentTrigger = nextTrigger ?? "overdue";
|
|
530
|
+
const currentNoSend = nextNoSend;
|
|
531
|
+
const currentExternalEvent = nextExternalEvent;
|
|
532
|
+
const currentExternalEventHeartbeat = nextExternalEventHeartbeat;
|
|
533
|
+
const currentCompletion = nextCompletion;
|
|
534
|
+
activeCompletion = currentCompletion;
|
|
535
|
+
nextExternalEventHeartbeat = undefined;
|
|
536
|
+
nextCompletion = undefined;
|
|
537
|
+
const currentHabitRun = currentReason === "habit" && currentHabitName
|
|
538
|
+
? nextHabitRun && nextHabitRun.habit.name === currentHabitName
|
|
539
|
+
? nextHabitRun
|
|
540
|
+
: await prepareHabitRun(currentHabitName, currentTrigger, new Date(nowSource()).toISOString())
|
|
544
541
|
: null;
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
542
|
+
nextHabitRun = null;
|
|
543
|
+
let currentHabitRunFinalized = false;
|
|
544
|
+
const finalizeCurrentHabitRun = () => {
|
|
545
|
+
if (!currentHabitRun || currentHabitRunFinalized)
|
|
546
|
+
return;
|
|
547
|
+
recordHabitCompletion(currentHabitRun, new Date(nowSource()).toISOString());
|
|
548
|
+
currentHabitRunFinalized = true;
|
|
549
|
+
};
|
|
550
|
+
const turnErrors = [];
|
|
551
|
+
if (!(currentReason === "habit" && currentHabitName === "heartbeat")) {
|
|
553
552
|
clearHeartbeatRestShield();
|
|
554
553
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
554
|
+
let turnResult;
|
|
555
|
+
let blockedAutonomyTurn = false;
|
|
556
|
+
try {
|
|
557
|
+
const turnStartedAt = new Date(nowSource()).toISOString();
|
|
558
|
+
const autonomyDecision = currentHabitRun && !currentHabitRun.blockedReason
|
|
559
|
+
? reserveHabitAutonomyBudget(currentHabitRun, turnStartedAt)
|
|
560
|
+
: null;
|
|
561
|
+
if (currentHabitRun)
|
|
562
|
+
recordHabitStartIfNeeded(currentHabitRun);
|
|
563
|
+
if (currentHabitRun?.blockedReason) {
|
|
564
|
+
blockedAutonomyTurn = true;
|
|
565
|
+
turnResult = {
|
|
566
|
+
turnOutcome: "blocked",
|
|
567
|
+
reason: currentHabitRun.blockedReason,
|
|
568
|
+
};
|
|
569
|
+
clearHeartbeatRestShield();
|
|
570
|
+
}
|
|
571
|
+
else if (autonomyDecision && !autonomyDecision.allowed) {
|
|
572
|
+
blockedAutonomyTurn = true;
|
|
573
|
+
const reason = `autonomy budget blocked: ${autonomyDecision.reason}`;
|
|
574
|
+
turnErrors.push(reason);
|
|
575
|
+
turnResult = {
|
|
576
|
+
turnOutcome: "blocked",
|
|
577
|
+
reason,
|
|
578
|
+
autonomyReceiptId: autonomyDecision.receiptId,
|
|
579
|
+
};
|
|
580
|
+
clearHeartbeatRestShield();
|
|
581
|
+
}
|
|
582
|
+
else {
|
|
583
|
+
const turnOptions = {
|
|
584
|
+
reason: nextReason,
|
|
585
|
+
taskId: nextTaskId,
|
|
586
|
+
habitName: nextHabitName,
|
|
587
|
+
awaitName: nextAwaitName,
|
|
588
|
+
...(currentNoSend ? { noSend: true } : {}),
|
|
589
|
+
...(nextPrivateTurnDecision ? { privateTurnDecision: nextPrivateTurnDecision } : {}),
|
|
590
|
+
...(nextExternalEvent ? { externalEvent: nextExternalEvent } : {}),
|
|
591
|
+
...(currentHabitRun
|
|
592
|
+
? {
|
|
580
593
|
trigger: currentHabitRun.trigger,
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
594
|
+
preparedHabit: {
|
|
595
|
+
runId: currentHabitRun.runId,
|
|
596
|
+
trigger: currentHabitRun.trigger,
|
|
597
|
+
operationId: currentHabitRun.operationId,
|
|
598
|
+
habit: currentHabitRun.habit,
|
|
599
|
+
priorSessionSummary: currentHabitRun.priorSessionSummary,
|
|
600
|
+
},
|
|
601
|
+
habitSession: {
|
|
602
|
+
runId: currentHabitRun.runId,
|
|
603
|
+
sessionPath: currentHabitRun.paths.sessionPath,
|
|
604
|
+
pendingDir: currentHabitRun.paths.pendingDir,
|
|
605
|
+
permissionEnvelope: currentHabitRun.permissionEnvelope,
|
|
606
|
+
toolPolicy: currentHabitRun.toolPolicy,
|
|
607
|
+
...(currentHabitRun.rsvpPolicy ? { rsvpPolicy: currentHabitRun.rsvpPolicy } : {}),
|
|
608
|
+
friendStore: currentHabitRun.friendStore,
|
|
609
|
+
recordProducedRef: (ref) => { currentHabitRun.producedRefs.push(ref); },
|
|
610
|
+
recordSurfaceAttempt: (attempt) => { currentHabitRun.surfaceAttempts.push(attempt); },
|
|
611
|
+
recordError: (error) => { currentHabitRun.errors.push(error); },
|
|
612
|
+
},
|
|
613
|
+
}
|
|
614
|
+
: {}),
|
|
615
|
+
};
|
|
616
|
+
turnResult = await runTurn(turnOptions);
|
|
617
|
+
}
|
|
601
618
|
}
|
|
602
|
-
|
|
603
|
-
catch (error) {
|
|
604
|
-
clearHeartbeatRestShield();
|
|
605
|
-
turnErrors.push(error instanceof Error ? error.message : String(error));
|
|
606
|
-
(0, runtime_1.emitNervesEvent)({
|
|
607
|
-
level: "error",
|
|
608
|
-
component: "senses",
|
|
609
|
-
event: "senses.private_runtime_worker_error",
|
|
610
|
-
message: "private-runtime worker turn failed",
|
|
611
|
-
meta: {
|
|
612
|
-
reason: nextReason,
|
|
613
|
-
error: error instanceof Error ? error.message : String(error),
|
|
614
|
-
},
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
currentExternalEventHeartbeat?.();
|
|
618
|
-
if (currentReason === "habit" && currentHabitName === "heartbeat") {
|
|
619
|
-
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
620
|
-
}
|
|
621
|
-
if (currentHabitRun) {
|
|
622
|
-
currentHabitRun.results.push(turnResult);
|
|
623
|
-
currentHabitRun.errors.push(...turnErrors);
|
|
624
|
-
}
|
|
625
|
-
// Drain queue first. Externally-queued work resets the instinct cap
|
|
626
|
-
// because a real outside trigger arrived between turns.
|
|
627
|
-
while (queue.length > 0) {
|
|
628
|
-
const next = queue.shift();
|
|
629
|
-
if (next.reason === "habit" && next.habitName === "heartbeat" && shouldReuseHeartbeatOkRest(next.habitName)) {
|
|
630
|
-
finalizeCurrentHabitRun();
|
|
631
|
-
await reuseHeartbeatOkRest(next.habitName);
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
if (!(next.reason === "habit" && next.habitName === "heartbeat")) {
|
|
619
|
+
catch (error) {
|
|
635
620
|
clearHeartbeatRestShield();
|
|
636
|
-
|
|
637
|
-
finalizeCurrentHabitRun();
|
|
638
|
-
nextReason = next.reason;
|
|
639
|
-
nextTaskId = next.taskId;
|
|
640
|
-
nextHabitName = next.habitName;
|
|
641
|
-
nextAwaitName = next.awaitName;
|
|
642
|
-
nextTrigger = next.trigger;
|
|
643
|
-
nextPrivateTurnDecision = next.privateTurnDecision;
|
|
644
|
-
nextExternalEvent = next.externalEvent;
|
|
645
|
-
nextExternalEventHeartbeat = next.stopExternalEventHeartbeat;
|
|
646
|
-
nextNoSend = next.reason === "habit" ? next.noSend : undefined;
|
|
647
|
-
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
648
|
-
continue runLoop;
|
|
649
|
-
}
|
|
650
|
-
if (blockedAutonomyTurn) {
|
|
651
|
-
finalizeCurrentHabitRun();
|
|
652
|
-
break;
|
|
653
|
-
}
|
|
654
|
-
// Then check hasPendingWork fallback. This is the loop site: any
|
|
655
|
-
// tool that writes to the private-runtime pending dir during a turn
|
|
656
|
-
// would cause hasPendingWork() to be true here, producing a
|
|
657
|
-
// self-sustaining "instinct" loop with no external input. Cap it.
|
|
658
|
-
if (hasPendingWork(currentHabitRun?.paths.pendingDir)) {
|
|
659
|
-
if (currentExternalEvent && (turnErrors.length > 0
|
|
660
|
-
|| (turnResult && typeof turnResult === "object" && "turnOutcome" in turnResult && turnResult.turnOutcome === "errored"))) {
|
|
661
|
-
finalizeCurrentHabitRun();
|
|
662
|
-
break;
|
|
663
|
-
}
|
|
664
|
-
clearHeartbeatRestShield();
|
|
665
|
-
if (consecutiveInstinctTurns >= exports.MAX_CONSECUTIVE_INSTINCT_TURNS) {
|
|
621
|
+
turnErrors.push(error instanceof Error ? error.message : String(error));
|
|
666
622
|
(0, runtime_1.emitNervesEvent)({
|
|
667
|
-
level: "
|
|
623
|
+
level: "error",
|
|
668
624
|
component: "senses",
|
|
669
|
-
event: "senses.
|
|
670
|
-
message: "private-runtime worker
|
|
625
|
+
event: "senses.private_runtime_worker_error",
|
|
626
|
+
message: "private-runtime worker turn failed",
|
|
671
627
|
meta: {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
lastReason: nextReason,
|
|
628
|
+
reason: nextReason,
|
|
629
|
+
error: error instanceof Error ? error.message : String(error),
|
|
675
630
|
},
|
|
676
631
|
});
|
|
677
|
-
finalizeCurrentHabitRun();
|
|
678
|
-
break;
|
|
679
632
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
633
|
+
currentExternalEventHeartbeat?.();
|
|
634
|
+
if (currentExternalEvent) {
|
|
635
|
+
const turnOutcome = turnResult && typeof turnResult === "object" && "turnOutcome" in turnResult
|
|
636
|
+
? turnResult.turnOutcome
|
|
637
|
+
: undefined;
|
|
638
|
+
const failureReason = turnErrors.length > 0 || turnOutcome === "errored"
|
|
639
|
+
? "private-runtime external-event turn failed"
|
|
640
|
+
: blockedAutonomyTurn || turnOutcome === "blocked" || turnOutcome === "suspended"
|
|
641
|
+
? "external-event turn was blocked"
|
|
642
|
+
: "external-event turn completed without a terminal disposition";
|
|
643
|
+
let settledIncomplete = false;
|
|
644
|
+
for (const member of [currentExternalEvent, ...(currentExternalEvent.relatedEvents ?? [])]) {
|
|
645
|
+
const result = (0, router_1.settleExternalEventFailureIfOwned)(member.recordPath, {
|
|
646
|
+
owner: member.claimOwner,
|
|
647
|
+
expectedGeneration: member.generation,
|
|
648
|
+
error: failureReason,
|
|
649
|
+
});
|
|
650
|
+
settledIncomplete = result.settled || settledIncomplete;
|
|
651
|
+
}
|
|
652
|
+
if (settledIncomplete && turnErrors.length === 0)
|
|
653
|
+
turnErrors.push(failureReason);
|
|
654
|
+
}
|
|
655
|
+
if (currentReason === "habit" && currentHabitName === "heartbeat") {
|
|
656
|
+
heartbeatOkRestedAt = isHeartbeatOkRestResult(turnResult) ? nowSource() : null;
|
|
657
|
+
}
|
|
658
|
+
if (currentHabitRun) {
|
|
659
|
+
currentHabitRun.results.push(turnResult);
|
|
660
|
+
currentHabitRun.errors.push(...turnErrors);
|
|
692
661
|
}
|
|
693
|
-
|
|
662
|
+
currentCompletion?.errors.push(...turnErrors);
|
|
663
|
+
const settleCurrentCompletion = () => {
|
|
664
|
+
/* v8 ignore next -- every run and queued entry is constructed with a completion @preserve */
|
|
665
|
+
if (!currentCompletion)
|
|
666
|
+
return;
|
|
667
|
+
if (currentCompletion.rejectOnError && currentCompletion.errors.length > 0)
|
|
668
|
+
currentCompletion.reject(new Error(currentCompletion.errors.join("; ")));
|
|
669
|
+
else
|
|
670
|
+
currentCompletion.resolve();
|
|
671
|
+
if (activeCompletion === currentCompletion)
|
|
672
|
+
activeCompletion = undefined;
|
|
673
|
+
};
|
|
674
|
+
// Drain queue first. Externally-queued work resets the instinct cap
|
|
675
|
+
// because a real outside trigger arrived between turns.
|
|
676
|
+
if (queue.length > 0)
|
|
677
|
+
settleCurrentCompletion();
|
|
678
|
+
while (queue.length > 0) {
|
|
679
|
+
const next = queue.shift();
|
|
680
|
+
activeCompletion = next.completion;
|
|
681
|
+
if (next.reason === "habit" && next.habitName === "heartbeat" && shouldReuseHeartbeatOkRest(next.habitName)) {
|
|
682
|
+
finalizeCurrentHabitRun();
|
|
683
|
+
await reuseHeartbeatOkRest(next.habitName);
|
|
684
|
+
next.completion.resolve();
|
|
685
|
+
/* v8 ignore next -- activeCompletion was assigned from this exact queue entry immediately above @preserve */
|
|
686
|
+
if (activeCompletion === next.completion)
|
|
687
|
+
activeCompletion = undefined;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (!(next.reason === "habit" && next.habitName === "heartbeat")) {
|
|
691
|
+
clearHeartbeatRestShield();
|
|
692
|
+
}
|
|
694
693
|
finalizeCurrentHabitRun();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
nextNoSend = undefined;
|
|
694
|
+
nextReason = next.reason;
|
|
695
|
+
nextTaskId = next.taskId;
|
|
696
|
+
nextHabitName = next.habitName;
|
|
697
|
+
nextAwaitName = next.awaitName;
|
|
698
|
+
nextTrigger = next.trigger;
|
|
699
|
+
nextPrivateTurnDecision = next.privateTurnDecision;
|
|
700
|
+
nextExternalEvent = next.externalEvent;
|
|
701
|
+
nextExternalEventHeartbeat = next.stopExternalEventHeartbeat;
|
|
702
|
+
nextCompletion = next.completion;
|
|
703
|
+
nextNoSend = next.reason === "habit" ? next.noSend : undefined;
|
|
704
|
+
consecutiveInstinctTurns = nextReason === "instinct" ? consecutiveInstinctTurns + 1 : 0;
|
|
705
|
+
continue runLoop;
|
|
705
706
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
707
|
+
if (blockedAutonomyTurn) {
|
|
708
|
+
finalizeCurrentHabitRun();
|
|
709
|
+
settleCurrentCompletion();
|
|
710
|
+
break;
|
|
711
|
+
}
|
|
712
|
+
// Then check hasPendingWork fallback. This is the loop site: any
|
|
713
|
+
// tool that writes to the private-runtime pending dir during a turn
|
|
714
|
+
// would cause hasPendingWork() to be true here, producing a
|
|
715
|
+
// self-sustaining "instinct" loop with no external input. Cap it.
|
|
716
|
+
if (hasPendingWork(currentHabitRun?.paths.pendingDir)) {
|
|
717
|
+
if (currentExternalEvent && (turnErrors.length > 0
|
|
718
|
+
|| (turnResult && typeof turnResult === "object" && "turnOutcome" in turnResult && turnResult.turnOutcome === "errored"))) {
|
|
719
|
+
finalizeCurrentHabitRun();
|
|
720
|
+
settleCurrentCompletion();
|
|
721
|
+
break;
|
|
722
|
+
}
|
|
723
|
+
clearHeartbeatRestShield();
|
|
724
|
+
if (consecutiveInstinctTurns >= exports.MAX_CONSECUTIVE_INSTINCT_TURNS) {
|
|
725
|
+
(0, runtime_1.emitNervesEvent)({
|
|
726
|
+
level: "warn",
|
|
727
|
+
component: "senses",
|
|
728
|
+
event: "senses.private_runtime_worker_instinct_loop_capped",
|
|
729
|
+
message: "private-runtime worker stopped chaining instinct turns; pending work remains for next external trigger",
|
|
730
|
+
meta: {
|
|
731
|
+
consecutiveInstinctTurns,
|
|
732
|
+
cap: exports.MAX_CONSECUTIVE_INSTINCT_TURNS,
|
|
733
|
+
lastReason: nextReason,
|
|
734
|
+
},
|
|
735
|
+
});
|
|
736
|
+
finalizeCurrentHabitRun();
|
|
737
|
+
settleCurrentCompletion();
|
|
738
|
+
break;
|
|
739
|
+
}
|
|
740
|
+
if (currentReason === "habit" && currentHabitName && currentHabitRun) {
|
|
741
|
+
consecutiveInstinctTurns += 1;
|
|
742
|
+
nextReason = "habit";
|
|
743
|
+
nextTaskId = undefined;
|
|
744
|
+
nextHabitName = currentHabitName;
|
|
745
|
+
nextAwaitName = undefined;
|
|
746
|
+
nextTrigger = currentTrigger;
|
|
747
|
+
nextPrivateTurnDecision = undefined;
|
|
748
|
+
nextExternalEvent = undefined;
|
|
749
|
+
nextExternalEventHeartbeat = undefined;
|
|
750
|
+
nextNoSend = currentNoSend;
|
|
751
|
+
nextHabitRun = currentHabitRun;
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
finalizeCurrentHabitRun();
|
|
755
|
+
consecutiveInstinctTurns += 1;
|
|
756
|
+
nextReason = "instinct";
|
|
757
|
+
nextTaskId = undefined;
|
|
758
|
+
nextHabitName = undefined;
|
|
759
|
+
nextAwaitName = undefined;
|
|
760
|
+
nextTrigger = undefined;
|
|
761
|
+
nextPrivateTurnDecision = undefined;
|
|
762
|
+
nextExternalEvent = undefined;
|
|
763
|
+
nextExternalEventHeartbeat = undefined;
|
|
764
|
+
nextNoSend = undefined;
|
|
765
|
+
}
|
|
766
|
+
nextCompletion = currentCompletion;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
finalizeCurrentHabitRun();
|
|
770
|
+
settleCurrentCompletion();
|
|
771
|
+
break;
|
|
772
|
+
} while (true);
|
|
773
|
+
}
|
|
774
|
+
finally {
|
|
775
|
+
running = false;
|
|
776
|
+
}
|
|
777
|
+
})().catch((error) => {
|
|
778
|
+
activeCompletion?.reject(error);
|
|
779
|
+
for (const queued of queue.splice(0)) {
|
|
780
|
+
queued.stopExternalEventHeartbeat?.();
|
|
781
|
+
queued.completion.reject(error);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
784
|
+
return completion.promise;
|
|
715
785
|
}
|
|
716
786
|
async function handleMessage(message) {
|
|
717
787
|
if (!message || typeof message !== "object")
|
|
@@ -725,7 +795,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
725
795
|
return;
|
|
726
796
|
}
|
|
727
797
|
recordHabitFireForRecursion(habitName);
|
|
728
|
-
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue", maybeMessage.privateTurnDecision, maybeMessage.noSend);
|
|
798
|
+
await run("habit", undefined, maybeMessage.habitName, undefined, maybeMessage.trigger ?? "overdue", maybeMessage.privateTurnDecision, maybeMessage.noSend, undefined, maybeMessage.dispatchId);
|
|
729
799
|
return;
|
|
730
800
|
}
|
|
731
801
|
if (maybeMessage.type === "await") {
|
|
@@ -733,7 +803,7 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
733
803
|
/* v8 ignore next -- defensive fallback: live await dispatch always sets awaitName @preserve */
|
|
734
804
|
const awaitName = maybeMessage.awaitName ?? "(unnamed)";
|
|
735
805
|
recordHabitFireForRecursion(`await:${awaitName}`);
|
|
736
|
-
await run("await", undefined, undefined, maybeMessage.awaitName, undefined, maybeMessage.privateTurnDecision);
|
|
806
|
+
await run("await", undefined, undefined, maybeMessage.awaitName, undefined, maybeMessage.privateTurnDecision, undefined, undefined, maybeMessage.dispatchId);
|
|
737
807
|
return;
|
|
738
808
|
}
|
|
739
809
|
if (maybeMessage.type === "heartbeat") {
|
|
@@ -743,25 +813,34 @@ function createPrivateRuntimeWorker(runTurn = (options) => (0, private_runtime_1
|
|
|
743
813
|
return;
|
|
744
814
|
}
|
|
745
815
|
recordHabitFireForRecursion("heartbeat");
|
|
746
|
-
await run("habit", undefined, "heartbeat", undefined, "overdue", maybeMessage.privateTurnDecision);
|
|
816
|
+
await run("habit", undefined, "heartbeat", undefined, "overdue", maybeMessage.privateTurnDecision, undefined, undefined, maybeMessage.dispatchId);
|
|
747
817
|
return;
|
|
748
818
|
}
|
|
749
819
|
if (maybeMessage.type === "poke") {
|
|
750
820
|
clearHeartbeatRestShield();
|
|
751
|
-
await run("instinct", maybeMessage.taskId, undefined, undefined, undefined, maybeMessage.privateTurnDecision);
|
|
821
|
+
await run("instinct", maybeMessage.taskId, undefined, undefined, undefined, maybeMessage.privateTurnDecision, undefined, undefined, maybeMessage.dispatchId);
|
|
752
822
|
return;
|
|
753
823
|
}
|
|
754
824
|
if (maybeMessage.type === "chat" ||
|
|
755
825
|
maybeMessage.type === "message") {
|
|
756
826
|
clearHeartbeatRestShield();
|
|
757
|
-
await run("instinct", undefined, undefined, undefined, undefined, maybeMessage.privateTurnDecision, undefined, maybeMessage.externalEvent);
|
|
827
|
+
await run("instinct", undefined, undefined, undefined, undefined, maybeMessage.privateTurnDecision, undefined, maybeMessage.externalEvent, maybeMessage.dispatchId);
|
|
758
828
|
return;
|
|
759
829
|
}
|
|
760
830
|
if (maybeMessage.type === "shutdown") {
|
|
761
831
|
process.exit(0);
|
|
762
832
|
}
|
|
763
833
|
}
|
|
764
|
-
|
|
834
|
+
function cancelMessage(dispatchId) {
|
|
835
|
+
const index = queue.findIndex((entry) => entry.dispatchId === dispatchId);
|
|
836
|
+
if (index < 0)
|
|
837
|
+
return false;
|
|
838
|
+
const [cancelled] = queue.splice(index, 1);
|
|
839
|
+
cancelled?.stopExternalEventHeartbeat?.();
|
|
840
|
+
cancelled?.completion.reject(new Error(`Private-runtime dispatch ${dispatchId} was cancelled.`));
|
|
841
|
+
return true;
|
|
842
|
+
}
|
|
843
|
+
return { run, handleMessage, cancelMessage };
|
|
765
844
|
}
|
|
766
845
|
async function startPrivateRuntimeWorker(options = {}) {
|
|
767
846
|
const worker = createPrivateRuntimeWorker();
|
|
@@ -837,10 +837,13 @@ function buildHeldReturnWakeMessage() {
|
|
|
837
837
|
}
|
|
838
838
|
function buildExternalEventLeaseMessage(event) {
|
|
839
839
|
const members = [event, ...(event.relatedEvents ?? [])];
|
|
840
|
+
const dispositionInstruction = members.length === 1
|
|
841
|
+
? "Investigate the current observation, then call external_event_disposition once for this lease before settling."
|
|
842
|
+
: "Investigate the current observations, then call external_event_disposition once with one bounded batch containing every listed lease before settling.";
|
|
840
843
|
return [
|
|
841
844
|
"[current external-event disposition contract]",
|
|
842
845
|
"This exact lease frame is authoritative for this turn. Ignore older receipts, failed tool arguments, held-work summaries, and checkpoints when choosing disposition arguments.",
|
|
843
|
-
|
|
846
|
+
dispositionInstruction,
|
|
844
847
|
...members.flatMap((member, index) => [
|
|
845
848
|
"",
|
|
846
849
|
`lease ${index + 1}:`,
|