@sema-agent/core 7.56.0 → 7.57.0

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 (173) hide show
  1. package/CHANGELOG.md +541 -0
  2. package/LICENSE +1 -1
  3. package/dist/agents/org-delivery-gate.js +3 -4
  4. package/dist/agents/peer-held-queue.d.ts +69 -49
  5. package/dist/agents/peer-held-queue.js +255 -106
  6. package/dist/agents/peer-session-drain.js +63 -43
  7. package/dist/agents/send-message-tool.js +3 -2
  8. package/dist/agents/subagent.js +8 -8
  9. package/dist/brain/anthropic.js +16 -8
  10. package/dist/brain/open-responses.js +10 -8
  11. package/dist/brain/openai.js +11 -9
  12. package/dist/brain/provider-dialect.d.ts +3 -2
  13. package/dist/brain/provider-dialect.js +2 -2
  14. package/dist/brain/request-params.d.ts +6 -0
  15. package/dist/brain/request-params.js +14 -1
  16. package/dist/brain/stream-engine.d.ts +9 -0
  17. package/dist/brain/stream-engine.js +24 -10
  18. package/dist/brain/stream-shared.d.ts +5 -4
  19. package/dist/brain/stream-shared.js +4 -4
  20. package/dist/core/allow-entry.d.ts +3 -10
  21. package/dist/core/allow-entry.js +17 -25
  22. package/dist/core/approval-object.d.ts +21 -5
  23. package/dist/core/approval-object.js +44 -17
  24. package/dist/core/ask-origin.d.ts +2 -2
  25. package/dist/core/auto-compaction.d.ts +71 -6
  26. package/dist/core/auto-compaction.js +42 -26
  27. package/dist/core/background-agent-store.d.ts +6 -2
  28. package/dist/core/background-agent-store.js +1 -1
  29. package/dist/core/checkpoint-requirements.d.ts +7 -1
  30. package/dist/core/checkpoint-requirements.js +2 -0
  31. package/dist/core/checkpoint-store.d.ts +17 -29
  32. package/dist/core/checkpoint-store.js +19 -11
  33. package/dist/core/cross-boundary-taint.js +4 -2
  34. package/dist/core/engine-notice.d.ts +3 -2
  35. package/dist/core/engine-notice.js +6 -0
  36. package/dist/core/file-history-store.d.ts +14 -8
  37. package/dist/core/file-history-store.js +14 -4
  38. package/dist/core/gate-lanes.js +2 -3
  39. package/dist/core/hooks.d.ts +2 -2
  40. package/dist/core/mailbox-store.d.ts +62 -1
  41. package/dist/core/mailbox-store.js +59 -0
  42. package/dist/core/mcp-name-judge.d.ts +46 -0
  43. package/dist/core/mcp-name-judge.js +61 -0
  44. package/dist/core/mcp-probe.d.ts +1 -1
  45. package/dist/core/mcp-probe.js +2 -5
  46. package/dist/core/mcp.js +10 -30
  47. package/dist/core/memory-engine/consolidation-driver.js +4 -3
  48. package/dist/core/memory-engine/distiller.js +26 -17
  49. package/dist/core/memory-engine/layout.js +5 -2
  50. package/dist/core/park-selfcheck.js +2 -0
  51. package/dist/core/permission-rule-consent.d.ts +5 -3
  52. package/dist/core/permission-rule-consent.js +20 -15
  53. package/dist/core/permission-rule-model.d.ts +43 -2
  54. package/dist/core/permission-rule-model.js +52 -8
  55. package/dist/core/permission-rule-org.js +1 -1
  56. package/dist/core/protocol-http-auth.d.ts +16 -2
  57. package/dist/core/protocol-http-auth.js +42 -11
  58. package/dist/core/protocol-naming.d.ts +20 -0
  59. package/dist/core/protocol-naming.js +20 -3
  60. package/dist/core/read-only-shell.d.ts +20 -5
  61. package/dist/core/read-only-shell.js +29 -14
  62. package/dist/core/remote-env.d.ts +37 -1
  63. package/dist/core/remote-env.js +11 -0
  64. package/dist/core/resume-startup-cause.d.ts +24 -2
  65. package/dist/core/resume-startup-cause.js +20 -1
  66. package/dist/core/roles.js +50 -7
  67. package/dist/core/runner/compact-session.d.ts +10 -0
  68. package/dist/core/runner/compact-session.js +202 -0
  69. package/dist/core/runner/compaction-call-options.d.ts +2 -2
  70. package/dist/core/runner/compaction-seams.d.ts +21 -1
  71. package/dist/core/runner/compaction-seams.js +11 -8
  72. package/dist/core/runner/contracts.d.ts +23 -16
  73. package/dist/core/runner/derived-route-fallback.d.ts +13 -0
  74. package/dist/core/runner/derived-route-fallback.js +6 -0
  75. package/dist/core/runner/env-capability-census.d.ts +3 -2
  76. package/dist/core/runner/env-capability-census.js +9 -3
  77. package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
  78. package/dist/core/runner/permission-rule-lanes.js +17 -11
  79. package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
  80. package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
  81. package/dist/core/runner/prepare-config-doors.d.ts +6 -0
  82. package/dist/core/runner/prepare-config-doors.js +4 -3
  83. package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
  84. package/dist/core/runner/prepare-hands-readface.js +2 -0
  85. package/dist/core/runner/prepare-park-ask.d.ts +2 -0
  86. package/dist/core/runner/prepare-park-ask.js +2 -2
  87. package/dist/core/runner/prepare-policy-chain.js +2 -4
  88. package/dist/core/runner/prepare-task.d.ts +4 -1
  89. package/dist/core/runner/prepare-task.js +23 -27
  90. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
  91. package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
  92. package/dist/core/runner/rewind-files.d.ts +2 -5
  93. package/dist/core/runner/rewind-files.js +9 -51
  94. package/dist/core/runner/run-leg.js +10 -21
  95. package/dist/core/runner/run-notification-lane.d.ts +2 -0
  96. package/dist/core/runner/run-notification-lane.js +2 -2
  97. package/dist/core/runner/run-recovery-lanes.js +3 -4
  98. package/dist/core/runner/run-settle-and-teardown.js +5 -11
  99. package/dist/core/runner/run-terminal-adoption.js +3 -4
  100. package/dist/core/runner/run-turn-boundary.js +3 -5
  101. package/dist/core/runner/runtask.d.ts +8 -1
  102. package/dist/core/runner/runtask.js +28 -34
  103. package/dist/core/runner/send-message-continuation.js +1 -1
  104. package/dist/core/runner/session-verb-doors.d.ts +28 -0
  105. package/dist/core/runner/session-verb-doors.js +48 -0
  106. package/dist/core/runner/session-write-ledger.d.ts +30 -0
  107. package/dist/core/runner/session-write-ledger.js +101 -0
  108. package/dist/core/runner/stream-settle-backstop.js +11 -14
  109. package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
  110. package/dist/core/runner/sub-agent-deny-lists.js +21 -10
  111. package/dist/core/runner/teardown-bounded.d.ts +1 -0
  112. package/dist/core/runner/teardown-bounded.js +1 -0
  113. package/dist/core/runner-deps.d.ts +2 -2
  114. package/dist/core/secret-env.d.ts +4 -0
  115. package/dist/core/secret-env.js +3 -0
  116. package/dist/core/session-policy-store.d.ts +7 -2
  117. package/dist/core/session-policy-store.js +1 -1
  118. package/dist/core/shell-wrapper-projection.d.ts +1 -1
  119. package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
  120. package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
  121. package/dist/core/store-contracts/file-history-store-contract.js +9 -0
  122. package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
  123. package/dist/core/task-registry-agent.d.ts +8 -4
  124. package/dist/core/task-registry-agent.js +14 -8
  125. package/dist/core/task-registry-shared.d.ts +35 -6
  126. package/dist/core/task-registry-shared.js +11 -0
  127. package/dist/core/task-registry.d.ts +1 -1
  128. package/dist/core/task-registry.js +1 -1
  129. package/dist/core/tool-catalog-entries.d.ts +6 -0
  130. package/dist/core/tool-catalog-entries.js +1 -0
  131. package/dist/core/tool-name-aliases.d.ts +32 -2
  132. package/dist/core/tool-name-aliases.js +23 -0
  133. package/dist/core/tool-name-readings.d.ts +4 -0
  134. package/dist/core/tool-name-readings.js +1 -0
  135. package/dist/core/tool-policy.d.ts +10 -6
  136. package/dist/core/tool-policy.js +4 -7
  137. package/dist/core/trace.d.ts +4 -1
  138. package/dist/core/trace.js +2 -1
  139. package/dist/core/untrusted-egress.d.ts +20 -0
  140. package/dist/core/untrusted-egress.js +77 -3
  141. package/dist/engine/harness/agent-harness.d.ts +7 -5
  142. package/dist/engine/harness/agent-harness.js +11 -2
  143. package/dist/engine/loop/agent-loop.d.ts +1 -4
  144. package/dist/engine/loop/agent-loop.js +26 -4
  145. package/dist/index.d.ts +12 -7
  146. package/dist/index.js +7 -4
  147. package/dist/internal/harness.d.ts +2 -1
  148. package/dist/internal/harness.js +1 -0
  149. package/dist/primitives/credential-ref.d.ts +89 -0
  150. package/dist/primitives/credential-ref.js +125 -0
  151. package/dist/primitives/index.d.ts +2 -0
  152. package/dist/primitives/index.js +1 -0
  153. package/dist/stores/file/checkpoint-store.d.ts +0 -4
  154. package/dist/stores/file/checkpoint-store.js +0 -1
  155. package/dist/stores/file/file-history-store.js +12 -4
  156. package/dist/stores/file/mailbox-store.d.ts +2 -1
  157. package/dist/stores/file/mailbox-store.js +26 -1
  158. package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
  159. package/dist/tools/fs/bash-readonly-classifier.js +5 -10
  160. package/dist/tools/fs/fs-bash.js +10 -3
  161. package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
  162. package/dist/tools/fs/pattern-kill-guard.js +68 -36
  163. package/dist/tools/fs/shell-read-check.d.ts +10 -0
  164. package/dist/tools/fs/shell-read-check.js +7 -1
  165. package/dist/tools/fs/shell-read-identity.d.ts +12 -9
  166. package/dist/tools/fs/shell-read-identity.js +9 -4
  167. package/dist/tools/web.js +9 -7
  168. package/package.json +1 -1
  169. package/test/export-surface.snapshot.json +79 -3
  170. package/wire-changes/core-stores-sql@7.57.0.json +29 -0
  171. package/wire-changes/core@7.57.0.json +1487 -0
  172. package/wire-changes/mods-kernel@7.57.0.json +29 -0
  173. package/wire-changes/primitives@7.57.0.json +245 -0
