@rivus/agent 0.14.4 → 0.15.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.
@@ -1,14 +1,14 @@
1
1
  import { _ as createAgentLoopToolExecutionStart, a as createTextAgentLoopFromCallback, c as toEffectAgentLoop, d as createAgentLoopModelExecutionStart, f as createAgentLoopSkillExecutionEnd, g as createAgentLoopToolExecutionEnd, h as createAgentLoopThinkingDelta, l as toEffectAgentLoopInput, m as createAgentLoopTextDelta, o as fromEffectAgentLoop, p as createAgentLoopSkillExecutionStart, s as toCompatibilityAgentLoopInput, t as createAgentLoopFromCallback, u as createAgentLoopModelExecutionEnd, v as createAgentLoopToolExecutionUpdate } from "./agent-loop.js";
2
+ import { d as resolveInvocationAuthority, i as requiresToolApproval$1, l as InvalidInvocationAuthority, o as InvalidToolInput, s as createToolInputDigest$1, t as RivusToolInputRejected, u as createInvocationAuthority$1 } from "./rivus-tool.js";
3
+ import { B as createRuntimeCache, C as createStableId, E as createAgentHostRuntimePool, H as invokeRuntimeControl, L as toEffectAgentRuntime, O as loadRivusDeploymentManifest$1, R as toEffectAgentRuntimeInput$1, S as createEffectAgentInstanceRegistry, T as AgentRuntimeDisposed, U as runDeploymentProcessEffect, V as disposeRuntimeCacheEntries, _ as resolveRivusPluginModule, b as createEffectAgentHost, d as createNodeRivusPluginModuleLoader$1, f as loadNodeRivusPluginModule$1, h as isPathWithin, r as resolveRivusProjectSpace, s as createRivusDeploymentControl, u as resolveRivusDeployment, v as createProcessDeploymentControlPorts, x as AgentInstanceConflict, y as InvalidAgentHostBinding, z as toProcessAgentRuntimeInput } from "./rivus-daemon-cli.js";
4
+ import { t as createSha256Digest } from "./sha256-digest.js";
2
5
  import { n as createRivusToolGrantSetOperations, p as deepFreeze$2, u as createRivusMemoryToolContract } from "./rivus-agent-definition-resolver.js";
3
- import { C as createEffectAgentInstanceRegistry, D as createAgentHostRuntimePool, E as AgentRuntimeDisposed, G as disposeRuntimeCacheEntries, H as toEffectAgentRuntimeInput$1, K as invokeRuntimeControl, S as AgentInstanceConflict, U as toProcessAgentRuntimeInput, V as toEffectAgentRuntime, W as createRuntimeCache, b as InvalidAgentHostBinding, c as createRivusDeploymentControl, d as resolveRivusDeployment, f as createNodeRivusPluginModuleLoader$1, g as isPathWithin, i as resolveRivusProjectSpace, k as loadRivusDeploymentManifest$1, p as loadNodeRivusPluginModule$1, r as runDeploymentProcessEffect, v as resolveRivusPluginModule, w as createStableId, x as createEffectAgentHost, y as createProcessDeploymentControlPorts } from "./rivus-daemon-cli.js";
4
6
  import { a as readBackgroundSessionWaitInput, i as readBackgroundSessionString, n as readBackgroundSessionObject, o as createRandomId, r as readBackgroundSessionPhase, t as readBackgroundSessionInteger } from "./background-session-control-input.js";
5
7
  import { d as createBackgroundSessionKey, l as narrowBackgroundSessionDefinition$1, o as createBackgroundSessionToolContracts, s as extendBackgroundSessionDefinition$1 } from "./background-session-authority.js";
6
- import { t as createSha256Digest } from "./sha256-digest.js";
7
- import { a as requiresToolApproval$1, c as resolveInvocationAuthority, l as RivusToolInputRejected, n as InvalidToolInput, o as InvalidInvocationAuthority, r as createToolInputDigest$1, s as createInvocationAuthority$1 } from "./tool-input-digest.js";
8
8
  import { i as createCompatibleRivusAgentCatalog } from "./rivus-plugin-testkit.js";
9
9
  import { Cause, Deferred, Effect, Either, Exit, Fiber, Option, Queue, Stream } from "effect";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
- import { appendFile, chmod, lstat, mkdir, readFile, readdir, realpath, rename, stat, truncate, unlink, writeFile } from "node:fs/promises";
11
+ import { appendFile, chmod, lstat, mkdir, open, readFile, readdir, realpath, rename, stat, truncate, unlink, writeFile } from "node:fs/promises";
12
12
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
13
  import { createServer } from "node:http";
14
14
  import { Buffer as Buffer$1 } from "node:buffer";
@@ -17,1591 +17,1898 @@ import { resourceFromAttributes } from "@opentelemetry/resources";
17
17
  import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base";
18
18
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
19
19
  import { ROOT_CONTEXT, SpanStatusCode, trace } from "@opentelemetry/api";
20
- //#region src/core/application/run-presentation/projection/conversation-progress.ts
21
- const DEFAULT_CONVERSATION_PROGRESS_DISPLAY = "collapsed";
20
+ //#region src/platform/persistence/files/write-atomic-text-file.ts
21
+ /** Replace one UTF-8 text file through a same-directory temporary file. */
22
+ async function writeAtomicTextFile(filePath, contents, options = {}) {
23
+ const directory = dirname(filePath);
24
+ await mkdir(directory, { recursive: true });
25
+ const existingMode = await readMode(filePath);
26
+ const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
27
+ try {
28
+ await writeFile(temporaryPath, contents, {
29
+ encoding: "utf8",
30
+ flag: "wx",
31
+ mode: existingMode ?? options.mode
32
+ });
33
+ if (options.durable === true) await syncFile(temporaryPath);
34
+ await rename(temporaryPath, filePath);
35
+ if (options.durable === true) await syncDirectory(directory);
36
+ } catch (error) {
37
+ await unlink(temporaryPath).catch(() => void 0);
38
+ throw error;
39
+ }
40
+ }
41
+ async function readMode(filePath) {
42
+ try {
43
+ return (await stat(filePath)).mode & 4095;
44
+ } catch (error) {
45
+ if (isMissing$1(error)) return void 0;
46
+ throw error;
47
+ }
48
+ }
49
+ async function syncFile(filePath) {
50
+ const handle = await open(filePath, "r");
51
+ try {
52
+ await handle.sync();
53
+ } finally {
54
+ await handle.close();
55
+ }
56
+ }
57
+ async function syncDirectory(directory) {
58
+ const handle = await open(directory, "r");
59
+ try {
60
+ await handle.sync();
61
+ } finally {
62
+ await handle.close();
63
+ }
64
+ }
65
+ function isMissing$1(error) {
66
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
67
+ }
22
68
  //#endregion
23
- //#region src/core/application/card-delivery/ledger/card-delivery.ts
24
- var CardDeliveryTransitionDenied = class extends Error {
25
- name = "CardDeliveryTransitionDenied";
26
- };
27
- function reserveCardDeliverySequence(current, runId) {
28
- const record = current ?? initialCardDeliveryRecord(runId);
29
- assertCardDeliveryIdentity(record, runId);
30
- if (record.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${runId} already has a terminal receipt`);
31
- const next = {
32
- ...record,
33
- revision: record.revision + 1,
34
- sequence: record.sequence + 1
35
- };
36
- return {
37
- record: next,
38
- sequence: next.sequence
39
- };
69
+ //#region src/adapters/pi/config/pi-model-overrides.ts
70
+ async function mergePiProviderBaseUrlOverride(options) {
71
+ await updatePiProviderOverride(options.filePath, options.provider, (providerOverride) => ({
72
+ ...providerOverride,
73
+ baseUrl: options.baseUrl
74
+ }));
40
75
  }
41
- function markCardDeliveryTerminal(current, runId) {
42
- const record = current ?? initialCardDeliveryRecord(runId);
43
- assertCardDeliveryIdentity(record, runId);
44
- return record.terminalPublished ? record : {
45
- ...record,
46
- revision: record.revision + 1,
47
- terminalPublished: true
48
- };
76
+ /**
77
+ * Persist one complete custom model definition in models.json.
78
+ *
79
+ * Pi composes these definitions with its built-in provider metadata. Keeping the
80
+ * complete definition here is deliberate: a missing SDK catalog entry must be
81
+ * represented by its exact server id, never by a fuzzy or legacy alias.
82
+ */
83
+ async function mergePiProviderModelDeclaration(options) {
84
+ await updatePiProviderOverride(options.filePath, options.provider, (providerOverride) => {
85
+ const models = providerOverride.models;
86
+ if (models !== void 0 && (!Array.isArray(models) || models.some((model) => !isJsonRecord(model)))) throw new Error(`${options.filePath} providers.${options.provider}.models must be a JSON array of objects`);
87
+ const existingModels = models ?? [];
88
+ const existingIndex = existingModels.findIndex((model) => model.id === options.model.id);
89
+ const nextModels = [...existingModels];
90
+ if (existingIndex >= 0) nextModels[existingIndex] = {
91
+ ...nextModels[existingIndex],
92
+ ...options.model
93
+ };
94
+ else nextModels.push({ ...options.model });
95
+ return {
96
+ ...providerOverride,
97
+ models: nextModels
98
+ };
99
+ });
49
100
  }
50
- function validateCardDeliveryRecord(record) {
51
- if (record.runId.trim().length === 0 || !Number.isSafeInteger(record.revision) || record.revision < 1 || !Number.isSafeInteger(record.sequence) || record.sequence < 0 || typeof record.terminalPublished !== "boolean") throw new CardDeliveryTransitionDenied("card delivery record is invalid");
52
- return { ...record };
101
+ async function updatePiProviderOverride(filePath, provider, update) {
102
+ const overrides = await readPiModelOverrides(filePath);
103
+ const providers = readJsonRecord(overrides.providers, filePath, "providers");
104
+ const providerOverride = readJsonRecord(providers[provider], filePath, `providers.${provider}`);
105
+ await writePiModelOverrides(filePath, {
106
+ ...overrides,
107
+ providers: {
108
+ ...providers,
109
+ [provider]: update(providerOverride)
110
+ }
111
+ });
53
112
  }
54
- function validateCardDeliveryTransition(previous, next) {
55
- validateCardDeliveryRecord(next);
56
- if (previous === void 0) {
57
- if (!(next.sequence === 1 && !next.terminalPublished || next.sequence === 0 && next.terminalPublished) || next.revision !== 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid initial revision`);
58
- return { ...next };
113
+ async function readPiModelOverrides(filePath) {
114
+ try {
115
+ return readJsonRecord(JSON.parse(await readFile(filePath, "utf8")), filePath, "root");
116
+ } catch (error) {
117
+ if (isFileNotFound(error)) return {};
118
+ throw error;
59
119
  }
60
- assertCardDeliveryIdentity(previous, next.runId);
61
- if (next.revision !== previous.revision + 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} skipped a revision`);
62
- if (previous.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} changed after its terminal receipt`);
63
- if (!(next.terminalPublished ? next.sequence === previous.sequence : next.sequence === previous.sequence + 1)) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid sequence transition`);
64
- return { ...next };
65
120
  }
66
- function initialCardDeliveryRecord(runId) {
67
- if (runId.trim().length === 0) throw new CardDeliveryTransitionDenied("card delivery run id must not be empty");
68
- return {
69
- revision: 0,
70
- runId,
71
- sequence: 0,
72
- terminalPublished: false
73
- };
121
+ async function writePiModelOverrides(filePath, overrides) {
122
+ await writeAtomicTextFile(filePath, `${JSON.stringify(overrides, null, 2)}\n`, {
123
+ durable: true,
124
+ mode: 384
125
+ });
74
126
  }
75
- function assertCardDeliveryIdentity(record, runId) {
76
- if (record.runId !== runId) throw new CardDeliveryTransitionDenied(`card delivery identity ${record.runId} does not match requested run ${runId}`);
127
+ function readJsonRecord(value, filePath, path) {
128
+ if (value === void 0) return {};
129
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${filePath} ${path} must be a JSON object`);
130
+ return value;
77
131
  }
78
- //#endregion
79
- //#region src/core/application/card-delivery/ledger/card-delivery-ledger.ts
80
- function createCardDeliveryLedger(repository) {
81
- return {
82
- isTerminalPublished: (runId) => repository.get(runId)?.terminalPublished ?? false,
83
- markTerminal: (runId) => repository.transact(runId, (current) => {
84
- const next = markCardDeliveryTerminal(current, runId);
85
- return next === current ? { result: void 0 } : {
86
- next,
87
- result: void 0
88
- };
89
- }),
90
- reserveSequence: (runId) => repository.transact(runId, (current) => {
91
- const reservation = reserveCardDeliverySequence(current, runId);
92
- return {
93
- next: reservation.record,
94
- result: reservation.sequence
95
- };
96
- })
97
- };
132
+ function isJsonRecord(value) {
133
+ return typeof value === "object" && value !== null && !Array.isArray(value);
98
134
  }
99
- //#endregion
100
- //#region src/adapters/outbound/persistence/card-delivery/in-memory-card-delivery-repository.ts
101
- function createInMemoryCardDeliveryRepository(options = {}) {
102
- const records = new Map(options.initial?.map((record) => [record.runId, cloneRecord(record)]) ?? []);
103
- const semaphore = Effect.unsafeMakeSemaphore(1);
104
- return {
105
- get: (runId) => cloneOptional(records.get(runId)),
106
- transact: (runId, decide) => semaphore.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
107
- const current = cloneOptional(records.get(runId));
108
- const decision = yield* Effect.try({
109
- try: () => decide(current),
110
- catch: (error) => error
111
- });
112
- if (!("next" in decision)) return decision.result;
113
- const next = yield* Effect.try({
114
- try: () => validateCardDeliveryTransition(current, decision.next),
115
- catch: (error) => error
116
- });
117
- yield* options.persist?.(cloneRecord(next)) ?? Effect.void;
118
- yield* Effect.sync(() => {
119
- records.set(runId, cloneRecord(next));
120
- });
121
- return decision.result;
122
- })))
123
- };
135
+ function isFileNotFound(error) {
136
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
124
137
  }
