@effect-agent/storage-memory 0.1.0-beta.114 → 0.1.0-beta.116

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.
@@ -24,7 +24,8 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
24
24
  const maxStoredValueBytes = yield* validateMessageDelivery(Schema.Int.check(Schema.isGreaterThan(0)), options.maxStoredValueBytes ?? 16777216, "stored-value-limit");
25
25
  const state = yield* Ref.make({
26
26
  records: /* @__PURE__ */ new Map(),
27
- pending: /* @__PURE__ */ new Map()
27
+ pending: /* @__PURE__ */ new Map(),
28
+ workers: /* @__PURE__ */ new Map()
28
29
  });
29
30
  const commit = (record, encoded) => Ref.update(state, (current) => {
30
31
  const key = messageDeliveryKeyString(record.key);
@@ -34,9 +35,26 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
34
35
  const pending = new Map(current.pending);
35
36
  if (keys.size === 0) pending.delete(record.key.ownerThreadId);
36
37
  else pending.set(record.key.ownerThreadId, keys);
38
+ const workers = new Map(current.workers);
39
+ const admission = record.envelope.workerAdmission;
40
+ if (admission !== void 0) {
41
+ const worker = admission.origin.worker;
42
+ const startKey = JSON.stringify([
43
+ record.key.ownerThreadId,
44
+ worker.delegationId,
45
+ worker.targetAgentId
46
+ ]);
47
+ if (record.key.messageId === admission.origin.firstMessageId) workers.set(startKey, /* @__PURE__ */ new Set([...workers.get(startKey) ?? [], record.key.messageId]));
48
+ const pendingKey = JSON.stringify([record.key.ownerThreadId, worker.threadId]);
49
+ const inputs = new Set(workers.get(pendingKey));
50
+ if ((record.status === "pending" || record.status === "parked") && record.receipt === null) inputs.add(record.key.messageId);
51
+ else inputs.delete(record.key.messageId);
52
+ workers.set(pendingKey, inputs);
53
+ }
37
54
  return {
38
55
  records: new Map(current.records).set(key, encoded),
39
- pending
56
+ pending,
57
+ workers
40
58
  };
41
59
  });
42
60
  const lock = yield* Semaphore.make(1);