@@ -1,3 +1,4 @@
1
+ import { isMailboxDecisionDeliverable, isMailboxDecisionFinal } from "../core/mailbox-store.js";
1
2
  import { PEER_HELD_QUEUE_CAP, PEER_HELD_REVIEW_CAUSES } from "./peer-notices.js";
2
3
  import { peerSessionBoxHandle } from "./peer-directory.js";
3
4
  export const realPeerClock = {
@@ -10,14 +11,23 @@ export const realPeerClock = {
10
11
  clearTimeout: (h) => clearTimeout(h),
11
12
  };
12
13
  const crashHandlers = new WeakMap();
13
- export const PEER_HELD_DECISION_MEMORY = PEER_HELD_QUEUE_CAP * 4;
14
14
  export function createPeerHeldQueue(key, opts = {}) {
15
15
  const cap = opts.cap ?? PEER_HELD_QUEUE_CAP;
16
16
  const clock = opts.clock ?? realPeerClock;
17
17
  const entries = [];
18
18
  const deadlines = new Map();
19
- const decisions = new Map();
20
19
  let cfg;
20
+ const cardChains = new Map();
21
+ const onCard = (seq, f) => {
22
+ const run = (cardChains.get(seq) ?? Promise.resolve()).then(f);
23
+ const tail = run.then(() => undefined, () => undefined);
24
+ cardChains.set(seq, tail);
25
+ void tail.then(() => {
26
+ if (cardChains.get(seq) === tail)
27
+ cardChains.delete(seq);
28
+ });
29
+ return run;
30
+ };
21
31
  const sinkOrThrow = () => {
22
32
  if (cfg === undefined)
23
33
  throw new Error("PeerHeldQueue: not configured — bind a run (configure) before holding messages");
@@ -43,103 +53,199 @@ export function createPeerHeldQueue(key, opts = {}) {
43
53
  entries.splice(i, 1);
44
54
  clearDeadline(entry.seq);
45
55
  };
46
- const settleOut = (entry, settlement, reason, receipt) => {
56
+ const leave = (entry, settlement, reason, receipt) => {
47
57
  const sink = sinkOrThrow();
48
58
  remove(entry);
49
- if (settlement !== "delivered") {
50
- decisions.set(entry.seq, settlement);
51
- while (decisions.size > PEER_HELD_DECISION_MEMORY) {
52
- const oldest = decisions.keys().next();
53
- if (oldest.done === true)
54
- break;
55
- decisions.delete(oldest.value);
56
- }
57
- }
58
59
  if (receipt !== undefined)
59
60
  safe(() => sink.receipt(entry, receipt));
60
61
  safe(() => sink.onSettled?.(entry, settlement, reason, entries.length));
61
62
  };
63
+ const move = async (entry, next) => {
64
+ const sink = sinkOrThrow();
65
+ let outcome;
66
+ try {
67
+ outcome = await sink.decide(entry, entry.decision, next);
68
+ }
69
+ catch (e) {
70
+ safe(() => sink.onFault?.(entry, e));
71
+ return "fault";
72
+ }
73
+ if (outcome.ok)
74
+ return "won";
75
+ adoptCurrent(entry, outcome.current);
76
+ return "lost";
77
+ };
78
+ const adoptCurrent = (entry, current) => {
79
+ if (!entries.includes(entry))
80
+ return;
81
+ if (current === "gone" || current === "unset")
82
+ return leave(entry, "retired", "superseded", undefined);
83
+ if (isMailboxDecisionFinal(current))
84
+ return leave(entry, current, "decided_elsewhere", undefined);
85
+ if (isMailboxDecisionDeliverable(current) && entry.decision !== current) {
86
+ entry.decision = current;
87
+ clearDeadline(entry.seq);
88
+ }
89
+ };
90
+ const settleLocal = async (entry, settlement, reason) => {
91
+ const sink = sinkOrThrow();
92
+ let tells = false;
93
+ try {
94
+ tells = sink.tellsSender(entry);
95
+ }
96
+ catch {
97
+ tells = false;
98
+ }
99
+ const from = settlement === "expired" ? "held" : "delivered";
100
+ if (!tells || entry.decision !== from)
101
+ return leave(entry, "retired", reason, undefined);
102
+ const r = await move(entry, settlement);
103
+ if (r === "won")
104
+ leave(entry, settlement, reason, settlement);
105
+ else if (r === "fault")
106
+ leave(entry, "retired", reason, undefined);
107
+ };
62
108
  const armDeadline = (entry) => {
63
109
  if (cfg === undefined || cfg.dialogExpiryMs === null || deadlines.has(entry.seq))
64
110
  return;
65
- if (entry.released === true)
111
+ if (entry.decision !== "held")
66
112
  return;
67
113
  if (!PEER_HELD_REVIEW_CAUSES.includes(entry.cause))
68
114
  return;
69
115
  const h = clock.setTimeout(() => {
70
116
  deadlines.delete(entry.seq);
71
- if (!entries.includes(entry))
72
- return;
73
- rejudge();
74
- if (!entries.includes(entry))
75
- return;
76
- settleOut(entry, "expired", "deadline", "expired");
117
+ void (async () => {
118
+ if (!entries.includes(entry))
119
+ return;
120
+ await rejudge();
121
+ await onCard(entry.seq, async () => {
122
+ if (!entries.includes(entry) || entry.decision !== "held")
123
+ return;
124
+ if ((await move(entry, "expired")) === "won")
125
+ leave(entry, "expired", "deadline", "expired");
126
+ });
127
+ })().catch(() => undefined);
77
128
  }, cfg.dialogExpiryMs);
78
129
  deadlines.set(entry.seq, h);
79
130
  };
80
- const tryDeliver = (entry, reason) => {
131
+ const mayHandOver = (entry) => {
81
132
  const sink = sinkOrThrow();
82
133
  if (sink.inject === undefined)
134
+ return false;
135
+ try {
136
+ return sink.canInject === undefined || sink.canInject(entry);
137
+ }
138
+ catch {
139
+ return false;
140
+ }
141
+ };
142
+ const tryDeliver = async (entry, reason) => {
143
+ const sink = sinkOrThrow();
144
+ if (sink.inject === undefined || !mayHandOver(entry))
83
145
  return "no_leg";
146
+ if (entry.decision !== "delivered") {
147
+ const r = await move(entry, "delivered");
148
+ if (r === "lost")
149
+ return "lost";
150
+ if (r === "fault")
151
+ return "no_leg";
152
+ entry.decision = "delivered";
153
+ clearDeadline(entry.seq);
154
+ }
84
155
  try {
85
156
  if (sink.inject(entry) === "deferred")
86
157
  return "no_leg";
87
158
  }
88
159
  catch {
89
- settleOut(entry, "dropped", "inject_failed", "dropped");
160
+ await settleLocal(entry, "dropped", "inject_failed");
90
161
  return "dropped";
91
162
  }
92
- settleOut(entry, "delivered", reason, "delivered");
163
+ leave(entry, "delivered", reason, "delivered");
93
164
  return "delivered";
94
165
  };
95
- const reconcile = (present) => {
166
+ const refuseCard = async (entry, reason) => {
167
+ const owed = entry.decision === "delivered";
168
+ if (owed && !mayHandOver(entry))
169
+ return "waits";
170
+ const word = owed ? "dropped" : "refused";
171
+ const r = await move(entry, word);
172
+ if (r === "won")
173
+ leave(entry, word, reason, word);
174
+ return r;
175
+ };
176
+ const rejudgeOne = async (entry, reannounce) => {
177
+ const sink = sinkOrThrow();
178
+ let verdict;
179
+ try {
180
+ verdict = sink.judge(entry);
181
+ }
182
+ catch {
183
+ return "held";
184
+ }
185
+ if (verdict.verdict === "refuse")
186
+ return (await refuseCard(entry, "rejudge")) === "won" ? "refused" : "held";
187
+ if (verdict.verdict === "deliver" || isMailboxDecisionDeliverable(entry.decision)) {
188
+ const r = await tryDeliver(entry, entry.decision === "approved" ? "approved" : "rejudge");
189
+ return r === "delivered" ? "released" : "held";
190
+ }
191
+ if (verdict.verdict === "hold" && verdict.cause !== entry.cause) {
192
+ entry.cause = verdict.cause;
193
+ reannounce.push([entry, verdict.cause]);
194
+ clearDeadline(entry.seq);
195
+ armDeadline(entry);
196
+ }
197
+ return "held";
198
+ };
199
+ const rejudge = async () => {
200
+ sinkOrThrow();
201
+ let released = 0;
202
+ let refused = 0;
203
+ const reannounce = [];
204
+ for (const entry of [...entries]) {
205
+ const r = await onCard(entry.seq, async () => (entries.includes(entry) ? rejudgeOne(entry, reannounce) : "held"));
206
+ if (r === "released")
207
+ released += 1;
208
+ else if (r === "refused")
209
+ refused += 1;
210
+ }
96
211
  const sink = sinkOrThrow();
212
+ for (const [entry, cause] of reannounce)
213
+ if (entries.includes(entry))
214
+ safe(() => sink.onHeld?.(entry, entries.length, cause, true));
215
+ return { released, refused, held: entries.length };
216
+ };
217
+ const reconcile = (snapshot) => {
218
+ sinkOrThrow();
97
219
  let retired = 0;
98
220
  for (const entry of [...entries]) {
99
- if (present.has(entry.seq))
221
+ if (cardChains.has(entry.seq))
100
222
  continue;
101
- remove(entry);
102
- retired += 1;
103
- safe(() => sink.onSettled?.(entry, "expired", "superseded", entries.length));
223
+ const present = snapshot.has(entry.seq);
224
+ const before = entries.length;
225
+ adoptCurrent(entry, present ? (snapshot.get(entry.seq) ?? "unset") : "gone");
226
+ if (entries.length < before)
227
+ retired += 1;
104
228
  }
105
229
  return retired;
106
230
  };
107
- const rejudge = () => {
231
+ const makeRoom = async () => {
232
+ if (entries.length < cap)
233
+ return true;
108
234
  const sink = sinkOrThrow();
109
- let released = 0;
110
- let refused = 0;
111
- const reannounce = [];
112
- for (const entry of [...entries]) {
113
- let verdict;
235
+ const victim = entries.find((e) => {
236
+ if (e.decision !== "held")
237
+ return false;
114
238
  try {
115
- verdict = sink.judge(entry);
239
+ return sink.tellsSender(e);
116
240
  }
117
241
  catch {
118
- continue;
242
+ return false;
119
243
  }
120
- if (verdict.verdict === "refuse") {
121
- settleOut(entry, "refused", "rejudge", "refused");
122
- refused += 1;
123
- continue;
124
- }
125
- if (verdict.verdict === "deliver" || entry.released === true) {
126
- const r = tryDeliver(entry, entry.released === true ? "approved" : "rejudge");
127
- if (r === "no_leg")
128
- entry.released = true;
129
- else if (r === "delivered")
130
- released += 1;
131
- continue;
132
- }
133
- if (verdict.cause !== entry.cause) {
134
- entry.cause = verdict.cause;
135
- reannounce.push([entry, verdict.cause]);
136
- clearDeadline(entry.seq);
137
- armDeadline(entry);
138
- }
139
- }
140
- for (const [entry, cause] of reannounce)
141
- safe(() => sink.onHeld?.(entry, entries.length, cause, true));
142
- return { released, refused, held: entries.length };
244
+ });
245
+ if (victim === undefined)
246
+ return false;
247
+ await onCard(victim.seq, async () => (entries.includes(victim) ? settleLocal(victim, "expired", "evicted") : undefined));
248
+ return entries.length < cap;
143
249
  };
144
250
  const queue = {
145
251
  key,
@@ -160,63 +266,55 @@ export function createPeerHeldQueue(key, opts = {}) {
160
266
  cfg = { ...cfg, sink: rest };
161
267
  },
162
268
  configured: () => cfg !== undefined,
163
- hold(input) {
269
+ async hold(input, known) {
164
270
  const sink = sinkOrThrow();
165
271
  if (entries.some((e) => e.box === input.box && e.seq === input.seq))
166
- return;
167
- const entry = { ...input, heldAt: clock.now() };
168
- if (entries.length >= cap) {
169
- const oldest = entries[0];
170
- settleOut(oldest, "expired", "evicted", "expired");
272
+ return "adopted";
273
+ let state = known ?? "held";
274
+ let fresh = false;
275
+ if (known === undefined) {
276
+ let outcome;
277
+ try {
278
+ outcome = await sink.decide({ ...input, heldAt: clock.now(), decision: "held" }, undefined, "held");
279
+ }
280
+ catch (e) {
281
+ safe(() => sink.onFault?.({ ...input, heldAt: clock.now(), decision: "held" }, e));
282
+ return "fault";
283
+ }
284
+ if (outcome.ok)
285
+ fresh = true;
286
+ else if (outcome.current === "gone" || outcome.current === "unset")
287
+ return "gone";
288
+ else if (isMailboxDecisionFinal(outcome.current))
289
+ return outcome.current;
290
+ else
291
+ state = outcome.current;
292
+ }
293
+ if (entries.some((e) => e.box === input.box && e.seq === input.seq))
294
+ return fresh ? "held" : "adopted";
295
+ if (!(await makeRoom())) {
296
+ if (fresh)
297
+ safe(() => sink.receipt({ ...input, heldAt: clock.now(), decision: state }, "held"));
298
+ return "uncarded";
171
299
  }
300
+ const entry = { ...input, heldAt: clock.now(), decision: state };
172
301
  entries.push(entry);
173
302
  armDeadline(entry);
174
- safe(() => sink.onHeld?.(entry, entries.length, entry.cause, false));
175
- safe(() => sink.receipt(entry, "held"));
303
+ if (state === "held")
304
+ safe(() => sink.onHeld?.(entry, entries.length, entry.cause, false));
305
+ if (fresh)
306
+ safe(() => sink.receipt(entry, "held"));
307
+ return fresh ? "held" : "adopted";
176
308
  },
177
309
  rejudge,
178
310
  reconcile,
179
311
  settle(seq, decision) {
180
- const sink = sinkOrThrow();
181
- const entry = entries.find((e) => e.seq === seq);
182
- if (entry === undefined)
183
- return "gone";
184
- if (decision === "deny") {
185
- settleOut(entry, "denied", "denied", "denied");
186
- return "denied";
187
- }
188
- if (decision === "cancel") {
189
- settleOut(entry, "expired", "cancelled", "expired");
190
- return "expired";
191
- }
192
- let verdict;
193
- try {
194
- verdict = sink.judge(entry);
195
- }
196
- catch {
197
- entry.released = true;
198
- return "released_pending";
199
- }
200
- if (verdict.verdict === "refuse") {
201
- settleOut(entry, "refused", "approved", "refused");
202
- return "dropped_by_policy";
203
- }
204
- const r = tryDeliver(entry, "approved");
205
- if (r === "no_leg") {
206
- entry.released = true;
207
- clearDeadline(entry.seq);
208
- return "released_pending";
209
- }
210
- return r;
312
+ return onCard(seq, () => settleCard(seq, decision));
211
313
  },
314
+ has: (seq) => entries.some((e) => e.seq === seq),
212
315
  list: () => entries.map((e) => ({ ...e, ...(e.hopChain !== undefined ? { hopChain: [...e.hopChain] } : {}), ...(e.peerMeta !== undefined ? { peerMeta: { ...e.peerMeta } } : {}) })),
213
316
  size: () => entries.length,
214
- decisions: () => decisions,
215
- forgetDecisions(seqs) {
216
- for (const seq of seqs)
217
- decisions.delete(seq);
218
- },
219
- shutdown() {
317
+ async shutdown() {
220
318
  if (cfg === undefined) {
221
319
  entries.length = 0;
222
320
  for (const h of deadlines.values())
@@ -224,12 +322,63 @@ export function createPeerHeldQueue(key, opts = {}) {
224
322
  deadlines.clear();
225
323
  return;
226
324
  }
227
- for (const entry of [...entries])
228
- settleOut(entry, "expired", "shutdown", "expired");
325
+ for (const entry of [...entries]) {
326
+ await onCard(entry.seq, async () => {
327
+ if (entries.includes(entry))
328
+ await settleLocal(entry, "expired", "shutdown");
329
+ });
330
+ }
229
331
  },
230
332
  };
333
+ async function settleCard(seq, decision) {
334
+ const sink = sinkOrThrow();
335
+ const entry = entries.find((e) => e.seq === seq);
336
+ if (entry === undefined)
337
+ return "gone";
338
+ const handedOver = entry.decision === "delivered";
339
+ if (decision === "deny" || decision === "cancel") {
340
+ if (handedOver)
341
+ return "delivered";
342
+ const word = decision === "deny" ? "denied" : "expired";
343
+ const r = await move(entry, word);
344
+ if (r === "fault")
345
+ throw new Error(`PeerHeldQueue.settle: the ${decision} of seq ${seq} could not be recorded (the store refused the move); nothing was decided`);
346
+ if (r === "lost")
347
+ return "decided_elsewhere";
348
+ leave(entry, word, decision === "deny" ? "denied" : "cancelled", word);
349
+ return word;
350
+ }
351
+ let verdict;
352
+ try {
353
+ verdict = sink.judge(entry);
354
+ }
355
+ catch {
356
+ verdict = undefined;
357
+ }
358
+ if (verdict?.verdict === "refuse") {
359
+ const r = await refuseCard(entry, "approved");
360
+ if (r === "fault")
361
+ throw new Error(`PeerHeldQueue.settle: the refusal of seq ${seq} could not be recorded (the store refused the move)`);
362
+ if (r !== "waits")
363
+ return r === "lost" ? "decided_elsewhere" : "dropped_by_policy";
364
+ }
365
+ if (handedOver)
366
+ return "delivered";
367
+ if (entry.decision === "held") {
368
+ const r = await move(entry, "approved");
369
+ if (r === "fault")
370
+ throw new Error(`PeerHeldQueue.settle: the approval of seq ${seq} could not be recorded (the store refused the move); nothing was decided`);
371
+ if (r === "lost")
372
+ return "decided_elsewhere";
373
+ entry.decision = "approved";
374
+ clearDeadline(entry.seq);
375
+ }
376
+ if (verdict === undefined)
377
+ return "released_pending";
378
+ const d = await tryDeliver(entry, "approved");
379
+ return d === "no_leg" ? "released_pending" : d === "lost" ? "decided_elsewhere" : d;
380
+ }
231
381
  crashHandlers.set(queue, () => {
232
- decisions.clear();
233
382
  entries.length = 0;
234
383
  for (const h of deadlines.values())
235
384
  clock.clearTimeout(h);