@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.
- package/dist/bootstrap/pi-feishu.d.ts +2 -2
- package/dist/bootstrap/pi-feishu.js +4118 -388
- package/dist/chunks/index.d.ts +117 -1
- package/dist/chunks/pi.js +56 -19
- package/dist/chunks/rivus-daemon-cli.js +452 -144
- package/dist/chunks/rivus-model-management-wire.js +344 -0
- package/dist/chunks/rivus-plugin-testkit.js +1 -1
- package/dist/chunks/{tool-input-digest.js → rivus-tool.js} +67 -67
- package/dist/chunks/src.js +1885 -1687
- package/dist/cli.js +152 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/mcp.js +1 -1
- package/dist/pi.d.ts +2 -0
- package/dist/pi.js +1 -1
- package/examples/pi-feishu-deployment.bootstrap.ts +557 -462
- package/package.json +5 -3
- package/skills/runtime-management/SKILL.md +61 -0
package/dist/chunks/src.js
CHANGED
|
@@ -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/
|
|
21
|
-
|
|
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/
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return {
|
|
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
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
76
|
-
if (
|
|
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
|
-
|
|
79
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|
|
129
|
-
return
|
|
143
|
+
function isNodeErrorWithCode(error, code) {
|
|
144
|
+
return error instanceof Error && "code" in error && error.code === code;
|
|
130
145
|
}
|
|
131
146
|
//#endregion
|
|
132
|
-
//#region src/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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/
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (value
|
|
169
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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)
|
|
224
|
-
|
|
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
|
-
|
|
182
|
+
ancestors.delete(value);
|
|
239
183
|
}
|
|
240
184
|
}
|
|
241
|
-
function
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
261
|
-
return
|
|
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/
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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
|
|
503
|
-
|
|
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
|
|
506
|
-
return value
|
|
516
|
+
function isRecord$12(value) {
|
|
517
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
507
518
|
}
|
|
508
519
|
//#endregion
|
|
509
|
-
//#region src/
|
|
510
|
-
function
|
|
511
|
-
|
|
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
|
-
|
|
567
|
-
|
|
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/
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
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
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
616
|
-
|
|
622
|
+
function requireOperation(operation) {
|
|
623
|
+
if (!operation) throw new Error("tool operation is not held by this invocation");
|
|
624
|
+
return operation;
|
|
617
625
|
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
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
|
|
633
|
+
record,
|
|
633
634
|
version: 1
|
|
634
635
|
});
|
|
635
|
-
}
|
|
636
|
-
function
|
|
637
|
-
const
|
|
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()
|
|
640
|
-
const
|
|
641
|
-
const previous =
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
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 [...
|
|
647
|
+
return [...latest.values()].map((operation) => operation.toSnapshot());
|
|
653
648
|
}
|
|
654
|
-
function
|
|
649
|
+
function decodeToolOperationSnapshot(line, lineNumber) {
|
|
655
650
|
const envelope = JSON.parse(line);
|
|
656
|
-
if (!isRecord$14(envelope) || envelope.version !== 1
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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/
|
|
667
|
-
function
|
|
668
|
-
return Effect.
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
|
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, `${
|
|
684
|
-
}
|
|
685
|
-
catch: (error) => error
|
|
681
|
+
await appendFile(filePath, `${encodeToolOperationSnapshot(record)}\n`, "utf8");
|
|
682
|
+
}
|
|
686
683
|
});
|
|
687
684
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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/
|
|
695
|
-
var
|
|
696
|
-
name = "
|
|
692
|
+
//#region src/core/application/tool-execution/brokerage/tool-broker-ports.ts
|
|
693
|
+
var ToolExecutorInputRejected = class extends Error {
|
|
694
|
+
name = "ToolExecutorInputRejected";
|
|
697
695
|
};
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
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
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
...
|
|
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
|
|
727
|
-
if (!
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
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
|
|
744
|
-
|
|
745
|
-
|
|
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
|
|
748
|
-
|
|
749
|
-
|
|
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
|
|
770
|
-
|
|
771
|
-
if (
|
|
772
|
-
|
|
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
|
|
775
|
-
if (
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
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
|
-
|
|
797
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
|
805
|
-
|
|
806
|
-
parseInboundDeliveryTimestamp(recovery.at, "recovery audit timestamp");
|
|
925
|
+
function cloneOptional(record) {
|
|
926
|
+
return record === void 0 ? void 0 : cloneRecord(record);
|
|
807
927
|
}
|
|
808
|
-
function
|
|
809
|
-
|
|
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
|
-
|
|
814
|
-
|
|
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
|
-
|
|
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
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
|
846
|
-
|
|
847
|
-
|
|
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
|
|
854
|
-
|
|
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
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
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
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
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
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
|
929
|
-
|
|
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
|
|
942
|
-
|
|
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/
|
|
947
|
-
function
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
|
1028
|
-
return
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
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
|
|
1035
|
-
|
|
1036
|
-
|
|
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
|
|
1039
|
-
const
|
|
1040
|
-
if (!
|
|
1041
|
-
return
|
|
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
|
|
1044
|
-
return
|
|
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
|
|
1047
|
-
|
|
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/
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
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
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
return
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
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
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
...
|
|
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
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
|
1109
|
-
|
|
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
|
|
1112
|
-
|
|
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
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
}
|
|
1119
|
-
function
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
if (
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
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
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1137
|
-
|
|
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
|
|
1161
|
-
if (
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
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
|
-
|
|
1181
|
-
} catch
|
|
1182
|
-
|
|
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
|
-
|
|
1196
|
-
|
|
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
|
|
1199
|
-
|
|
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
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
return
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
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
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
case "
|
|
1393
|
-
if (!
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
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
|
-
|
|
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
|
|
1408
|
-
if (
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
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
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
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
|
|
1436
|
-
|
|
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
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
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
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
|
1462
|
-
if (
|
|
1463
|
-
|
|
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
|
|
1478
|
-
|
|
1479
|
-
if (
|
|
1480
|
-
|
|
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
|
|
1483
|
-
|
|
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
|
|
1492
|
-
|
|
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/
|
|
1496
|
-
|
|
1497
|
-
|
|
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
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
1533
|
-
|
|
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
|
|
1598
|
-
|
|
1599
|
-
return operation;
|
|
1890
|
+
function mapOptionalFromInboundDelivery(delivery) {
|
|
1891
|
+
return delivery === void 0 ? void 0 : fromInboundDelivery(delivery);
|
|
1600
1892
|
}
|
|
1601
|
-
function
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
7697
|
+
handleForRun = handle;
|
|
7428
7698
|
dispose = handle.dispose ?? (() => handle.session.dispose?.());
|
|
7699
|
+
activationAttempted = true;
|
|
7700
|
+
await handle.activate?.(input);
|
|
7429
7701
|
const abortSession = () => {
|
|
7430
|
-
|
|
7431
|
-
|
|
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
|
-
|
|
7435
|
-
|
|
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
|
-
|
|
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 {
|
|
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 };
|