@frockbot/kernel-agent-loop 0.3.10 → 0.3.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.
Files changed (2) hide show
  1. package/package.json +5 -5
  2. package/src/index.ts +31 -39
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frockbot/kernel-agent-loop",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -12,13 +12,13 @@
12
12
  "typecheck": "tsc --noEmit -p tsconfig.json"
13
13
  },
14
14
  "dependencies": {
15
- "@frockbot/kernel-contracts": "0.3.10",
15
+ "@frockbot/kernel-contracts": "0.3.12",
16
16
  "cordis": "4.0.0-rc.8"
17
17
  },
18
18
  "devDependencies": {
19
- "@frockbot/plugin-models": "0.3.10",
20
- "@frockbot/plugin-prompt": "0.3.10",
21
- "@frockbot/plugin-tools": "0.3.10",
19
+ "@frockbot/plugin-models": "0.3.12",
20
+ "@frockbot/plugin-prompt": "0.3.12",
21
+ "@frockbot/plugin-tools": "0.3.12",
22
22
  "@types/bun": "1.4.0",
23
23
  "@types/node": "26.2.0",
24
24
  "typescript": "^7.0.2"
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  type ToolCallOccurrence,
23
23
  type ToolExecutionResult,
24
24
  type TurnTypeV1,
25
+ TURN_DEADLINE_MS_V1,
25
26
  toolCallOccurrences,
26
27
  turnEndReason,
27
28
  validateSettledToolOccurrenceJournal,
@@ -112,12 +113,12 @@ class StepLimitReachedError extends Error {
112
113
  /**
113
114
  * The longest a single Turn may run before the loop stops waiting for it.
114
115
  *
115
- * Nothing bounded a Turn's wall clock before this: one hung for seventeen
116
- * minutes with an animated avatar and nothing else, and would have hung until
117
- * the isolate died. Fifteen minutes is well past any Turn a person is watching
118
- * and well inside the point at which they have concluded the product is broken.
116
+ * Defined in the contracts, because the loop is not its only reader: anything
117
+ * deciding whether a run still marked `running` can still be running needs the
118
+ * same number. Re-exported here because this is where every caller looks for
119
+ * it.
119
120
  */
120
- export const TURN_DEADLINE_MS_V1 = 15 * 60 * 1000;
121
+ export { TURN_DEADLINE_MS_V1 };
121
122
 
122
123
  /**
123
124
  * How many times one step will send its model request.
@@ -686,24 +687,8 @@ class LoopAgent implements Agent {
686
687
  throw new StepLimitReachedError(this.#maxSteps);
687
688
  } catch (error) {
688
689
  if (this.#turnDeadlineReached) {
689
- // Ahead of both the reconciliation and the cancellation branch on
690
- // purpose. Cancellation, because the deadline aborts the same
691
- // controller Stop does and a Turn the clock ended must not be reported
692
- // to the person as one they stopped. Reconciliation, because the
693
- // reconciliation branch writes no `turn/end` on the promise that the
694
- // run may still resume — and a run the deadline stopped never will.
695
- // Deferring to it left `model/reconciliation-required` as the last
696
- // event of an open Turn, and every later Turn on that Bot refused with
697
- // `409` for the life of the Bot.
698
- //
699
- // The uncertainty is still recorded: whatever the model request wrote
700
- // before the clock ran out stays in the journal. What changes is that
701
- // the Turn is settled here — the open step's tool occurrences closed
702
- // as `interrupted`, then `step/end` and `turn/end` — exactly as
703
- // `kernel-do`'s `settledEventsV1` settles a Stop or a supersede.
704
690
  turnOutcome = "interrupted";
705
- turnReason = turnEndReason(TURN_DEADLINE_REASON_V1);
706
- this.#ctx.emit("agent/error", this, error);
691
+ turnReason = this.#deadlineTurnReason(error);
707
692
  } else if (
708
693
  error instanceof ModelEffectReconciliationRequiredError ||
709
694
  error instanceof ToolEffectReconciliationRequiredError ||
@@ -886,24 +871,8 @@ class LoopAgent implements Agent {
886
871
  throw new StepLimitReachedError(this.#maxSteps);
887
872
  } catch (error) {
888
873
  if (this.#turnDeadlineReached) {
889
- // Ahead of both the reconciliation and the cancellation branch on
890
- // purpose. Cancellation, because the deadline aborts the same
891
- // controller Stop does and a Turn the clock ended must not be reported
892
- // to the person as one they stopped. Reconciliation, because the
893
- // reconciliation branch writes no `turn/end` on the promise that the
894
- // run may still resume — and a run the deadline stopped never will.
895
- // Deferring to it left `model/reconciliation-required` as the last
896
- // event of an open Turn, and every later Turn on that Bot refused with
897
- // `409` for the life of the Bot.
898
- //
899
- // The uncertainty is still recorded: whatever the model request wrote
900
- // before the clock ran out stays in the journal. What changes is that
901
- // the Turn is settled here — the open step's tool occurrences closed
902
- // as `interrupted`, then `step/end` and `turn/end` — exactly as
903
- // `kernel-do`'s `settledEventsV1` settles a Stop or a supersede.
904
874
  turnOutcome = "interrupted";
905
- turnReason = turnEndReason(TURN_DEADLINE_REASON_V1);
906
- this.#ctx.emit("agent/error", this, error);
875
+ turnReason = this.#deadlineTurnReason(error);
907
876
  } else if (
908
877
  error instanceof ModelEffectReconciliationRequiredError ||
909
878
  error instanceof ToolEffectReconciliationRequiredError ||
@@ -1421,6 +1390,29 @@ class LoopAgent implements Agent {
1421
1390
  return decision.kind === "stop";
1422
1391
  }
1423
1392
 
1393
+ /**
1394
+ * The reason a Turn the clock ended carries, and the one place that decides
1395
+ * a deadline is not a cancellation and not a reconciliation.
1396
+ *
1397
+ * Its branch runs ahead of both. Ahead of cancellation, because the deadline
1398
+ * aborts the same controller Stop does and a Turn the clock ended must not
1399
+ * be reported to the person as one they stopped. Ahead of reconciliation,
1400
+ * because that branch writes no `turn/end` on the promise the run may still
1401
+ * resume — and a run the deadline stopped never will. Deferring to it left
1402
+ * `model/reconciliation-required` as the last event of an open Turn, and
1403
+ * every later Turn on that Bot refused with `409` for the life of the Bot.
1404
+ *
1405
+ * The uncertainty is still recorded: whatever the model request wrote before
1406
+ * the clock ran out stays in the journal. What changes is that the Turn is
1407
+ * settled — the open step's tool occurrences closed as `interrupted`, then
1408
+ * `step/end` and `turn/end` — exactly as `kernel-do`'s `settledEventsV1`
1409
+ * settles a Stop or a supersede.
1410
+ */
1411
+ #deadlineTurnReason(error: unknown): string | undefined {
1412
+ this.#ctx.emit("agent/error", this, error);
1413
+ return turnEndReason(TURN_DEADLINE_REASON_V1);
1414
+ }
1415
+
1424
1416
  async #settleCancelledStep(turn: number, step: number): Promise<void> {
1425
1417
  const assistant = this.session.events.findLast(
1426
1418
  (event) =>