@@ -124,7 +142,12 @@ const memoryMessageDeliveryStoreLayer = (limits = defaultMessageDeliveryStoreLim
124
142
  list: Effect.fn("MemoryMessageDeliveryStore.list")(function* (request) {
125
143
  const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, "list");
126
144
  const current = yield* Ref.get(state);
127
- const records = (input.pendingOnly ? yield* Effect.forEach([...current.pending.get(input.ownerThreadId) ?? []].filter((messageId) => input.after === void 0 || messageId > input.after).sort().slice(0, input.limit + 1), (messageId) => decode(current.records.get(messageDeliveryKeyString({
145
+ const workerKey = input.workerStarts !== void 0 ? JSON.stringify([
146
+ input.ownerThreadId,
147
+ input.workerStarts.delegationId,
148
+ input.workerStarts.targetAgentId
149
+ ]) : input.pendingWorker !== void 0 ? JSON.stringify([input.ownerThreadId, input.pendingWorker]) : void 0;
150
+ const records = (workerKey !== void 0 || input.pendingOnly ? yield* Effect.forEach([...(workerKey === void 0 ? current.pending.get(input.ownerThreadId) : current.workers.get(workerKey)) ?? []].filter((messageId) => input.after === void 0 || messageId > input.after).sort().slice(0, input.limit + 1), (messageId) => decode(current.records.get(messageDeliveryKeyString({
128
151
  ownerThreadId: input.ownerThreadId,
129
152
  messageId
130
153
  })) ?? "")) : yield* all()).filter((record) => record.key.ownerThreadId === input.ownerThreadId && (input.after === void 0 || record.key.messageId > input.after)).sort((left, right) => left.key.messageId < right.key.messageId ? -1 : left.key.messageId > right.key.messageId ? 1 : 0);
@@ -1 +1 @@
1
- {"version":3,"file":"MemoryMessageDeliveryStore.mjs","names":[],"sources":["../src/MemoryMessageDeliveryStore.ts"],"sourcesContent":["import { Effect, Layer, Ref, Schema, Semaphore } from \"effect\";\nimport { ThreadId } from \"effect-agent/identifiers\";\nimport {\n applyMessageDeliveryChange,\n defaultMessageDeliveryStoreLimits,\n MessageDeliveryChange,\n MessageDeliveryError,\n MessageDeliveryFailpoint,\n MessageDeliveryKey,\n MessageDeliveryPageRequest,\n MessageDeliveryRecord,\n MessageDeliveryStore,\n MessageDeliveryStoreLimits,\n messageDeliveryDeadline,\n messageDeliveryKeyString,\n messageDeliveryUsesCapacity,\n isWorkerUpdateDelivery,\n messageDeliveryCapacity,\n sameMessageDeliveryIdentity,\n validateMessageDelivery,\n} from \"effect-agent/message-delivery\";\nimport { ScheduleInstant } from \"effect-agent/schedule\";\n\nconst codec = Schema.fromJsonString(MessageDeliveryRecord);\n\nconst decode = (text: string) =>\n Schema.decodeEffect(codec)(text).pipe(\n Effect.mapError(() => MessageDeliveryError.make({ reason: \"corrupt\", operation: \"decode\" })),\n );\n\nconst Scan = Schema.Struct({\n nowMillis: ScheduleInstant,\n limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),\n ownerThreadId: Schema.optionalKey(ThreadId),\n});\n\nexport interface MemoryMessageDeliveryStoreOptions {\n /** UTF-8 bound on the complete stored record, including a processed Settlement. */\n readonly maxStoredValueBytes?: number;\n}\n\n/** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */\nexport const memoryMessageDeliveryStoreLayer = (\n limits: MessageDeliveryStoreLimits = defaultMessageDeliveryStoreLimits,\n options: MemoryMessageDeliveryStoreOptions = {},\n): Layer.Layer<MessageDeliveryStore, MessageDeliveryError> =>\n Layer.effect(\n MessageDeliveryStore,\n Effect.gen(function* () {\n const config = yield* validateMessageDelivery(MessageDeliveryStoreLimits, limits, \"limits\");\n\n const maxStoredValueBytes = yield* validateMessageDelivery(\n Schema.Int.check(Schema.isGreaterThan(0)),\n options.maxStoredValueBytes ?? 16 * 1_024 * 1_024,\n \"stored-value-limit\",\n );\n\n const state = yield* Ref.make({\n records: new Map<string, string>(),\n pending: new Map<ThreadId, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n });\n\n const commit = (record: MessageDeliveryRecord, encoded: string) =>\n Ref.update(state, (current) => {\n const key = messageDeliveryKeyString(record.key);\n const keys = new Set(current.pending.get(record.key.ownerThreadId));\n\n if (messageDeliveryUsesCapacity(record)) keys.add(record.key.messageId);\n else keys.delete(record.key.messageId);\n const pending = new Map(current.pending);\n\n if (keys.size === 0) pending.delete(record.key.ownerThreadId);\n else pending.set(record.key.ownerThreadId, keys);\n\n return { records: new Map(current.records).set(key, encoded), pending };\n });\n\n const lock = yield* Semaphore.make(1);\n const failpoint = yield* MessageDeliveryFailpoint;\n\n const encode = Effect.fn(\"MemoryMessageDeliveryStore.encode\")(function* (\n record: MessageDeliveryRecord,\n ) {\n const text = yield* Schema.encodeEffect(codec)(record).pipe(\n Effect.mapError(() =>\n MessageDeliveryError.make({ reason: \"corrupt\", operation: \"encode\" }),\n ),\n );\n\n if (new TextEncoder().encode(text).byteLength > maxStoredValueBytes) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"stored-value-bytes\",\n });\n }\n\n return text;\n });\n\n const all = Effect.fn(\"MemoryMessageDeliveryStore.all\")(function* () {\n return yield* Effect.forEach((yield* Ref.get(state)).records.values(), decode);\n });\n\n const insert: MessageDeliveryStore[\"Service\"][\"insert\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.insert\",\n )(function* (record) {\n const encoded = yield* encode(record);\n const input = yield* decode(encoded);\n\n if (\n input.version !== 1 ||\n input.status !== \"pending\" ||\n input.leaseUntilMillis !== null ||\n input.retry.attempts !== 0 ||\n input.retry.generation !== 0 ||\n input.retry.automaticAttempts !== 0 ||\n input.retry.nextAttemptAtMillis !== input.createdAtMillis ||\n input.retry.lastAttemptAtMillis !== null ||\n input.retry.lastFailure !== null ||\n input.deadlineAtMillis !== input.initialDeadlineAtMillis\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"validation\",\n operation: \"insert-state\",\n });\n }\n if (\n new TextEncoder().encode(JSON.stringify(input.envelope)).byteLength >\n config.maxEnvelopeBytes\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"envelope-bytes\",\n });\n }\n yield* failpoint.hit(\"message-delivery:insert:before\");\n\n const inserted = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const { records } = yield* Ref.get(state);\n const key = messageDeliveryKeyString(input.key);\n const existingText = records.get(key);\n\n if (existingText !== undefined) {\n const existing = yield* decode(existingText);\n\n if (!sameMessageDeliveryIdentity(existing, input))\n return yield* MessageDeliveryError.make({\n reason: \"conflict\",\n operation: \"insert\",\n });\n\n return existing;\n }\n\n const owned = (yield* all()).filter(\n (record) =>\n record.key.ownerThreadId === input.key.ownerThreadId &&\n isWorkerUpdateDelivery(record) === isWorkerUpdateDelivery(input),\n );\n\n const capacity = messageDeliveryCapacity(config, isWorkerUpdateDelivery(input));\n\n if (\n owned.length >= capacity.retained ||\n owned.filter(messageDeliveryUsesCapacity).length >= capacity.pending\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"insert\",\n });\n }\n yield* commit(input, encoded);\n\n return input;\n }),\n ),\n );\n\n yield* failpoint.hit(\"message-delivery:insert:after\");\n\n return yield* decode(yield* encode(inserted));\n });\n\n const get: MessageDeliveryStore[\"Service\"][\"get\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.get\",\n )(function* (key) {\n const input = yield* validateMessageDelivery(MessageDeliveryKey, key, \"get\");\n const text = (yield* Ref.get(state)).records.get(messageDeliveryKeyString(input));\n\n return text === undefined ? null : yield* decode(text);\n });\n\n const change: MessageDeliveryStore[\"Service\"][\"change\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.change\",\n )(function* (key, change) {\n const input = yield* validateMessageDelivery(MessageDeliveryChange, change, \"change\");\n const decodedKey = yield* validateMessageDelivery(MessageDeliveryKey, key, \"change\");\n const point = `message-delivery:${input._tag.toLowerCase()}`;\n\n yield* failpoint.hit(`${point}:before`);\n\n const changed = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const existing = yield* get(decodedKey);\n\n if (existing === null)\n return yield* MessageDeliveryError.make({\n reason: \"not-found\",\n operation: \"change\",\n });\n const next = yield* Effect.fromResult(applyMessageDeliveryChange(existing, input));\n const encoded = yield* encode(next);\n\n yield* commit(next, encoded);\n\n return next;\n }),\n ),\n );\n\n yield* failpoint.hit(`${point}:after`);\n\n return yield* decode(yield* encode(changed));\n });\n\n return MessageDeliveryStore.of({\n limits: config,\n maxStoredValueBytes,\n insert,\n get,\n change,\n list: Effect.fn(\"MemoryMessageDeliveryStore.list\")(function* (request) {\n const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, \"list\");\n\n const current = yield* Ref.get(state);\n\n const retained = input.pendingOnly\n ? yield* Effect.forEach(\n [...(current.pending.get(input.ownerThreadId) ?? [])]\n .filter((messageId) => input.after === undefined || messageId > input.after)\n .sort()\n .slice(0, input.limit + 1),\n (messageId) =>\n decode(\n current.records.get(\n messageDeliveryKeyString({ ownerThreadId: input.ownerThreadId, messageId }),\n ) ?? \"\",\n ),\n )\n : yield* all();\n\n const records = retained\n .filter(\n (record) =>\n record.key.ownerThreadId === input.ownerThreadId &&\n (input.after === undefined || record.key.messageId > input.after),\n )\n .sort((left, right) =>\n left.key.messageId < right.key.messageId\n ? -1\n : left.key.messageId > right.key.messageId\n ? 1\n : 0,\n );\n\n const items = records.slice(0, input.limit);\n\n return {\n items,\n next: records.length > input.limit ? (items.at(-1)?.key.messageId ?? null) : null,\n };\n }),\n due: Effect.fn(\"MemoryMessageDeliveryStore.due\")(\n function* (nowMillis, limit, ownerThreadId) {\n const input = yield* validateMessageDelivery(\n Scan,\n { nowMillis, limit, ...(ownerThreadId === undefined ? {} : { ownerThreadId }) },\n \"due\",\n );\n\n return (yield* all())\n .flatMap((record) => {\n const deadline = messageDeliveryDeadline(record);\n\n return deadline !== null &&\n deadline <= input.nowMillis &&\n (input.ownerThreadId === undefined ||\n input.ownerThreadId === record.key.ownerThreadId)\n ? [{ key: record.key, deadline }]\n : [];\n })\n .sort(\n (left, right) =>\n left.deadline - right.deadline ||\n (messageDeliveryKeyString(left.key) < messageDeliveryKeyString(right.key)\n ? -1\n : 1),\n )\n .slice(0, input.limit)\n .map((record) => record.key);\n },\n ),\n nextDeadline: Effect.fn(\"MemoryMessageDeliveryStore.nextDeadline\")(\n function* (ownerThreadId) {\n if (ownerThreadId !== undefined)\n yield* validateMessageDelivery(ThreadId, ownerThreadId, \"nextDeadline\");\n let next: number | null = null;\n\n for (const record of yield* all()) {\n if (ownerThreadId !== undefined && ownerThreadId !== record.key.ownerThreadId)\n continue;\n const deadline = messageDeliveryDeadline(record);\n\n if (deadline !== null && (next === null || deadline < next)) next = deadline;\n }\n\n return next;\n },\n ),\n });\n }),\n );\n\nexport const MemoryMessageDeliveryStoreLive = memoryMessageDeliveryStoreLayer();\n"],"mappings":"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"}
1
+ {"version":3,"file":"MemoryMessageDeliveryStore.mjs","names":[],"sources":["../src/MemoryMessageDeliveryStore.ts"],"sourcesContent":["import { Effect, Layer, Ref, Schema, Semaphore } from \"effect\";\nimport { ThreadId } from \"effect-agent/identifiers\";\nimport {\n applyMessageDeliveryChange,\n defaultMessageDeliveryStoreLimits,\n MessageDeliveryChange,\n MessageDeliveryError,\n MessageDeliveryFailpoint,\n MessageDeliveryKey,\n MessageDeliveryPageRequest,\n MessageDeliveryRecord,\n MessageDeliveryStore,\n MessageDeliveryStoreLimits,\n messageDeliveryDeadline,\n messageDeliveryKeyString,\n messageDeliveryUsesCapacity,\n isWorkerUpdateDelivery,\n messageDeliveryCapacity,\n sameMessageDeliveryIdentity,\n validateMessageDelivery,\n} from \"effect-agent/message-delivery\";\nimport { ScheduleInstant } from \"effect-agent/schedule\";\n\nconst codec = Schema.fromJsonString(MessageDeliveryRecord);\n\nconst decode = (text: string) =>\n Schema.decodeEffect(codec)(text).pipe(\n Effect.mapError(() => MessageDeliveryError.make({ reason: \"corrupt\", operation: \"decode\" })),\n );\n\nconst Scan = Schema.Struct({\n nowMillis: ScheduleInstant,\n limit: Schema.Int.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(100)),\n ownerThreadId: Schema.optionalKey(ThreadId),\n});\n\nexport interface MemoryMessageDeliveryStoreOptions {\n /** UTF-8 bound on the complete stored record, including a processed Settlement. */\n readonly maxStoredValueBytes?: number;\n}\n\n/** Reference adapter. All retained rows are bounded; completed deduplication evidence is never evicted. */\nexport const memoryMessageDeliveryStoreLayer = (\n limits: MessageDeliveryStoreLimits = defaultMessageDeliveryStoreLimits,\n options: MemoryMessageDeliveryStoreOptions = {},\n): Layer.Layer<MessageDeliveryStore, MessageDeliveryError> =>\n Layer.effect(\n MessageDeliveryStore,\n Effect.gen(function* () {\n const config = yield* validateMessageDelivery(MessageDeliveryStoreLimits, limits, \"limits\");\n\n const maxStoredValueBytes = yield* validateMessageDelivery(\n Schema.Int.check(Schema.isGreaterThan(0)),\n options.maxStoredValueBytes ?? 16 * 1_024 * 1_024,\n \"stored-value-limit\",\n );\n\n const state = yield* Ref.make({\n records: new Map<string, string>(),\n pending: new Map<ThreadId, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n workers: new Map<string, ReadonlySet<MessageDeliveryKey[\"messageId\"]>>(),\n });\n\n const commit = (record: MessageDeliveryRecord, encoded: string) =>\n Ref.update(state, (current) => {\n const key = messageDeliveryKeyString(record.key);\n const keys = new Set(current.pending.get(record.key.ownerThreadId));\n\n if (messageDeliveryUsesCapacity(record)) keys.add(record.key.messageId);\n else keys.delete(record.key.messageId);\n const pending = new Map(current.pending);\n\n if (keys.size === 0) pending.delete(record.key.ownerThreadId);\n else pending.set(record.key.ownerThreadId, keys);\n\n const workers = new Map(current.workers);\n const admission = record.envelope.workerAdmission;\n\n if (admission !== undefined) {\n const worker = admission.origin.worker;\n\n const startKey = JSON.stringify([\n record.key.ownerThreadId,\n worker.delegationId,\n worker.targetAgentId,\n ]);\n\n if (record.key.messageId === admission.origin.firstMessageId)\n workers.set(\n startKey,\n new Set([...(workers.get(startKey) ?? []), record.key.messageId]),\n );\n const pendingKey = JSON.stringify([record.key.ownerThreadId, worker.threadId]);\n const inputs = new Set(workers.get(pendingKey));\n\n if (\n (record.status === \"pending\" || record.status === \"parked\") &&\n record.receipt === null\n )\n inputs.add(record.key.messageId);\n else inputs.delete(record.key.messageId);\n workers.set(pendingKey, inputs);\n }\n\n return { records: new Map(current.records).set(key, encoded), pending, workers };\n });\n\n const lock = yield* Semaphore.make(1);\n const failpoint = yield* MessageDeliveryFailpoint;\n\n const encode = Effect.fn(\"MemoryMessageDeliveryStore.encode\")(function* (\n record: MessageDeliveryRecord,\n ) {\n const text = yield* Schema.encodeEffect(codec)(record).pipe(\n Effect.mapError(() =>\n MessageDeliveryError.make({ reason: \"corrupt\", operation: \"encode\" }),\n ),\n );\n\n if (new TextEncoder().encode(text).byteLength > maxStoredValueBytes) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"stored-value-bytes\",\n });\n }\n\n return text;\n });\n\n const all = Effect.fn(\"MemoryMessageDeliveryStore.all\")(function* () {\n return yield* Effect.forEach((yield* Ref.get(state)).records.values(), decode);\n });\n\n const insert: MessageDeliveryStore[\"Service\"][\"insert\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.insert\",\n )(function* (record) {\n const encoded = yield* encode(record);\n const input = yield* decode(encoded);\n\n if (\n input.version !== 1 ||\n input.status !== \"pending\" ||\n input.leaseUntilMillis !== null ||\n input.retry.attempts !== 0 ||\n input.retry.generation !== 0 ||\n input.retry.automaticAttempts !== 0 ||\n input.retry.nextAttemptAtMillis !== input.createdAtMillis ||\n input.retry.lastAttemptAtMillis !== null ||\n input.retry.lastFailure !== null ||\n input.deadlineAtMillis !== input.initialDeadlineAtMillis\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"validation\",\n operation: \"insert-state\",\n });\n }\n if (\n new TextEncoder().encode(JSON.stringify(input.envelope)).byteLength >\n config.maxEnvelopeBytes\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"envelope-bytes\",\n });\n }\n yield* failpoint.hit(\"message-delivery:insert:before\");\n\n const inserted = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const { records } = yield* Ref.get(state);\n const key = messageDeliveryKeyString(input.key);\n const existingText = records.get(key);\n\n if (existingText !== undefined) {\n const existing = yield* decode(existingText);\n\n if (!sameMessageDeliveryIdentity(existing, input))\n return yield* MessageDeliveryError.make({\n reason: \"conflict\",\n operation: \"insert\",\n });\n\n return existing;\n }\n\n const owned = (yield* all()).filter(\n (record) =>\n record.key.ownerThreadId === input.key.ownerThreadId &&\n isWorkerUpdateDelivery(record) === isWorkerUpdateDelivery(input),\n );\n\n const capacity = messageDeliveryCapacity(config, isWorkerUpdateDelivery(input));\n\n if (\n owned.length >= capacity.retained ||\n owned.filter(messageDeliveryUsesCapacity).length >= capacity.pending\n ) {\n return yield* MessageDeliveryError.make({\n reason: \"capacity\",\n operation: \"insert\",\n });\n }\n yield* commit(input, encoded);\n\n return input;\n }),\n ),\n );\n\n yield* failpoint.hit(\"message-delivery:insert:after\");\n\n return yield* decode(yield* encode(inserted));\n });\n\n const get: MessageDeliveryStore[\"Service\"][\"get\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.get\",\n )(function* (key) {\n const input = yield* validateMessageDelivery(MessageDeliveryKey, key, \"get\");\n const text = (yield* Ref.get(state)).records.get(messageDeliveryKeyString(input));\n\n return text === undefined ? null : yield* decode(text);\n });\n\n const change: MessageDeliveryStore[\"Service\"][\"change\"] = Effect.fn(\n \"MemoryMessageDeliveryStore.change\",\n )(function* (key, change) {\n const input = yield* validateMessageDelivery(MessageDeliveryChange, change, \"change\");\n const decodedKey = yield* validateMessageDelivery(MessageDeliveryKey, key, \"change\");\n const point = `message-delivery:${input._tag.toLowerCase()}`;\n\n yield* failpoint.hit(`${point}:before`);\n\n const changed = yield* lock.withPermit(\n Effect.uninterruptible(\n Effect.gen(function* () {\n const existing = yield* get(decodedKey);\n\n if (existing === null)\n return yield* MessageDeliveryError.make({\n reason: \"not-found\",\n operation: \"change\",\n });\n const next = yield* Effect.fromResult(applyMessageDeliveryChange(existing, input));\n const encoded = yield* encode(next);\n\n yield* commit(next, encoded);\n\n return next;\n }),\n ),\n );\n\n yield* failpoint.hit(`${point}:after`);\n\n return yield* decode(yield* encode(changed));\n });\n\n return MessageDeliveryStore.of({\n limits: config,\n maxStoredValueBytes,\n insert,\n get,\n change,\n list: Effect.fn(\"MemoryMessageDeliveryStore.list\")(function* (request) {\n const input = yield* validateMessageDelivery(MessageDeliveryPageRequest, request, \"list\");\n\n const current = yield* Ref.get(state);\n\n const workerKey =\n input.workerStarts !== undefined\n ? JSON.stringify([\n input.ownerThreadId,\n input.workerStarts.delegationId,\n input.workerStarts.targetAgentId,\n ])\n : input.pendingWorker !== undefined\n ? JSON.stringify([input.ownerThreadId, input.pendingWorker])\n : undefined;\n\n const retained =\n workerKey !== undefined || input.pendingOnly\n ? yield* Effect.forEach(\n [\n ...((workerKey === undefined\n ? current.pending.get(input.ownerThreadId)\n : current.workers.get(workerKey)) ?? []),\n ]\n .filter((messageId) => input.after === undefined || messageId > input.after)\n .sort()\n .slice(0, input.limit + 1),\n (messageId) =>\n decode(\n current.records.get(\n messageDeliveryKeyString({ ownerThreadId: input.ownerThreadId, messageId }),\n ) ?? \"\",\n ),\n )\n : yield* all();\n\n const records = retained\n .filter(\n (record) =>\n record.key.ownerThreadId === input.ownerThreadId &&\n (input.after === undefined || record.key.messageId > input.after),\n )\n .sort((left, right) =>\n left.key.messageId < right.key.messageId\n ? -1\n : left.key.messageId > right.key.messageId\n ? 1\n : 0,\n );\n\n const items = records.slice(0, input.limit);\n\n return {\n items,\n next: records.length > input.limit ? (items.at(-1)?.key.messageId ?? null) : null,\n };\n }),\n due: Effect.fn(\"MemoryMessageDeliveryStore.due\")(\n function* (nowMillis, limit, ownerThreadId) {\n const input = yield* validateMessageDelivery(\n Scan,\n { nowMillis, limit, ...(ownerThreadId === undefined ? {} : { ownerThreadId }) },\n \"due\",\n );\n\n return (yield* all())\n .flatMap((record) => {\n const deadline = messageDeliveryDeadline(record);\n\n return deadline !== null &&\n deadline <= input.nowMillis &&\n (input.ownerThreadId === undefined ||\n input.ownerThreadId === record.key.ownerThreadId)\n ? [{ key: record.key, deadline }]\n : [];\n })\n .sort(\n (left, right) =>\n left.deadline - right.deadline ||\n (messageDeliveryKeyString(left.key) < messageDeliveryKeyString(right.key)\n ? -1\n : 1),\n )\n .slice(0, input.limit)\n .map((record) => record.key);\n },\n ),\n nextDeadline: Effect.fn(\"MemoryMessageDeliveryStore.nextDeadline\")(\n function* (ownerThreadId) {\n if (ownerThreadId !== undefined)\n yield* validateMessageDelivery(ThreadId, ownerThreadId, \"nextDeadline\");\n let next: number | null = null;\n\n for (const record of yield* all()) {\n if (ownerThreadId !== undefined && ownerThreadId !== record.key.ownerThreadId)\n continue;\n const deadline = messageDeliveryDeadline(record);\n\n if (deadline !== null && (next === null || deadline < next)) next = deadline;\n }\n\n return next;\n },\n ),\n });\n }),\n );\n\nexport const MemoryMessageDeliveryStoreLive = memoryMessageDeliveryStoreLayer();\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { Cause, Clock, DateTime, Duration, Effect, Exit, Fiber, Layer, Option, R
3
3
  import { AttemptId, ReceiptId, SubmissionId } from "effect-agent/identifiers";
4
4
  import { InputMessage } from "effect-agent/messaging";
5
5
  import { PersistedJson, ProducerEpoch, WorkerAdmission } from "effect-agent/records";
6
- import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, settlementFailureFromRecord } from "effect-agent/submission-ledger";
6
+ import { AbortCommand, AbortIntent, AbortIntentRequest, AdmissionAdmitted, AdmissionConflict, AdmissionIndeterminate, AdmissionNotAdmitted, AdmissionPolicyError, AdmissionRequest, AdmissionResult, ApprovalConflict, ApprovalDecisionCommand, ApprovalDecisionIntent, AttachChildToReservationRequest, BeginChildBudgetReleaseRequest, ChildAttachmentSnapshot, ChildBudgetReservationRequest, ChildBudgetReservationSnapshot, ChildReservationConflict, ChildSettledNotification, Claim, ClaimJoiningRequest, ClaimRequest, DEFAULT_OWNERSHIP_LEASE_DURATION, InputAppliedMarker, JoinSnapshot, JoinedToHost, JoiningClaim, LedgerCapabilities, LedgerError, MarkInputAppliedRequest, MarkJoinedRequest, MarkReadyRequest, MarkUnknownRequest, OwnershipLost, OwnershipRenewal, OwnershipSnapshot, OwnershipToken, QueueSequence, RecoverySnapshot, RecoverySnapshotRequest, ReleaseChildBudgetRequest, ReleaseOwnershipRequest, RenewOwnershipRequest, ReservedChildBudget, ReservedSettlement, RevertJoiningRequest, Settlement, SettlementConflict, SettlementFinalization, SettlementReservation, SettlementReservationSnapshot, SubmissionAdmissionFence, SubmissionLedger, SubmissionLookup, SubmissionLookupByKey, SubmissionSnapshot, SubmissionWorkItem, SuspendRequest, SuspensionSnapshot, UnknownResolution, UnknownResolutionCommand, UnknownResolutionConflict, UnknownResolutionIntent, WorkerLedgerState, WorkerStopCommand, settlementFailureFromRecord, workerTerminalFromRecord } from "effect-agent/submission-ledger";
7
7
  //#region src/MemorySubmissionLedger.ts
8
8
  var MemorySubmissionLedger_exports = /* @__PURE__ */ __exportAll({
9
9
  MemorySubmissionLedgerLive: () => MemorySubmissionLedgerLive,
@@ -112,10 +112,16 @@ const ownershipLost = (state, stored) => OwnershipLost.make({
112
112
  });
113
113
  /** A retained row token cannot outlive a newer owner of the same Thread. */
114
114
  const ownsLane = (state, stored, ownershipToken) => stored.ownership !== void 0 && stored.ownership.ownershipToken === ownershipToken && stored.ownership.producerEpoch === laneEpoch(state, stored.row.threadId);
115
- const withSubmission = (state, stored) => ({
116
- ...state,
117
- submissions: new Map(state.submissions).set(stored.row.submissionId, stored)
118
- });
115
+ const withSubmission = (state, stored) => {
116
+ const active = new Set(state.activeByThread.get(stored.row.threadId));
117
+ if (stored.row.state === "settled") active.delete(stored.row.submissionId);
118
+ else active.add(stored.row.submissionId);
119
+ return {
120
+ ...state,
121
+ submissions: new Map(state.submissions).set(stored.row.submissionId, stored),
122
+ activeByThread: new Map(state.activeByThread).set(stored.row.threadId, active)
123
+ };
124
+ };
119
125
  const withChildReservation = (state, reservation) => ({
120
126
  ...state,
121
127
  childReservations: new Map(state.childReservations).set(reservation.reservationId, reservation)
@@ -168,7 +174,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
168
174
  admissionIndex: /* @__PURE__ */ new Map(),
169
175
  lanes: /* @__PURE__ */ new Map(),
170
176
  childReservations: /* @__PURE__ */ new Map(),
171
- mintCounter: 0
177
+ mintCounter: 0,
178
+ stoppedWorkers: /* @__PURE__ */ new Map(),
179
+ latestByThread: /* @__PURE__ */ new Map(),
180
+ activeByThread: /* @__PURE__ */ new Map()
172
181
  });
173
182
  const admissionFence = yield* SubmissionAdmissionFence;
174
183
  const leaseMillis = Duration.toMillis(DEFAULT_OWNERSHIP_LEASE_DURATION);
@@ -200,6 +209,10 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
200
209
  replayed: true
201
210
  })), current];
202
211
  }
212
+ if (current.stoppedWorkers.has(request.threadId)) return [failure(AdmissionPolicyError.make({
213
+ reason: "refused",
214
+ code: "worker-stopped"
215
+ })), current];
203
216
  const first = [...current.submissions.values()].find(({ row }) => row.threadId === request.threadId);
204
217
  if (first !== void 0) {
205
218
  const previous = first.row.workerAdmissionJson === void 0 ? void 0 : Schema.decodeSync(Schema.fromJsonString(WorkerAdmission))(first.row.workerAdmissionJson);
@@ -273,7 +286,9 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
273
286
  submissions,
274
287
  admissionIndex,
275
288
  lanes,
276
- mintCounter
289
+ mintCounter,
290
+ latestByThread: new Map(current.latestByThread).set(request.threadId, row.submissionId),
291
+ activeByThread: new Map(current.activeByThread).set(request.threadId, /* @__PURE__ */ new Set([...current.activeByThread.get(request.threadId) ?? [], row.submissionId]))
277
292
  }];
278
293
  });
279
294
  if (decision._tag === "failure") return yield* decision.error;
@@ -506,7 +521,33 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
506
521
  ...settlementFailure === void 0 ? {} : { failure: settlementFailure },
507
522
  settledAt: utc(reservation.finalizedAtMillis)
508
523
  })), current];
