@zq-silk/yui 0.7.0 → 0.7.1

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.
@@ -121,22 +121,26 @@ function appendUnique(existing, incoming, keyOf) {
121
121
  }
122
122
  return result;
123
123
  }
124
- function mergeBatches(earlier, later) {
124
+ function mergeBatches(left, right) {
125
125
  const merged = {
126
- fromSequence: earlier.fromSequence,
127
- toSequence: later.toSequence,
128
- reasons: appendUnique(earlier.reasons, later.reasons, (reason) => reason),
129
- refs: appendUnique(earlier.refs, later.refs, mailboxEntityRefKey),
130
- requestCount: earlier.requestCount + later.requestCount,
131
- firstQueuedAt: earlier.firstQueuedAt,
132
- lastQueuedAt: later.lastQueuedAt,
133
- sources: appendUnique(earlier.sources, later.sources, (source) => source),
134
- dedupeKeys: appendUnique(earlier.dedupeKeys, later.dedupeKeys, (key) => key),
135
- deliveryModes: appendUnique(earlier.deliveryModes, later.deliveryModes, (mode) => mode),
136
- ...((earlier.highestFactRevision ?? later.highestFactRevision) === undefined
126
+ fromSequence: Math.min(left.fromSequence, right.fromSequence),
127
+ toSequence: Math.max(left.toSequence, right.toSequence),
128
+ reasons: appendUnique(left.reasons, right.reasons, (reason) => reason),
129
+ refs: appendUnique(left.refs, right.refs, mailboxEntityRefKey),
130
+ requestCount: left.requestCount + right.requestCount,
131
+ firstQueuedAt: Date.parse(left.firstQueuedAt) <= Date.parse(right.firstQueuedAt)
132
+ ? left.firstQueuedAt
133
+ : right.firstQueuedAt,
134
+ lastQueuedAt: Date.parse(left.lastQueuedAt) >= Date.parse(right.lastQueuedAt)
135
+ ? left.lastQueuedAt
136
+ : right.lastQueuedAt,
137
+ sources: appendUnique(left.sources, right.sources, (source) => source),
138
+ dedupeKeys: appendUnique(left.dedupeKeys, right.dedupeKeys, (key) => key),
139
+ deliveryModes: appendUnique(left.deliveryModes, right.deliveryModes, (mode) => mode),
140
+ ...((left.highestFactRevision ?? right.highestFactRevision) === undefined
137
141
  ? {}
138
142
  : {
139
- highestFactRevision: Math.max(earlier.highestFactRevision ?? 0, later.highestFactRevision ?? 0)
143
+ highestFactRevision: Math.max(left.highestFactRevision ?? 0, right.highestFactRevision ?? 0)
140
144
  })
141
145
  };
142
146
  return merged;
@@ -196,17 +200,16 @@ export function validateWorkMailbox(value) {
196
200
  pending.userCorrection,
197
201
  inputDelivery?.batch
198
202
  ].filter((batch) => batch !== null && batch !== undefined);
203
+ const activeDedupeKeys = new Set();
199
204
  for (const batch of batches) {
200
205
  if (batch.toSequence >= nextSequence) {
201
206
  throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
202
207
  }
203
- }
204
- const ranges = batches
205
- .map((batch) => [batch.fromSequence, batch.toSequence])
206
- .sort((left, right) => left[0] - right[0]);
207
- for (let index = 1; index < ranges.length; index += 1) {
208
- if (ranges[index - 1][1] >= ranges[index][0]) {
209
- throw new Error("WorkMailbox batch sequences overlap");
208
+ for (const dedupeKey of batch.dedupeKeys) {
209
+ if (activeDedupeKeys.has(dedupeKey)) {
210
+ throw new Error("WorkMailbox active batches share a dedupe key");
211
+ }
212
+ activeDedupeKeys.add(dedupeKey);
210
213
  }
211
214
  }
212
215
  if (inputDelivery !== null && target.kind !== "role" && target.kind !== "operator") {
@@ -304,8 +307,8 @@ function parseBatch(value, label) {
304
307
  const fromSequence = requireInteger(batch.fromSequence, 1, `${label} fromSequence`);
305
308
  const toSequence = requireInteger(batch.toSequence, fromSequence, `${label} toSequence`);
306
309
  const requestCount = requireInteger(batch.requestCount, 1, `${label} requestCount`);
307
- if (requestCount !== toSequence - fromSequence + 1) {
308
- throw new Error(`${label} requestCount does not match its sequence range`);
310
+ if (requestCount > toSequence - fromSequence + 1) {
311
+ throw new Error(`${label} requestCount exceeds its sequence envelope`);
309
312
  }
310
313
  const reasons = requireStringArray(batch.reasons, `${label} reasons`);
311
314
  if (reasons.length === 0)
@@ -1923,13 +1923,20 @@ export class SqliteTaskStore {
1923
1923
  return rows.map((row) => this.#rowToMailbox(row));
1924
1924
  }
1925
1925
  saveWorkMailbox(mailbox) {
1926
- const cols = this.#mailboxCols(mailbox.target);
1926
+ let validated;
1927
+ try {
1928
+ validated = validateWorkMailbox(mailbox);
1929
+ }
1930
+ catch (error) {
1931
+ throw new StorageRecordError(error instanceof Error ? error.message : String(error));
1932
+ }
1933
+ const cols = this.#mailboxCols(validated.target);
1927
1934
  this.#mutate(() => {
1928
1935
  this.#db.prepare(`INSERT INTO mailboxes (target_kind, task_id, role_name, target_key, next_sequence, processing, pending, input_delivery)
1929
1936
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1930
1937
  ON CONFLICT(target_key) DO UPDATE SET next_sequence = excluded.next_sequence,
1931
1938
  processing = excluded.processing, pending = excluded.pending,
1932
- input_delivery = excluded.input_delivery`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey, mailbox.nextSequence, mailbox.processing === null ? null : this.#json(mailbox.processing), this.#json(mailbox.pending), mailbox.inputDelivery === null ? null : this.#json(mailbox.inputDelivery));
1939
+ input_delivery = excluded.input_delivery`).run(cols.targetKind, cols.taskId, cols.roleName, cols.targetKey, validated.nextSequence, validated.processing === null ? null : this.#json(validated.processing), this.#json(validated.pending), validated.inputDelivery === null ? null : this.#json(validated.inputDelivery));
1933
1940
  });
1934
1941
  }
1935
1942
  removeWorkMailbox(target) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zq-silk/yui",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Local control plane for long-running native agent CLI sessions backed by tmux.",
5
5
  "license": "MIT",
6
6
  "private": false,