125
- function cloneOptional(record) {
126
- return record === void 0 ? void 0 : cloneRecord(record);
138
+ //#endregion
139
+ //#region src/platform/persistence/persistence-value.ts
140
+ function isRecord$14(value) {
141
+ return value !== null && typeof value === "object" && !Array.isArray(value);
127
142
  }
128
- function cloneRecord(record) {
129
- return { ...validateCardDeliveryRecord(record) };
143
+ function isNodeErrorWithCode(error, code) {
144
+ return error instanceof Error && "code" in error && error.code === code;
130
145
  }
131
146
  //#endregion
132
- //#region src/core/application/run-presentation/projection/run-presentation.ts
133
- const RUN_PRESENTATION_SCHEMA_VERSION = 2;
134
- function hasInspectableRunProgress(presentation) {
135
- return presentation.steps.some((step) => step.kind === "skill" || step.kind === "tool");
147
+ //#region src/platform/persistence/files/read-persistence-file.ts
148
+ async function readPersistenceFile(filePath) {
149
+ try {
150
+ return await readFile(filePath, "utf8");
151
+ } catch (error) {
152
+ if (isNodeErrorWithCode(error, "ENOENT")) return void 0;
153
+ throw error;
154
+ }
136
155
  }
137
156
  //#endregion
138
- //#region src/core/application/run-presentation/projection/presented-value.ts
139
- const DEFAULT_MAX_COLLECTION_ITEMS = 20;
140
- const DEFAULT_MAX_DEPTH = 5;
141
- const DEFAULT_MAX_NODES = 100;
142
- const DEFAULT_MAX_STRING_CHARACTERS = 800;
143
- const SENSITIVE_KEY = /authorization|cookie|credential|password|passwd|private[-_]?key|secret|session[-_]?token|(?:^|[-_])token(?:$|[-_])|api[-_]?key|access[-_]?(?:key|token)|refresh[-_]?token|client[-_]?secret/i;
144
- const WORKING_DIRECTORY_KEY = /^(?:cwd|workdir|workingDirectory|workspace|workspaceRoot)$/i;
145
- const INLINE_SECRET = /\b(Bearer\s+)[^\s,;]+|\b(?:gh[opusr]_|sk-|xox[baprs]-)[A-Za-z0-9_-]{8,}/gi;
146
- const NAMED_SECRET_ARGUMENT = /((?:--?|\b)(?:authorization|password|passwd|secret|token|api[-_]?key|access[-_]?token)(?:\s+|=))([^\s,;]+)/gi;
147
- const SECRET_ENVIRONMENT_ASSIGNMENT = /\b([A-Z0-9_]*(?:PASSWORD|SECRET|TOKEN|API_KEY|ACCESS_KEY)=)([^\s,;]+)/g;
148
- function createPresentedValue(value, options = {}) {
149
- return present(value, {
150
- ancestors: /* @__PURE__ */ new Set(),
151
- depth: 0,
152
- maxCollectionItems: options.maxCollectionItems ?? DEFAULT_MAX_COLLECTION_ITEMS,
153
- maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
154
- nodeBudget: { remaining: options.maxNodes ?? DEFAULT_MAX_NODES },
155
- maxStringCharacters: options.maxStringCharacters ?? DEFAULT_MAX_STRING_CHARACTERS
156
- });
157
+ //#region src/core/domain/tool-operation/value-objects/stable-tool-input.ts
158
+ function freezeStableJson(value) {
159
+ return deepFreeze$1(canonicalize(value, /* @__PURE__ */ new Set()));
157
160
  }
158
- function present(value, context, key) {
159
- if (key && SENSITIVE_KEY.test(key)) return {
160
- kind: "redacted",
161
- reason: "sensitive-key"
162
- };
163
- if (context.nodeBudget.remaining <= 0) return {
164
- kind: "unsupported",
165
- valueType: "node-budget"
166
- };
167
- context.nodeBudget.remaining -= 1;
168
- if (value === null) return { kind: "null" };
169
- if (typeof value === "boolean") return {
170
- kind: "boolean",
171
- value
172
- };
173
- if (typeof value === "number") return Number.isFinite(value) ? {
174
- kind: "number",
175
- value
176
- } : {
177
- kind: "unsupported",
178
- valueType: "number"
179
- };
180
- if (typeof value === "string") return presentText(value, context.maxStringCharacters, key);
181
- if (typeof value !== "object") return {
182
- kind: "unsupported",
183
- valueType: typeof value
184
- };
185
- if (context.depth >= context.maxDepth) return {
186
- kind: "unsupported",
187
- valueType: "max-depth"
188
- };
189
- if (context.ancestors.has(value)) return {
190
- kind: "unsupported",
191
- valueType: "circular"
192
- };
193
- context.ancestors.add(value);
194
- const nested = {
195
- ...context,
196
- depth: context.depth + 1
197
- };
161
+ var StableToolOperationValueError = class extends Error {
162
+ name = "StableToolOperationValueError";
163
+ };
164
+ function canonicalize(value, ancestors) {
165
+ if (value === null || typeof value === "string" || typeof value === "boolean") return value;
166
+ if (typeof value === "number") {
167
+ if (!Number.isFinite(value)) throw new StableToolOperationValueError("stable JSON numbers must be finite");
168
+ return Object.is(value, -0) ? 0 : value;
169
+ }
170
+ if (typeof value !== "object") throw new StableToolOperationValueError("value must contain only stable JSON values");
171
+ if (ancestors.has(value)) throw new StableToolOperationValueError("stable JSON must not contain cycles");
172
+ ancestors.add(value);
198
173
  try {
199
- if (Array.isArray(value)) {
200
- const visibleLength = Math.min(value.length, context.maxCollectionItems);
201
- const items = Array.from({ length: visibleLength }, (_, index) => Object.hasOwn(value, index) ? present(value[index], nested) : {
202
- kind: "unsupported",
203
- valueType: "array-hole"
204
- });
205
- return {
206
- items,
207
- kind: "list",
208
- omittedItems: Math.max(0, value.length - items.length)
209
- };
210
- }
211
- if (value instanceof Error) return {
212
- entries: [{
213
- key: "name",
214
- value: present(value.name, nested, "name")
215
- }, {
216
- key: "message",
217
- value: present(value.message, nested, "message")
218
- }],
219
- kind: "record",
220
- omittedEntries: 0
221
- };
174
+ if (Array.isArray(value)) return Array.from({ length: value.length }, (_, index) => {
175
+ if (!Object.hasOwn(value, index)) throw new StableToolOperationValueError("stable JSON arrays must not contain holes");
176
+ return canonicalize(value[index], ancestors);
177
+ });
222
178
  const prototype = Object.getPrototypeOf(value);
223
- if (prototype !== Object.prototype && prototype !== null) return {
224
- kind: "unsupported",
225
- valueType: prototype?.constructor?.name ?? "object"
226
- };
227
- const allEntries = Object.entries(value);
228
- const entries = allEntries.slice(0, context.maxCollectionItems).map(([entryKey, entryValue]) => ({
229
- key: safeKey(entryKey),
230
- value: present(entryValue, nested, entryKey)
231
- }));
232
- return {
233
- entries,
234
- kind: "record",
235
- omittedEntries: Math.max(0, allEntries.length - entries.length)
236
- };
179
+ if (prototype !== Object.prototype && prototype !== null) throw new StableToolOperationValueError("stable JSON objects must be plain objects");
180
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, canonicalize(entry, ancestors)]));
237
181
  } finally {
238
- context.ancestors.delete(value);
182
+ ancestors.delete(value);
239
183
  }
240
184
  }
241
- function presentText(value, maxCharacters, key) {
242
- if (INLINE_SECRET.test(value)) {
243
- INLINE_SECRET.lastIndex = 0;
244
- value = value.replace(INLINE_SECRET, (_match, bearerPrefix) => bearerPrefix ? `${bearerPrefix}<redacted>` : "<redacted>");
185
+ function deepFreeze$1(value) {
186
+ if (Array.isArray(value)) {
187
+ for (const entry of value) deepFreeze$1(entry);
188
+ return Object.freeze(value);
245
189
  }
246
- INLINE_SECRET.lastIndex = 0;
247
- value = value.replace(NAMED_SECRET_ARGUMENT, "$1<redacted>").replace(SECRET_ENVIRONMENT_ASSIGNMENT, "$1<redacted>");
248
- value = normalizeMachinePaths(value, key);
249
- const preview = value.slice(0, maxCharacters);
250
- return {
251
- kind: "text",
252
- omittedCharacters: Math.max(0, value.length - preview.length),
253
- value: preview
254
- };
190
+ if (value !== null && typeof value === "object") {
191
+ for (const entry of Object.values(value)) deepFreeze$1(entry);
192
+ return Object.freeze(value);
193
+ }
194
+ return value;
255
195
  }
256
- function normalizeMachinePaths(value, key) {
257
- if (key && WORKING_DIRECTORY_KEY.test(key)) return "<workspace>";
258
- return value.replace(/\/(?:Users|home)\/[^/\s]+/g, "<home>").replace(/\/(?:private\/)?tmp\/[^\s'";]*/g, (path) => `<tmp>/${path.split("/").at(-1) || "item"}`);
196
+ //#endregion
197
+ //#region src/core/domain/tool-operation/value-objects/binding.ts
198
+ var ToolOperationBindingError = class extends Error {
199
+ name = "ToolOperationBindingError";
200
+ };
201
+ function freezeToolOperationBinding(value) {
202
+ if (!isRecord$13(value)) throw new ToolOperationBindingError("tool operation binding must be an object");
203
+ let detached;
204
+ try {
205
+ detached = freezeStableJson(value);
206
+ } catch (error) {
207
+ if (error instanceof Error) throw new ToolOperationBindingError(error.message);
208
+ throw error;
209
+ }
210
+ if ([
211
+ "agentId",
212
+ "inputDigest",
213
+ "instanceId",
214
+ "sourceMessageId",
215
+ "toolId",
216
+ "toolVersion"
217
+ ].some((field) => typeof detached[field] !== "string" || detached[field] === "")) throw new ToolOperationBindingError("tool operation binding fields must be non-empty strings");
218
+ return detached;
259
219
  }
260
- function safeKey(value) {
261
- return value.replace(/[^\p{L}\p{N}._:/-]+/gu, " ").trim().slice(0, 80) || "unknown";
220
+ function sameToolOperationBinding(left, right) {
221
+ return left.agentId === right.agentId && left.inputDigest === right.inputDigest && left.instanceId === right.instanceId && left.sourceMessageId === right.sourceMessageId && left.toolId === right.toolId && left.toolVersion === right.toolVersion;
222
+ }
223
+ function isRecord$13(value) {
224
+ return value !== null && typeof value === "object" && !Array.isArray(value);
262
225
  }
263
226
  //#endregion
264
- //#region src/core/application/run-presentation/projection/run-presentation-projector.ts
265
- function createRunPresentationProjector$1() {
266
- const runs = /* @__PURE__ */ new Map();
267
- const stateFor = (runId) => {
268
- const existing = runs.get(runId);
269
- if (existing) return existing;
270
- const created = {
271
- answer: "",
272
- answerStartedAt: void 0,
273
- assistantSequence: 0,
274
- currentSkillId: void 0,
275
- order: [],
276
- phase: "running",
277
- revision: 0,
278
- steps: /* @__PURE__ */ new Map()
279
- };
280
- runs.set(runId, created);
281
- return created;
282
- };
283
- const snapshot = (runId) => {
284
- const state = runs.get(runId);
285
- if (!state) return void 0;
286
- const visibleKeys = state.order.slice(-8);
287
- return {
288
- answer: {
289
- format: "markdown",
290
- text: state.answer
291
- },
292
- omittedStepCount: Math.max(0, state.order.length - visibleKeys.length),
293
- phase: state.phase,
294
- revision: state.revision,
295
- runId,
296
- schemaVersion: 2,
297
- steps: visibleKeys.map((key) => state.steps.get(key)).filter((step) => step !== void 0),
298
- totalStepCount: state.order.length,
299
- totalToolCallCount: [...state.steps.values()].filter((step) => step.kind === "tool").length
300
- };
301
- };
302
- const mutate = (runId, mutation) => {
303
- const state = stateFor(runId);
304
- mutation(state);
305
- state.revision += 1;
306
- return snapshot(runId);
307
- };
308
- const upsert = (state, step) => {
309
- if (!state.steps.has(step.id)) state.order.push(step.id);
310
- state.steps.set(step.id, step);
311
- };
312
- const commitAssistantNarration = (state, endedAt) => {
313
- const text = state.answer.trim();
314
- if (!text) return;
315
- const id = `assistant:${state.assistantSequence + 1}`;
316
- state.assistantSequence += 1;
317
- const startedAt = state.answerStartedAt ?? endedAt.toISOString();
318
- upsert(state, {
319
- durationMs: elapsed(startedAt, endedAt),
320
- endedAt: endedAt.toISOString(),
321
- id,
322
- kind: "assistant",
323
- label: "过程说明",
324
- startedAt,
325
- status: "completed",
326
- text
227
+ //#region src/core/domain/tool-operation/aggregate/tool-operation.ts
228
+ var ToolOperationTransitionDenied = class extends Error {
229
+ name = "ToolOperationTransitionDenied";
230
+ };
231
+ /** Immutable transitions do not serialize concurrent writes; the ledger serializes persisted transitions. */
232
+ var ToolOperation = class ToolOperation {
233
+ #snapshot;
234
+ constructor(snapshot) {
235
+ this.#snapshot = freezeSnapshot(snapshot);
236
+ Object.freeze(this);
237
+ }
238
+ static create(input) {
239
+ if (typeof input.operationId !== "string") throw new ToolOperationTransitionDenied("tool operation id must be a string");
240
+ return new ToolOperation({
241
+ binding: freezeToolOperationBinding(input.binding),
242
+ operationId: input.operationId,
243
+ revision: 1,
244
+ state: { status: "pending" }
327
245
  });
328
- state.answer = "";
329
- state.answerStartedAt = void 0;
330
- };
331
- return {
332
- apply: (event, canonicalText) => {
333
- switch (event.type) {
334
- case "agent_run_accepted":
335
- runs.set(event.runId, {
336
- answer: "",
337
- answerStartedAt: void 0,
338
- assistantSequence: 0,
339
- currentSkillId: void 0,
340
- order: [],
341
- phase: "running",
342
- revision: 1,
343
- steps: /* @__PURE__ */ new Map()
344
- });
345
- return snapshot(event.runId);
346
- case "agent_turn_started": return mutate(event.runId, (state) => {
347
- state.answer = "";
348
- state.answerStartedAt = void 0;
349
- state.currentSkillId = void 0;
350
- });
351
- case "agent_model_execution_started": return mutate(event.runId, (state) => {
352
- state.answer = "";
353
- state.answerStartedAt = void 0;
354
- state.currentSkillId = void 0;
355
- upsert(state, {
356
- id: `model:${event.modelCallId}`,
357
- kind: "model",
358
- label: "分析问题",
359
- model: safeIdentifier(event.model),
360
- provider: safeIdentifier(event.provider),
361
- startedAt: event.occurredAt.toISOString(),
362
- status: "running"
363
- });
364
- });
365
- case "agent_model_execution_ended": return mutate(event.runId, (state) => {
366
- const id = `model:${event.modelCallId}`;
367
- upsert(state, {
368
- ...completedTiming(state, id, event.occurredAt),
369
- id,
370
- kind: "model",
371
- label: "分析问题",
372
- model: safeIdentifier(event.responseModel ?? event.model),
373
- provider: safeIdentifier(event.provider),
374
- status: event.errorMessage ? "failed" : "completed"
375
- });
376
- });
377
- case "agent_skill_execution_started": return mutate(event.runId, (state) => {
378
- commitAssistantNarration(state, event.occurredAt);
379
- state.currentSkillId = safeIdentifier(event.skillId);
380
- upsert(state, {
381
- id: `skill:${event.skillCallId}`,
382
- kind: "skill",
383
- label: "读取技能",
384
- skillId: safeIdentifier(event.skillId),
385
- startedAt: event.occurredAt.toISOString(),
386
- status: "running"
387
- });
388
- });
389
- case "agent_skill_execution_ended": return mutate(event.runId, (state) => {
390
- const id = `skill:${event.skillCallId}`;
391
- upsert(state, {
392
- ...completedTiming(state, id, event.occurredAt),
393
- id,
394
- kind: "skill",
395
- label: "读取技能",
396
- skillId: safeIdentifier(event.skillId),
397
- status: event.isError ? "failed" : "completed"
398
- });
399
- });
400
- case "agent_tool_execution_started": return mutate(event.runId, (state) => {
401
- commitAssistantNarration(state, event.occurredAt);
402
- upsert(state, runningToolStep(state, event));
403
- });
404
- case "agent_tool_execution_updated": return mutate(event.runId, (state) => {
405
- upsert(state, runningToolStep(state, event));
406
- });
407
- case "agent_tool_execution_ended": return mutate(event.runId, (state) => {
408
- const id = `tool:${event.toolCallId}`;
409
- const existing = state.steps.get(id);
410
- const startedAt = existing?.startedAt ?? event.occurredAt.toISOString();
411
- const input = existing?.kind === "tool" ? existing.input : createPresentedValue(void 0);
412
- upsert(state, {
413
- durationMs: elapsed(startedAt, event.occurredAt),
414
- endedAt: event.occurredAt.toISOString(),
415
- ...event.isError ? { error: createPresentedValue(event.result) } : { output: createPresentedValue(event.result) },
416
- id,
417
- input,
418
- kind: "tool",
419
- label: "执行工具",
420
- ...existing?.kind === "tool" && existing.skillId ? { skillId: existing.skillId } : {},
421
- startedAt,
422
- status: event.isError ? "failed" : "completed",
423
- toolName: safeIdentifier(event.toolName)
424
- });
425
- });
426
- case "assistant_text_delta": return mutate(event.runId, (state) => {
427
- state.answerStartedAt ??= event.occurredAt.toISOString();
428
- state.answer += event.delta;
429
- });
430
- case "assistant_thinking_delta":
431
- case "agent_turn_completed": return snapshot(event.runId);
432
- case "agent_run_completed": {
433
- const result = finish(event.runId, "completed", "回答已完成", event.occurredAt, canonicalText, event.finalText, stateFor, mutate, upsert);
434
- runs.delete(event.runId);
435
- return result;
436
- }
437
- case "agent_run_failed": {
438
- const result = finish(event.runId, "failed", "运行未完成", event.occurredAt, void 0, canonicalText, stateFor, mutate, upsert);
439
- runs.delete(event.runId);
440
- return result;
441
- }
442
- case "agent_run_cancelled": {
443
- const result = finish(event.runId, "cancelled", "运行已取消", event.occurredAt, void 0, canonicalText, stateFor, mutate, upsert);
444
- runs.delete(event.runId);
445
- return result;
446
- }
246
+ }
247
+ static restore(value) {
248
+ return new ToolOperation(readToolOperationSnapshot(value));
249
+ }
250
+ get binding() {
251
+ return this.#snapshot.binding;
252
+ }
253
+ get operationId() {
254
+ return this.#snapshot.operationId;
255
+ }
256
+ get reconciliation() {
257
+ return this.#snapshot.reconciliation;
258
+ }
259
+ get revision() {
260
+ return this.#snapshot.revision;
261
+ }
262
+ get state() {
263
+ return this.#snapshot.state;
264
+ }
265
+ matchesBinding(binding) {
266
+ try {
267
+ return sameToolOperationBinding(this.binding, freezeToolOperationBinding(binding));
268
+ } catch {
269
+ return false;
270
+ }
271
+ }
272
+ complete(result) {
273
+ this.requirePending();
274
+ let stableResult;
275
+ try {
276
+ stableResult = freezeStableJson(result);
277
+ } catch (error) {
278
+ if (error instanceof Error) throw new ToolOperationTransitionDenied(error.message);
279
+ throw error;
280
+ }
281
+ return this.next({
282
+ revision: this.revision + 1,
283
+ state: {
284
+ result: stableResult,
285
+ status: "completed"
447
286
  }
448
- },
449
- snapshot
450
- };
451
- }
452
- function runningToolStep(state, event) {
453
- const id = `tool:${event.toolCallId}`;
454
- const existing = state.steps.get(id);
455
- const skillId = existing?.kind === "tool" ? existing.skillId : state.currentSkillId;
456
- const output = event.type === "agent_tool_execution_updated" ? createPresentedValue(event.partialResult) : existing?.kind === "tool" ? existing.output : void 0;
287
+ });
288
+ }
289
+ abort() {
290
+ this.requirePending();
291
+ return this.next({
292
+ revision: this.revision + 1,
293
+ state: { status: "aborted" }
294
+ });
295
+ }
296
+ requireReconciliation(reason) {
297
+ this.requirePending();
298
+ if (typeof reason !== "string" || reason.trim() === "") throw new ToolOperationTransitionDenied("tool operation reconciliation requires a reason");
299
+ return this.next({
300
+ revision: this.revision + 1,
301
+ state: {
302
+ reason,
303
+ status: "reconciliation-required"
304
+ }
305
+ });
306
+ }
307
+ retry() {
308
+ if (this.state.status !== "aborted") throw new ToolOperationTransitionDenied(`tool operation cannot retry while ${this.state.status}`);
309
+ return this.next({
310
+ revision: this.revision + 1,
311
+ state: { status: "pending" }
312
+ });
313
+ }
314
+ recoverInterrupted() {
315
+ this.requirePending();
316
+ return this.next({
317
+ revision: this.revision + 1,
318
+ state: {
319
+ reason: "process exited before the tool outcome was durably recorded",
320
+ status: "reconciliation-required"
321
+ }
322
+ });
323
+ }
324
+ reconcile(input) {
325
+ if (input.operationId !== this.operationId || this.state.status !== "reconciliation-required" || this.revision !== input.expectedRevision) throw new ToolOperationTransitionDenied(`tool operation is not the expected reconciliation revision: ${input.operationId}`);
326
+ const action = normalizeReconciliationAction(input.action);
327
+ const nextState = input.outcome.status === "applied" ? {
328
+ result: freezeStableJson(input.outcome.result),
329
+ status: "completed"
330
+ } : { status: "aborted" };
331
+ return this.next({
332
+ reconciliation: {
333
+ ...action,
334
+ outcome: input.outcome.status
335
+ },
336
+ revision: this.revision + 1,
337
+ state: nextState
338
+ });
339
+ }
340
+ validateTransitionTo(next) {
341
+ if (this.operationId !== next.operationId || !sameToolOperationBinding(this.binding, next.binding) || next.revision !== this.revision + 1) return false;
342
+ switch (this.state.status) {
343
+ case "pending": return (next.state.status === "completed" || next.state.status === "reconciliation-required" || next.state.status === "aborted") && next.reconciliation === void 0;
344
+ case "aborted": return next.state.status === "pending" && next.reconciliation === void 0;
345
+ case "completed": return false;
346
+ case "reconciliation-required": return (next.state.status === "completed" || next.state.status === "aborted") && next.reconciliation !== void 0 && isValidReconciliation(next.reconciliation) && next.reconciliation.outcome === (next.state.status === "completed" ? "applied" : "not-applied") && !sameReconciliation(this.reconciliation, next.reconciliation);
347
+ }
348
+ }
349
+ toSnapshot() {
350
+ return this.#snapshot;
351
+ }
352
+ requirePending() {
353
+ if (this.state.status !== "pending") throw new ToolOperationTransitionDenied(`tool operation cannot transition while ${this.state.status}`);
354
+ }
355
+ next(change) {
356
+ const extensions = retainUnknownFields$2(this.#snapshot, [
357
+ "binding",
358
+ "operationId",
359
+ "reconciliation",
360
+ "revision",
361
+ "state"
362
+ ]);
363
+ return new ToolOperation({
364
+ ...extensions,
365
+ binding: this.binding,
366
+ operationId: this.operationId,
367
+ ...change.reconciliation === void 0 ? {} : { reconciliation: change.reconciliation },
368
+ revision: change.revision,
369
+ state: change.state
370
+ });
371
+ }
372
+ };
373
+ function readToolOperationSnapshot(value) {
374
+ if (!isRecord$12(value) || typeof value.operationId !== "string") throw new ToolOperationTransitionDenied("tool operation snapshot is invalid");
375
+ if (!Number.isInteger(value.revision) || value.revision < 1 || !isRecord$12(value.state)) throw new ToolOperationTransitionDenied("tool operation snapshot is invalid");
376
+ const binding = freezeToolOperationBinding(value.binding);
377
+ const state = readState$1(value.state);
378
+ const reconciliation = value.reconciliation === void 0 ? void 0 : readReconciliation(value.reconciliation);
379
+ if (value.revision === 1 && state.status !== "pending") throw new ToolOperationTransitionDenied("tool operation revision one must be pending");
380
+ if (state.status === "pending" || state.status === "reconciliation-required") {
381
+ if (reconciliation !== void 0) throw new ToolOperationTransitionDenied("tool operation reconciliation evidence is only terminal");
382
+ }
383
+ validateRevisionShape(value.revision, state, reconciliation);
384
+ if (reconciliation !== void 0 && reconciliation.outcome !== (state.status === "completed" ? "applied" : "not-applied")) throw new ToolOperationTransitionDenied("tool operation reconciliation outcome does not match state");
457
385
  return {
458
- id,
459
- input: createPresentedValue(event.input),
460
- kind: "tool",
461
- label: "执行工具",
462
- ...output === void 0 ? {} : { output },
463
- ...skillId ? { skillId } : {},
464
- startedAt: existing?.startedAt ?? event.occurredAt.toISOString(),
465
- status: "running",
466
- toolName: safeIdentifier(event.toolName)
386
+ ...retainUnknownFields$2(value, [
387
+ "binding",
388
+ "operationId",
389
+ "reconciliation",
390
+ "revision",
391
+ "state"
392
+ ]),
393
+ binding,
394
+ operationId: value.operationId,
395
+ ...reconciliation === void 0 ? {} : { reconciliation },
396
+ revision: value.revision,
397
+ state
467
398
  };
468
399
  }
469
- function finish(runId, phase, label, occurredAt, preferredText, fallbackText, stateFor, mutate, upsert) {
470
- stateFor(runId);
471
- const startedAt = occurredAt.toISOString();
472
- return mutate(runId, (state) => {
473
- state.phase = phase;
474
- state.answer = preferredText?.trim() || state.answer.trim() || fallbackText?.trim() || state.answer;
475
- for (const step of state.steps.values()) if (step.status === "running") state.steps.set(step.id, finishRunningStep(step, occurredAt, phase === "completed" ? "completed" : "failed"));
476
- upsert(state, {
477
- endedAt: occurredAt.toISOString(),
478
- id: `response:${phase}`,
479
- kind: "response",
480
- label,
481
- startedAt,
482
- status: phase === "completed" ? "completed" : "failed"
483
- });
400
+ function readState$1(value) {
401
+ switch (value.status) {
402
+ case "pending": return {
403
+ ...retainUnknownFields$2(value, ["status"]),
404
+ status: "pending"
405
+ };
406
+ case "aborted": return {
407
+ ...retainUnknownFields$2(value, ["status"]),
408
+ status: "aborted"
409
+ };
410
+ case "reconciliation-required":
411
+ if (typeof value.reason !== "string" || value.reason.trim() === "") throw new ToolOperationTransitionDenied("tool operation reconciliation reason is invalid");
412
+ return {
413
+ ...retainUnknownFields$2(value, ["reason", "status"]),
414
+ reason: value.reason,
415
+ status: "reconciliation-required"
416
+ };
417
+ case "completed":
418
+ if (!Object.hasOwn(value, "result")) throw new ToolOperationTransitionDenied("completed tool operation must contain a result");
419
+ try {
420
+ return {
421
+ ...retainUnknownFields$2(value, ["result", "status"]),
422
+ result: freezeStableJson(value.result),
423
+ status: "completed"
424
+ };
425
+ } catch (error) {
426
+ if (error instanceof Error) throw new ToolOperationTransitionDenied(error.message);
427
+ throw error;
428
+ }
429
+ default: throw new ToolOperationTransitionDenied("tool operation state is invalid");
430
+ }
431
+ }
432
+ function normalizeReconciliationAction(action) {
433
+ if (!isRecord$12(action)) throw new ToolOperationTransitionDenied("tool operation reconciliation action is invalid");
434
+ const actorId = typeof action.actorId === "string" ? action.actorId.trim() : "";
435
+ const note = typeof action.note === "string" ? action.note.trim() : "";
436
+ if (!actorId) throw new ToolOperationTransitionDenied("recovery actor must not be empty");
437
+ if (!note) throw new ToolOperationTransitionDenied("recovery note must not be empty");
438
+ if (typeof action.at !== "string" || !Number.isFinite(Date.parse(action.at))) throw new ToolOperationTransitionDenied("recovery timestamp must be an ISO timestamp");
439
+ return Object.freeze({
440
+ actorId,
441
+ at: action.at,
442
+ note
443
+ });
444
+ }
445
+ function readReconciliation(value) {
446
+ if (!isRecord$12(value)) throw new ToolOperationTransitionDenied("tool operation reconciliation is invalid");
447
+ const action = normalizeReconciliationAction(value);
448
+ if (value.outcome !== "applied" && value.outcome !== "not-applied") throw new ToolOperationTransitionDenied("tool operation reconciliation outcome is invalid");
449
+ return Object.freeze({
450
+ ...retainUnknownFields$2(value, [
451
+ "actorId",
452
+ "at",
453
+ "note",
454
+ "outcome"
455
+ ]),
456
+ ...action,
457
+ outcome: value.outcome
458
+ });
459
+ }
460
+ function isValidReconciliation(value) {
461
+ return value.actorId.trim().length > 0 && Number.isFinite(Date.parse(value.at)) && value.note.trim().length > 0 && (value.outcome === "applied" || value.outcome === "not-applied");
462
+ }
463
+ function sameReconciliation(left, right) {
464
+ if (left === void 0 || right === void 0) return left === right;
465
+ return left.actorId === right.actorId && left.at === right.at && left.note === right.note && left.outcome === right.outcome;
466
+ }
467
+ function freezeSnapshot(snapshot) {
468
+ const binding = freezeToolOperationBinding(snapshot.binding);
469
+ const state = freezeState(snapshot.state);
470
+ const reconciliation = snapshot.reconciliation === void 0 ? void 0 : readReconciliation(snapshot.reconciliation);
471
+ return Object.freeze({
472
+ ...retainUnknownFields$2(snapshot, [
473
+ "binding",
474
+ "operationId",
475
+ "reconciliation",
476
+ "revision",
477
+ "state"
478
+ ]),
479
+ binding,
480
+ operationId: snapshot.operationId,
481
+ ...reconciliation === void 0 ? {} : { reconciliation },
482
+ revision: snapshot.revision,
483
+ state
484
+ });
485
+ }
486
+ function freezeState(state) {
487
+ if (state.status === "completed") return Object.freeze({
488
+ ...retainUnknownFields$2(state, ["result", "status"]),
489
+ result: freezeStableJson(state.result),
490
+ status: "completed"
491
+ });
492
+ if (state.status === "reconciliation-required") return Object.freeze({
493
+ ...retainUnknownFields$2(state, ["reason", "status"]),
494
+ reason: state.reason,
495
+ status: "reconciliation-required"
496
+ });
497
+ return Object.freeze({
498
+ ...retainUnknownFields$2(state, ["status"]),
499
+ status: state.status
484
500
  });
485
501
  }
486
- function finishRunningStep(step, occurredAt, status) {
487
- return {
488
- ...step,
489
- durationMs: elapsed(step.startedAt, occurredAt),
490
- endedAt: occurredAt.toISOString(),
491
- status
492
- };
493
- }
494
- function completedTiming(state, id, occurredAt) {
495
- const startedAt = state.steps.get(id)?.startedAt ?? occurredAt.toISOString();
496
- return {
497
- durationMs: elapsed(startedAt, occurredAt),
498
- endedAt: occurredAt.toISOString(),
499
- startedAt
500
- };
502
+ function validateRevisionShape(revision, state, reconciliation) {
503
+ if (state.status === "pending" && revision === 2) throw new ToolOperationTransitionDenied("pending tool operation revision is invalid");
504
+ if (state.status !== "pending" && revision === 1) throw new ToolOperationTransitionDenied("tool operation revision one must be pending");
505
+ if (reconciliation !== void 0 && revision < 3) throw new ToolOperationTransitionDenied("reconciled tool operation revision is invalid");
501
506
  }
502
- function elapsed(startedAt, endedAt) {
503
- return Math.max(0, endedAt.getTime() - Date.parse(startedAt));
507
+ function retainUnknownFields$2(value, known) {
508
+ const extensions = {};
509
+ const allowed = new Set(known);
510
+ for (const [key, entry] of Object.entries(value)) {
511
+ if (allowed.has(key)) continue;
512
+ extensions[key] = freezeStableJson(entry);
513
+ }
514
+ return extensions;
504
515
  }
505
- function safeIdentifier(value) {
506
- return value.replace(/[^\p{L}\p{N}._:/-]+/gu, " ").trim().slice(0, 80) || "unknown";
516
+ function isRecord$12(value) {
517
+ return value !== null && typeof value === "object" && !Array.isArray(value);
507
518
  }
508
519
  //#endregion
509
- //#region src/adapters/feishu/card-presentation/streaming/feishu-stream-projector.ts
510
- function createFeishuStreamProjector() {
511
- const presentations = createRunPresentationProjector$1();
512
- const applyEvent = (event, canonicalText) => {
513
- const presentation = presentations.apply(event, canonicalText);
514
- switch (event.type) {
515
- case "agent_run_accepted":
516
- case "agent_turn_started":
517
- case "assistant_thinking_delta":
518
- case "agent_turn_completed": return [];
519
- case "agent_model_execution_started":
520
- case "agent_model_execution_ended":
521
- case "agent_skill_execution_started":
522
- case "agent_skill_execution_ended":
523
- case "agent_tool_execution_started":
524
- case "agent_tool_execution_updated":
525
- case "agent_tool_execution_ended": return presentation ? [{
526
- presentation,
527
- runId: event.runId,
528
- type: "update_progress"
529
- }] : [];
530
- case "assistant_text_delta": return [{
531
- runId: event.runId,
532
- text: presentation?.answer.text ?? event.delta,
533
- type: "update_text"
534
- }];
535
- case "agent_run_completed": return [{
536
- ...hasMeaningfulProgress(presentation) ? { presentation } : {},
537
- runId: event.runId,
538
- text: presentation?.answer.text.trim() || canonicalText?.trim() || event.finalText,
539
- type: "finish"
540
- }];
541
- case "agent_run_failed": return [{
542
- errorMessage: event.errorMessage,
543
- ...hasMeaningfulProgress(presentation) ? { presentation } : {},
544
- runId: event.runId,
545
- type: "fail"
546
- }];
547
- case "agent_run_cancelled": return [{
548
- ...hasMeaningfulProgress(presentation) ? { presentation } : {},
549
- ...event.reason ? { reason: event.reason } : {},
550
- runId: event.runId,
551
- type: "cancel"
552
- }];
553
- }
554
- };
555
- return {
556
- apply: (event) => applyEvent(event),
557
- applyUpdate: ({ event, state }) => event.type === "agent_run_failed" ? applyEvent({
558
- ...event,
559
- errorMessage: state.errorMessage ?? event.errorMessage
560
- }, state.finalText) : event.type === "agent_run_cancelled" ? applyEvent(state.cancellationReason ? {
561
- ...event,
562
- reason: state.cancellationReason
563
- } : event, state.finalText) : applyEvent(event, state.finalText)
564
- };
520
+ //#region src/core/domain/tool-operation/factories/create-tool-operation.ts
521
+ function createToolOperation(input) {
522
+ return ToolOperation.create(input);
565
523
  }
566
- function hasMeaningfulProgress(presentation) {
567
- return presentation !== void 0 && hasInspectableRunProgress(presentation);
524
+ //#endregion
525
+ //#region src/core/domain/tool-operation/factories/restore-tool-operation.ts
526
+ function restoreToolOperation(snapshot) {
527
+ return ToolOperation.restore(snapshot);
568
528
  }
569
529
  //#endregion
570
- //#region src/adapters/feishu/card-presentation/delivery/feishu-card-delivery-ledger.ts
571
- function createFeishuCardDeliveryLedger(options = {}) {
572
- const persist = options.persist;
573
- return createCardDeliveryLedger(createInMemoryCardDeliveryRepository({
574
- ...options.initial === void 0 ? {} : { initial: options.initial },
575
- ...persist === void 0 ? {} : { persist: (record) => Effect.tryPromise({
576
- try: () => persist(record),
577
- catch: (error) => error
578
- }) }
530
+ //#region src/core/application/tool-execution/operation/tool-operation-ledger.ts
531
+ var ToolOperationLedgerError = class extends Error {
532
+ name = "ToolOperationLedgerError";
533
+ };
534
+ function createToolOperationLedger$1(options = {}) {
535
+ const records = /* @__PURE__ */ new Map();
536
+ for (const snapshot of options.initial ?? []) {
537
+ const operation = restoreToolOperation(snapshot);
538
+ if (records.has(operation.operationId)) throw new ToolOperationLedgerError(`duplicate initial tool operation: ${operation.operationId}`);
539
+ records.set(operation.operationId, operation);
540
+ }
541
+ const semaphore = Effect.unsafeMakeSemaphore(1);
542
+ const exclusive = (effect) => semaphore.withPermits(1)(Effect.uninterruptible(effect));
543
+ const save = (operation) => {
544
+ const snapshot = operation.toSnapshot();
545
+ return (options.persist ? options.persist(snapshot) : Effect.void).pipe(Effect.tap(() => Effect.sync(() => {
546
+ records.set(operation.operationId, operation);
547
+ })));
548
+ };
549
+ const transition = (operationId, next) => exclusive(Effect.gen(function* () {
550
+ const operation = yield* Effect.try({
551
+ catch: (error) => error,
552
+ try: () => next(records.get(operationId))
553
+ });
554
+ yield* save(operation);
579
555
  }));
580
- }
581
- function createFeishuCardDeliveryReconciler(options) {
582
- return { reconcile: () => Effect.gen(function* () {
583
- const projector = createFeishuStreamProjector();
584
- const terminalByRun = /* @__PURE__ */ new Map();
585
- for (const event of options.events) for (const action of projector.apply(event)) if (action.type === "cancel" || action.type === "fail" || action.type === "finish") terminalByRun.set(action.runId, action);
586
- let repaired = 0;
587
- let skipped = 0;
588
- for (const [runId, action] of terminalByRun) {
589
- if (options.ledger.isTerminalPublished(runId)) continue;
590
- yield* options.publish(action).pipe(Effect.catchAll((error) => {
591
- if (isMissingPresentationError(error)) {
592
- skipped += 1;
593
- return Effect.void;
556
+ return {
557
+ abort: (operationId, binding) => transition(operationId, (current) => requireBoundOperation(current, binding).abort()),
558
+ begin: (operationId, binding) => exclusive(Effect.gen(function* () {
559
+ const current = records.get(operationId);
560
+ if (!current) {
561
+ const created = createToolOperation({
562
+ binding,
563
+ operationId
564
+ });
565
+ yield* save(created);
566
+ return { status: "acquired" };
567
+ }
568
+ if (!current.matchesBinding(binding)) return {
569
+ reason: "operation id is bound to another invocation",
570
+ status: "blocked"
571
+ };
572
+ switch (current.state.status) {
573
+ case "completed": return {
574
+ result: current.state.result,
575
+ status: "completed"
576
+ };
577
+ case "pending": return {
578
+ reason: "operation is already in progress",
579
+ status: "blocked"
580
+ };
581
+ case "reconciliation-required": return {
582
+ reason: current.state.reason,
583
+ status: "blocked"
584
+ };
585
+ case "aborted": {
586
+ const retried = current.retry();
587
+ yield* save(retried);
588
+ return { status: "acquired" };
594
589
  }
595
- return Effect.fail(error);
596
- }));
597
- yield* options.ledger.markTerminal(runId);
598
- repaired += 1;
599
- }
600
- return {
601
- repaired,
602
- skipped,
603
- terminalRuns: terminalByRun.size
604
- };
605
- }) };
606
- }
607
- function isMissingPresentationError(error) {
608
- return error !== null && typeof error === "object" && error._tag === "FeishuCardTargetNotFound";
609
- }
610
- //#endregion
611
- //#region src/platform/persistence/persistence-value.ts
612
- function isRecord$14(value) {
613
- return value !== null && typeof value === "object" && !Array.isArray(value);
590
+ }
591
+ })),
592
+ complete: (operationId, binding, result) => transition(operationId, (current) => requireBoundOperation(current, binding).complete(result)),
593
+ inspect: (operationId, binding) => exclusive(Effect.sync(() => {
594
+ const current = records.get(operationId);
595
+ if (!current || current.state.status === "aborted" && current.matchesBinding(binding)) return { status: "missing" };
596
+ if (!current.matchesBinding(binding)) return {
597
+ reason: "operation id is bound to another invocation",
598
+ status: "blocked"
599
+ };
600
+ return current.state.status === "completed" ? {
601
+ result: current.state.result,
602
+ status: "completed"
603
+ } : {
604
+ reason: current.state.status === "reconciliation-required" ? current.state.reason : "operation is already in progress",
605
+ status: "blocked"
606
+ };
607
+ })),
608
+ reconciliationRequired: () => [...records.values()].filter((operation) => operation.state.status === "reconciliation-required").map((operation) => operation.toSnapshot()),
609
+ reconcile: (input) => exclusive(Effect.gen(function* () {
610
+ const current = records.get(input.operationId);
611
+ const operation = yield* Effect.try({
612
+ catch: (error) => error,
613
+ try: () => requireOperation(current).reconcile(input)
614
+ });
615
+ yield* save(operation);
616
+ return operation.toSnapshot();
617
+ })),
618
+ requireReconciliation: (operationId, binding, reason) => transition(operationId, (current) => requireBoundOperation(current, binding).requireReconciliation(reason)),
619
+ unresolvedForSource: (sourceMessageId) => [...records.values()].filter((operation) => operation.binding.sourceMessageId === sourceMessageId && (operation.state.status === "pending" || operation.state.status === "reconciliation-required")).map((operation) => operation.toSnapshot())
620
+ };
614
621
  }
615
- function isNodeErrorWithCode(error, code) {
616
- return error instanceof Error && "code" in error && error.code === code;
622
+ function requireOperation(operation) {
623
+ if (!operation) throw new Error("tool operation is not held by this invocation");
624
+ return operation;
617
625
  }
618
- //#endregion
619
- //#region src/platform/persistence/files/read-persistence-file.ts
620
- async function readPersistenceFile(filePath) {
621
- try {
622
- return await readFile(filePath, "utf8");
623
- } catch (error) {
624
- if (isNodeErrorWithCode(error, "ENOENT")) return void 0;
625
- throw error;
626
- }
626
+ function requireBoundOperation(operation, binding) {
627
+ const current = requireOperation(operation);
628
+ if (!current.matchesBinding(binding)) throw new Error("operation id is bound to another invocation");
629
+ return current;
627
630
  }
628
- //#endregion
629
- //#region src/adapters/outbound/persistence/card-delivery/card-delivery-snapshot-codec.ts
630
- function encodeCardDeliverySnapshot(record) {
631
+ function encodeToolOperationSnapshot(record) {
631
632
  return JSON.stringify({
632
- record: validateCardDeliveryRecord(record),
633
+ record,
633
634
  version: 1
634
635
  });
635
- }
636
- function decodeCardDeliverySnapshots(raw) {
637
- const records = /* @__PURE__ */ new Map();
636
+ }
637
+ function decodeToolOperationSnapshots(raw) {
638
+ const latest = /* @__PURE__ */ new Map();
638
639
  for (const [index, line] of raw.split("\n").entries()) {
639
- if (line.trim().length === 0) continue;
640
- const record = readEnvelope(line, index + 1);
641
- const previous = records.get(record.runId);
642
- try {
643
- if (previous?.terminalPublished && isLegacyPostTerminalSequence(previous, record)) {
644
- records.set(record.runId, { ...record });
645
- continue;
646
- }
647
- records.set(record.runId, { ...validateCardDeliveryTransition(previous, record) });
648
- } catch (error) {
649
- throw new Error(`invalid card delivery revision at line ${index + 1}`, { cause: error });
650
- }
640
+ if (!line.trim()) continue;
641
+ const operation = decodeToolOperationSnapshot(line, index + 1);
642
+ const previous = latest.get(operation.operationId);
643
+ if (operation.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid tool operation revision at line ${index + 1}`);
644
+ if (previous ? !previous.validateTransitionTo(operation) : operation.reconciliation !== void 0 || operation.state.status !== "pending") throw new Error(`invalid tool operation transition at line ${index + 1}`);
645
+ latest.set(operation.operationId, operation);
651
646
  }
652
- return [...records.values()];
647
+ return [...latest.values()].map((operation) => operation.toSnapshot());
653
648
  }
654
- function readEnvelope(line, lineNumber) {
649
+ function decodeToolOperationSnapshot(line, lineNumber) {
655
650
  const envelope = JSON.parse(line);
656
- if (!isRecord$14(envelope) || envelope.version !== 1 || !isCardDeliveryRecord(envelope.record)) throw new Error(`invalid card delivery snapshot at line ${lineNumber}`);
657
- return envelope.record;
658
- }
659
- function isLegacyPostTerminalSequence(previous, next) {
660
- return next.runId === previous.runId && next.revision === previous.revision + 1 && next.sequence === previous.sequence + 1 && next.terminalPublished;
661
- }
662
- function isCardDeliveryRecord(value) {
663
- return isRecord$14(value) && Number.isSafeInteger(value.revision) && value.revision > 0 && typeof value.runId === "string" && value.runId.length > 0 && Number.isSafeInteger(value.sequence) && value.sequence >= 0 && typeof value.terminalPublished === "boolean";
651
+ if (!isRecord$14(envelope) || envelope.version !== 1) throw new Error(`invalid tool operation snapshot at line ${lineNumber}`);
652
+ try {
653
+ return restoreToolOperation(envelope.record);
654
+ } catch {
655
+ throw new Error(`invalid tool operation snapshot at line ${lineNumber}`);
656
+ }
664
657
  }
665
658
  //#endregion
666
- //#region src/adapters/outbound/persistence/card-delivery/jsonl-card-delivery-repository.ts
667
- function openJsonlCardDeliveryRepository(options) {
668
- return Effect.tryPromise({
669
- try: () => readPersistenceFile(options.filePath),
670
- catch: (error) => error
671
- }).pipe(Effect.flatMap((raw) => Effect.try({
672
- try: () => raw === void 0 ? [] : decodeCardDeliverySnapshots(raw),
673
- catch: (error) => error
674
- })), Effect.map((initial) => createInMemoryCardDeliveryRepository({
675
- initial,
676
- persist: (record) => append(options.filePath, record)
677
- })));
659
+ //#region src/adapters/outbound/persistence/tool-operation/jsonl-tool-operation-ledger.ts
660
+ function openJsonlToolOperationLedger$1(options) {
661
+ return Effect.gen(function* () {
662
+ const loaded = yield* load(options.filePath);
663
+ const initial = [];
664
+ for (const snapshot of loaded) {
665
+ const operation = restoreToolOperation(snapshot);
666
+ const recovered = operation.state.status === "pending" ? operation.recoverInterrupted() : operation;
667
+ if (recovered !== operation) yield* persist(options.filePath, recovered.toSnapshot());
668
+ initial.push(recovered.toSnapshot());
669
+ }
670
+ return createToolOperationLedger$1({
671
+ initial,
672
+ persist: (record) => persist(options.filePath, record)
673
+ });
674
+ });
678
675
  }
679
- function append(filePath, record) {
676
+ function persist(filePath, record) {
680
677
  return Effect.tryPromise({
678
+ catch: (error) => error,
681
679
  try: async () => {
682
680
  await mkdir(dirname(filePath), { recursive: true });
683
- await appendFile(filePath, `${encodeCardDeliverySnapshot(record)}\n`, "utf8");
684
- },
685
- catch: (error) => error
681
+ await appendFile(filePath, `${encodeToolOperationSnapshot(record)}\n`, "utf8");
682
+ }
686
683
  });
687
684
  }
688
- //#endregion
689
- //#region src/adapters/compatibility/card-presentation/jsonl-feishu-card-delivery-ledger.ts
690
- async function openJsonlFeishuCardDeliveryLedger(options) {
691
- return Effect.runPromise(openJsonlCardDeliveryRepository(options).pipe(Effect.map(createCardDeliveryLedger)));
685
+ function load(filePath) {
686
+ return Effect.tryPromise({
687
+ catch: (error) => error,
688
+ try: () => readPersistenceFile(filePath)
689
+ }).pipe(Effect.map((raw) => raw === void 0 ? [] : decodeToolOperationSnapshots(raw)));
692
690
  }
693
691
  //#endregion
694
- //#region src/platform/durable-queue/records/inbound-delivery-record.ts
695
- var InvalidInboundDelivery = class extends Error {
696
- name = "InvalidInboundDelivery";
692
+ //#region src/core/application/tool-execution/brokerage/tool-broker-ports.ts
693
+ var ToolExecutorInputRejected = class extends Error {
694
+ name = "ToolExecutorInputRejected";
697
695
  };
698
- function createInboundDelivery(input) {
699
- const delivery = {
700
- acceptedAt: input.acceptedAt,
701
- attempts: 0,
702
- id: input.id,
703
- laneKey: input.laneKey,
704
- ...input.metadata === void 0 ? {} : { metadata: input.metadata },
705
- payload: input.payload,
706
- revision: 1,
707
- state: {
708
- availableAt: input.acceptedAt,
709
- status: "pending"
696
+ //#endregion
697
+ //#region src/core/application/tool-execution/brokerage/tool-broker.ts
698
+ var ToolInvocationDenied = class extends Error {
699
+ name = "ToolInvocationDenied";
700
+ };
701
+ function createToolBroker$1(options) {
702
+ const operations = options.operations ?? createToolOperationLedger$1();
703
+ const hostTools = new Map(options.hostTools?.map((tool) => [tool.id, tool]) ?? []);
704
+ if (hostTools.size !== (options.hostTools?.length ?? 0)) throw new ToolInvocationDenied("duplicate Host Tool id");
705
+ return { execute: (request) => Effect.gen(function* () {
706
+ const prepared = yield* Effect.try({
707
+ catch: (error) => error,
708
+ try: () => prepareInvocation(hostTools, options.catalog, request)
709
+ });
710
+ const policy = yield* options.policy.current();
711
+ if (policy.revokedToolIds.includes(request.toolId)) return yield* Effect.fail(new ToolInvocationDenied(`tool has been revoked: ${request.toolId}`));
712
+ if (prepared.tool.idempotency === "required" && !request.operationId) return yield* Effect.fail(new ToolInvocationDenied(`tool requires a stable operation id: ${request.toolId}`));
713
+ if (requiresToolApproval$1(prepared.tool.risk) && !request.operationId) return yield* Effect.fail(new ToolInvocationDenied(`tool approval requires a stable operation id: ${request.toolId}`));
714
+ if (requiresToolApproval$1(prepared.tool.risk) && !request.approvalId) return yield* Effect.fail(new ToolInvocationDenied(`tool requires trusted approval: ${request.toolId}`));
715
+ const binding = yield* Effect.try({
716
+ catch: (error) => error,
717
+ try: () => createOperationBinding(prepared.authority, prepared.tool, request.input, options.digest)
718
+ });
719
+ const context = createExecutionContext(prepared.authority, request, policy.epoch, prepared.tool);
720
+ const replayCompleted = (result) => replay(prepared.tool, request.input, result, context);
721
+ if (request.operationId) {
722
+ const inspected = yield* operations.inspect(request.operationId, binding);
723
+ if (inspected.status === "completed") return yield* replayCompleted(inspected.result);
724
+ if (inspected.status === "blocked") return yield* Effect.fail(new ToolInvocationDenied(`tool operation is fenced: ${inspected.reason}`));
725
+ }
726
+ if (requiresToolApproval$1(prepared.tool.risk)) {
727
+ if (!(yield* options.approvals.consume({
728
+ agentId: prepared.authority.agentId,
729
+ approvalId: request.approvalId,
730
+ callId: request.callId,
731
+ inputDigest: binding.inputDigest,
732
+ instanceId: prepared.authority.instanceId,
733
+ operationId: request.operationId,
734
+ risk: prepared.tool.risk,
735
+ runId: prepared.authority.runId,
736
+ sessionKey: prepared.authority.sessionKey,
737
+ tenantKey: prepared.authority.tenantKey,
738
+ toolId: request.toolId,
739
+ toolVersion: prepared.tool.version
740
+ }))) return yield* Effect.fail(new ToolInvocationDenied(`tool approval is invalid or already consumed: ${request.toolId}`));
741
+ }
742
+ if (request.operationId) {
743
+ const reservation = yield* operations.begin(request.operationId, binding);
744
+ if (reservation.status === "completed") return yield* replayCompleted(reservation.result);
745
+ if (reservation.status === "blocked") return yield* Effect.fail(new ToolInvocationDenied(`tool operation is fenced: ${reservation.reason}`));
710
746
  }
747
+ return yield* Effect.gen(function* () {
748
+ const result = yield* (yield* Effect.try({
749
+ catch: (error) => error,
750
+ try: () => prepared.tool.createExecutor({
751
+ toolId: prepared.tool.id,
752
+ toolVersion: prepared.tool.version
753
+ })
754
+ })).execute(request.input, context);
755
+ if (request.operationId) yield* operations.complete(request.operationId, binding, result);
756
+ return result;
757
+ }).pipe(Effect.catchAll((error) => {
758
+ if (!request.operationId) return Effect.fail(error);
759
+ return (prepared.tool.risk === "observe" || error instanceof ToolExecutorInputRejected ? operations.abort(request.operationId, binding) : operations.requireReconciliation(request.operationId, binding, error instanceof Error ? error.message : String(error))).pipe(Effect.zipRight(Effect.fail(error)));
760
+ }));
761
+ }) };
762
+ }
763
+ function prepareInvocation(hostTools, catalog, request) {
764
+ const authority = resolveInvocationAuthority(request.authority);
765
+ if (!authority.toolGrantSet.toolIds.includes(request.toolId)) throw new ToolInvocationDenied(`tool is not granted for this run: ${request.toolId}`);
766
+ const tool = hostTools.get(request.toolId) ?? catalog.snapshot().tools.find(({ id }) => id === request.toolId);
767
+ if (!tool) throw new ToolInvocationDenied(`tool is not present in the trusted catalog: ${request.toolId}`);
768
+ if (tool.version !== request.version) throw new ToolInvocationDenied(`tool version mismatch for ${request.toolId}: expected ${tool.version}`);
769
+ return {
770
+ authority,
771
+ tool
711
772
  };
712
- validateInboundDeliverySnapshot(delivery);
713
- return freezeInboundDeliverySnapshot(delivery);
714
773
  }
715
- function freezeInboundDeliverySnapshot(delivery) {
716
- const payload = cloneOpaqueValue(delivery.payload);
717
- const metadata = delivery.metadata === void 0 ? void 0 : cloneOpaqueValue(delivery.metadata);
774
+ function createOperationBinding(authority, tool, input, digest) {
775
+ let inputDigest;
776
+ try {
777
+ inputDigest = createToolInputDigest$1(input, digest);
778
+ } catch (error) {
779
+ if (error instanceof InvalidToolInput) throw new ToolInvocationDenied(`tool input is not stable JSON: ${tool.id}`);
780
+ throw error;
781
+ }
782
+ return {
783
+ agentId: authority.agentId,
784
+ inputDigest,
785
+ instanceId: authority.instanceId,
786
+ sourceMessageId: authority.sourceMessageId,
787
+ toolId: tool.id,
788
+ toolVersion: tool.version
789
+ };
790
+ }
791
+ function createExecutionContext(authority, request, policyEpoch, tool) {
718
792
  return Object.freeze({
719
- ...delivery,
720
- ...metadata === void 0 ? {} : { metadata },
721
- payload,
722
- state: Object.freeze({ ...delivery.state }),
723
- ...delivery.recovery === void 0 ? {} : { recovery: Object.freeze({ ...delivery.recovery }) }
793
+ agentId: authority.agentId,
794
+ callId: request.callId,
795
+ instanceId: authority.instanceId,
796
+ ...authority.memory === void 0 ? {} : { memory: authority.memory },
797
+ ...request.operationId === void 0 ? {} : { operationId: request.operationId },
798
+ policyEpoch,
799
+ runId: authority.runId,
800
+ sessionKey: authority.sessionKey,
801
+ toolId: tool.id,
802
+ toolVersion: tool.version,
803
+ ...authority.endpointId === void 0 ? {} : { origin: Object.freeze({
804
+ allowedActorOpenIds: Object.freeze(authority.allowedActorOpenIds ?? []),
805
+ endpointId: authority.endpointId,
806
+ tenantKey: authority.tenantKey,
807
+ ...authority.conversationId === void 0 ? {} : { conversationId: authority.conversationId }
808
+ }) },
809
+ ...authority.sourceMessageId ? { sourceMessageId: authority.sourceMessageId } : {}
724
810
  });
725
811
  }
726
- function readInboundDeliverySnapshot(value) {
727
- if (!isRecord$14(value)) throw new InvalidInboundDelivery("Inbound Delivery snapshot must be an object");
728
- if (typeof value.acceptedAt !== "string" || typeof value.attempts !== "number" || typeof value.id !== "string" || typeof value.laneKey !== "string" || typeof value.revision !== "number" || !isRecord$14(value.state)) throw new InvalidInboundDelivery("Inbound Delivery snapshot is missing required fields");
729
- const delivery = {
730
- acceptedAt: value.acceptedAt,
731
- attempts: value.attempts,
732
- id: value.id,
733
- laneKey: value.laneKey,
734
- ...value.metadata === void 0 ? {} : { metadata: value.metadata },
735
- payload: value.payload,
736
- ...value.recovery === void 0 ? {} : { recovery: readRecoveryAudit(value.recovery) },
737
- revision: value.revision,
738
- state: readState$1(value.state)
812
+ function replay(tool, input, result, context) {
813
+ if (!tool.replayCompleted) return Effect.succeed(result);
814
+ return Effect.try({
815
+ catch: (error) => error,
816
+ try: () => tool.replayCompleted(input, result, context)
817
+ }).pipe(Effect.flatMap((effect) => effect));
818
+ }
819
+ //#endregion
820
+ //#region src/core/application/run-presentation/projection/conversation-progress.ts
821
+ const DEFAULT_CONVERSATION_PROGRESS_DISPLAY = "collapsed";
822
+ //#endregion
823
+ //#region src/core/application/card-delivery/ledger/card-delivery.ts
824
+ var CardDeliveryTransitionDenied = class extends Error {
825
+ name = "CardDeliveryTransitionDenied";
826
+ };
827
+ function reserveCardDeliverySequence(current, runId) {
828
+ const record = current ?? initialCardDeliveryRecord(runId);
829
+ assertCardDeliveryIdentity(record, runId);
830
+ if (record.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${runId} already has a terminal receipt`);
831
+ const next = {
832
+ ...record,
833
+ revision: record.revision + 1,
834
+ sequence: record.sequence + 1
835
+ };
836
+ return {
837
+ record: next,
838
+ sequence: next.sequence
739
839
  };
740
- validateInboundDeliverySnapshot(delivery);
741
- return freezeInboundDeliverySnapshot(delivery);
742
840
  }
743
- function assertNewInboundDelivery(delivery) {
744
- validateInboundDeliverySnapshot(delivery);
745
- if (delivery.revision !== 1 || delivery.attempts !== 0 || delivery.recovery !== void 0 || delivery.state.status !== "pending") throw new Error("new Inbound Delivery must start pending at revision 1 with zero attempts");
841
+ function markCardDeliveryTerminal(current, runId) {
842
+ const record = current ?? initialCardDeliveryRecord(runId);
843
+ assertCardDeliveryIdentity(record, runId);
844
+ return record.terminalPublished ? record : {
845
+ ...record,
846
+ revision: record.revision + 1,
847
+ terminalPublished: true
848
+ };
746
849
  }
747
- function validateInboundDeliverySnapshot(delivery) {
748
- parseInboundDeliveryTimestamp(delivery.acceptedAt, "acceptance timestamp");
749
- if (!nonEmpty$2(delivery.id)) throw new InvalidInboundDelivery("Inbound Delivery id must not be empty");
750
- if (!nonEmpty$2(delivery.laneKey)) throw new InvalidInboundDelivery("Inbound Delivery lane must not be empty");
751
- if (!Number.isSafeInteger(delivery.attempts) || delivery.attempts < 0) throw new InvalidInboundDelivery("Inbound Delivery attempts must be a non-negative integer");
752
- if (!Number.isSafeInteger(delivery.revision) || delivery.revision < 1) throw new InvalidInboundDelivery("Inbound Delivery revision must be a positive integer");
753
- if (delivery.recovery !== void 0) validateInboundDeliveryRecoveryAudit(delivery.recovery);
754
- switch (delivery.state.status) {
755
- case "pending":
756
- parseInboundDeliveryTimestamp(delivery.state.availableAt, "availability timestamp");
757
- return;
758
- case "leased":
759
- requireInboundDeliveryLease(delivery.state.leaseId, delivery.state.leasedAt, delivery.state.leaseExpiresAt);
760
- return;
761
- case "completed":
762
- parseInboundDeliveryTimestamp(delivery.state.completedAt, "completion timestamp");
763
- return;
764
- case "dead":
765
- parseInboundDeliveryTimestamp(delivery.state.failedAt, "failure timestamp");
766
- if (!nonEmpty$2(delivery.state.reason)) throw new InvalidInboundDelivery("Inbound Delivery Dead Letter reason must not be empty");
767
- }
850
+ function validateCardDeliveryRecord(record) {
851
+ if (record.runId.trim().length === 0 || !Number.isSafeInteger(record.revision) || record.revision < 1 || !Number.isSafeInteger(record.sequence) || record.sequence < 0 || typeof record.terminalPublished !== "boolean") throw new CardDeliveryTransitionDenied("card delivery record is invalid");
852
+ return { ...record };
768
853
  }
769
- function parseInboundDeliveryTimestamp(value, label) {
770
- const timestamp = Date.parse(value);
771
- if (!Number.isFinite(timestamp)) throw new InvalidInboundDelivery(`Inbound Delivery ${label} is invalid`);
772
- return timestamp;
854
+ function validateCardDeliveryTransition(previous, next) {
855
+ validateCardDeliveryRecord(next);
856
+ if (previous === void 0) {
857
+ if (!(next.sequence === 1 && !next.terminalPublished || next.sequence === 0 && next.terminalPublished) || next.revision !== 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid initial revision`);
858
+ return { ...next };
859
+ }
860
+ assertCardDeliveryIdentity(previous, next.runId);
861
+ if (next.revision !== previous.revision + 1) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} skipped a revision`);
862
+ if (previous.terminalPublished) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} changed after its terminal receipt`);
863
+ if (!(next.terminalPublished ? next.sequence === previous.sequence : next.sequence === previous.sequence + 1)) throw new CardDeliveryTransitionDenied(`card delivery for run ${next.runId} has an invalid sequence transition`);
864
+ return { ...next };
773
865
  }
774
- function readState$1(value) {
775
- if (value.status === "pending" && typeof value.availableAt === "string") return {
776
- availableAt: value.availableAt,
777
- status: "pending"
778
- };
779
- if (value.status === "leased" && typeof value.leaseExpiresAt === "string" && typeof value.leaseId === "string" && typeof value.leasedAt === "string") return {
780
- leaseExpiresAt: value.leaseExpiresAt,
781
- leaseId: value.leaseId,
782
- leasedAt: value.leasedAt,
783
- status: "leased"
784
- };
785
- if (value.status === "completed" && typeof value.completedAt === "string") return {
786
- completedAt: value.completedAt,
787
- status: "completed"
866
+ function initialCardDeliveryRecord(runId) {
867
+ if (runId.trim().length === 0) throw new CardDeliveryTransitionDenied("card delivery run id must not be empty");
868
+ return {
869
+ revision: 0,
870
+ runId,
871
+ sequence: 0,
872
+ terminalPublished: false
788
873
  };
789
- if (value.status === "dead" && typeof value.failedAt === "string" && typeof value.reason === "string") return {
790
- failedAt: value.failedAt,
791
- reason: value.reason,
792
- status: "dead"
874
+ }
875
+ function assertCardDeliveryIdentity(record, runId) {
876
+ if (record.runId !== runId) throw new CardDeliveryTransitionDenied(`card delivery identity ${record.runId} does not match requested run ${runId}`);
877
+ }
878
+ //#endregion
879
+ //#region src/core/application/card-delivery/ledger/card-delivery-ledger.ts
880
+ function createCardDeliveryLedger(repository) {
881
+ return {
882
+ isTerminalPublished: (runId) => repository.get(runId)?.terminalPublished ?? false,
883
+ markTerminal: (runId) => repository.transact(runId, (current) => {
884
+ const next = markCardDeliveryTerminal(current, runId);
885
+ return next === current ? { result: void 0 } : {
886
+ next,
887
+ result: void 0
888
+ };
889
+ }),
890
+ reserveSequence: (runId) => repository.transact(runId, (current) => {
891
+ const reservation = reserveCardDeliverySequence(current, runId);
892
+ return {
893
+ next: reservation.record,
894
+ result: reservation.sequence
895
+ };
896
+ })
793
897
  };
794
- throw new InvalidInboundDelivery("Inbound Delivery state is invalid");
795
898
  }
796
- function readRecoveryAudit(value) {
797
- if (!isRecord$14(value) || typeof value.actorId !== "string" || typeof value.at !== "string" || typeof value.note !== "string") throw new InvalidInboundDelivery("Inbound Delivery recovery audit is incomplete");
899
+ //#endregion
900
+ //#region src/adapters/outbound/persistence/card-delivery/in-memory-card-delivery-repository.ts
901
+ function createInMemoryCardDeliveryRepository(options = {}) {
902
+ const records = new Map(options.initial?.map((record) => [record.runId, cloneRecord(record)]) ?? []);
903
+ const semaphore = Effect.unsafeMakeSemaphore(1);
798
904
  return {
799
- actorId: value.actorId,
800
- at: value.at,
801
- note: value.note
905
+ get: (runId) => cloneOptional(records.get(runId)),
906
+ transact: (runId, decide) => semaphore.withPermits(1)(Effect.uninterruptible(Effect.gen(function* () {
907
+ const current = cloneOptional(records.get(runId));
908
+ const decision = yield* Effect.try({
909
+ try: () => decide(current),
910
+ catch: (error) => error
911
+ });
912
+ if (!("next" in decision)) return decision.result;
913
+ const next = yield* Effect.try({
914
+ try: () => validateCardDeliveryTransition(current, decision.next),
915
+ catch: (error) => error
916
+ });
917
+ yield* options.persist?.(cloneRecord(next)) ?? Effect.void;
918
+ yield* Effect.sync(() => {
919
+ records.set(runId, cloneRecord(next));
920
+ });
921
+ return decision.result;
922
+ })))
802
923
  };
803
924
  }
804
- function validateInboundDeliveryRecoveryAudit(recovery) {
805
- if (!nonEmpty$2(recovery.actorId) || !nonEmpty$2(recovery.note)) throw new InvalidInboundDelivery("Inbound Delivery recovery audit is incomplete");
806
- parseInboundDeliveryTimestamp(recovery.at, "recovery audit timestamp");
925
+ function cloneOptional(record) {
926
+ return record === void 0 ? void 0 : cloneRecord(record);
807
927
  }
808
- function requireInboundDeliveryLease(leaseId, leasedAt, leaseExpiresAt) {
809
- if (!nonEmpty$2(leaseId)) throw new InvalidInboundDelivery("Inbound Delivery lease id must not be empty");
810
- const leasedAtMilliseconds = parseInboundDeliveryTimestamp(leasedAt, "lease timestamp");
811
- if (parseInboundDeliveryTimestamp(leaseExpiresAt, "lease expiration") <= leasedAtMilliseconds) throw new InvalidInboundDelivery("Inbound Delivery lease expiration must be after its lease timestamp");
928
+ function cloneRecord(record) {
929
+ return { ...validateCardDeliveryRecord(record) };
812
930
  }
813
- function nonEmpty$2(value) {
814
- return value.trim().length > 0;
931
+ //#endregion
932
+ //#region src/core/application/run-presentation/projection/run-presentation.ts
933
+ const RUN_PRESENTATION_SCHEMA_VERSION = 2;
934
+ function hasInspectableRunProgress(presentation) {
935
+ return presentation.steps.some((step) => step.kind === "skill" || step.kind === "tool");
815
936
  }
816
- function cloneOpaqueValue(value) {
937
+ //#endregion
938
+ //#region src/core/application/run-presentation/projection/presented-value.ts
939
+ const DEFAULT_MAX_COLLECTION_ITEMS = 20;
940
+ const DEFAULT_MAX_DEPTH = 5;
941
+ const DEFAULT_MAX_NODES = 100;
942
+ const DEFAULT_MAX_STRING_CHARACTERS = 800;
943
+ const SENSITIVE_KEY = /authorization|cookie|credential|password|passwd|private[-_]?key|secret|session[-_]?token|(?:^|[-_])token(?:$|[-_])|api[-_]?key|access[-_]?(?:key|token)|refresh[-_]?token|client[-_]?secret/i;
944
+ const WORKING_DIRECTORY_KEY = /^(?:cwd|workdir|workingDirectory|workspace|workspaceRoot)$/i;
945
+ const INLINE_SECRET = /\b(Bearer\s+)[^\s,;]+|\b(?:gh[opusr]_|sk-|xox[baprs]-)[A-Za-z0-9_-]{8,}/gi;
946
+ const NAMED_SECRET_ARGUMENT = /((?:--?|\b)(?:authorization|password|passwd|secret|token|api[-_]?key|access[-_]?token)(?:\s+|=))([^\s,;]+)/gi;
947
+ const SECRET_ENVIRONMENT_ASSIGNMENT = /\b([A-Z0-9_]*(?:PASSWORD|SECRET|TOKEN|API_KEY|ACCESS_KEY)=)([^\s,;]+)/g;
948
+ function createPresentedValue(value, options = {}) {
949
+ return present(value, {
950
+ ancestors: /* @__PURE__ */ new Set(),
951
+ depth: 0,
952
+ maxCollectionItems: options.maxCollectionItems ?? DEFAULT_MAX_COLLECTION_ITEMS,
953
+ maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
954
+ nodeBudget: { remaining: options.maxNodes ?? DEFAULT_MAX_NODES },
955
+ maxStringCharacters: options.maxStringCharacters ?? DEFAULT_MAX_STRING_CHARACTERS
956
+ });
957
+ }
958
+ function present(value, context, key) {
959
+ if (key && SENSITIVE_KEY.test(key)) return {
960
+ kind: "redacted",
961
+ reason: "sensitive-key"
962
+ };
963
+ if (context.nodeBudget.remaining <= 0) return {
964
+ kind: "unsupported",
965
+ valueType: "node-budget"
966
+ };
967
+ context.nodeBudget.remaining -= 1;
968
+ if (value === null) return { kind: "null" };
969
+ if (typeof value === "boolean") return {
970
+ kind: "boolean",
971
+ value
972
+ };
973
+ if (typeof value === "number") return Number.isFinite(value) ? {
974
+ kind: "number",
975
+ value
976
+ } : {
977
+ kind: "unsupported",
978
+ valueType: "number"
979
+ };
980
+ if (typeof value === "string") return presentText(value, context.maxStringCharacters, key);
981
+ if (typeof value !== "object") return {
982
+ kind: "unsupported",
983
+ valueType: typeof value
984
+ };
985
+ if (context.depth >= context.maxDepth) return {
986
+ kind: "unsupported",
987
+ valueType: "max-depth"
988
+ };
989
+ if (context.ancestors.has(value)) return {
990
+ kind: "unsupported",
991
+ valueType: "circular"
992
+ };
993
+ context.ancestors.add(value);
994
+ const nested = {
995
+ ...context,
996
+ depth: context.depth + 1
997
+ };
817
998
  try {
818
- return deepFreeze$2(structuredClone(value));
819
- } catch {
820
- throw new InvalidInboundDelivery("Inbound Delivery opaque values must be cloneable");
999
+ if (Array.isArray(value)) {
1000
+ const visibleLength = Math.min(value.length, context.maxCollectionItems);
1001
+ const items = Array.from({ length: visibleLength }, (_, index) => Object.hasOwn(value, index) ? present(value[index], nested) : {
1002
+ kind: "unsupported",
1003
+ valueType: "array-hole"
1004
+ });
1005
+ return {
1006
+ items,
1007
+ kind: "list",
1008
+ omittedItems: Math.max(0, value.length - items.length)
1009
+ };
1010
+ }
1011
+ if (value instanceof Error) return {
1012
+ entries: [{
1013
+ key: "name",
1014
+ value: present(value.name, nested, "name")
1015
+ }, {
1016
+ key: "message",
1017
+ value: present(value.message, nested, "message")
1018
+ }],
1019
+ kind: "record",
1020
+ omittedEntries: 0
1021
+ };
1022
+ const prototype = Object.getPrototypeOf(value);
1023
+ if (prototype !== Object.prototype && prototype !== null) return {
1024
+ kind: "unsupported",
1025
+ valueType: prototype?.constructor?.name ?? "object"
1026
+ };
1027
+ const allEntries = Object.entries(value);
1028
+ const entries = allEntries.slice(0, context.maxCollectionItems).map(([entryKey, entryValue]) => ({
1029
+ key: safeKey(entryKey),
1030
+ value: present(entryValue, nested, entryKey)
1031
+ }));
1032
+ return {
1033
+ entries,
1034
+ kind: "record",
1035
+ omittedEntries: Math.max(0, allEntries.length - entries.length)
1036
+ };
1037
+ } finally {
1038
+ context.ancestors.delete(value);
821
1039
  }
822
1040
  }
823
- //#endregion
824
- //#region src/platform/durable-queue/records/inbound-delivery-transitions.ts
825
- function claimInboundDelivery(delivery, input) {
826
- if (delivery.state.status !== "pending" || Date.parse(delivery.state.availableAt) > parseInboundDeliveryTimestamp(input.now, "claim timestamp")) throw new InvalidInboundDelivery(`Inbound Delivery is not claimable: ${delivery.id}`);
827
- return leaseInboundDelivery(delivery, input);
828
- }
829
- function reclaimInboundDelivery(delivery, input) {
830
- if (delivery.state.status !== "leased" || Date.parse(delivery.state.leaseExpiresAt) > parseInboundDeliveryTimestamp(input.now, "lease recovery timestamp")) throw new InvalidInboundDelivery(`Inbound Delivery lease has not expired: ${delivery.id}`);
831
- return leaseInboundDelivery(delivery, input);
832
- }
833
- function renewInboundDelivery(delivery, input) {
834
- const lease = requireCurrentLease(delivery, input.leaseId);
835
- requireInboundDeliveryLease(input.leaseId, input.now, input.leaseExpiresAt);
836
- const now = parseInboundDeliveryTimestamp(input.now, "renewal timestamp");
837
- const expiresAt = parseInboundDeliveryTimestamp(input.leaseExpiresAt, "lease expiration");
838
- if (now < Date.parse(lease.leasedAt) || now >= Date.parse(lease.leaseExpiresAt)) throw new InvalidInboundDelivery(`Inbound Delivery lease is not live at renewal: ${delivery.id}`);
839
- if (expiresAt <= Date.parse(lease.leaseExpiresAt)) throw new InvalidInboundDelivery(`Inbound Delivery lease renewal must extend the lease: ${delivery.id}`);
840
- return nextInboundDelivery(delivery, { state: {
841
- ...lease,
842
- leaseExpiresAt: input.leaseExpiresAt
843
- } });
1041
+ function presentText(value, maxCharacters, key) {
1042
+ if (INLINE_SECRET.test(value)) {
1043
+ INLINE_SECRET.lastIndex = 0;
1044
+ value = value.replace(INLINE_SECRET, (_match, bearerPrefix) => bearerPrefix ? `${bearerPrefix}<redacted>` : "<redacted>");
1045
+ }
1046
+ INLINE_SECRET.lastIndex = 0;
1047
+ value = value.replace(NAMED_SECRET_ARGUMENT, "$1<redacted>").replace(SECRET_ENVIRONMENT_ASSIGNMENT, "$1<redacted>");
1048
+ value = normalizeMachinePaths(value, key);
1049
+ const preview = value.slice(0, maxCharacters);
1050
+ return {
1051
+ kind: "text",
1052
+ omittedCharacters: Math.max(0, value.length - preview.length),
1053
+ value: preview
1054
+ };
844
1055
  }
845
- function completeInboundDelivery(delivery, input) {
846
- requireCurrentLease(delivery, input.leaseId);
847
- parseInboundDeliveryTimestamp(input.completedAt, "completion timestamp");
848
- return nextInboundDelivery(delivery, { state: {
849
- completedAt: input.completedAt,
850
- status: "completed"
851
- } });
1056
+ function normalizeMachinePaths(value, key) {
1057
+ if (key && WORKING_DIRECTORY_KEY.test(key)) return "<workspace>";
1058
+ return value.replace(/\/(?:Users|home)\/[^/\s]+/g, "<home>").replace(/\/(?:private\/)?tmp\/[^\s'";]*/g, (path) => `<tmp>/${path.split("/").at(-1) || "item"}`);
852
1059
  }
853
- function failInboundDelivery(delivery, input) {
854
- requireCurrentLease(delivery, input.leaseId);
855
- parseInboundDeliveryTimestamp(input.failedAt, "failure timestamp");
856
- if (input.terminal) {
857
- if (input.reason.trim().length === 0) throw new InvalidInboundDelivery("Inbound Delivery Dead Letter reason must not be empty");
858
- return nextInboundDelivery(delivery, { state: {
859
- failedAt: input.failedAt,
860
- reason: input.reason,
861
- status: "dead"
862
- } });
863
- }
864
- return releaseInboundDelivery(delivery, {
865
- availableAt: input.availableAt ?? input.failedAt,
866
- leaseId: input.leaseId
867
- });
1060
+ function safeKey(value) {
1061
+ return value.replace(/[^\p{L}\p{N}._:/-]+/gu, " ").trim().slice(0, 80) || "unknown";
868
1062
  }
869
- function releaseInboundDelivery(delivery, input) {
870
- requireCurrentLease(delivery, input.leaseId);
871
- parseInboundDeliveryTimestamp(input.availableAt, "availability timestamp");
872
- return nextInboundDelivery(delivery, { state: {
873
- availableAt: input.availableAt,
874
- status: "pending"
875
- } });
1063
+ //#endregion
1064
+ //#region src/core/application/run-presentation/projection/run-presentation-projector.ts
1065
+ function createRunPresentationProjector$1() {
1066
+ const runs = /* @__PURE__ */ new Map();
1067
+ const stateFor = (runId) => {
1068
+ const existing = runs.get(runId);
1069
+ if (existing) return existing;
1070
+ const created = {
1071
+ answer: "",
1072
+ answerStartedAt: void 0,
1073
+ assistantSequence: 0,
1074
+ currentSkillId: void 0,
1075
+ order: [],
1076
+ phase: "running",
1077
+ revision: 0,
1078
+ steps: /* @__PURE__ */ new Map()
1079
+ };
1080
+ runs.set(runId, created);
1081
+ return created;
1082
+ };
1083
+ const snapshot = (runId) => {
1084
+ const state = runs.get(runId);
1085
+ if (!state) return void 0;
1086
+ const visibleKeys = state.order.slice(-8);
1087
+ return {
1088
+ answer: {
1089
+ format: "markdown",
1090
+ text: state.answer
1091
+ },
1092
+ omittedStepCount: Math.max(0, state.order.length - visibleKeys.length),
1093
+ phase: state.phase,
1094
+ revision: state.revision,
1095
+ runId,
1096
+ schemaVersion: 2,
1097
+ steps: visibleKeys.map((key) => state.steps.get(key)).filter((step) => step !== void 0),
1098
+ totalStepCount: state.order.length,
1099
+ totalToolCallCount: [...state.steps.values()].filter((step) => step.kind === "tool").length
1100
+ };
1101
+ };
1102
+ const mutate = (runId, mutation) => {
1103
+ const state = stateFor(runId);
1104
+ mutation(state);
1105
+ state.revision += 1;
1106
+ return snapshot(runId);
1107
+ };
1108
+ const upsert = (state, step) => {
1109
+ if (!state.steps.has(step.id)) state.order.push(step.id);
1110
+ state.steps.set(step.id, step);
1111
+ };
1112
+ const commitAssistantNarration = (state, endedAt) => {
1113
+ const text = state.answer.trim();
1114
+ if (!text) return;
1115
+ const id = `assistant:${state.assistantSequence + 1}`;
1116
+ state.assistantSequence += 1;
1117
+ const startedAt = state.answerStartedAt ?? endedAt.toISOString();
1118
+ upsert(state, {
1119
+ durationMs: elapsed(startedAt, endedAt),
1120
+ endedAt: endedAt.toISOString(),
1121
+ id,
1122
+ kind: "assistant",
1123
+ label: "过程说明",
1124
+ startedAt,
1125
+ status: "completed",
1126
+ text
1127
+ });
1128
+ state.answer = "";
1129
+ state.answerStartedAt = void 0;
1130
+ };
1131
+ return {
1132
+ apply: (event, canonicalText) => {
1133
+ switch (event.type) {
1134
+ case "agent_run_accepted":
1135
+ runs.set(event.runId, {
1136
+ answer: "",
1137
+ answerStartedAt: void 0,
1138
+ assistantSequence: 0,
1139
+ currentSkillId: void 0,
1140
+ order: [],
1141
+ phase: "running",
1142
+ revision: 1,
1143
+ steps: /* @__PURE__ */ new Map()
1144
+ });
1145
+ return snapshot(event.runId);
1146
+ case "agent_turn_started": return mutate(event.runId, (state) => {
1147
+ state.answer = "";
1148
+ state.answerStartedAt = void 0;
1149
+ state.currentSkillId = void 0;
1150
+ });
1151
+ case "agent_model_execution_started": return mutate(event.runId, (state) => {
1152
+ state.answer = "";
1153
+ state.answerStartedAt = void 0;
1154
+ state.currentSkillId = void 0;
1155
+ upsert(state, {
1156
+ id: `model:${event.modelCallId}`,
1157
+ kind: "model",
1158
+ label: "分析问题",
1159
+ model: safeIdentifier(event.model),
1160
+ provider: safeIdentifier(event.provider),
1161
+ startedAt: event.occurredAt.toISOString(),
1162
+ status: "running"
1163
+ });
1164
+ });
1165
+ case "agent_model_execution_ended": return mutate(event.runId, (state) => {
1166
+ const id = `model:${event.modelCallId}`;
1167
+ upsert(state, {
1168
+ ...completedTiming(state, id, event.occurredAt),
1169
+ id,
1170
+ kind: "model",
1171
+ label: "分析问题",
1172
+ model: safeIdentifier(event.responseModel ?? event.model),
1173
+ provider: safeIdentifier(event.provider),
1174
+ status: event.errorMessage ? "failed" : "completed"
1175
+ });
1176
+ });
1177
+ case "agent_skill_execution_started": return mutate(event.runId, (state) => {
1178
+ commitAssistantNarration(state, event.occurredAt);
1179
+ state.currentSkillId = safeIdentifier(event.skillId);
1180
+ upsert(state, {
1181
+ id: `skill:${event.skillCallId}`,
1182
+ kind: "skill",
1183
+ label: "读取技能",
1184
+ skillId: safeIdentifier(event.skillId),
1185
+ startedAt: event.occurredAt.toISOString(),
1186
+ status: "running"
1187
+ });
1188
+ });
1189
+ case "agent_skill_execution_ended": return mutate(event.runId, (state) => {
1190
+ const id = `skill:${event.skillCallId}`;
1191
+ upsert(state, {
1192
+ ...completedTiming(state, id, event.occurredAt),
1193
+ id,
1194
+ kind: "skill",
1195
+ label: "读取技能",
1196
+ skillId: safeIdentifier(event.skillId),
1197
+ status: event.isError ? "failed" : "completed"
1198
+ });
1199
+ });
1200
+ case "agent_tool_execution_started": return mutate(event.runId, (state) => {
1201
+ commitAssistantNarration(state, event.occurredAt);
1202
+ upsert(state, runningToolStep(state, event));
1203
+ });
1204
+ case "agent_tool_execution_updated": return mutate(event.runId, (state) => {
1205
+ upsert(state, runningToolStep(state, event));
1206
+ });
1207
+ case "agent_tool_execution_ended": return mutate(event.runId, (state) => {
1208
+ const id = `tool:${event.toolCallId}`;
1209
+ const existing = state.steps.get(id);
1210
+ const startedAt = existing?.startedAt ?? event.occurredAt.toISOString();
1211
+ const input = existing?.kind === "tool" ? existing.input : createPresentedValue(void 0);
1212
+ upsert(state, {
1213
+ durationMs: elapsed(startedAt, event.occurredAt),
1214
+ endedAt: event.occurredAt.toISOString(),
1215
+ ...event.isError ? { error: createPresentedValue(event.result) } : { output: createPresentedValue(event.result) },
1216
+ id,
1217
+ input,
1218
+ kind: "tool",
1219
+ label: "执行工具",
1220
+ ...existing?.kind === "tool" && existing.skillId ? { skillId: existing.skillId } : {},
1221
+ startedAt,
1222
+ status: event.isError ? "failed" : "completed",
1223
+ toolName: safeIdentifier(event.toolName)
1224
+ });
1225
+ });
1226
+ case "assistant_text_delta": return mutate(event.runId, (state) => {
1227
+ state.answerStartedAt ??= event.occurredAt.toISOString();
1228
+ state.answer += event.delta;
1229
+ });
1230
+ case "assistant_thinking_delta":
1231
+ case "agent_turn_completed": return snapshot(event.runId);
1232
+ case "agent_run_completed": {
1233
+ const result = finish(event.runId, "completed", "回答已完成", event.occurredAt, canonicalText, event.finalText, stateFor, mutate, upsert);
1234
+ runs.delete(event.runId);
1235
+ return result;
1236
+ }
1237
+ case "agent_run_failed": {
1238
+ const result = finish(event.runId, "failed", "运行未完成", event.occurredAt, void 0, canonicalText, stateFor, mutate, upsert);
1239
+ runs.delete(event.runId);
1240
+ return result;
1241
+ }
1242
+ case "agent_run_cancelled": {
1243
+ const result = finish(event.runId, "cancelled", "运行已取消", event.occurredAt, void 0, canonicalText, stateFor, mutate, upsert);
1244
+ runs.delete(event.runId);
1245
+ return result;
1246
+ }
1247
+ }
1248
+ },
1249
+ snapshot
1250
+ };
876
1251
  }
877
- function requeueInboundDelivery(delivery, input) {
878
- if (delivery.state.status !== "dead" || delivery.revision !== input.expectedRevision) throw new InvalidInboundDelivery(`Inbound Delivery is not the expected Dead Letter revision: ${delivery.id}`);
879
- validateInboundDeliveryRecoveryAudit(input.recovery);
880
- if (input.availableAt !== input.recovery.at) throw new InvalidInboundDelivery("Inbound Delivery recovery availability must match its audit timestamp");
881
- return nextInboundDelivery(delivery, {
882
- attempts: 0,
883
- recovery: input.recovery,
884
- state: {
885
- availableAt: input.availableAt,
886
- status: "pending"
887
- }
888
- });
1252
+ function runningToolStep(state, event) {
1253
+ const id = `tool:${event.toolCallId}`;
1254
+ const existing = state.steps.get(id);
1255
+ const skillId = existing?.kind === "tool" ? existing.skillId : state.currentSkillId;
1256
+ const output = event.type === "agent_tool_execution_updated" ? createPresentedValue(event.partialResult) : existing?.kind === "tool" ? existing.output : void 0;
1257
+ return {
1258
+ id,
1259
+ input: createPresentedValue(event.input),
1260
+ kind: "tool",
1261
+ label: "执行工具",
1262
+ ...output === void 0 ? {} : { output },
1263
+ ...skillId ? { skillId } : {},
1264
+ startedAt: existing?.startedAt ?? event.occurredAt.toISOString(),
1265
+ status: "running",
1266
+ toolName: safeIdentifier(event.toolName)
1267
+ };
889
1268
  }
890
- function isValidInboundDeliveryTransition(previous, next) {
891
- try {
892
- validateInboundDeliverySnapshot(next);
893
- } catch {
894
- return false;
895
- }
896
- if (previous.id !== next.id || previous.acceptedAt !== next.acceptedAt || previous.laneKey !== next.laneKey || next.revision !== previous.revision + 1 || !sameOpaqueValue(previous.payload, next.payload) || !sameOpaqueValue(previous.metadata, next.metadata)) return false;
897
- switch (previous.state.status) {
898
- case "pending": return next.state.status === "leased" && next.attempts === previous.attempts + 1 && sameOpaqueValue(previous.recovery, next.recovery);
899
- case "leased":
900
- if (!sameOpaqueValue(previous.recovery, next.recovery)) return false;
901
- if (next.state.status === "leased") {
902
- const renewed = next.attempts === previous.attempts && next.state.leaseId === previous.state.leaseId && next.state.leasedAt === previous.state.leasedAt && Date.parse(next.state.leaseExpiresAt) > Date.parse(previous.state.leaseExpiresAt);
903
- const reclaimed = next.attempts === previous.attempts + 1 && next.state.leaseId !== previous.state.leaseId && Date.parse(previous.state.leaseExpiresAt) <= Date.parse(next.state.leasedAt);
904
- return renewed || reclaimed;
905
- }
906
- return next.attempts === previous.attempts && (next.state.status === "pending" || next.state.status === "completed" || next.state.status === "dead");
907
- case "completed": return false;
908
- case "dead": return next.state.status === "pending" && next.attempts === 0 && next.recovery !== void 0 && next.state.availableAt === next.recovery.at && !sameOpaqueValue(previous.recovery, next.recovery);
909
- }
1269
+ function finish(runId, phase, label, occurredAt, preferredText, fallbackText, stateFor, mutate, upsert) {
1270
+ stateFor(runId);
1271
+ const startedAt = occurredAt.toISOString();
1272
+ return mutate(runId, (state) => {
1273
+ state.phase = phase;
1274
+ state.answer = preferredText?.trim() || state.answer.trim() || fallbackText?.trim() || state.answer;
1275
+ for (const step of state.steps.values()) if (step.status === "running") state.steps.set(step.id, finishRunningStep(step, occurredAt, phase === "completed" ? "completed" : "failed"));
1276
+ upsert(state, {
1277
+ endedAt: occurredAt.toISOString(),
1278
+ id: `response:${phase}`,
1279
+ kind: "response",
1280
+ label,
1281
+ startedAt,
1282
+ status: phase === "completed" ? "completed" : "failed"
1283
+ });
1284
+ });
910
1285
  }
911
- function sameOpaqueValue(left, right) {
912
- if (Object.is(left, right)) return true;
913
- if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => sameOpaqueValue(value, right[index]));
914
- if (!isRecord$14(left) || !isRecord$14(right)) return false;
915
- const leftKeys = Object.keys(left).sort();
916
- const rightKeys = Object.keys(right).sort();
917
- return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && sameOpaqueValue(left[key], right[key]));
1286
+ function finishRunningStep(step, occurredAt, status) {
1287
+ return {
1288
+ ...step,
1289
+ durationMs: elapsed(step.startedAt, occurredAt),
1290
+ endedAt: occurredAt.toISOString(),
1291
+ status
1292
+ };
918
1293
  }
919
- function nextInboundDelivery(delivery, changes) {
920
- const next = {
921
- ...delivery,
922
- ...changes,
923
- revision: delivery.revision + 1
1294
+ function completedTiming(state, id, occurredAt) {
1295
+ const startedAt = state.steps.get(id)?.startedAt ?? occurredAt.toISOString();
1296
+ return {
1297
+ durationMs: elapsed(startedAt, occurredAt),
1298
+ endedAt: occurredAt.toISOString(),
1299
+ startedAt
924
1300
  };
925
- validateInboundDeliverySnapshot(next);
926
- return freezeInboundDeliverySnapshot(next);
927
1301
  }
928
- function leaseInboundDelivery(delivery, input) {
929
- parseInboundDeliveryTimestamp(input.now, "lease timestamp");
930
- requireInboundDeliveryLease(input.leaseId, input.now, input.leaseExpiresAt);
931
- return nextInboundDelivery(delivery, {
932
- attempts: delivery.attempts + 1,
933
- state: {
934
- leaseExpiresAt: input.leaseExpiresAt,
935
- leaseId: input.leaseId,
936
- leasedAt: input.now,
937
- status: "leased"
938
- }
939
- });
1302
+ function elapsed(startedAt, endedAt) {
1303
+ return Math.max(0, endedAt.getTime() - Date.parse(startedAt));
940
1304
  }
941
- function requireCurrentLease(delivery, leaseId) {
942
- if (delivery.state.status !== "leased" || delivery.state.leaseId !== leaseId) throw new InvalidInboundDelivery(`Inbound Delivery lease does not match: ${delivery.id}`);
943
- return delivery.state;
1305
+ function safeIdentifier(value) {
1306
+ return value.replace(/[^\p{L}\p{N}._:/-]+/gu, " ").trim().slice(0, 80) || "unknown";
944
1307
  }
945
1308
  //#endregion
946
- //#region src/platform/durable-queue/store/in-memory-durable-queue.ts
947
- function createInMemoryDurableQueueStore(options = {}) {
948
- const deliveries = /* @__PURE__ */ new Map();
949
- for (const delivery of options.initial ?? []) {
950
- const restored = readInboundDeliverySnapshot(delivery);
951
- if (deliveries.has(restored.id)) throw new Error(`duplicate Inbound Delivery snapshot: ${restored.id}`);
952
- deliveries.set(restored.id, restored);
953
- }
954
- const serial = Effect.unsafeMakeSemaphore(1);
955
- let lastLaneKey;
956
- const save = (current, delivery) => {
957
- const snapshot = readInboundDeliverySnapshot(current === void 0 ? delivery : resolveUpdate(current, delivery));
958
- return (options.persist?.(readInboundDeliverySnapshot(snapshot)) ?? Effect.void).pipe(Effect.flatMap(() => Effect.sync(() => {
959
- deliveries.set(snapshot.id, snapshot);
960
- })));
1309
+ //#region src/adapters/feishu/card-presentation/streaming/feishu-stream-projector.ts
1310
+ function createFeishuStreamProjector() {
1311
+ const presentations = createRunPresentationProjector$1();
1312
+ const applyEvent = (event, canonicalText) => {
1313
+ const presentation = presentations.apply(event, canonicalText);
1314
+ switch (event.type) {
1315
+ case "agent_run_accepted":
1316
+ case "agent_turn_started":
1317
+ case "assistant_thinking_delta":
1318
+ case "agent_turn_completed": return [];
1319
+ case "agent_model_execution_started":
1320
+ case "agent_model_execution_ended":
1321
+ case "agent_skill_execution_started":
1322
+ case "agent_skill_execution_ended":
1323
+ case "agent_tool_execution_started":
1324
+ case "agent_tool_execution_updated":
1325
+ case "agent_tool_execution_ended": return presentation ? [{
1326
+ presentation,
1327
+ runId: event.runId,
1328
+ type: "update_progress"
1329
+ }] : [];
1330
+ case "assistant_text_delta": return [{
1331
+ runId: event.runId,
1332
+ text: presentation?.answer.text ?? event.delta,
1333
+ type: "update_text"
1334
+ }];
1335
+ case "agent_run_completed": return [{
1336
+ ...hasMeaningfulProgress(presentation) ? { presentation } : {},
1337
+ runId: event.runId,
1338
+ text: presentation?.answer.text.trim() || canonicalText?.trim() || event.finalText,
1339
+ type: "finish"
1340
+ }];
1341
+ case "agent_run_failed": return [{
1342
+ errorMessage: event.errorMessage,
1343
+ ...hasMeaningfulProgress(presentation) ? { presentation } : {},
1344
+ runId: event.runId,
1345
+ type: "fail"
1346
+ }];
1347
+ case "agent_run_cancelled": return [{
1348
+ ...hasMeaningfulProgress(presentation) ? { presentation } : {},
1349
+ ...event.reason ? { reason: event.reason } : {},
1350
+ runId: event.runId,
1351
+ type: "cancel"
1352
+ }];
1353
+ }
961
1354
  };
962
- const serialized = (effect) => serial.withPermits(1)(Effect.uninterruptible(effect));
963
1355
  return {
964
- admit: (delivery, maxPending) => serialized(Effect.gen(function* () {
965
- if (deliveries.has(delivery.id)) return "duplicate";
966
- if (!Number.isSafeInteger(maxPending) || maxPending < 1) return yield* Effect.fail(/* @__PURE__ */ new Error("Inbound Delivery capacity must be a positive integer"));
967
- if (pendingCount(deliveries) >= maxPending) return "capacity";
968
- assertNewInboundDelivery(delivery);
969
- yield* save(void 0, delivery);
970
- return "created";
971
- })),
972
- claim: (input) => serialized(Effect.gen(function* () {
973
- validateNow(input.now);
974
- const now = Date.parse(input.now);
975
- const leasedLanes = new Set([...deliveries.values()].filter((delivery) => delivery.state.status === "leased" && Date.parse(delivery.state.leaseExpiresAt) > now).map((delivery) => delivery.laneKey));
976
- const eligible = [...deliveries.values()].filter((delivery) => !leasedLanes.has(delivery.laneKey) && (delivery.state.status === "pending" && Date.parse(delivery.state.availableAt) <= now || delivery.state.status === "leased" && Date.parse(delivery.state.leaseExpiresAt) <= now));
977
- if (eligible.length === 0) return void 0;
978
- const laneKeys = [...new Set(eligible.map((delivery) => delivery.laneKey))];
979
- const laneKey = laneKeys[((lastLaneKey === void 0 ? -1 : laneKeys.indexOf(lastLaneKey)) + 1) % laneKeys.length];
980
- const laneDeliveries = eligible.filter((delivery) => delivery.laneKey === laneKey);
981
- const current = laneDeliveries.find((delivery) => delivery.state.status === "leased") ?? laneDeliveries[0];
982
- const claimed = current.state.status === "leased" ? reclaimInboundDelivery(current, input) : claimInboundDelivery(current, input);
983
- yield* save(current, claimed);
984
- lastLaneKey = laneKey;
985
- return readInboundDeliverySnapshot(claimed);
986
- })),
987
- claimById: (input) => serialized(Effect.gen(function* () {
988
- validateNow(input.now);
989
- const current = deliveries.get(input.id);
990
- if (!current) return void 0;
991
- if (current.state.status === "leased") {
992
- if (Date.parse(current.state.leaseExpiresAt) > Date.parse(input.now)) return void 0;
993
- const reclaimed = reclaimInboundDelivery(current, input);
994
- yield* save(current, reclaimed);
995
- return readInboundDeliverySnapshot(reclaimed);
996
- }
997
- if (current.state.status !== "pending" || Date.parse(current.state.availableAt) > Date.parse(input.now)) return;
998
- const claimed = claimInboundDelivery(current, input);
999
- yield* save(current, claimed);
1000
- return readInboundDeliverySnapshot(claimed);
1001
- })),
1002
- complete: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => completeInboundDelivery(current, input))),
1003
- create: (delivery) => serialized(Effect.gen(function* () {
1004
- if (deliveries.has(delivery.id)) return false;
1005
- assertNewInboundDelivery(delivery);
1006
- yield* save(void 0, delivery);
1007
- return true;
1008
- })),
1009
- deadLetters: () => [...deliveries.values()].filter((delivery) => delivery.state.status === "dead").map((delivery) => readInboundDeliverySnapshot(delivery)),
1010
- fail: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => failInboundDelivery(current, input))),
1011
- pendingCount: () => pendingCount(deliveries),
1012
- renew: (input) => serialized(Effect.gen(function* () {
1013
- const current = requireDelivery(deliveries, input.id);
1014
- const renewed = renewInboundDelivery(current, input);
1015
- yield* save(current, renewed);
1016
- return readInboundDeliverySnapshot(renewed);
1017
- })),
1018
- requeueDeadLetter: (input) => serialized(Effect.gen(function* () {
1019
- const current = requireDelivery(deliveries, input.id);
1020
- const requeued = requeueInboundDelivery(current, input);
1021
- yield* save(current, requeued);
1022
- return readInboundDeliverySnapshot(requeued);
1023
- })),
1024
- release: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => releaseInboundDelivery(current, input)))
1356
+ apply: (event) => applyEvent(event),
1357
+ applyUpdate: ({ event, state }) => event.type === "agent_run_failed" ? applyEvent({
1358
+ ...event,
1359
+ errorMessage: state.errorMessage ?? event.errorMessage
1360
+ }, state.finalText) : event.type === "agent_run_cancelled" ? applyEvent(state.cancellationReason ? {
1361
+ ...event,
1362
+ reason: state.cancellationReason
1363
+ } : event, state.finalText) : applyEvent(event, state.finalText)
1025
1364
  };
1026
1365
  }
1027
- function mutateLeased(deliveries, id, leaseId, save, transition) {
1028
- return Effect.gen(function* () {
1029
- const current = requireDelivery(deliveries, id);
1030
- if (current.state.status !== "leased" || current.state.leaseId !== leaseId) return yield* Effect.fail(/* @__PURE__ */ new Error(`Inbound Delivery lease does not match: ${id}`));
1031
- yield* save(current, transition(current));
1366
+ function hasMeaningfulProgress(presentation) {
1367
+ return presentation !== void 0 && hasInspectableRunProgress(presentation);
1368
+ }
1369
+ //#endregion
1370
+ //#region src/adapters/feishu/card-presentation/delivery/feishu-card-delivery-ledger.ts
1371
+ function createFeishuCardDeliveryLedger(options = {}) {
1372
+ const persist = options.persist;
1373
+ return createCardDeliveryLedger(createInMemoryCardDeliveryRepository({
1374
+ ...options.initial === void 0 ? {} : { initial: options.initial },
1375
+ ...persist === void 0 ? {} : { persist: (record) => Effect.tryPromise({
1376
+ try: () => persist(record),
1377
+ catch: (error) => error
1378
+ }) }
1379
+ }));
1380
+ }
1381
+ function createFeishuCardDeliveryReconciler(options) {
1382
+ return { reconcile: () => Effect.gen(function* () {
1383
+ const projector = createFeishuStreamProjector();
1384
+ const terminalByRun = /* @__PURE__ */ new Map();
1385
+ for (const event of options.events) for (const action of projector.apply(event)) if (action.type === "cancel" || action.type === "fail" || action.type === "finish") terminalByRun.set(action.runId, action);
1386
+ let repaired = 0;
1387
+ let skipped = 0;
1388
+ for (const [runId, action] of terminalByRun) {
1389
+ if (options.ledger.isTerminalPublished(runId)) continue;
1390
+ yield* options.publish(action).pipe(Effect.catchAll((error) => {
1391
+ if (isMissingPresentationError(error)) {
1392
+ skipped += 1;
1393
+ return Effect.void;
1394
+ }
1395
+ return Effect.fail(error);
1396
+ }));
1397
+ yield* options.ledger.markTerminal(runId);
1398
+ repaired += 1;
1399
+ }
1400
+ return {
1401
+ repaired,
1402
+ skipped,
1403
+ terminalRuns: terminalByRun.size
1404
+ };
1405
+ }) };
1406
+ }
1407
+ function isMissingPresentationError(error) {
1408
+ return error !== null && typeof error === "object" && error._tag === "FeishuCardTargetNotFound";
1409
+ }
1410
+ //#endregion
1411
+ //#region src/adapters/outbound/persistence/card-delivery/card-delivery-snapshot-codec.ts
1412
+ function encodeCardDeliverySnapshot(record) {
1413
+ return JSON.stringify({
1414
+ record: validateCardDeliveryRecord(record),
1415
+ version: 1
1032
1416
  });
1033
1417
  }
1034
- function resolveUpdate(current, next) {
1035
- if (!isValidInboundDeliveryTransition(current, next)) throw new Error(`invalid Inbound Delivery transition: ${current.id}`);
1036
- return next;
1418
+ function decodeCardDeliverySnapshots(raw) {
1419
+ const records = /* @__PURE__ */ new Map();
1420
+ for (const [index, line] of raw.split("\n").entries()) {
1421
+ if (line.trim().length === 0) continue;
1422
+ const record = readEnvelope(line, index + 1);
1423
+ const previous = records.get(record.runId);
1424
+ try {
1425
+ if (previous?.terminalPublished && isLegacyPostTerminalSequence(previous, record)) {
1426
+ records.set(record.runId, { ...record });
1427
+ continue;
1428
+ }
1429
+ records.set(record.runId, { ...validateCardDeliveryTransition(previous, record) });
1430
+ } catch (error) {
1431
+ throw new Error(`invalid card delivery revision at line ${index + 1}`, { cause: error });
1432
+ }
1433
+ }
1434
+ return [...records.values()];
1037
1435
  }
1038
- function requireDelivery(deliveries, id) {
1039
- const current = deliveries.get(id);
1040
- if (!current) throw new Error(`Inbound Delivery does not exist: ${id}`);
1041
- return current;
1436
+ function readEnvelope(line, lineNumber) {
1437
+ const envelope = JSON.parse(line);
1438
+ if (!isRecord$14(envelope) || envelope.version !== 1 || !isCardDeliveryRecord(envelope.record)) throw new Error(`invalid card delivery snapshot at line ${lineNumber}`);
1439
+ return envelope.record;
1042
1440
  }
1043
- function pendingCount(deliveries) {
1044
- return [...deliveries.values()].filter((delivery) => delivery.state.status === "pending" || delivery.state.status === "leased").length;
1441
+ function isLegacyPostTerminalSequence(previous, next) {
1442
+ return next.runId === previous.runId && next.revision === previous.revision + 1 && next.sequence === previous.sequence + 1 && next.terminalPublished;
1045
1443
  }
1046
- function validateNow(now) {
1047
- if (!Number.isFinite(Date.parse(now))) throw new Error("Inbound Delivery claim timestamp is invalid");
1444
+ function isCardDeliveryRecord(value) {
1445
+ return isRecord$14(value) && Number.isSafeInteger(value.revision) && value.revision > 0 && typeof value.runId === "string" && value.runId.length > 0 && Number.isSafeInteger(value.sequence) && value.sequence >= 0 && typeof value.terminalPublished === "boolean";
1048
1446
  }
1049
1447
  //#endregion
1050
- //#region src/adapters/compatibility/inbound-delivery/feishu-inbox-repository.ts
1051
- const inboundStores = /* @__PURE__ */ new WeakMap();
1052
- function createFeishuInboxRepository(options = {}) {
1053
- return fromDurableQueueStore(createInMemoryDurableQueueStore({
1054
- ...options.initial ? { initial: options.initial.map(toInboundDelivery) } : {},
1055
- ...options.persist ? { persist: (delivery) => Effect.tryPromise({
1056
- catch: (error) => error,
1057
- try: () => options.persist(fromInboundDelivery(delivery))
1058
- }) } : {}
1059
- }));
1448
+ //#region src/adapters/outbound/persistence/card-delivery/jsonl-card-delivery-repository.ts
1449
+ function openJsonlCardDeliveryRepository(options) {
1450
+ return Effect.tryPromise({
1451
+ try: () => readPersistenceFile(options.filePath),
1452
+ catch: (error) => error
1453
+ }).pipe(Effect.flatMap((raw) => Effect.try({
1454
+ try: () => raw === void 0 ? [] : decodeCardDeliverySnapshots(raw),
1455
+ catch: (error) => error
1456
+ })), Effect.map((initial) => createInMemoryCardDeliveryRepository({
1457
+ initial,
1458
+ persist: (record) => append(options.filePath, record)
1459
+ })));
1060
1460
  }
1061
- function fromDurableQueueStore(repository) {
1062
- const facade = {
1063
- admit: (delivery, maxPending) => repository.admit(toInboundDelivery(delivery), maxPending),
1064
- claim: (input) => repository.claim(input).pipe(Effect.map(mapOptionalFromInboundDelivery)),
1065
- claimById: (input) => repository.claimById(input).pipe(Effect.map(mapOptionalFromInboundDelivery)),
1066
- complete: (input) => repository.complete(input),
1067
- create: (delivery) => repository.create(toInboundDelivery(delivery)),
1068
- deadLetters: () => repository.deadLetters().map(fromInboundDelivery),
1069
- fail: (input) => repository.fail(input),
1070
- pendingCount: () => repository.pendingCount(),
1071
- requeueDeadLetter: (input) => repository.requeueDeadLetter(input).pipe(Effect.map(fromInboundDelivery)),
1072
- release: (input) => repository.release(input)
1073
- };
1074
- inboundStores.set(facade, repository);
1075
- return facade;
1461
+ function append(filePath, record) {
1462
+ return Effect.tryPromise({
1463
+ try: async () => {
1464
+ await mkdir(dirname(filePath), { recursive: true });
1465
+ await appendFile(filePath, `${encodeCardDeliverySnapshot(record)}\n`, "utf8");
1466
+ },
1467
+ catch: (error) => error
1468
+ });
1076
1469
  }
1077
- function toDurableQueueStore(repository) {
1078
- const existing = inboundStores.get(repository);
1079
- if (existing) return existing;
1080
- return {
1081
- admit: (delivery, maxPending) => repository.admit(fromInboundDelivery(delivery), maxPending),
1082
- claim: (input) => repository.claim(input).pipe(Effect.map(mapOptionalToInboundDelivery)),
1083
- claimById: (input) => repository.claimById(input).pipe(Effect.map(mapOptionalToInboundDelivery)),
1084
- complete: (input) => repository.complete(input),
1085
- create: (delivery) => repository.create(fromInboundDelivery(delivery)),
1086
- deadLetters: () => repository.deadLetters().map(toInboundDelivery),
1087
- fail: (input) => repository.fail(input),
1088
- pendingCount: () => repository.pendingCount(),
1089
- renew: (input) => Effect.fail(/* @__PURE__ */ new Error(`legacy Feishu Inbox Repository cannot renew lease: ${input.id}`)),
1090
- requeueDeadLetter: (input) => repository.requeueDeadLetter(input).pipe(Effect.map(toInboundDelivery)),
1091
- release: (input) => repository.release(input)
1470
+ //#endregion
1471
+ //#region src/adapters/compatibility/card-presentation/jsonl-feishu-card-delivery-ledger.ts
1472
+ async function openJsonlFeishuCardDeliveryLedger(options) {
1473
+ return Effect.runPromise(openJsonlCardDeliveryRepository(options).pipe(Effect.map(createCardDeliveryLedger)));
1474
+ }
1475
+ //#endregion
1476
+ //#region src/platform/durable-queue/records/inbound-delivery-record.ts
1477
+ var InvalidInboundDelivery = class extends Error {
1478
+ name = "InvalidInboundDelivery";
1479
+ };
1480
+ function createInboundDelivery(input) {
1481
+ const delivery = {
1482
+ acceptedAt: input.acceptedAt,
1483
+ attempts: 0,
1484
+ id: input.id,
1485
+ laneKey: input.laneKey,
1486
+ ...input.metadata === void 0 ? {} : { metadata: input.metadata },
1487
+ payload: input.payload,
1488
+ revision: 1,
1489
+ state: {
1490
+ availableAt: input.acceptedAt,
1491
+ status: "pending"
1492
+ }
1092
1493
  };
1494
+ validateInboundDeliverySnapshot(delivery);
1495
+ return freezeInboundDeliverySnapshot(delivery);
1093
1496
  }
1094
- function toInboundDelivery(delivery) {
1095
- const { options, ...shared } = delivery;
1096
- return readInboundDeliverySnapshot({
1097
- ...shared,
1098
- ...options === void 0 ? {} : { metadata: options }
1497
+ function freezeInboundDeliverySnapshot(delivery) {
1498
+ const payload = cloneOpaqueValue(delivery.payload);
1499
+ const metadata = delivery.metadata === void 0 ? void 0 : cloneOpaqueValue(delivery.metadata);
1500
+ return Object.freeze({
1501
+ ...delivery,
1502
+ ...metadata === void 0 ? {} : { metadata },
1503
+ payload,
1504
+ state: Object.freeze({ ...delivery.state }),
1505
+ ...delivery.recovery === void 0 ? {} : { recovery: Object.freeze({ ...delivery.recovery }) }
1099
1506
  });
1100
1507
  }
1101
- function fromInboundDelivery(delivery) {
1102
- const { metadata, ...shared } = delivery;
1103
- return {
1104
- ...shared,
1105
- ...metadata === void 0 ? {} : { options: metadata }
1508
+ function readInboundDeliverySnapshot(value) {
1509
+ if (!isRecord$14(value)) throw new InvalidInboundDelivery("Inbound Delivery snapshot must be an object");
1510
+ if (typeof value.acceptedAt !== "string" || typeof value.attempts !== "number" || typeof value.id !== "string" || typeof value.laneKey !== "string" || typeof value.revision !== "number" || !isRecord$14(value.state)) throw new InvalidInboundDelivery("Inbound Delivery snapshot is missing required fields");
1511
+ const delivery = {
1512
+ acceptedAt: value.acceptedAt,
1513
+ attempts: value.attempts,
1514
+ id: value.id,
1515
+ laneKey: value.laneKey,
1516
+ ...value.metadata === void 0 ? {} : { metadata: value.metadata },
1517
+ payload: value.payload,
1518
+ ...value.recovery === void 0 ? {} : { recovery: readRecoveryAudit(value.recovery) },
1519
+ revision: value.revision,
1520
+ state: readState(value.state)
1106
1521
  };
1522
+ validateInboundDeliverySnapshot(delivery);
1523
+ return freezeInboundDeliverySnapshot(delivery);
1107
1524
  }
1108
- function mapOptionalFromInboundDelivery(delivery) {
1109
- return delivery === void 0 ? void 0 : fromInboundDelivery(delivery);
1525
+ function assertNewInboundDelivery(delivery) {
1526
+ validateInboundDeliverySnapshot(delivery);
1527
+ if (delivery.revision !== 1 || delivery.attempts !== 0 || delivery.recovery !== void 0 || delivery.state.status !== "pending") throw new Error("new Inbound Delivery must start pending at revision 1 with zero attempts");
1110
1528
  }
1111
- function mapOptionalToInboundDelivery(delivery) {
1112
- return delivery === void 0 ? void 0 : toInboundDelivery(delivery);
1529
+ function validateInboundDeliverySnapshot(delivery) {
1530
+ parseInboundDeliveryTimestamp(delivery.acceptedAt, "acceptance timestamp");
1531
+ if (!nonEmpty$2(delivery.id)) throw new InvalidInboundDelivery("Inbound Delivery id must not be empty");
1532
+ if (!nonEmpty$2(delivery.laneKey)) throw new InvalidInboundDelivery("Inbound Delivery lane must not be empty");
1533
+ if (!Number.isSafeInteger(delivery.attempts) || delivery.attempts < 0) throw new InvalidInboundDelivery("Inbound Delivery attempts must be a non-negative integer");
1534
+ if (!Number.isSafeInteger(delivery.revision) || delivery.revision < 1) throw new InvalidInboundDelivery("Inbound Delivery revision must be a positive integer");
1535
+ if (delivery.recovery !== void 0) validateInboundDeliveryRecoveryAudit(delivery.recovery);
1536
+ switch (delivery.state.status) {
1537
+ case "pending":
1538
+ parseInboundDeliveryTimestamp(delivery.state.availableAt, "availability timestamp");
1539
+ return;
1540
+ case "leased":
1541
+ requireInboundDeliveryLease(delivery.state.leaseId, delivery.state.leasedAt, delivery.state.leaseExpiresAt);
1542
+ return;
1543
+ case "completed":
1544
+ parseInboundDeliveryTimestamp(delivery.state.completedAt, "completion timestamp");
1545
+ return;
1546
+ case "dead":
1547
+ parseInboundDeliveryTimestamp(delivery.state.failedAt, "failure timestamp");
1548
+ if (!nonEmpty$2(delivery.state.reason)) throw new InvalidInboundDelivery("Inbound Delivery Dead Letter reason must not be empty");
1549
+ }
1113
1550
  }
1114
- //#endregion
1115
- //#region src/core/application/recovery/commands/recovery-action.ts
1116
- var InvalidRecoveryAction = class extends Error {
1117
- name = "InvalidRecoveryAction";
1118
- };
1119
- function createRecoveryAction(input) {
1120
- const actorId = input.actorId.trim();
1121
- const note = input.note.trim();
1122
- if (actorId.length === 0) throw new InvalidRecoveryAction("recovery actor must not be empty");
1123
- if (note.length === 0) throw new InvalidRecoveryAction("recovery note must not be empty");
1124
- if (!Number.isFinite(Date.parse(input.at))) throw new InvalidRecoveryAction("recovery timestamp must be an ISO timestamp");
1125
- return Object.freeze({
1126
- actorId,
1127
- at: input.at,
1128
- note
1129
- });
1551
+ function parseInboundDeliveryTimestamp(value, label) {
1552
+ const timestamp = Date.parse(value);
1553
+ if (!Number.isFinite(timestamp)) throw new InvalidInboundDelivery(`Inbound Delivery ${label} is invalid`);
1554
+ return timestamp;
1555
+ }
1556
+ function readState(value) {
1557
+ if (value.status === "pending" && typeof value.availableAt === "string") return {
1558
+ availableAt: value.availableAt,
1559
+ status: "pending"
1560
+ };
1561
+ if (value.status === "leased" && typeof value.leaseExpiresAt === "string" && typeof value.leaseId === "string" && typeof value.leasedAt === "string") return {
1562
+ leaseExpiresAt: value.leaseExpiresAt,
1563
+ leaseId: value.leaseId,
1564
+ leasedAt: value.leasedAt,
1565
+ status: "leased"
1566
+ };
1567
+ if (value.status === "completed" && typeof value.completedAt === "string") return {
1568
+ completedAt: value.completedAt,
1569
+ status: "completed"
1570
+ };
1571
+ if (value.status === "dead" && typeof value.failedAt === "string" && typeof value.reason === "string") return {
1572
+ failedAt: value.failedAt,
1573
+ reason: value.reason,
1574
+ status: "dead"
1575
+ };
1576
+ throw new InvalidInboundDelivery("Inbound Delivery state is invalid");
1130
1577
  }
1131
- //#endregion
1132
- //#region src/core/domain/tool-operation/value-objects/stable-tool-input.ts
1133
- function freezeStableJson(value) {
1134
- return deepFreeze$1(canonicalize(value, /* @__PURE__ */ new Set()));
1578
+ function readRecoveryAudit(value) {
1579
+ if (!isRecord$14(value) || typeof value.actorId !== "string" || typeof value.at !== "string" || typeof value.note !== "string") throw new InvalidInboundDelivery("Inbound Delivery recovery audit is incomplete");
1580
+ return {
1581
+ actorId: value.actorId,
1582
+ at: value.at,
1583
+ note: value.note
1584
+ };
1135
1585
  }
1136
- var StableToolOperationValueError = class extends Error {
1137
- name = "StableToolOperationValueError";
1138
- };
1139
- function canonicalize(value, ancestors) {
1140
- if (value === null || typeof value === "string" || typeof value === "boolean") return value;
1141
- if (typeof value === "number") {
1142
- if (!Number.isFinite(value)) throw new StableToolOperationValueError("stable JSON numbers must be finite");
1143
- return Object.is(value, -0) ? 0 : value;
1144
- }
1145
- if (typeof value !== "object") throw new StableToolOperationValueError("value must contain only stable JSON values");
1146
- if (ancestors.has(value)) throw new StableToolOperationValueError("stable JSON must not contain cycles");
1147
- ancestors.add(value);
1148
- try {
1149
- if (Array.isArray(value)) return Array.from({ length: value.length }, (_, index) => {
1150
- if (!Object.hasOwn(value, index)) throw new StableToolOperationValueError("stable JSON arrays must not contain holes");
1151
- return canonicalize(value[index], ancestors);
1152
- });
1153
- const prototype = Object.getPrototypeOf(value);
1154
- if (prototype !== Object.prototype && prototype !== null) throw new StableToolOperationValueError("stable JSON objects must be plain objects");
1155
- return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, entry]) => [key, canonicalize(entry, ancestors)]));
1156
- } finally {
1157
- ancestors.delete(value);
1158
- }
1586
+ function validateInboundDeliveryRecoveryAudit(recovery) {
1587
+ if (!nonEmpty$2(recovery.actorId) || !nonEmpty$2(recovery.note)) throw new InvalidInboundDelivery("Inbound Delivery recovery audit is incomplete");
1588
+ parseInboundDeliveryTimestamp(recovery.at, "recovery audit timestamp");
1159
1589
  }
1160
- function deepFreeze$1(value) {
1161
- if (Array.isArray(value)) {
1162
- for (const entry of value) deepFreeze$1(entry);
1163
- return Object.freeze(value);
1164
- }
1165
- if (value !== null && typeof value === "object") {
1166
- for (const entry of Object.values(value)) deepFreeze$1(entry);
1167
- return Object.freeze(value);
1168
- }
1169
- return value;
1590
+ function requireInboundDeliveryLease(leaseId, leasedAt, leaseExpiresAt) {
1591
+ if (!nonEmpty$2(leaseId)) throw new InvalidInboundDelivery("Inbound Delivery lease id must not be empty");
1592
+ const leasedAtMilliseconds = parseInboundDeliveryTimestamp(leasedAt, "lease timestamp");
1593
+ if (parseInboundDeliveryTimestamp(leaseExpiresAt, "lease expiration") <= leasedAtMilliseconds) throw new InvalidInboundDelivery("Inbound Delivery lease expiration must be after its lease timestamp");
1170
1594
  }
1171
- //#endregion
1172
- //#region src/core/domain/tool-operation/value-objects/binding.ts
1173
- var ToolOperationBindingError = class extends Error {
1174
- name = "ToolOperationBindingError";
1175
- };
1176
- function freezeToolOperationBinding(value) {
1177
- if (!isRecord$13(value)) throw new ToolOperationBindingError("tool operation binding must be an object");
1178
- let detached;
1595
+ function nonEmpty$2(value) {
1596
+ return value.trim().length > 0;
1597
+ }
1598
+ function cloneOpaqueValue(value) {
1179
1599
  try {
1180
- detached = freezeStableJson(value);
1181
- } catch (error) {
1182
- if (error instanceof Error) throw new ToolOperationBindingError(error.message);
1183
- throw error;
1600
+ return deepFreeze$2(structuredClone(value));
1601
+ } catch {
1602
+ throw new InvalidInboundDelivery("Inbound Delivery opaque values must be cloneable");
1184
1603
  }
1185
- if ([
1186
- "agentId",
1187
- "inputDigest",
1188
- "instanceId",
1189
- "sourceMessageId",
1190
- "toolId",
1191
- "toolVersion"
1192
- ].some((field) => typeof detached[field] !== "string" || detached[field] === "")) throw new ToolOperationBindingError("tool operation binding fields must be non-empty strings");
1193
- return detached;
1194
1604
  }
1195
- function sameToolOperationBinding(left, right) {
1196
- return left.agentId === right.agentId && left.inputDigest === right.inputDigest && left.instanceId === right.instanceId && left.sourceMessageId === right.sourceMessageId && left.toolId === right.toolId && left.toolVersion === right.toolVersion;
1605
+ //#endregion
1606
+ //#region src/platform/durable-queue/records/inbound-delivery-transitions.ts
1607
+ function claimInboundDelivery(delivery, input) {
1608
+ if (delivery.state.status !== "pending" || Date.parse(delivery.state.availableAt) > parseInboundDeliveryTimestamp(input.now, "claim timestamp")) throw new InvalidInboundDelivery(`Inbound Delivery is not claimable: ${delivery.id}`);
1609
+ return leaseInboundDelivery(delivery, input);
1197
1610
  }
1198
- function isRecord$13(value) {
1199
- return value !== null && typeof value === "object" && !Array.isArray(value);
1611
+ function reclaimInboundDelivery(delivery, input) {
1612
+ if (delivery.state.status !== "leased" || Date.parse(delivery.state.leaseExpiresAt) > parseInboundDeliveryTimestamp(input.now, "lease recovery timestamp")) throw new InvalidInboundDelivery(`Inbound Delivery lease has not expired: ${delivery.id}`);
1613
+ return leaseInboundDelivery(delivery, input);
1200
1614
  }
1201
- //#endregion
1202
- //#region src/core/domain/tool-operation/aggregate/tool-operation.ts
1203
- var ToolOperationTransitionDenied = class extends Error {
1204
- name = "ToolOperationTransitionDenied";
1205
- };
1206
- /** Immutable transitions do not serialize concurrent writes; the ledger serializes persisted transitions. */
1207
- var ToolOperation = class ToolOperation {
1208
- #snapshot;
1209
- constructor(snapshot) {
1210
- this.#snapshot = freezeSnapshot(snapshot);
1211
- Object.freeze(this);
1212
- }
1213
- static create(input) {
1214
- if (typeof input.operationId !== "string") throw new ToolOperationTransitionDenied("tool operation id must be a string");
1215
- return new ToolOperation({
1216
- binding: freezeToolOperationBinding(input.binding),
1217
- operationId: input.operationId,
1218
- revision: 1,
1219
- state: { status: "pending" }
1220
- });
1221
- }
1222
- static restore(value) {
1223
- return new ToolOperation(readToolOperationSnapshot(value));
1224
- }
1225
- get binding() {
1226
- return this.#snapshot.binding;
1227
- }
1228
- get operationId() {
1229
- return this.#snapshot.operationId;
1230
- }
1231
- get reconciliation() {
1232
- return this.#snapshot.reconciliation;
1233
- }
1234
- get revision() {
1235
- return this.#snapshot.revision;
1236
- }
1237
- get state() {
1238
- return this.#snapshot.state;
1239
- }
1240
- matchesBinding(binding) {
1241
- try {
1242
- return sameToolOperationBinding(this.binding, freezeToolOperationBinding(binding));
1243
- } catch {
1244
- return false;
1245
- }
1246
- }
1247
- complete(result) {
1248
- this.requirePending();
1249
- let stableResult;
1250
- try {
1251
- stableResult = freezeStableJson(result);
1252
- } catch (error) {
1253
- if (error instanceof Error) throw new ToolOperationTransitionDenied(error.message);
1254
- throw error;
1255
- }
1256
- return this.next({
1257
- revision: this.revision + 1,
1258
- state: {
1259
- result: stableResult,
1260
- status: "completed"
1261
- }
1262
- });
1263
- }
1264
- abort() {
1265
- this.requirePending();
1266
- return this.next({
1267
- revision: this.revision + 1,
1268
- state: { status: "aborted" }
1269
- });
1270
- }
1271
- requireReconciliation(reason) {
1272
- this.requirePending();
1273
- if (typeof reason !== "string" || reason.trim() === "") throw new ToolOperationTransitionDenied("tool operation reconciliation requires a reason");
1274
- return this.next({
1275
- revision: this.revision + 1,
1276
- state: {
1277
- reason,
1278
- status: "reconciliation-required"
1279
- }
1280
- });
1281
- }
1282
- retry() {
1283
- if (this.state.status !== "aborted") throw new ToolOperationTransitionDenied(`tool operation cannot retry while ${this.state.status}`);
1284
- return this.next({
1285
- revision: this.revision + 1,
1286
- state: { status: "pending" }
1287
- });
1288
- }
1289
- recoverInterrupted() {
1290
- this.requirePending();
1291
- return this.next({
1292
- revision: this.revision + 1,
1293
- state: {
1294
- reason: "process exited before the tool outcome was durably recorded",
1295
- status: "reconciliation-required"
1296
- }
1297
- });
1298
- }
1299
- reconcile(input) {
1300
- if (input.operationId !== this.operationId || this.state.status !== "reconciliation-required" || this.revision !== input.expectedRevision) throw new ToolOperationTransitionDenied(`tool operation is not the expected reconciliation revision: ${input.operationId}`);
1301
- const action = normalizeReconciliationAction(input.action);
1302
- const nextState = input.outcome.status === "applied" ? {
1303
- result: freezeStableJson(input.outcome.result),
1304
- status: "completed"
1305
- } : { status: "aborted" };
1306
- return this.next({
1307
- reconciliation: {
1308
- ...action,
1309
- outcome: input.outcome.status
1310
- },
1311
- revision: this.revision + 1,
1312
- state: nextState
1313
- });
1314
- }
1315
- validateTransitionTo(next) {
1316
- if (this.operationId !== next.operationId || !sameToolOperationBinding(this.binding, next.binding) || next.revision !== this.revision + 1) return false;
1317
- switch (this.state.status) {
1318
- case "pending": return (next.state.status === "completed" || next.state.status === "reconciliation-required" || next.state.status === "aborted") && next.reconciliation === void 0;
1319
- case "aborted": return next.state.status === "pending" && next.reconciliation === void 0;
1320
- case "completed": return false;
1321
- case "reconciliation-required": return (next.state.status === "completed" || next.state.status === "aborted") && next.reconciliation !== void 0 && isValidReconciliation(next.reconciliation) && next.reconciliation.outcome === (next.state.status === "completed" ? "applied" : "not-applied") && !sameReconciliation(this.reconciliation, next.reconciliation);
1322
- }
1323
- }
1324
- toSnapshot() {
1325
- return this.#snapshot;
1326
- }
1327
- requirePending() {
1328
- if (this.state.status !== "pending") throw new ToolOperationTransitionDenied(`tool operation cannot transition while ${this.state.status}`);
1329
- }
1330
- next(change) {
1331
- const extensions = retainUnknownFields$2(this.#snapshot, [
1332
- "binding",
1333
- "operationId",
1334
- "reconciliation",
1335
- "revision",
1336
- "state"
1337
- ]);
1338
- return new ToolOperation({
1339
- ...extensions,
1340
- binding: this.binding,
1341
- operationId: this.operationId,
1342
- ...change.reconciliation === void 0 ? {} : { reconciliation: change.reconciliation },
1343
- revision: change.revision,
1344
- state: change.state
1345
- });
1346
- }
1347
- };
1348
- function readToolOperationSnapshot(value) {
1349
- if (!isRecord$12(value) || typeof value.operationId !== "string") throw new ToolOperationTransitionDenied("tool operation snapshot is invalid");
1350
- if (!Number.isInteger(value.revision) || value.revision < 1 || !isRecord$12(value.state)) throw new ToolOperationTransitionDenied("tool operation snapshot is invalid");
1351
- const binding = freezeToolOperationBinding(value.binding);
1352
- const state = readState(value.state);
1353
- const reconciliation = value.reconciliation === void 0 ? void 0 : readReconciliation(value.reconciliation);
1354
- if (value.revision === 1 && state.status !== "pending") throw new ToolOperationTransitionDenied("tool operation revision one must be pending");
1355
- if (state.status === "pending" || state.status === "reconciliation-required") {
1356
- if (reconciliation !== void 0) throw new ToolOperationTransitionDenied("tool operation reconciliation evidence is only terminal");
1615
+ function renewInboundDelivery(delivery, input) {
1616
+ const lease = requireCurrentLease(delivery, input.leaseId);
1617
+ requireInboundDeliveryLease(input.leaseId, input.now, input.leaseExpiresAt);
1618
+ const now = parseInboundDeliveryTimestamp(input.now, "renewal timestamp");
1619
+ const expiresAt = parseInboundDeliveryTimestamp(input.leaseExpiresAt, "lease expiration");
1620
+ if (now < Date.parse(lease.leasedAt) || now >= Date.parse(lease.leaseExpiresAt)) throw new InvalidInboundDelivery(`Inbound Delivery lease is not live at renewal: ${delivery.id}`);
1621
+ if (expiresAt <= Date.parse(lease.leaseExpiresAt)) throw new InvalidInboundDelivery(`Inbound Delivery lease renewal must extend the lease: ${delivery.id}`);
1622
+ return nextInboundDelivery(delivery, { state: {
1623
+ ...lease,
1624
+ leaseExpiresAt: input.leaseExpiresAt
1625
+ } });
1626
+ }
1627
+ function completeInboundDelivery(delivery, input) {
1628
+ requireCurrentLease(delivery, input.leaseId);
1629
+ parseInboundDeliveryTimestamp(input.completedAt, "completion timestamp");
1630
+ return nextInboundDelivery(delivery, { state: {
1631
+ completedAt: input.completedAt,
1632
+ status: "completed"
1633
+ } });
1634
+ }
1635
+ function failInboundDelivery(delivery, input) {
1636
+ requireCurrentLease(delivery, input.leaseId);
1637
+ parseInboundDeliveryTimestamp(input.failedAt, "failure timestamp");
1638
+ if (input.terminal) {
1639
+ if (input.reason.trim().length === 0) throw new InvalidInboundDelivery("Inbound Delivery Dead Letter reason must not be empty");
1640
+ return nextInboundDelivery(delivery, { state: {
1641
+ failedAt: input.failedAt,
1642
+ reason: input.reason,
1643
+ status: "dead"
1644
+ } });
1357
1645
  }
1358
- validateRevisionShape(value.revision, state, reconciliation);
1359
- if (reconciliation !== void 0 && reconciliation.outcome !== (state.status === "completed" ? "applied" : "not-applied")) throw new ToolOperationTransitionDenied("tool operation reconciliation outcome does not match state");
1360
- return {
1361
- ...retainUnknownFields$2(value, [
1362
- "binding",
1363
- "operationId",
1364
- "reconciliation",
1365
- "revision",
1366
- "state"
1367
- ]),
1368
- binding,
1369
- operationId: value.operationId,
1370
- ...reconciliation === void 0 ? {} : { reconciliation },
1371
- revision: value.revision,
1372
- state
1373
- };
1646
+ return releaseInboundDelivery(delivery, {
1647
+ availableAt: input.availableAt ?? input.failedAt,
1648
+ leaseId: input.leaseId
1649
+ });
1374
1650
  }
1375
- function readState(value) {
1376
- switch (value.status) {
1377
- case "pending": return {
1378
- ...retainUnknownFields$2(value, ["status"]),
1651
+ function releaseInboundDelivery(delivery, input) {
1652
+ requireCurrentLease(delivery, input.leaseId);
1653
+ parseInboundDeliveryTimestamp(input.availableAt, "availability timestamp");
1654
+ return nextInboundDelivery(delivery, { state: {
1655
+ availableAt: input.availableAt,
1656
+ status: "pending"
1657
+ } });
1658
+ }
1659
+ function requeueInboundDelivery(delivery, input) {
1660
+ if (delivery.state.status !== "dead" || delivery.revision !== input.expectedRevision) throw new InvalidInboundDelivery(`Inbound Delivery is not the expected Dead Letter revision: ${delivery.id}`);
1661
+ validateInboundDeliveryRecoveryAudit(input.recovery);
1662
+ if (input.availableAt !== input.recovery.at) throw new InvalidInboundDelivery("Inbound Delivery recovery availability must match its audit timestamp");
1663
+ return nextInboundDelivery(delivery, {
1664
+ attempts: 0,
1665
+ recovery: input.recovery,
1666
+ state: {
1667
+ availableAt: input.availableAt,
1379
1668
  status: "pending"
1380
- };
1381
- case "aborted": return {
1382
- ...retainUnknownFields$2(value, ["status"]),
1383
- status: "aborted"
1384
- };
1385
- case "reconciliation-required":
1386
- if (typeof value.reason !== "string" || value.reason.trim() === "") throw new ToolOperationTransitionDenied("tool operation reconciliation reason is invalid");
1387
- return {
1388
- ...retainUnknownFields$2(value, ["reason", "status"]),
1389
- reason: value.reason,
1390
- status: "reconciliation-required"
1391
- };
1392
- case "completed":
1393
- if (!Object.hasOwn(value, "result")) throw new ToolOperationTransitionDenied("completed tool operation must contain a result");
1394
- try {
1395
- return {
1396
- ...retainUnknownFields$2(value, ["result", "status"]),
1397
- result: freezeStableJson(value.result),
1398
- status: "completed"
1399
- };
1400
- } catch (error) {
1401
- if (error instanceof Error) throw new ToolOperationTransitionDenied(error.message);
1402
- throw error;
1669
+ }
1670
+ });
1671
+ }
1672
+ function isValidInboundDeliveryTransition(previous, next) {
1673
+ try {
1674
+ validateInboundDeliverySnapshot(next);
1675
+ } catch {
1676
+ return false;
1677
+ }
1678
+ if (previous.id !== next.id || previous.acceptedAt !== next.acceptedAt || previous.laneKey !== next.laneKey || next.revision !== previous.revision + 1 || !sameOpaqueValue(previous.payload, next.payload) || !sameOpaqueValue(previous.metadata, next.metadata)) return false;
1679
+ switch (previous.state.status) {
1680
+ case "pending": return next.state.status === "leased" && next.attempts === previous.attempts + 1 && sameOpaqueValue(previous.recovery, next.recovery);
1681
+ case "leased":
1682
+ if (!sameOpaqueValue(previous.recovery, next.recovery)) return false;
1683
+ if (next.state.status === "leased") {
1684
+ const renewed = next.attempts === previous.attempts && next.state.leaseId === previous.state.leaseId && next.state.leasedAt === previous.state.leasedAt && Date.parse(next.state.leaseExpiresAt) > Date.parse(previous.state.leaseExpiresAt);
1685
+ const reclaimed = next.attempts === previous.attempts + 1 && next.state.leaseId !== previous.state.leaseId && Date.parse(previous.state.leaseExpiresAt) <= Date.parse(next.state.leasedAt);
1686
+ return renewed || reclaimed;
1403
1687
  }
1404
- default: throw new ToolOperationTransitionDenied("tool operation state is invalid");
1688
+ return next.attempts === previous.attempts && (next.state.status === "pending" || next.state.status === "completed" || next.state.status === "dead");
1689
+ case "completed": return false;
1690
+ case "dead": return next.state.status === "pending" && next.attempts === 0 && next.recovery !== void 0 && next.state.availableAt === next.recovery.at && !sameOpaqueValue(previous.recovery, next.recovery);
1405
1691
  }
1406
1692
  }
1407
- function normalizeReconciliationAction(action) {
1408
- if (!isRecord$12(action)) throw new ToolOperationTransitionDenied("tool operation reconciliation action is invalid");
1409
- const actorId = typeof action.actorId === "string" ? action.actorId.trim() : "";
1410
- const note = typeof action.note === "string" ? action.note.trim() : "";
1411
- if (!actorId) throw new ToolOperationTransitionDenied("recovery actor must not be empty");
1412
- if (!note) throw new ToolOperationTransitionDenied("recovery note must not be empty");
1413
- if (typeof action.at !== "string" || !Number.isFinite(Date.parse(action.at))) throw new ToolOperationTransitionDenied("recovery timestamp must be an ISO timestamp");
1414
- return Object.freeze({
1415
- actorId,
1416
- at: action.at,
1417
- note
1418
- });
1693
+ function sameOpaqueValue(left, right) {
1694
+ if (Object.is(left, right)) return true;
1695
+ if (Array.isArray(left) || Array.isArray(right)) return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((value, index) => sameOpaqueValue(value, right[index]));
1696
+ if (!isRecord$14(left) || !isRecord$14(right)) return false;
1697
+ const leftKeys = Object.keys(left).sort();
1698
+ const rightKeys = Object.keys(right).sort();
1699
+ return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index] && sameOpaqueValue(left[key], right[key]));
1419
1700
  }
1420
- function readReconciliation(value) {
1421
- if (!isRecord$12(value)) throw new ToolOperationTransitionDenied("tool operation reconciliation is invalid");
1422
- const action = normalizeReconciliationAction(value);
1423
- if (value.outcome !== "applied" && value.outcome !== "not-applied") throw new ToolOperationTransitionDenied("tool operation reconciliation outcome is invalid");
1424
- return Object.freeze({
1425
- ...retainUnknownFields$2(value, [
1426
- "actorId",
1427
- "at",
1428
- "note",
1429
- "outcome"
1430
- ]),
1431
- ...action,
1432
- outcome: value.outcome
1701
+ function nextInboundDelivery(delivery, changes) {
1702
+ const next = {
1703
+ ...delivery,
1704
+ ...changes,
1705
+ revision: delivery.revision + 1
1706
+ };
1707
+ validateInboundDeliverySnapshot(next);
1708
+ return freezeInboundDeliverySnapshot(next);
1709
+ }
1710
+ function leaseInboundDelivery(delivery, input) {
1711
+ parseInboundDeliveryTimestamp(input.now, "lease timestamp");
1712
+ requireInboundDeliveryLease(input.leaseId, input.now, input.leaseExpiresAt);
1713
+ return nextInboundDelivery(delivery, {
1714
+ attempts: delivery.attempts + 1,
1715
+ state: {
1716
+ leaseExpiresAt: input.leaseExpiresAt,
1717
+ leaseId: input.leaseId,
1718
+ leasedAt: input.now,
1719
+ status: "leased"
1720
+ }
1433
1721
  });
1434
1722
  }
1435
- function isValidReconciliation(value) {
1436
- return value.actorId.trim().length > 0 && Number.isFinite(Date.parse(value.at)) && value.note.trim().length > 0 && (value.outcome === "applied" || value.outcome === "not-applied");
1723
+ function requireCurrentLease(delivery, leaseId) {
1724
+ if (delivery.state.status !== "leased" || delivery.state.leaseId !== leaseId) throw new InvalidInboundDelivery(`Inbound Delivery lease does not match: ${delivery.id}`);
1725
+ return delivery.state;
1437
1726
  }
1438
- function sameReconciliation(left, right) {
1439
- if (left === void 0 || right === void 0) return left === right;
1440
- return left.actorId === right.actorId && left.at === right.at && left.note === right.note && left.outcome === right.outcome;
1727
+ //#endregion
1728
+ //#region src/platform/durable-queue/store/in-memory-durable-queue.ts
1729
+ function createInMemoryDurableQueueStore(options = {}) {
1730
+ const deliveries = /* @__PURE__ */ new Map();
1731
+ for (const delivery of options.initial ?? []) {
1732
+ const restored = readInboundDeliverySnapshot(delivery);
1733
+ if (deliveries.has(restored.id)) throw new Error(`duplicate Inbound Delivery snapshot: ${restored.id}`);
1734
+ deliveries.set(restored.id, restored);
1735
+ }
1736
+ const serial = Effect.unsafeMakeSemaphore(1);
1737
+ let lastLaneKey;
1738
+ const save = (current, delivery) => {
1739
+ const snapshot = readInboundDeliverySnapshot(current === void 0 ? delivery : resolveUpdate(current, delivery));
1740
+ return (options.persist?.(readInboundDeliverySnapshot(snapshot)) ?? Effect.void).pipe(Effect.flatMap(() => Effect.sync(() => {
1741
+ deliveries.set(snapshot.id, snapshot);
1742
+ })));
1743
+ };
1744
+ const serialized = (effect) => serial.withPermits(1)(Effect.uninterruptible(effect));
1745
+ return {
1746
+ admit: (delivery, maxPending) => serialized(Effect.gen(function* () {
1747
+ if (deliveries.has(delivery.id)) return "duplicate";
1748
+ if (!Number.isSafeInteger(maxPending) || maxPending < 1) return yield* Effect.fail(/* @__PURE__ */ new Error("Inbound Delivery capacity must be a positive integer"));
1749
+ if (pendingCount(deliveries) >= maxPending) return "capacity";
1750
+ assertNewInboundDelivery(delivery);
1751
+ yield* save(void 0, delivery);
1752
+ return "created";
1753
+ })),
1754
+ claim: (input) => serialized(Effect.gen(function* () {
1755
+ validateNow(input.now);
1756
+ const now = Date.parse(input.now);
1757
+ const leasedLanes = new Set([...deliveries.values()].filter((delivery) => delivery.state.status === "leased" && Date.parse(delivery.state.leaseExpiresAt) > now).map((delivery) => delivery.laneKey));
1758
+ const eligible = [...deliveries.values()].filter((delivery) => !leasedLanes.has(delivery.laneKey) && (delivery.state.status === "pending" && Date.parse(delivery.state.availableAt) <= now || delivery.state.status === "leased" && Date.parse(delivery.state.leaseExpiresAt) <= now));
1759
+ if (eligible.length === 0) return void 0;
1760
+ const laneKeys = [...new Set(eligible.map((delivery) => delivery.laneKey))];
1761
+ const laneKey = laneKeys[((lastLaneKey === void 0 ? -1 : laneKeys.indexOf(lastLaneKey)) + 1) % laneKeys.length];
1762
+ const laneDeliveries = eligible.filter((delivery) => delivery.laneKey === laneKey);
1763
+ const current = laneDeliveries.find((delivery) => delivery.state.status === "leased") ?? laneDeliveries[0];
1764
+ const claimed = current.state.status === "leased" ? reclaimInboundDelivery(current, input) : claimInboundDelivery(current, input);
1765
+ yield* save(current, claimed);
1766
+ lastLaneKey = laneKey;
1767
+ return readInboundDeliverySnapshot(claimed);
1768
+ })),
1769
+ claimById: (input) => serialized(Effect.gen(function* () {
1770
+ validateNow(input.now);
1771
+ const current = deliveries.get(input.id);
1772
+ if (!current) return void 0;
1773
+ if (current.state.status === "leased") {
1774
+ if (Date.parse(current.state.leaseExpiresAt) > Date.parse(input.now)) return void 0;
1775
+ const reclaimed = reclaimInboundDelivery(current, input);
1776
+ yield* save(current, reclaimed);
1777
+ return readInboundDeliverySnapshot(reclaimed);
1778
+ }
1779
+ if (current.state.status !== "pending" || Date.parse(current.state.availableAt) > Date.parse(input.now)) return;
1780
+ const claimed = claimInboundDelivery(current, input);
1781
+ yield* save(current, claimed);
1782
+ return readInboundDeliverySnapshot(claimed);
1783
+ })),
1784
+ complete: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => completeInboundDelivery(current, input))),
1785
+ create: (delivery) => serialized(Effect.gen(function* () {
1786
+ if (deliveries.has(delivery.id)) return false;
1787
+ assertNewInboundDelivery(delivery);
1788
+ yield* save(void 0, delivery);
1789
+ return true;
1790
+ })),
1791
+ deadLetters: () => [...deliveries.values()].filter((delivery) => delivery.state.status === "dead").map((delivery) => readInboundDeliverySnapshot(delivery)),
1792
+ fail: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => failInboundDelivery(current, input))),
1793
+ pendingCount: () => pendingCount(deliveries),
1794
+ renew: (input) => serialized(Effect.gen(function* () {
1795
+ const current = requireDelivery(deliveries, input.id);
1796
+ const renewed = renewInboundDelivery(current, input);
1797
+ yield* save(current, renewed);
1798
+ return readInboundDeliverySnapshot(renewed);
1799
+ })),
1800
+ requeueDeadLetter: (input) => serialized(Effect.gen(function* () {
1801
+ const current = requireDelivery(deliveries, input.id);
1802
+ const requeued = requeueInboundDelivery(current, input);
1803
+ yield* save(current, requeued);
1804
+ return readInboundDeliverySnapshot(requeued);
1805
+ })),
1806
+ release: (input) => serialized(mutateLeased(deliveries, input.id, input.leaseId, save, (current) => releaseInboundDelivery(current, input)))
1807
+ };
1441
1808
  }
1442
- function freezeSnapshot(snapshot) {
1443
- const binding = freezeToolOperationBinding(snapshot.binding);
1444
- const state = freezeState(snapshot.state);
1445
- const reconciliation = snapshot.reconciliation === void 0 ? void 0 : readReconciliation(snapshot.reconciliation);
1446
- return Object.freeze({
1447
- ...retainUnknownFields$2(snapshot, [
1448
- "binding",
1449
- "operationId",
1450
- "reconciliation",
1451
- "revision",
1452
- "state"
1453
- ]),
1454
- binding,
1455
- operationId: snapshot.operationId,
1456
- ...reconciliation === void 0 ? {} : { reconciliation },
1457
- revision: snapshot.revision,
1458
- state
1809
+ function mutateLeased(deliveries, id, leaseId, save, transition) {
1810
+ return Effect.gen(function* () {
1811
+ const current = requireDelivery(deliveries, id);
1812
+ if (current.state.status !== "leased" || current.state.leaseId !== leaseId) return yield* Effect.fail(/* @__PURE__ */ new Error(`Inbound Delivery lease does not match: ${id}`));
1813
+ yield* save(current, transition(current));
1459
1814
  });
1460
1815
  }
1461
- function freezeState(state) {
1462
- if (state.status === "completed") return Object.freeze({
1463
- ...retainUnknownFields$2(state, ["result", "status"]),
1464
- result: freezeStableJson(state.result),
1465
- status: "completed"
1466
- });
1467
- if (state.status === "reconciliation-required") return Object.freeze({
1468
- ...retainUnknownFields$2(state, ["reason", "status"]),
1469
- reason: state.reason,
1470
- status: "reconciliation-required"
1471
- });
1472
- return Object.freeze({
1473
- ...retainUnknownFields$2(state, ["status"]),
1474
- status: state.status
1475
- });
1816
+ function resolveUpdate(current, next) {
1817
+ if (!isValidInboundDeliveryTransition(current, next)) throw new Error(`invalid Inbound Delivery transition: ${current.id}`);
1818
+ return next;
1476
1819
  }
1477
- function validateRevisionShape(revision, state, reconciliation) {
1478
- if (state.status === "pending" && revision === 2) throw new ToolOperationTransitionDenied("pending tool operation revision is invalid");
1479
- if (state.status !== "pending" && revision === 1) throw new ToolOperationTransitionDenied("tool operation revision one must be pending");
1480
- if (reconciliation !== void 0 && revision < 3) throw new ToolOperationTransitionDenied("reconciled tool operation revision is invalid");
1820
+ function requireDelivery(deliveries, id) {
1821
+ const current = deliveries.get(id);
1822
+ if (!current) throw new Error(`Inbound Delivery does not exist: ${id}`);
1823
+ return current;
1481
1824
  }
1482
- function retainUnknownFields$2(value, known) {
1483
- const extensions = {};
1484
- const allowed = new Set(known);
1485
- for (const [key, entry] of Object.entries(value)) {
1486
- if (allowed.has(key)) continue;
1487
- extensions[key] = freezeStableJson(entry);
1488
- }
1489
- return extensions;
1825
+ function pendingCount(deliveries) {
1826
+ return [...deliveries.values()].filter((delivery) => delivery.state.status === "pending" || delivery.state.status === "leased").length;
1490
1827
  }
1491
- function isRecord$12(value) {
1492
- return value !== null && typeof value === "object" && !Array.isArray(value);
1828
+ function validateNow(now) {
1829
+ if (!Number.isFinite(Date.parse(now))) throw new Error("Inbound Delivery claim timestamp is invalid");
1493
1830
  }
1494
1831
  //#endregion
1495
- //#region src/core/domain/tool-operation/factories/create-tool-operation.ts
1496
- function createToolOperation(input) {
1497
- return ToolOperation.create(input);
1832
+ //#region src/adapters/compatibility/inbound-delivery/feishu-inbox-repository.ts
1833
+ const inboundStores = /* @__PURE__ */ new WeakMap();
1834
+ function createFeishuInboxRepository(options = {}) {
1835
+ return fromDurableQueueStore(createInMemoryDurableQueueStore({
1836
+ ...options.initial ? { initial: options.initial.map(toInboundDelivery) } : {},
1837
+ ...options.persist ? { persist: (delivery) => Effect.tryPromise({
1838
+ catch: (error) => error,
1839
+ try: () => options.persist(fromInboundDelivery(delivery))
1840
+ }) } : {}
1841
+ }));
1498
1842
  }
1499
- //#endregion
1500
- //#region src/core/domain/tool-operation/factories/restore-tool-operation.ts
1501
- function restoreToolOperation(snapshot) {
1502
- return ToolOperation.restore(snapshot);
1843
+ function fromDurableQueueStore(repository) {
1844
+ const facade = {
1845
+ admit: (delivery, maxPending) => repository.admit(toInboundDelivery(delivery), maxPending),
1846
+ claim: (input) => repository.claim(input).pipe(Effect.map(mapOptionalFromInboundDelivery)),
1847
+ claimById: (input) => repository.claimById(input).pipe(Effect.map(mapOptionalFromInboundDelivery)),
1848
+ complete: (input) => repository.complete(input),
1849
+ create: (delivery) => repository.create(toInboundDelivery(delivery)),
1850
+ deadLetters: () => repository.deadLetters().map(fromInboundDelivery),
1851
+ fail: (input) => repository.fail(input),
1852
+ pendingCount: () => repository.pendingCount(),
1853
+ requeueDeadLetter: (input) => repository.requeueDeadLetter(input).pipe(Effect.map(fromInboundDelivery)),
1854
+ release: (input) => repository.release(input)
1855
+ };
1856
+ inboundStores.set(facade, repository);
1857
+ return facade;
1503
1858
  }
1504
- //#endregion
1505
- //#region src/core/application/tool-execution/operation/tool-operation-ledger.ts
1506
- var ToolOperationLedgerError = class extends Error {
1507
- name = "ToolOperationLedgerError";
1508
- };
1509
- function createToolOperationLedger$1(options = {}) {
1510
- const records = /* @__PURE__ */ new Map();
1511
- for (const snapshot of options.initial ?? []) {
1512
- const operation = restoreToolOperation(snapshot);
1513
- if (records.has(operation.operationId)) throw new ToolOperationLedgerError(`duplicate initial tool operation: ${operation.operationId}`);
1514
- records.set(operation.operationId, operation);
1515
- }
1516
- const semaphore = Effect.unsafeMakeSemaphore(1);
1517
- const exclusive = (effect) => semaphore.withPermits(1)(Effect.uninterruptible(effect));
1518
- const save = (operation) => {
1519
- const snapshot = operation.toSnapshot();
1520
- return (options.persist ? options.persist(snapshot) : Effect.void).pipe(Effect.tap(() => Effect.sync(() => {
1521
- records.set(operation.operationId, operation);
1522
- })));
1859
+ function toDurableQueueStore(repository) {
1860
+ const existing = inboundStores.get(repository);
1861
+ if (existing) return existing;
1862
+ return {
1863
+ admit: (delivery, maxPending) => repository.admit(fromInboundDelivery(delivery), maxPending),
1864
+ claim: (input) => repository.claim(input).pipe(Effect.map(mapOptionalToInboundDelivery)),
1865
+ claimById: (input) => repository.claimById(input).pipe(Effect.map(mapOptionalToInboundDelivery)),
1866
+ complete: (input) => repository.complete(input),
1867
+ create: (delivery) => repository.create(fromInboundDelivery(delivery)),
1868
+ deadLetters: () => repository.deadLetters().map(toInboundDelivery),
1869
+ fail: (input) => repository.fail(input),
1870
+ pendingCount: () => repository.pendingCount(),
1871
+ renew: (input) => Effect.fail(/* @__PURE__ */ new Error(`legacy Feishu Inbox Repository cannot renew lease: ${input.id}`)),
1872
+ requeueDeadLetter: (input) => repository.requeueDeadLetter(input).pipe(Effect.map(toInboundDelivery)),
1873
+ release: (input) => repository.release(input)
1523
1874
  };
1524
- const transition = (operationId, next) => exclusive(Effect.gen(function* () {
1525
- const operation = yield* Effect.try({
1526
- catch: (error) => error,
1527
- try: () => next(records.get(operationId))
1528
- });
1529
- yield* save(operation);
1530
- }));
1875
+ }
1876
+ function toInboundDelivery(delivery) {
1877
+ const { options, ...shared } = delivery;
1878
+ return readInboundDeliverySnapshot({
1879
+ ...shared,
1880
+ ...options === void 0 ? {} : { metadata: options }
1881
+ });
1882
+ }
1883
+ function fromInboundDelivery(delivery) {
1884
+ const { metadata, ...shared } = delivery;
1531
1885
  return {
1532
- abort: (operationId, binding) => transition(operationId, (current) => requireBoundOperation(current, binding).abort()),
1533
- begin: (operationId, binding) => exclusive(Effect.gen(function* () {
1534
- const current = records.get(operationId);
1535
- if (!current) {
1536
- const created = createToolOperation({
1537
- binding,
1538
- operationId
1539
- });
1540
- yield* save(created);
1541
- return { status: "acquired" };
1542
- }
1543
- if (!current.matchesBinding(binding)) return {
1544
- reason: "operation id is bound to another invocation",
1545
- status: "blocked"
1546
- };
1547
- switch (current.state.status) {
1548
- case "completed": return {
1549
- result: current.state.result,
1550
- status: "completed"
1551
- };
1552
- case "pending": return {
1553
- reason: "operation is already in progress",
1554
- status: "blocked"
1555
- };
1556
- case "reconciliation-required": return {
1557
- reason: current.state.reason,
1558
- status: "blocked"
1559
- };
1560
- case "aborted": {
1561
- const retried = current.retry();
1562
- yield* save(retried);
1563
- return { status: "acquired" };
1564
- }
1565
- }
1566
- })),
1567
- complete: (operationId, binding, result) => transition(operationId, (current) => requireBoundOperation(current, binding).complete(result)),
1568
- inspect: (operationId, binding) => exclusive(Effect.sync(() => {
1569
- const current = records.get(operationId);
1570
- if (!current || current.state.status === "aborted" && current.matchesBinding(binding)) return { status: "missing" };
1571
- if (!current.matchesBinding(binding)) return {
1572
- reason: "operation id is bound to another invocation",
1573
- status: "blocked"
1574
- };
1575
- return current.state.status === "completed" ? {
1576
- result: current.state.result,
1577
- status: "completed"
1578
- } : {
1579
- reason: current.state.status === "reconciliation-required" ? current.state.reason : "operation is already in progress",
1580
- status: "blocked"
1581
- };
1582
- })),
1583
- reconciliationRequired: () => [...records.values()].filter((operation) => operation.state.status === "reconciliation-required").map((operation) => operation.toSnapshot()),
1584
- reconcile: (input) => exclusive(Effect.gen(function* () {
1585
- const current = records.get(input.operationId);
1586
- const operation = yield* Effect.try({
1587
- catch: (error) => error,
1588
- try: () => requireOperation(current).reconcile(input)
1589
- });
1590
- yield* save(operation);
1591
- return operation.toSnapshot();
1592
- })),
1593
- requireReconciliation: (operationId, binding, reason) => transition(operationId, (current) => requireBoundOperation(current, binding).requireReconciliation(reason)),
1594
- unresolvedForSource: (sourceMessageId) => [...records.values()].filter((operation) => operation.binding.sourceMessageId === sourceMessageId && (operation.state.status === "pending" || operation.state.status === "reconciliation-required")).map((operation) => operation.toSnapshot())
1886
+ ...shared,
1887
+ ...metadata === void 0 ? {} : { options: metadata }
1595
1888
  };
1596
1889
  }
1597
- function requireOperation(operation) {
1598
- if (!operation) throw new Error("tool operation is not held by this invocation");
1599
- return operation;
1890
+ function mapOptionalFromInboundDelivery(delivery) {
1891
+ return delivery === void 0 ? void 0 : fromInboundDelivery(delivery);
1600
1892
  }
1601
- function requireBoundOperation(operation, binding) {
1602
- const current = requireOperation(operation);
1603
- if (!current.matchesBinding(binding)) throw new Error("operation id is bound to another invocation");
1604
- return current;
1893
+ function mapOptionalToInboundDelivery(delivery) {
1894
+ return delivery === void 0 ? void 0 : toInboundDelivery(delivery);
1895
+ }
1896
+ //#endregion
1897
+ //#region src/core/application/recovery/commands/recovery-action.ts
1898
+ var InvalidRecoveryAction = class extends Error {
1899
+ name = "InvalidRecoveryAction";
1900
+ };
1901
+ function createRecoveryAction(input) {
1902
+ const actorId = input.actorId.trim();
1903
+ const note = input.note.trim();
1904
+ if (actorId.length === 0) throw new InvalidRecoveryAction("recovery actor must not be empty");
1905
+ if (note.length === 0) throw new InvalidRecoveryAction("recovery note must not be empty");
1906
+ if (!Number.isFinite(Date.parse(input.at))) throw new InvalidRecoveryAction("recovery timestamp must be an ISO timestamp");
1907
+ return Object.freeze({
1908
+ actorId,
1909
+ at: input.at,
1910
+ note
1911
+ });
1605
1912
  }
1606
1913
  //#endregion
1607
1914
  //#region src/adapters/compatibility/tool-execution/runtime/effect-runner.ts
@@ -2041,26 +2348,15 @@ function appendInboundDeliverySnapshot(filePath, delivery) {
2041
2348
  function openJsonlFeishuInboxRepository(options) {
2042
2349
  return Effect.runPromise(openJsonlInboundDeliveryStore({
2043
2350
  acceptLegacySameAttemptClaims: true,
2044
- filePath: options.filePath,
2045
- label: "Feishu inbox",
2046
- payload: feishuInboundDeliveryPayloadCodec
2047
- }).pipe(Effect.map(fromDurableQueueStore)));
2048
- }
2049
- //#endregion
2050
- //#region src/platform/persistence/files/write-persistence-file.ts
2051
- async function writePersistenceFile(filePath, value) {
2052
- await mkdir(dirname(filePath), { recursive: true });
2053
- const temporaryPath = `${filePath}.${randomUUID()}.tmp`;
2054
- try {
2055
- await writeFile(temporaryPath, `${JSON.stringify(value)}\n`, {
2056
- encoding: "utf8",
2057
- flag: "wx"
2058
- });
2059
- await rename(temporaryPath, filePath);
2060
- } catch (error) {
2061
- await unlink(temporaryPath).catch(() => void 0);
2062
- throw error;
2063
- }
2351
+ filePath: options.filePath,
2352
+ label: "Feishu inbox",
2353
+ payload: feishuInboundDeliveryPayloadCodec
2354
+ }).pipe(Effect.map(fromDurableQueueStore)));
2355
+ }
2356
+ //#endregion
2357
+ //#region src/platform/persistence/files/write-persistence-file.ts
2358
+ async function writePersistenceFile(filePath, value) {
2359
+ await writeAtomicTextFile(filePath, `${JSON.stringify(value)}\n`);
2064
2360
  }
2065
2361
  //#endregion
2066
2362
  //#region src/adapters/feishu/conversation-session/feishu-session-store.ts
@@ -3684,39 +3980,6 @@ function createAgentRuntime(harness, options = {}) {
3684
3980
  };
3685
3981
  }
3686
3982
  //#endregion
3687
- //#region src/adapters/pi/config/pi-model-overrides.ts
3688
- async function mergePiProviderBaseUrlOverride(options) {
3689
- const overrides = await readPiModelOverrides(options.filePath);
3690
- const providers = readJsonRecord(overrides.providers, options.filePath, "providers");
3691
- const providerOverride = readJsonRecord(providers[options.provider], options.filePath, `providers.${options.provider}`);
3692
- await writeFile(options.filePath, `${JSON.stringify({
3693
- ...overrides,
3694
- providers: {
3695
- ...providers,
3696
- [options.provider]: {
3697
- ...providerOverride,
3698
- baseUrl: options.baseUrl
3699
- }
3700
- }
3701
- }, null, 2)}\n`, "utf8");
3702
- }
3703
- async function readPiModelOverrides(filePath) {
3704
- try {
3705
- return readJsonRecord(JSON.parse(await readFile(filePath, "utf8")), filePath, "root");
3706
- } catch (error) {
3707
- if (isFileNotFound(error)) return {};
3708
- throw error;
3709
- }
3710
- }
3711
- function readJsonRecord(value, filePath, path) {
3712
- if (value === void 0) return {};
3713
- if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${filePath} ${path} must be a JSON object`);
3714
- return value;
3715
- }
3716
- function isFileNotFound(error) {
3717
- return error instanceof Error && "code" in error && error.code === "ENOENT";
3718
- }
3719
- //#endregion
3720
3983
  //#region src/adapters/feishu/intake/feishu-card-action-intake.ts
3721
3984
  var InvalidFeishuCardAction = class {
3722
3985
  reason;
@@ -7393,7 +7656,11 @@ function resolveContentMode(value) {
7393
7656
  //#endregion
7394
7657
  //#region src/adapters/pi/execution/pi-agent-loop.ts
7395
7658
  function createPiAgentLoop$1(options) {
7396
- return { run: (input) => Stream.fromAsyncIterable(runPiSession(input, options), (error) => error) };
7659
+ return { run: (input) => {
7660
+ const stream = Stream.fromAsyncIterable(runPiSession(input, options), (error) => error);
7661
+ if (!options.runBoundary) return stream;
7662
+ return Stream.unwrapScoped(Effect.acquireRelease(options.runBoundary.acquireRun({ signal: input.abortSignal }), (lease) => Effect.sync(lease.release)).pipe(Effect.as(stream)));
7663
+ } };
7397
7664
  }
7398
7665
  function createPiSdkAgentLoop$1(options) {
7399
7666
  return createPiAgentLoop$1({ resolveSession: async () => {
@@ -7409,6 +7676,9 @@ async function* runPiSession(input, options) {
7409
7676
  let removeAbortListener;
7410
7677
  let unsubscribe;
7411
7678
  let dispose;
7679
+ let handleForRun;
7680
+ let activationAttempted = false;
7681
+ let abortCompletion;
7412
7682
  let failure;
7413
7683
  let terminalErrorMessage;
7414
7684
  let done = false;
@@ -7421,20 +7691,27 @@ async function* runPiSession(input, options) {
7421
7691
  done = true;
7422
7692
  queue.wake();
7423
7693
  };
7424
- (async () => {
7694
+ const sessionTask = (async () => {
7425
7695
  try {
7426
7696
  const handle = await options.resolveSession(input);
7427
- await handle.activate?.(input);
7697
+ handleForRun = handle;
7428
7698
  dispose = handle.dispose ?? (() => handle.session.dispose?.());
7699
+ activationAttempted = true;
7700
+ await handle.activate?.(input);
7429
7701
  const abortSession = () => {
7430
- Promise.resolve(handle.session.abort?.()).catch((error) => {
7431
- if (!input.abortSignal.aborted) complete(error);
7702
+ if (abortCompletion) return abortCompletion;
7703
+ abortCompletion = Promise.resolve(handle.session.abort?.()).then(() => void 0, (error) => {
7704
+ if (!input.abortSignal.aborted) throw error;
7432
7705
  });
7706
+ return abortCompletion;
7433
7707
  };
7434
- input.abortSignal.addEventListener("abort", abortSession, { once: true });
7435
- removeAbortListener = () => input.abortSignal.removeEventListener("abort", abortSession);
7708
+ const requestAbort = () => {
7709
+ abortSession().then(() => complete(), (error) => complete(error));
7710
+ };
7711
+ input.abortSignal.addEventListener("abort", requestAbort, { once: true });
7712
+ removeAbortListener = () => input.abortSignal.removeEventListener("abort", requestAbort);
7436
7713
  if (input.abortSignal.aborted) {
7437
- abortSession();
7714
+ await abortSession();
7438
7715
  complete();
7439
7716
  return;
7440
7717
  }
@@ -7468,7 +7745,12 @@ async function* runPiSession(input, options) {
7468
7745
  } finally {
7469
7746
  removeAbortListener?.();
7470
7747
  unsubscribe?.();
7471
- if (options.disposeSessionAfterRun !== false) await dispose?.();
7748
+ await Promise.all([sessionTask, ...abortCompletion ? [abortCompletion] : []]);
7749
+ try {
7750
+ if (activationAttempted) await handleForRun?.deactivate?.();
7751
+ } finally {
7752
+ if (options.disposeSessionAfterRun !== false) await dispose?.();
7753
+ }
7472
7754
  }
7473
7755
  }
7474
7756
  function observeModelContent(observer, session, event, runId, mapped) {
@@ -7706,6 +7988,7 @@ function createPiAgentLoop(options) {
7706
7988
  return fromEffectAgentLoop(createPiAgentLoop$1({
7707
7989
  ...options.disposeSessionAfterRun === void 0 ? {} : { disposeSessionAfterRun: options.disposeSessionAfterRun },
7708
7990
  ...options.modelContentObserver ? { modelContentObserver: options.modelContentObserver } : {},
7991
+ ...options.runBoundary ? { runBoundary: options.runBoundary } : {},
7709
7992
  resolveSession: async (input) => toEffectPiAgentSessionHandle(await options.resolveSession(toCompatibilityAgentLoopInput(input)))
7710
7993
  }));
7711
7994
  }
@@ -7716,8 +7999,10 @@ function toEffectPiAgentSessionHandle(handle) {
7716
7999
  return {
7717
8000
  ...handle.activate ? { activate: (input) => handle.activate(toCompatibilityAgentLoopInput(input)) } : {},
7718
8001
  ...handle.dispose ? { dispose: handle.dispose } : {},
8002
+ ...handle.deactivate ? { deactivate: handle.deactivate } : {},
7719
8003
  ...handle.preparePrompt ? { preparePrompt: (input) => handle.preparePrompt(toCompatibilityAgentLoopInput(input)) } : {},
7720
8004
  ...handle.resolveToolName ? { resolveToolName: handle.resolveToolName } : {},
8005
+ ...handle.refreshResources ? { refreshResources: handle.refreshResources } : {},
7721
8006
  session: handle.session
7722
8007
  };
7723
8008
  }
@@ -7725,8 +8010,10 @@ function fromEffectPiAgentSessionHandle(handle) {
7725
8010
  return {
7726
8011
  ...handle.activate ? { activate: (input) => handle.activate(toEffectAgentLoopInput(input)) } : {},
7727
8012
  ...handle.dispose ? { dispose: handle.dispose } : {},
8013
+ ...handle.deactivate ? { deactivate: handle.deactivate } : {},
7728
8014
  ...handle.preparePrompt ? { preparePrompt: (input) => handle.preparePrompt(toEffectAgentLoopInput(input)) } : {},
7729
8015
  ...handle.resolveToolName ? { resolveToolName: handle.resolveToolName } : {},
8016
+ ...handle.refreshResources ? { refreshResources: handle.refreshResources } : {},
7730
8017
  session: handle.session
7731
8018
  };
7732
8019
  }
@@ -7755,6 +8042,7 @@ function createPiSessionRegistry$1(options) {
7755
8042
  disposeAll: async () => {
7756
8043
  await Promise.all([...sessions.keys()].map(dispose));
7757
8044
  },
8045
+ list: async () => Promise.all(sessions.values()),
7758
8046
  resolve,
7759
8047
  size: () => sessions.size
7760
8048
  };
@@ -7772,6 +8060,13 @@ function createPiSessionRegistry(options) {
7772
8060
  return {
7773
8061
  dispose: (sessionKey) => registry.dispose(sessionKey),
7774
8062
  disposeAll: () => registry.disposeAll(),
8063
+ list: async () => (await registry.list()).map((effectHandle) => {
8064
+ const cached = compatibilityHandles.get(effectHandle);
8065
+ if (cached) return cached;
8066
+ const handle = fromEffectPiAgentSessionHandle(effectHandle);
8067
+ compatibilityHandles.set(effectHandle, handle);
8068
+ return handle;
8069
+ }),
7775
8070
  resolve: async (input) => {
7776
8071
  const effectHandle = await registry.resolve(toEffectAgentLoopInput(input));
7777
8072
  const cached = compatibilityHandles.get(effectHandle);
@@ -8818,134 +9113,6 @@ function requiresToolApproval(risk) {
8818
9113
  return requiresToolApproval$1(risk);
8819
9114
  }
8820
9115
  //#endregion
8821
- //#region src/core/application/tool-execution/brokerage/tool-broker-ports.ts
8822
- var ToolExecutorInputRejected = class extends Error {
8823
- name = "ToolExecutorInputRejected";
8824
- };
8825
- //#endregion
8826
- //#region src/core/application/tool-execution/brokerage/tool-broker.ts
8827
- var ToolInvocationDenied = class extends Error {
8828
- name = "ToolInvocationDenied";
8829
- };
8830
- function createToolBroker$1(options) {
8831
- const operations = options.operations ?? createToolOperationLedger$1();
8832
- const hostTools = new Map(options.hostTools?.map((tool) => [tool.id, tool]) ?? []);
8833
- if (hostTools.size !== (options.hostTools?.length ?? 0)) throw new ToolInvocationDenied("duplicate Host Tool id");
8834
- return { execute: (request) => Effect.gen(function* () {
8835
- const prepared = yield* Effect.try({
8836
- catch: (error) => error,
8837
- try: () => prepareInvocation(hostTools, options.catalog, request)
8838
- });
8839
- const policy = yield* options.policy.current();
8840
- if (policy.revokedToolIds.includes(request.toolId)) return yield* Effect.fail(new ToolInvocationDenied(`tool has been revoked: ${request.toolId}`));
8841
- if (prepared.tool.idempotency === "required" && !request.operationId) return yield* Effect.fail(new ToolInvocationDenied(`tool requires a stable operation id: ${request.toolId}`));
8842
- if (requiresToolApproval$1(prepared.tool.risk) && !request.operationId) return yield* Effect.fail(new ToolInvocationDenied(`tool approval requires a stable operation id: ${request.toolId}`));
8843
- if (requiresToolApproval$1(prepared.tool.risk) && !request.approvalId) return yield* Effect.fail(new ToolInvocationDenied(`tool requires trusted approval: ${request.toolId}`));
8844
- const binding = yield* Effect.try({
8845
- catch: (error) => error,
8846
- try: () => createOperationBinding(prepared.authority, prepared.tool, request.input, options.digest)
8847
- });
8848
- const context = createExecutionContext(prepared.authority, request, policy.epoch, prepared.tool);
8849
- const replayCompleted = (result) => replay(prepared.tool, request.input, result, context);
8850
- if (request.operationId) {
8851
- const inspected = yield* operations.inspect(request.operationId, binding);
8852
- if (inspected.status === "completed") return yield* replayCompleted(inspected.result);
8853
- if (inspected.status === "blocked") return yield* Effect.fail(new ToolInvocationDenied(`tool operation is fenced: ${inspected.reason}`));
8854
- }
8855
- if (requiresToolApproval$1(prepared.tool.risk)) {
8856
- if (!(yield* options.approvals.consume({
8857
- agentId: prepared.authority.agentId,
8858
- approvalId: request.approvalId,
8859
- callId: request.callId,
8860
- inputDigest: binding.inputDigest,
8861
- instanceId: prepared.authority.instanceId,
8862
- operationId: request.operationId,
8863
- risk: prepared.tool.risk,
8864
- runId: prepared.authority.runId,
8865
- sessionKey: prepared.authority.sessionKey,
8866
- tenantKey: prepared.authority.tenantKey,
8867
- toolId: request.toolId,
8868
- toolVersion: prepared.tool.version
8869
- }))) return yield* Effect.fail(new ToolInvocationDenied(`tool approval is invalid or already consumed: ${request.toolId}`));
8870
- }
8871
- if (request.operationId) {
8872
- const reservation = yield* operations.begin(request.operationId, binding);
8873
- if (reservation.status === "completed") return yield* replayCompleted(reservation.result);
8874
- if (reservation.status === "blocked") return yield* Effect.fail(new ToolInvocationDenied(`tool operation is fenced: ${reservation.reason}`));
8875
- }
8876
- return yield* Effect.gen(function* () {
8877
- const result = yield* (yield* Effect.try({
8878
- catch: (error) => error,
8879
- try: () => prepared.tool.createExecutor({
8880
- toolId: prepared.tool.id,
8881
- toolVersion: prepared.tool.version
8882
- })
8883
- })).execute(request.input, context);
8884
- if (request.operationId) yield* operations.complete(request.operationId, binding, result);
8885
- return result;
8886
- }).pipe(Effect.catchAll((error) => {
8887
- if (!request.operationId) return Effect.fail(error);
8888
- return (prepared.tool.risk === "observe" || error instanceof ToolExecutorInputRejected ? operations.abort(request.operationId, binding) : operations.requireReconciliation(request.operationId, binding, error instanceof Error ? error.message : String(error))).pipe(Effect.zipRight(Effect.fail(error)));
8889
- }));
8890
- }) };
8891
- }
8892
- function prepareInvocation(hostTools, catalog, request) {
8893
- const authority = resolveInvocationAuthority(request.authority);
8894
- if (!authority.toolGrantSet.toolIds.includes(request.toolId)) throw new ToolInvocationDenied(`tool is not granted for this run: ${request.toolId}`);
8895
- const tool = hostTools.get(request.toolId) ?? catalog.snapshot().tools.find(({ id }) => id === request.toolId);
8896
- if (!tool) throw new ToolInvocationDenied(`tool is not present in the trusted catalog: ${request.toolId}`);
8897
- if (tool.version !== request.version) throw new ToolInvocationDenied(`tool version mismatch for ${request.toolId}: expected ${tool.version}`);
8898
- return {
8899
- authority,
8900
- tool
8901
- };
8902
- }
8903
- function createOperationBinding(authority, tool, input, digest) {
8904
- let inputDigest;
8905
- try {
8906
- inputDigest = createToolInputDigest$1(input, digest);
8907
- } catch (error) {
8908
- if (error instanceof InvalidToolInput) throw new ToolInvocationDenied(`tool input is not stable JSON: ${tool.id}`);
8909
- throw error;
8910
- }
8911
- return {
8912
- agentId: authority.agentId,
8913
- inputDigest,
8914
- instanceId: authority.instanceId,
8915
- sourceMessageId: authority.sourceMessageId,
8916
- toolId: tool.id,
8917
- toolVersion: tool.version
8918
- };
8919
- }
8920
- function createExecutionContext(authority, request, policyEpoch, tool) {
8921
- return Object.freeze({
8922
- agentId: authority.agentId,
8923
- callId: request.callId,
8924
- instanceId: authority.instanceId,
8925
- ...authority.memory === void 0 ? {} : { memory: authority.memory },
8926
- ...request.operationId === void 0 ? {} : { operationId: request.operationId },
8927
- policyEpoch,
8928
- runId: authority.runId,
8929
- sessionKey: authority.sessionKey,
8930
- toolId: tool.id,
8931
- toolVersion: tool.version,
8932
- ...authority.endpointId === void 0 ? {} : { origin: Object.freeze({
8933
- allowedActorOpenIds: Object.freeze(authority.allowedActorOpenIds ?? []),
8934
- endpointId: authority.endpointId,
8935
- tenantKey: authority.tenantKey,
8936
- ...authority.conversationId === void 0 ? {} : { conversationId: authority.conversationId }
8937
- }) },
8938
- ...authority.sourceMessageId ? { sourceMessageId: authority.sourceMessageId } : {}
8939
- });
8940
- }
8941
- function replay(tool, input, result, context) {
8942
- if (!tool.replayCompleted) return Effect.succeed(result);
8943
- return Effect.try({
8944
- catch: (error) => error,
8945
- try: () => tool.replayCompleted(input, result, context)
8946
- }).pipe(Effect.flatMap((effect) => effect));
8947
- }
8948
- //#endregion
8949
9116
  //#region src/adapters/compatibility/tool-execution/tool-broker.ts
8950
9117
  function createToolBroker(options) {
8951
9118
  return adaptEffectToolBroker(createToolBroker$1({
@@ -8992,66 +9159,6 @@ function adaptToolDescriptor(tool) {
8992
9159
  version: tool.version
8993
9160
  };
8994
9161
  }
8995
- function encodeToolOperationSnapshot(record) {
8996
- return JSON.stringify({
8997
- record,
8998
- version: 1
8999
- });
9000
- }
9001
- function decodeToolOperationSnapshots(raw) {
9002
- const latest = /* @__PURE__ */ new Map();
9003
- for (const [index, line] of raw.split("\n").entries()) {
9004
- if (!line.trim()) continue;
9005
- const operation = decodeToolOperationSnapshot(line, index + 1);
9006
- const previous = latest.get(operation.operationId);
9007
- if (operation.revision !== (previous?.revision ?? 0) + 1) throw new Error(`invalid tool operation revision at line ${index + 1}`);
9008
- if (previous ? !previous.validateTransitionTo(operation) : operation.reconciliation !== void 0 || operation.state.status !== "pending") throw new Error(`invalid tool operation transition at line ${index + 1}`);
9009
- latest.set(operation.operationId, operation);
9010
- }
9011
- return [...latest.values()].map((operation) => operation.toSnapshot());
9012
- }
9013
- function decodeToolOperationSnapshot(line, lineNumber) {
9014
- const envelope = JSON.parse(line);
9015
- if (!isRecord$14(envelope) || envelope.version !== 1) throw new Error(`invalid tool operation snapshot at line ${lineNumber}`);
9016
- try {
9017
- return restoreToolOperation(envelope.record);
9018
- } catch {
9019
- throw new Error(`invalid tool operation snapshot at line ${lineNumber}`);
9020
- }
9021
- }
9022
- //#endregion
9023
- //#region src/adapters/outbound/persistence/tool-operation/jsonl-tool-operation-ledger.ts
9024
- function openJsonlToolOperationLedger$1(options) {
9025
- return Effect.gen(function* () {
9026
- const loaded = yield* load(options.filePath);
9027
- const initial = [];
9028
- for (const snapshot of loaded) {
9029
- const operation = restoreToolOperation(snapshot);
9030
- const recovered = operation.state.status === "pending" ? operation.recoverInterrupted() : operation;
9031
- if (recovered !== operation) yield* persist(options.filePath, recovered.toSnapshot());
9032
- initial.push(recovered.toSnapshot());
9033
- }
9034
- return createToolOperationLedger$1({
9035
- initial,
9036
- persist: (record) => persist(options.filePath, record)
9037
- });
9038
- });
9039
- }
9040
- function persist(filePath, record) {
9041
- return Effect.tryPromise({
9042
- catch: (error) => error,
9043
- try: async () => {
9044
- await mkdir(dirname(filePath), { recursive: true });
9045
- await appendFile(filePath, `${encodeToolOperationSnapshot(record)}\n`, "utf8");
9046
- }
9047
- });
9048
- }
9049
- function load(filePath) {
9050
- return Effect.tryPromise({
9051
- catch: (error) => error,
9052
- try: () => readPersistenceFile(filePath)
9053
- }).pipe(Effect.map((raw) => raw === void 0 ? [] : decodeToolOperationSnapshots(raw)));
9054
- }
9055
9162
  //#endregion
9056
9163
  //#region src/adapters/compatibility/tool-execution/jsonl-tool-operation-ledger.ts
9057
9164
  async function openJsonlToolOperationLedger(options) {
@@ -14349,6 +14456,97 @@ function createHumanInteractionToolApprovalGateway(options) {
14349
14456
  } };
14350
14457
  }
14351
14458
  //#endregion
14459
+ //#region src/adapters/compatibility/human-interaction/model-change-approval.ts
14460
+ /** Route accepted model requests through the existing durable human decision service. */
14461
+ function createHumanInteractionModelChangeApproval(options) {
14462
+ const service = () => {
14463
+ const selected = options.registry.resolve(options.endpointId);
14464
+ if (!selected) throw new Error("Model approval endpoint is unavailable.");
14465
+ return selected;
14466
+ };
14467
+ const requestOrGet = (input) => Effect.gen(function* () {
14468
+ const interactions = service();
14469
+ const existing = yield* interactions.get(input.interactionId);
14470
+ if (existing) return projectApproval(existing, input);
14471
+ const authority = input.authority;
14472
+ if (!authority || authority.endpointId !== options.endpointId || authority.sourceMessageId !== input.principal.source.reference) return {
14473
+ status: "rejected",
14474
+ reason: "The original trusted approval source is unavailable."
14475
+ };
14476
+ return projectApproval(yield* interactions.requestUserDecision({
14477
+ agentId: authority.agentId,
14478
+ allowedActorOpenIds: [input.principal.ownerId],
14479
+ expiresAt: input.expiresAt,
14480
+ facts: approvalFacts(input),
14481
+ instanceId: authority.instanceId,
14482
+ interactionId: input.interactionId,
14483
+ options: [{
14484
+ description: "在所列预算内验证并应用本次模型变更。",
14485
+ id: "approve",
14486
+ label: "批准本次变更"
14487
+ }, {
14488
+ description: "保留当前选择,不执行付费探测。",
14489
+ id: "reject",
14490
+ label: "拒绝"
14491
+ }],
14492
+ runId: authority.runId,
14493
+ sessionKey: authority.sessionKey,
14494
+ sourceMessageId: authority.sourceMessageId,
14495
+ summary: "仅批准这一次请求;不会扩大到其他 provider 或增加预算。",
14496
+ tenantKey: authority.tenantKey,
14497
+ title: "确认模型变更"
14498
+ }), input);
14499
+ });
14500
+ return {
14501
+ requestOrGet,
14502
+ waitForResolution: (input) => Effect.gen(function* () {
14503
+ const initial = yield* requestOrGet(input);
14504
+ if (initial.status !== "pending") return initial;
14505
+ return projectApproval(yield* service().waitForResolution({ interactionId: input.interactionId }), input);
14506
+ })
14507
+ };
14508
+ }
14509
+ function approvalFacts(input) {
14510
+ return [
14511
+ {
14512
+ label: "请求",
14513
+ value: input.request.requestId
14514
+ },
14515
+ {
14516
+ label: "操作",
14517
+ value: input.request.operation
14518
+ },
14519
+ {
14520
+ label: "目标",
14521
+ value: input.request.target ? `${input.request.target.provider}/${input.request.target.model}` : "上一模型"
14522
+ },
14523
+ {
14524
+ label: "基准 revision",
14525
+ value: String(input.request.expectedRevision)
14526
+ },
14527
+ {
14528
+ label: "预算",
14529
+ value: `最多 ${input.budget.maxPaidRequests} 次付费请求、${input.budget.maxOutputTokens} 个输出 token;包含一次恢复预留。`
14530
+ },
14531
+ {
14532
+ label: "截止时间",
14533
+ value: input.expiresAt
14534
+ }
14535
+ ];
14536
+ }
14537
+ function projectApproval(interaction, input) {
14538
+ if (interaction.kind !== "user-decision" || interaction.id !== input.interactionId || interaction.sourceMessageId !== input.principal.source.reference || interaction.allowedActorOpenIds.length !== 1 || interaction.allowedActorOpenIds[0] !== input.principal.ownerId || JSON.stringify(interaction.facts) !== JSON.stringify(approvalFacts(input)) || interaction.expiresAt !== input.expiresAt) return {
14539
+ status: "rejected",
14540
+ reason: "The stored approval does not match this model request."
14541
+ };
14542
+ if (interaction.state.status === "pending") return { status: "pending" };
14543
+ if (interaction.state.status === "selected" && interaction.state.optionId === "approve") return { status: "approved" };
14544
+ return {
14545
+ status: "rejected",
14546
+ reason: "The model change approval was declined or expired."
14547
+ };
14548
+ }
14549
+ //#endregion
14352
14550
  //#region src/adapters/compatibility/human-interaction/routed-tool-approval-service.ts
14353
14551
  function createRoutedHumanInteractionToolApprovalService(registry) {
14354
14552
  return { consume: async (request) => {
@@ -14844,4 +15042,4 @@ function mapOptionalSnapshot(interaction) {
14844
15042
  return interaction === void 0 ? void 0 : toHumanInteractionSnapshot(interaction);
14845
15043
  }
14846
15044
  //#endregion
14847
- export { completeBackgroundSessionStop as $, createFeishuAgentRunCard as $n, createUuidRunIds as $r, openJsonlToolOperationLedger as $t, AUTOMATION_SUPPRESSION_PREFIX as A, createRecoveryControl as Ai, createLangfuseAgentTelemetry as An, createAgentCommandFromFeishuMessage as Ar, openJsonlBackgroundSessionRepository as At, validateProjectSkillCatalog as B, RUN_PRESENTATION_SCHEMA_VERSION as Bi, createFeishuTopicContextResolver as Bn, createAgentCommandFromFeishuCardAction as Br, progressDeliveryId as Bt, commitAutomationOutcome as C, createJsonFetchRequest as Ci, InvalidAutomationPresentation as Cn, createCompositeRivusDaemonTransport as Cr, resolveBackgroundSessionSupervisorIntervalMs as Ct, createPluginStateStore as D, openJsonFeishuSessionStore as Di, createPiAgentLoop as Dn, shouldAcceptFeishuEndpointMessage as Dr, createBackgroundSessionSupervisor as Dt, PluginStateConflict as E, createSessionScheduler as Ei, createPiSessionRegistry as En, createFeishuMessageWorker as Er, createBackgroundSessionHostTools as Et, CompactionError as F, openJsonlFeishuCardDeliveryLedger as Fi, AgentEventLogStoreError as Fn, InvalidFeishuSessionReference as Fr, isBackgroundSessionState as Ft, createRivusMemoryToolDescriptor as G, createConfiguredFeishuCardKitTargetCreator as Gn, createDefaultAgentHarnessClientFromCallback as Gr, InvalidRivusEndpointBinding as Gt, createAgentLoopPromptTransformer as H, DEFAULT_CONVERSATION_PROGRESS_DISPLAY as Hi, createConfiguredFeishuMessageReactionSender as Hn, createAgentRuntime as Hr, terminalDeliveryId as Ht, AgentContextBudgetExceeded as I, createFeishuCardDeliveryLedger as Ii, createJsonlAgentEventLog as In, createFeishuConversationId as Ir, createBackgroundSessionService as It, MEMORY_SCOPES as J, createConfiguredFeishuCardKitPublisher as Jn, createDefaultAgentHarnessFromTextCallback as Jr, createFeishuDeploymentEndpoint as Jt, createMemoryNamespace as K, createFeishuCardKitOpenApiTargetCreator as Kn, createDefaultAgentHarnessClientFromTextCallback as Kr, createRivusAgentHost as Kt, assembleAgentContext as L, createFeishuCardDeliveryReconciler as Li, createConfiguredRivusDaemonBootstrap as Ln, createFeishuSessionKey as Lr, BackgroundSessionRepositoryConflict as Lt, createAutomationMandateStore as M, InvalidRecoveryAction as Mi, createOpenTelemetryAgentEventSink as Mn, InvalidFeishuMessageContent as Mr, openJsonlBackgroundSessionDeliveryStore as Mt, AutomationMandateError as N, createRecoveryAction as Ni, createOpenTelemetryAgentTelemetry as Nn, UnsupportedFeishuMessage as Nr, createBackgroundSessionDeliveryStore as Nt, openJsonAutomationTickRepository as O, createFeishuSessionStore as Oi, createPiSdkAgentLoop as On, createFeishuMessageQueue as Or, BackgroundSessionDeliveryConflict as Ot, createCompactionService as P, createFeishuInboxRepository as Pi, createTelemetryContentRedactor as Pn, readFeishuMessageContent as Pr, BACKGROUND_SESSION_JSONL_VERSION as Pt, completeBackgroundSessionStep as Q, FEISHU_AGENT_CARD_PROGRESS_ELEMENT_ID as Qn, createSystemClock as Qr, openJsonlRecoveryControl as Qt, openJsonlAgentMemoryService as R, createFeishuStreamProjector as Ri, restoreConfiguredRivusDaemonBootstrap as Rn, InvalidFeishuCardAction as Rr, BackgroundSessionRepositoryCorrupted as Rt, DelegationDenied as S, isTerminalAgentRunPhase as Si, AUTOMATION_PRESENTATION_SCHEMA_VERSION as Sn, createRivusDaemonStatusReporter as Sr, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS as St, createDeliveryOutbox as T, SessionSchedulerDisposed as Ti, readAutomationPresentation as Tn, createFeishuWorkerLoop as Tr, narrowBackgroundSessionDefinition as Tt, createProjectMemoryPromptPreparer as U, createConfiguredFeishuTextReplySender as Un, createDefaultAgentHarness as Ur, createAgentMemoryService as Ut, validateProjectSkillCommand as V, hasInspectableRunProgress as Vi, composeFeishuTopicPrompt as Vn, mergePiProviderBaseUrlOverride as Vr, sessionIdFromSessionKey as Vt, createRivusMemoryTool as W, createConfiguredFeishuCardRolloverRuntime as Wn, createDefaultAgentHarnessClient as Wr, AgentMemoryError as Wt, appendBackgroundSessionInput as X, createFeishuCardKitOpenApiClient as Xn, createDefaultAgentRuntimeFromCallback as Xr, createAgentRuntimePool as Xt, BackgroundSessionTransitionDenied as Y, createFeishuCardKitPublisher as Yn, createDefaultAgentRuntime as Yr, createAgentHarnessPooledRuntime as Yt, claimBackgroundSession as Z, FEISHU_AGENT_CARD_ELEMENT_ID as Zn, createDefaultAgentRuntimeFromTextCallback as Zr, createAgentInstanceRegistry as Zt, loadNodeRivusPluginModule as _, isAssistantTextDeltaEvent as _i, FeishuCardTargetNotFound as _n, createFeishuAgentRuntime as _r, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS as _t, createInMemoryHumanInteractionRepository as a, createAgentHarness as ai, createAgentsMdInstructionsProvider as an, createRateLimitedFeishuPublisher as ar, parkBackgroundSessionForReconciliation as at, createDelegationService as b, evolveAgentRun as bi, createJsonFileFeishuCardTargetRegistry as bn, createFeishuCardActionErrorResponse as br, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS as bt, createConfiguredFeishuAutomationCardSender as c, AgentHarnessBusy as ci, WorkspaceInstructionsSourceError as cn, createFeishuCardRollover as cr, requestBackgroundSessionStop as ct, createHumanInteractionEndpointRegistry as d, createAgentConversationMessages as di, createTestClock as dn, acceptsCardPresentationProgress as dr, suspendBackgroundSession as dt, createAgentHarnessClient as ei, createToolBroker as en, FeishuOpenApiError as er, createBackgroundSession as et, HumanInteractionTransitionDenied as f, createAgentTranscriptMessages as fi, createFeishuPeriodicFlush as fn, activeCardPresentation as fr, createBackgroundSessionCard as ft, createRivusDeploymentDaemon as g, isAgentToolExecutionEvent as gi, createFeishuCardPresentationStore as gn, restoreAgentHistory as gr, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS as gt, loadRivusDeploymentManifest as h, replayAgentHistory as hi, FeishuCardPresentationNotFound as hn, createLazyFeishuWebSocketEventDispatcher as hr, DEFAULT_BACKGROUND_SESSION_LEASE_MS as ht, createFeishuHumanInteractionPresenter as i, createAgentDomainEventSink as ii, requiresToolApproval as in, createFeishuTenantAccessTokenProvider as ir, isBackgroundSessionTerminalPhase as it, createDailyAutomationSchedule as j, createToolOperationLedger as ji, resolveLangfuseTelemetryConfig as jn, describeFeishuMessageIntake as jr, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION as jt, createScheduledAutomation as k, openJsonlFeishuInboxRepository as ki, LangfuseTelemetryConfigError as kn, createFeishuAgentDaemon as kr, createBackgroundSessionRepository as kt, createRoutedHumanInteractionToolApprovalService as l, AgentLoopFailed as li, createFixedClock as ln, DEFAULT_CARD_STREAM_LEASE_MS as lr, requeueInterruptedBackgroundSessionStep as lt, createConfiguredRivusDeploymentDaemon as m, replayAgentTranscript as mi, createFeishuCotPublisher as mn, createFeishuWebSocketDaemon as mr, resolveFeishuDeliveryChatId as mt, createConfiguredFeishuHumanInteractionPresenter as n, createAgentDomainEventSinkFromCallback as ni, createInvocationAuthority as nn, createFeishuOpenApiClient as nr, isBackgroundSessionDue as nt, createJsonlHumanInteractionRepository as o, AgentEventHandlerFailed as oi, createWorkspaceRootHandle as on, createCoalescingFeishuPublisher as or, releaseBackgroundSessionLease as ot, transitionHumanInteraction as p, createAgentTranscriptTurn as pi, FeishuCotProtocolError as pn, isCardPresentationHandoffDue as pr, createConfiguredFeishuBackgroundSessionDelivery as pt, restrictMemoryScopesForAudience as q, createFeishuCardTargetPreparation as qn, createDefaultAgentHarnessFromCallback as qr, createFeishuPresentationPreparation as qt, createFeishuHumanInteractionCard as r, createAgentRunUpdateHandler as ri, createToolInputDigest as rn, FeishuTenantAccessTokenError as rr, isBackgroundSessionLeaseExpired as rt, HumanInteractionRepositoryError as s, AgentEventSinkFailed as si, InvalidWorkspaceRoot as sn, createFeishuCardRolloverSupervisor as sr, renewBackgroundSessionLease as st, createHumanInteractionService as t, createAgentDomainEventHandler as ti, ToolInvocationDenied as tn, createConfiguredFeishuOpenApiClient as tr, failBackgroundSessionStep as tt, createHumanInteractionToolApprovalGateway as u, AgentRunCancelled as ui, createSequenceRunIds as un, CardPresentationTransitionDenied as ur, resolveBackgroundSessionReconciliation as ut, loadRivusDeployment as v, isAssistantThinkingDeltaEvent as vi, FeishuCardTargetRegistryStoreError as vn, createFeishuEventHandlers as vr, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS as vt, DeliveryOutboxError as w, SessionSchedulerCapacityExceeded as wi, createAutomationPresentation as wn, createRivusDaemonProcess as wr, extendBackgroundSessionDefinition as wt, intersectToolIds as x, initialAgentRunState as xi, createRunPresentationProjector as xn, createRivusDaemonStatusHttpServer as xr, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS as xt, createSubagentCoordinator as y, isTerminalAgentDomainEvent as yi, createInMemoryFeishuCardTargetRegistry as yn, createFeishuCardActionCallbackResponse as yr, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES as yt, InvalidProjectSkillCatalog as z, createPresentedValue as zi, InvalidFeishuTopicContext as zn, UnsupportedFeishuCardAction as zr, BackgroundSessionCallerDenied as zt };
15045
+ export { completeBackgroundSessionStep as $, createFeishuAgentRunCard as $n, createAgentHarnessClient as $r, openJsonlRecoveryControl as $t, createScheduledAutomation as A, createToolOperationLedger as Ai, createLangfuseAgentTelemetry as An, createAgentCommandFromFeishuMessage as Ar, createBackgroundSessionRepository as At, InvalidProjectSkillCatalog as B, hasInspectableRunProgress as Bi, createFeishuTopicContextResolver as Bn, createAgentCommandFromFeishuCardAction as Br, BackgroundSessionCallerDenied as Bt, DelegationDenied as C, SessionSchedulerCapacityExceeded as Ci, InvalidAutomationPresentation as Cn, createCompositeRivusDaemonTransport as Cr, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS as Ct, PluginStateConflict as D, createFeishuSessionStore as Di, createPiAgentLoop as Dn, shouldAcceptFeishuEndpointMessage as Dr, createBackgroundSessionHostTools as Dt, createDeliveryOutbox as E, openJsonFeishuSessionStore as Ei, createPiSessionRegistry as En, createFeishuMessageWorker as Er, narrowBackgroundSessionDefinition as Et, createCompactionService as F, createFeishuCardDeliveryLedger as Fi, AgentEventLogStoreError as Fn, InvalidFeishuSessionReference as Fr, BACKGROUND_SESSION_JSONL_VERSION as Ft, createRivusMemoryTool as G, readPersistenceFile as Gi, createConfiguredFeishuCardKitTargetCreator as Gn, createDefaultAgentHarnessClientFromTextCallback as Gr, AgentMemoryError as Gt, validateProjectSkillCommand as H, ToolInvocationDenied as Hi, createConfiguredFeishuMessageReactionSender as Hn, createDefaultAgentHarness as Hr, sessionIdFromSessionKey as Ht, CompactionError as I, createFeishuCardDeliveryReconciler as Ii, createJsonlAgentEventLog as In, createFeishuConversationId as Ir, isBackgroundSessionState as It, restrictMemoryScopesForAudience as J, mergePiProviderModelDeclaration as Ji, createConfiguredFeishuCardKitPublisher as Jn, createDefaultAgentRuntime as Jr, createFeishuPresentationPreparation as Jt, createRivusMemoryToolDescriptor as K, isRecord$14 as Ki, createFeishuCardKitOpenApiTargetCreator as Kn, createDefaultAgentHarnessFromCallback as Kr, InvalidRivusEndpointBinding as Kt, AgentContextBudgetExceeded as L, createFeishuStreamProjector as Li, createConfiguredRivusDaemonBootstrap as Ln, createFeishuSessionKey as Lr, createBackgroundSessionService as Lt, createDailyAutomationSchedule as M, createRecoveryAction as Mi, createOpenTelemetryAgentEventSink as Mn, InvalidFeishuMessageContent as Mr, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION as Mt, createAutomationMandateStore as N, createFeishuInboxRepository as Ni, createOpenTelemetryAgentTelemetry as Nn, UnsupportedFeishuMessage as Nr, openJsonlBackgroundSessionDeliveryStore as Nt, createPluginStateStore as O, openJsonlFeishuInboxRepository as Oi, createPiSdkAgentLoop as On, createFeishuMessageQueue as Or, createBackgroundSessionSupervisor as Ot, AutomationMandateError as P, openJsonlFeishuCardDeliveryLedger as Pi, createTelemetryContentRedactor as Pn, readFeishuMessageContent as Pr, createBackgroundSessionDeliveryStore as Pt, claimBackgroundSession as Q, FEISHU_AGENT_CARD_PROGRESS_ELEMENT_ID as Qn, createUuidRunIds as Qr, createAgentInstanceRegistry as Qt, assembleAgentContext as R, createPresentedValue as Ri, restoreConfiguredRivusDaemonBootstrap as Rn, InvalidFeishuCardAction as Rr, BackgroundSessionRepositoryConflict as Rt, intersectToolIds as S, createJsonFetchRequest as Si, AUTOMATION_PRESENTATION_SCHEMA_VERSION as Sn, createRivusDaemonStatusReporter as Sr, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS as St, DeliveryOutboxError as T, createSessionScheduler as Ti, readAutomationPresentation as Tn, createFeishuWorkerLoop as Tr, extendBackgroundSessionDefinition as Tt, createAgentLoopPromptTransformer as U, createToolBroker$1 as Ui, createConfiguredFeishuTextReplySender as Un, createDefaultAgentHarnessClient as Ur, terminalDeliveryId as Ut, validateProjectSkillCatalog as V, DEFAULT_CONVERSATION_PROGRESS_DISPLAY as Vi, composeFeishuTopicPrompt as Vn, createAgentRuntime as Vr, progressDeliveryId as Vt, createProjectMemoryPromptPreparer as W, openJsonlToolOperationLedger$1 as Wi, createConfiguredFeishuCardRolloverRuntime as Wn, createDefaultAgentHarnessClientFromCallback as Wr, createAgentMemoryService as Wt, BackgroundSessionTransitionDenied as X, createFeishuCardKitOpenApiClient as Xn, createDefaultAgentRuntimeFromTextCallback as Xr, createAgentHarnessPooledRuntime as Xt, MEMORY_SCOPES as Y, writeAtomicTextFile as Yi, createFeishuCardKitPublisher as Yn, createDefaultAgentRuntimeFromCallback as Yr, createFeishuDeploymentEndpoint as Yt, appendBackgroundSessionInput as Z, FEISHU_AGENT_CARD_ELEMENT_ID as Zn, createSystemClock as Zr, createAgentRuntimePool as Zt, createRivusDeploymentDaemon as _, isAssistantThinkingDeltaEvent as _i, FeishuCardTargetNotFound as _n, createFeishuAgentRuntime as _r, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS as _t, createInMemoryHumanInteractionRepository as a, AgentEventHandlerFailed as ai, createAgentsMdInstructionsProvider as an, createRateLimitedFeishuPublisher as ar, isBackgroundSessionTerminalPhase as at, createSubagentCoordinator as b, initialAgentRunState as bi, createJsonFileFeishuCardTargetRegistry as bn, createFeishuCardActionErrorResponse as br, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES as bt, createConfiguredFeishuAutomationCardSender as c, AgentLoopFailed as ci, WorkspaceInstructionsSourceError as cn, createFeishuCardRollover as cr, renewBackgroundSessionLease as ct, createHumanInteractionToolApprovalGateway as d, createAgentTranscriptMessages as di, createTestClock as dn, acceptsCardPresentationProgress as dr, resolveBackgroundSessionReconciliation as dt, createAgentDomainEventHandler as ei, openJsonlToolOperationLedger as en, FeishuOpenApiError as er, completeBackgroundSessionStop as et, createHumanInteractionEndpointRegistry as f, createAgentTranscriptTurn as fi, createFeishuPeriodicFlush as fn, activeCardPresentation as fr, suspendBackgroundSession as ft, loadRivusDeploymentManifest as g, isAssistantTextDeltaEvent as gi, createFeishuCardPresentationStore as gn, restoreAgentHistory as gr, DEFAULT_BACKGROUND_SESSION_LEASE_MS as gt, createConfiguredRivusDeploymentDaemon as h, isAgentToolExecutionEvent as hi, FeishuCardPresentationNotFound as hn, createLazyFeishuWebSocketEventDispatcher as hr, resolveFeishuDeliveryChatId as ht, createFeishuHumanInteractionPresenter as i, createAgentHarness as ii, requiresToolApproval as in, createFeishuTenantAccessTokenProvider as ir, isBackgroundSessionLeaseExpired as it, AUTOMATION_SUPPRESSION_PREFIX as j, InvalidRecoveryAction as ji, resolveLangfuseTelemetryConfig as jn, describeFeishuMessageIntake as jr, openJsonlBackgroundSessionRepository as jt, openJsonAutomationTickRepository as k, createRecoveryControl as ki, LangfuseTelemetryConfigError as kn, createFeishuAgentDaemon as kr, BackgroundSessionDeliveryConflict as kt, createRoutedHumanInteractionToolApprovalService as l, AgentRunCancelled as li, createFixedClock as ln, DEFAULT_CARD_STREAM_LEASE_MS as lr, requestBackgroundSessionStop as lt, transitionHumanInteraction as m, replayAgentHistory as mi, createFeishuCotPublisher as mn, createFeishuWebSocketDaemon as mr, createConfiguredFeishuBackgroundSessionDelivery as mt, createConfiguredFeishuHumanInteractionPresenter as n, createAgentRunUpdateHandler as ni, createInvocationAuthority as nn, createFeishuOpenApiClient as nr, failBackgroundSessionStep as nt, createJsonlHumanInteractionRepository as o, AgentEventSinkFailed as oi, createWorkspaceRootHandle as on, createCoalescingFeishuPublisher as or, parkBackgroundSessionForReconciliation as ot, HumanInteractionTransitionDenied as p, replayAgentTranscript as pi, FeishuCotProtocolError as pn, isCardPresentationHandoffDue as pr, createBackgroundSessionCard as pt, createMemoryNamespace as q, mergePiProviderBaseUrlOverride as qi, createFeishuCardTargetPreparation as qn, createDefaultAgentHarnessFromTextCallback as qr, createRivusAgentHost as qt, createFeishuHumanInteractionCard as r, createAgentDomainEventSink as ri, createToolInputDigest as rn, FeishuTenantAccessTokenError as rr, isBackgroundSessionDue as rt, HumanInteractionRepositoryError as s, AgentHarnessBusy as si, InvalidWorkspaceRoot as sn, createFeishuCardRolloverSupervisor as sr, releaseBackgroundSessionLease as st, createHumanInteractionService as t, createAgentDomainEventSinkFromCallback as ti, createToolBroker as tn, createConfiguredFeishuOpenApiClient as tr, createBackgroundSession as tt, createHumanInteractionModelChangeApproval as u, createAgentConversationMessages as ui, createSequenceRunIds as un, CardPresentationTransitionDenied as ur, requeueInterruptedBackgroundSessionStep as ut, loadNodeRivusPluginModule as v, isTerminalAgentDomainEvent as vi, FeishuCardTargetRegistryStoreError as vn, createFeishuEventHandlers as vr, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS as vt, commitAutomationOutcome as w, SessionSchedulerDisposed as wi, createAutomationPresentation as wn, createRivusDaemonProcess as wr, resolveBackgroundSessionSupervisorIntervalMs as wt, createDelegationService as x, isTerminalAgentRunPhase as xi, createRunPresentationProjector as xn, createRivusDaemonStatusHttpServer as xr, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS as xt, loadRivusDeployment as y, evolveAgentRun as yi, createInMemoryFeishuCardTargetRegistry as yn, createFeishuCardActionCallbackResponse as yr, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS as yt, openJsonlAgentMemoryService as z, RUN_PRESENTATION_SCHEMA_VERSION as zi, InvalidFeishuTopicContext as zn, UnsupportedFeishuCardAction as zr, BackgroundSessionRepositoryCorrupted as zt };