509
- const next = withSubmission(current, {
524
+ const terminal = stored.row.workerAdmissionJson === void 0 ? void 0 : workerTerminalFromRecord(toSnapshot(stored.row), reservation.record);
525
+ const latestId = current.latestByThread.get(stored.row.threadId);
526
+ const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
527
+ const pendingNewer = terminal === "completed" && latest !== void 0 && latest.row.queueSequence > stored.row.queueSequence && !(latest.joinedHostSubmissionId === stored.row.submissionId && latest.inputApplied !== void 0);
528
+ let sealed = current;
529
+ if (terminal !== void 0 && !pendingNewer && !current.stoppedWorkers.has(stored.row.threadId)) {
530
+ const submissions = new Map(current.submissions);
531
+ for (const id of current.activeByThread.get(stored.row.threadId) ?? []) {
532
+ const other = submissions.get(id);
533
+ if (other === void 0) continue;
534
+ if (id !== stored.row.submissionId && !(other.joinedHostSubmissionId === stored.row.submissionId && other.inputApplied !== void 0) && other.abortIntent === void 0) submissions.set(id, {
535
+ ...other,
536
+ abortIntent: AbortIntent.make({
537
+ submissionId: id,
538
+ author: stored.row.principal,
539
+ reason: `Worker assignment ${terminal}`,
540
+ requestedAt: utc(nowMillis)
541
+ })
542
+ });
543
+ }
544
+ sealed = {
545
+ ...current,
546
+ submissions,
547
+ stoppedWorkers: new Map(current.stoppedWorkers).set(stored.row.threadId, terminal)
548
+ };
549
+ }
550
+ const next = withSubmission(sealed, {
510
551
  ...stored,
511
552
  row: {
512
553
  ...stored.row,
@@ -531,6 +572,49 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
531
572
  if (decision._tag === "failure") return yield* decision.error;
532
573
  return decision.value;
533
574
  }));
575
+ const inspectWorker = Effect.fn("MemorySubmissionLedger.inspectWorker")(function* (threadId) {
576
+ const current = yield* Ref.get(state);
577
+ const latestId = current.latestByThread.get(threadId);
578
+ const latest = latestId === void 0 ? void 0 : current.submissions.get(latestId);
579
+ const active = [...current.activeByThread.get(threadId) ?? []].flatMap((id) => {
580
+ const row = current.submissions.get(id);
581
+ return row === void 0 ? [] : [row];
582
+ }).sort((a, b) => a.row.queueSequence - b.row.queueSequence)[0];
583
+ const terminal = current.stoppedWorkers.get(threadId);
584
+ return WorkerLedgerState.make({
585
+ latest: latest === void 0 ? null : toSnapshot(latest.row),
586
+ active: active === void 0 ? null : toSnapshot(active.row),
587
+ stopped: current.stoppedWorkers.has(threadId),
588
+ ...terminal === void 0 ? {} : { terminal }
589
+ });
590
+ });
591
+ const stopWorker = Effect.fn("MemorySubmissionLedger.stopWorker")(function* (unvalidated) {
592
+ const request = yield* validate(WorkerStopCommand, "stopWorker", unvalidated);
593
+ const now = yield* Clock.currentTimeMillis;
594
+ return yield* Ref.modify(state, (current) => {
595
+ const submissions = new Map(current.submissions);
596
+ let owned = 0;
597
+ for (const id of current.activeByThread.get(request.threadId) ?? []) {
598
+ const stored = submissions.get(id);
599
+ if (stored === void 0) continue;
600
+ if (stored.ownership !== void 0) owned++;
601
+ if (stored.abortIntent === void 0) submissions.set(id, {
602
+ ...stored,
603
+ abortIntent: AbortIntent.make({
604
+ submissionId: id,
605
+ author: request.author,
606
+ reason: "Worker owner stopped the worker",
607
+ requestedAt: utc(now)
608
+ })
609
+ });
610
+ }
611
+ return [owned, {
612
+ ...current,
613
+ submissions,
614
+ stoppedWorkers: current.stoppedWorkers.has(request.threadId) ? current.stoppedWorkers : new Map(current.stoppedWorkers).set(request.threadId, void 0)
615
+ }];
616
+ });
617
+ });
534
618
  const requestAbort = Effect.fn("MemorySubmissionLedger.requestAbort")((unvalidated) => Effect.gen(function* () {
535
619
  const request = yield* validate(AbortCommand, "requestAbort", unvalidated);
536
620
  const nowMillis = yield* Clock.currentTimeMillis;
@@ -569,6 +653,7 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
569
653
  const claimJoining = Effect.fn("MemorySubmissionLedger.claimJoining")((unvalidated) => Effect.gen(function* () {
570
654
  const request = yield* validate(ClaimJoiningRequest, "claimJoining", unvalidated);
571
655
  const decision = yield* Ref.modify(state, (current) => {
656
+ if (current.stoppedWorkers.has(request.threadId)) return [success([]), current];
572
657
  const host = current.submissions.get(request.hostSubmissionId);
573
658
  if (host === void 0) return [failure(ledgerError("claimJoining", `Unknown Submission ${request.hostSubmissionId}`)), current];
574
659
  if (host.row.threadId !== request.threadId) return [failure(ledgerError("claimJoining", `Host Submission ${request.hostSubmissionId} does not belong to Thread ${request.threadId}`)), current];
@@ -1038,6 +1123,8 @@ const makeSubmissionLedger = (options = {}) => Effect.gen(function* () {
1038
1123
  reserveSettlement,
1039
1124
  finalizeSettlement,
1040
1125
  requestAbort,
1126
+ stopWorker,
1127
+ inspectWorker,
1041
1128
  claimJoining,
1042
1129
  markJoined,
1043
1130
  revertJoining,