@rivus/agent 0.6.1 → 0.7.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/README.md +12 -0
- package/dist/agent-loop.d.ts +10 -1
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +493 -3
- package/dist/index.js +1589 -79
- package/dist/pi-tool-proxy.d.ts +3 -0
- package/dist/pi.js +3 -0
- package/dist/rivus-daemon-cli.js +163 -23
- package/dist/rivus-plugin-registry.js +227 -3
- package/dist/rivus-plugin.d.ts +8 -0
- package/dist/tool-input-digest.js +2 -0
- package/examples/pi-feishu-deployment.bootstrap.ts +187 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { a as createAgentLoopSkillExecutionStart, c as createAgentLoopToolExecutionEnd, d as createAsyncIterableAgentLoop, f as createEventAgentLoop, i as createAgentLoopSkillExecutionEnd, l as createAgentLoopToolExecutionStart, m as createTextAgentLoopFromCallback, n as createAgentLoopModelExecutionEnd, o as createAgentLoopTextDelta, p as createTextAgentLoop, r as createAgentLoopModelExecutionStart, s as createAgentLoopThinkingDelta, t as createAgentLoopFromCallback, u as createAgentLoopToolExecutionUpdate } from "./agent-loop.js";
|
|
2
2
|
import { a as createMemoryNamespace, c as InvalidRivusPlugin, d as requiresToolApproval, i as RIVUS_MEMORY_TOOL_VERSION, l as RIVUS_PLUGIN_API_VERSION, n as RIVUS_MEMORY_TOOL_ID, o as createRivusMemoryToolContract, r as RIVUS_MEMORY_TOOL_PLUGIN_ID, s as restrictMemoryScopesForAudience, t as MEMORY_SCOPES, u as RivusToolInputRejected } from "./agent-memory.js";
|
|
3
|
-
import { n as resolveRivusAgentDefinition, t as createRivusPluginCatalog } from "./rivus-plugin-registry.js";
|
|
4
|
-
import { A as
|
|
3
|
+
import { a as BACKGROUND_SESSION_START_TOOL_ID, c as BACKGROUND_SESSION_TOOL_VERSION, d as createBackgroundSessionStepSourceMessageId, f as createBackgroundSessionToolContracts, h as narrowBackgroundSessionDefinition, i as BACKGROUND_SESSION_SESSION_KEY_PREFIX, l as backgroundSessionToolIds, m as isBackgroundSessionToolId, n as resolveRivusAgentDefinition, o as BACKGROUND_SESSION_TOOL_IDS, p as extendBackgroundSessionDefinition, s as BACKGROUND_SESSION_TOOL_PLUGIN_ID, t as createRivusPluginCatalog, u as createBackgroundSessionKey } from "./rivus-plugin-registry.js";
|
|
4
|
+
import { $ as loadRivusDeployment, A as DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, B as acceptsCardPresentationProgress, C as loadRivusDaemonConfig, D as loadRivusDeploymentManifest, E as RivusDeploymentManifestError, F as DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, G as createCardPresentationChain, H as beginCardPresentationHandoff, I as resolveBackgroundSessionSupervisorIntervalMs, J as markCardPresentationTerminal, K as failCardPresentationHandoff, L as DEFAULT_CARD_STREAM_LEASE_MS, M as DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, N as DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, O as DEFAULT_BACKGROUND_SESSION_LEASE_MS, P as DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, Q as RivusPluginLoadError, R as createFeishuCardRollover, S as RivusDaemonConfigError, U as compensateCardPresentationHandoff, V as activeCardPresentation, W as completeCardPresentationHandoff, X as resolveFeishuEndpointCredentials, Y as FeishuEndpointCredentialError, _ as createStableId, a as InvalidRivusProjectSpace, b as createRivusEnvFromOpenClawConfig, c as RivusDeploymentReadinessError, d as createRivusAgentHost, et as validateRivusDeploymentManifest, f as AgentInstanceBusy, g as createAgentInstanceRegistry, h as AgentInstanceConflict, i as resolveRivusProjectSpace, j as DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, k as DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, l as createRivusDeploymentDaemon, m as createAgentRuntimePool, n as createRivusDeploymentCliProcess, o as RivusDeploymentAutomationReadinessError, p as AgentRuntimeDisposed, q as isCardPresentationHandoffDue, r as createConfiguredRivusDeploymentDaemon, s as RivusDeploymentDaemonLifecycleError, t as runRivusDaemonCli, u as InvalidRivusEndpointBinding, v as createRivusDaemonShutdownController, w as loadNodeRivusPluginModule, x as formatRivusEnvFile, y as OpenClawEnvImportError, z as CardPresentationTransitionDenied } from "./rivus-daemon-cli.js";
|
|
5
5
|
import { a as InvalidInvocationAuthority, i as normalizeStableJson, n as InvalidToolInput, o as createInvocationAuthority, r as createToolInputDigest, s as resolveInvocationAuthority, t as InvalidStableJson } from "./tool-input-digest.js";
|
|
6
6
|
import { n as assertRivusPluginConforms, r as createFakeRivusPlugin, t as RivusPluginConformanceError } from "./rivus-plugin-testkit.js";
|
|
7
7
|
import { Cause, Deferred, Effect, Either, Exit, Fiber, Option, Stream } from "effect";
|
|
8
8
|
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
10
|
-
import { appendFile, lstat, mkdir, readFile, readdir, realpath, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
10
|
+
import { appendFile, lstat, mkdir, readFile, readdir, realpath, rename, stat, truncate, unlink, writeFile } from "node:fs/promises";
|
|
11
11
|
import { isDeepStrictEqual } from "node:util";
|
|
12
12
|
import { createServer } from "node:http";
|
|
13
13
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
@@ -2866,8 +2866,8 @@ function resolveRunPresentation(input) {
|
|
|
2866
2866
|
title: input.generation ? "正在处理(续)" : "正在处理"
|
|
2867
2867
|
};
|
|
2868
2868
|
case "handoff": return {
|
|
2869
|
-
content: "任务仍在后台处理,进度将继续显示在下一条消息。",
|
|
2870
|
-
note: "
|
|
2869
|
+
content: input.text || "任务仍在后台处理,进度将继续显示在下一条消息。",
|
|
2870
|
+
note: "本卡片已停止更新;后续进度见下一条消息",
|
|
2871
2871
|
template: "blue",
|
|
2872
2872
|
title: "已转到新消息"
|
|
2873
2873
|
};
|
|
@@ -2883,14 +2883,35 @@ function resolveRunPresentation(input) {
|
|
|
2883
2883
|
template: "red",
|
|
2884
2884
|
title: "处理失败"
|
|
2885
2885
|
};
|
|
2886
|
-
case "cancelled": return
|
|
2887
|
-
content: "已停止生成。",
|
|
2888
|
-
note: "本次运行已取消",
|
|
2889
|
-
template: "grey",
|
|
2890
|
-
title: "已取消"
|
|
2891
|
-
};
|
|
2886
|
+
case "cancelled": return resolveCancellationPresentation(input.reason, input.text);
|
|
2892
2887
|
}
|
|
2893
2888
|
}
|
|
2889
|
+
function resolveCancellationPresentation(reason, text) {
|
|
2890
|
+
const timeoutMs = readRunTimeoutMs(reason);
|
|
2891
|
+
if (timeoutMs !== void 0) return {
|
|
2892
|
+
content: text || `任务执行超过 ${formatDuration(timeoutMs)},已停止。本次没有形成最终结论。`,
|
|
2893
|
+
note: `达到 ${formatDuration(timeoutMs)}运行时限;以上是超时前进度,不是最终结论`,
|
|
2894
|
+
template: "orange",
|
|
2895
|
+
title: "执行超时"
|
|
2896
|
+
};
|
|
2897
|
+
return {
|
|
2898
|
+
content: text || "已停止生成。",
|
|
2899
|
+
note: "本次运行已取消",
|
|
2900
|
+
template: "grey",
|
|
2901
|
+
title: "已取消"
|
|
2902
|
+
};
|
|
2903
|
+
}
|
|
2904
|
+
function readRunTimeoutMs(reason) {
|
|
2905
|
+
const match = /^run timed out after (\d+)ms$/.exec(reason ?? "");
|
|
2906
|
+
if (!match) return void 0;
|
|
2907
|
+
const milliseconds = Number(match[1]);
|
|
2908
|
+
return Number.isSafeInteger(milliseconds) && milliseconds > 0 ? milliseconds : void 0;
|
|
2909
|
+
}
|
|
2910
|
+
function formatDuration(milliseconds) {
|
|
2911
|
+
if (milliseconds % 36e5 === 0) return `${milliseconds / 36e5} 小时`;
|
|
2912
|
+
if (milliseconds % 6e4 === 0) return `${milliseconds / 6e4} 分钟`;
|
|
2913
|
+
return `${Math.ceil(milliseconds / 1e3)} 秒`;
|
|
2914
|
+
}
|
|
2894
2915
|
//#endregion
|
|
2895
2916
|
//#region src/infrastructure/feishu/feishu-tenant-token-provider.ts
|
|
2896
2917
|
var FeishuTenantAccessTokenError = class {
|
|
@@ -3004,10 +3025,20 @@ function isStreamingClosed(error) {
|
|
|
3004
3025
|
return error instanceof FeishuOpenApiError && (error.code === 200850 || error.code === 300309);
|
|
3005
3026
|
}
|
|
3006
3027
|
function updateWholeCard(baseUrl, agentName, request, status, call) {
|
|
3028
|
+
const requestText = "text" in request ? request.text : void 0;
|
|
3007
3029
|
const card = status === "completed" ? createFeishuAgentRunCard({
|
|
3008
3030
|
agentName,
|
|
3009
3031
|
status,
|
|
3010
|
-
text:
|
|
3032
|
+
text: requestText ?? ""
|
|
3033
|
+
}) : status === "cancelled" ? createFeishuAgentRunCard({
|
|
3034
|
+
agentName,
|
|
3035
|
+
..."reason" in request && request.reason ? { reason: request.reason } : {},
|
|
3036
|
+
status,
|
|
3037
|
+
...requestText === void 0 ? {} : { text: requestText }
|
|
3038
|
+
}) : status === "handoff" ? createFeishuAgentRunCard({
|
|
3039
|
+
agentName,
|
|
3040
|
+
status,
|
|
3041
|
+
...requestText === void 0 ? {} : { text: requestText }
|
|
3011
3042
|
}) : createFeishuAgentRunCard({
|
|
3012
3043
|
agentName,
|
|
3013
3044
|
status
|
|
@@ -3040,7 +3071,8 @@ function createFeishuCardKitPublisher(options) {
|
|
|
3040
3071
|
});
|
|
3041
3072
|
case "handoff": return yield* options.client.handoff({
|
|
3042
3073
|
cardId: target.cardId,
|
|
3043
|
-
sequence
|
|
3074
|
+
sequence,
|
|
3075
|
+
...action.text === void 0 ? {} : { text: action.text }
|
|
3044
3076
|
});
|
|
3045
3077
|
case "finish":
|
|
3046
3078
|
yield* options.client.finish({
|
|
@@ -3060,7 +3092,8 @@ function createFeishuCardKitPublisher(options) {
|
|
|
3060
3092
|
yield* options.client.cancel({
|
|
3061
3093
|
cardId: target.cardId,
|
|
3062
3094
|
...action.reason ? { reason: action.reason } : {},
|
|
3063
|
-
sequence
|
|
3095
|
+
sequence,
|
|
3096
|
+
...action.text === void 0 ? {} : { text: action.text }
|
|
3064
3097
|
});
|
|
3065
3098
|
return yield* ledger.markTerminal(action.runId);
|
|
3066
3099
|
}
|
|
@@ -3168,7 +3201,7 @@ function createFeishuCardKitOpenApiTargetCreator(options) {
|
|
|
3168
3201
|
runId: run.runId,
|
|
3169
3202
|
sessionKey: run.sessionKey,
|
|
3170
3203
|
status: "running",
|
|
3171
|
-
text: initialContent
|
|
3204
|
+
text: createOptions?.initialContent ?? initialContent
|
|
3172
3205
|
})),
|
|
3173
3206
|
type: "card_json"
|
|
3174
3207
|
},
|
|
@@ -3225,7 +3258,10 @@ function createConfiguredFeishuCardRolloverRuntime(options) {
|
|
|
3225
3258
|
});
|
|
3226
3259
|
const cardRollover = createFeishuCardRollover({
|
|
3227
3260
|
clock: options.clock,
|
|
3228
|
-
createSuccessor: ({ generation, run }) => creator.createTarget(run, {
|
|
3261
|
+
createSuccessor: ({ generation, run, text }) => creator.createTarget(run, {
|
|
3262
|
+
generation,
|
|
3263
|
+
...text === void 0 ? {} : { initialContent: text }
|
|
3264
|
+
}),
|
|
3229
3265
|
leaseMs,
|
|
3230
3266
|
...options.observe ? { observe: options.observe } : {},
|
|
3231
3267
|
publisher,
|
|
@@ -3407,36 +3443,36 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3407
3443
|
switch (value.type) {
|
|
3408
3444
|
case "agent_run_accepted": return {
|
|
3409
3445
|
occurredAt,
|
|
3410
|
-
prompt: readString(value, "prompt", lineNumber),
|
|
3411
|
-
runId: readString(value, "runId", lineNumber),
|
|
3412
|
-
sessionKey: readString(value, "sessionKey", lineNumber),
|
|
3446
|
+
prompt: readString$1(value, "prompt", lineNumber),
|
|
3447
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3448
|
+
sessionKey: readString$1(value, "sessionKey", lineNumber),
|
|
3413
3449
|
type: value.type
|
|
3414
3450
|
};
|
|
3415
3451
|
case "agent_turn_started": return {
|
|
3416
3452
|
occurredAt,
|
|
3417
|
-
runId: readString(value, "runId", lineNumber),
|
|
3418
|
-
sessionKey: readString(value, "sessionKey", lineNumber),
|
|
3453
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3454
|
+
sessionKey: readString$1(value, "sessionKey", lineNumber),
|
|
3419
3455
|
type: value.type
|
|
3420
3456
|
};
|
|
3421
3457
|
case "assistant_text_delta": return {
|
|
3422
|
-
delta: readString(value, "delta", lineNumber),
|
|
3458
|
+
delta: readString$1(value, "delta", lineNumber),
|
|
3423
3459
|
occurredAt,
|
|
3424
|
-
runId: readString(value, "runId", lineNumber),
|
|
3460
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3425
3461
|
type: value.type
|
|
3426
3462
|
};
|
|
3427
3463
|
case "assistant_thinking_delta": return {
|
|
3428
|
-
delta: readString(value, "delta", lineNumber),
|
|
3464
|
+
delta: readString$1(value, "delta", lineNumber),
|
|
3429
3465
|
occurredAt,
|
|
3430
|
-
runId: readString(value, "runId", lineNumber),
|
|
3466
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3431
3467
|
type: value.type
|
|
3432
3468
|
};
|
|
3433
3469
|
case "agent_model_execution_started": return {
|
|
3434
|
-
api: readString(value, "api", lineNumber),
|
|
3435
|
-
model: readString(value, "model", lineNumber),
|
|
3436
|
-
modelCallId: readString(value, "modelCallId", lineNumber),
|
|
3470
|
+
api: readString$1(value, "api", lineNumber),
|
|
3471
|
+
model: readString$1(value, "model", lineNumber),
|
|
3472
|
+
modelCallId: readString$1(value, "modelCallId", lineNumber),
|
|
3437
3473
|
occurredAt,
|
|
3438
|
-
provider: readString(value, "provider", lineNumber),
|
|
3439
|
-
runId: readString(value, "runId", lineNumber),
|
|
3474
|
+
provider: readString$1(value, "provider", lineNumber),
|
|
3475
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3440
3476
|
type: value.type
|
|
3441
3477
|
};
|
|
3442
3478
|
case "agent_model_execution_ended": {
|
|
@@ -3445,15 +3481,15 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3445
3481
|
const responseModel = readOptionalString(value, "responseModel", lineNumber);
|
|
3446
3482
|
const reasoning = readOptionalNumber(usage, "reasoning", lineNumber);
|
|
3447
3483
|
return {
|
|
3448
|
-
api: readString(value, "api", lineNumber),
|
|
3484
|
+
api: readString$1(value, "api", lineNumber),
|
|
3449
3485
|
...errorMessage ? { errorMessage } : {},
|
|
3450
|
-
model: readString(value, "model", lineNumber),
|
|
3451
|
-
modelCallId: readString(value, "modelCallId", lineNumber),
|
|
3486
|
+
model: readString$1(value, "model", lineNumber),
|
|
3487
|
+
modelCallId: readString$1(value, "modelCallId", lineNumber),
|
|
3452
3488
|
occurredAt,
|
|
3453
|
-
provider: readString(value, "provider", lineNumber),
|
|
3489
|
+
provider: readString$1(value, "provider", lineNumber),
|
|
3454
3490
|
...responseModel ? { responseModel } : {},
|
|
3455
|
-
runId: readString(value, "runId", lineNumber),
|
|
3456
|
-
stopReason: readString(value, "stopReason", lineNumber),
|
|
3491
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3492
|
+
stopReason: readString$1(value, "stopReason", lineNumber),
|
|
3457
3493
|
type: value.type,
|
|
3458
3494
|
usage: {
|
|
3459
3495
|
cacheRead: readNumber(usage, "cacheRead", lineNumber),
|
|
@@ -3483,10 +3519,10 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3483
3519
|
} : {
|
|
3484
3520
|
...common,
|
|
3485
3521
|
contentLength: readNumber(value, "contentLength", lineNumber),
|
|
3486
|
-
digest: readString(value, "digest", lineNumber),
|
|
3522
|
+
digest: readString$1(value, "digest", lineNumber),
|
|
3487
3523
|
isError: false,
|
|
3488
|
-
title: readString(value, "title", lineNumber),
|
|
3489
|
-
version: readString(value, "version", lineNumber)
|
|
3524
|
+
title: readString$1(value, "title", lineNumber),
|
|
3525
|
+
version: readString$1(value, "version", lineNumber)
|
|
3490
3526
|
};
|
|
3491
3527
|
}
|
|
3492
3528
|
case "agent_tool_execution_started": return {
|
|
@@ -3510,21 +3546,21 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3510
3546
|
type: value.type
|
|
3511
3547
|
};
|
|
3512
3548
|
case "agent_turn_completed": return {
|
|
3513
|
-
finalText: readString(value, "finalText", lineNumber),
|
|
3549
|
+
finalText: readString$1(value, "finalText", lineNumber),
|
|
3514
3550
|
occurredAt,
|
|
3515
|
-
runId: readString(value, "runId", lineNumber),
|
|
3551
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3516
3552
|
type: value.type
|
|
3517
3553
|
};
|
|
3518
3554
|
case "agent_run_completed": return {
|
|
3519
|
-
finalText: readString(value, "finalText", lineNumber),
|
|
3555
|
+
finalText: readString$1(value, "finalText", lineNumber),
|
|
3520
3556
|
occurredAt,
|
|
3521
|
-
runId: readString(value, "runId", lineNumber),
|
|
3557
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3522
3558
|
type: value.type
|
|
3523
3559
|
};
|
|
3524
3560
|
case "agent_run_failed": return {
|
|
3525
|
-
errorMessage: readString(value, "errorMessage", lineNumber),
|
|
3561
|
+
errorMessage: readString$1(value, "errorMessage", lineNumber),
|
|
3526
3562
|
occurredAt,
|
|
3527
|
-
runId: readString(value, "runId", lineNumber),
|
|
3563
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3528
3564
|
type: value.type
|
|
3529
3565
|
};
|
|
3530
3566
|
case "agent_run_cancelled": {
|
|
@@ -3532,7 +3568,7 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3532
3568
|
return {
|
|
3533
3569
|
occurredAt,
|
|
3534
3570
|
...reason ? { reason } : {},
|
|
3535
|
-
runId: readString(value, "runId", lineNumber),
|
|
3571
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3536
3572
|
type: value.type
|
|
3537
3573
|
};
|
|
3538
3574
|
}
|
|
@@ -3541,19 +3577,19 @@ function parseAgentDomainEvent(value, lineNumber) {
|
|
|
3541
3577
|
}
|
|
3542
3578
|
function readSkillExecutionIdentity(value, lineNumber) {
|
|
3543
3579
|
return {
|
|
3544
|
-
runId: readString(value, "runId", lineNumber),
|
|
3545
|
-
skillCallId: readString(value, "skillCallId", lineNumber),
|
|
3546
|
-
skillId: readString(value, "skillId", lineNumber)
|
|
3580
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3581
|
+
skillCallId: readString$1(value, "skillCallId", lineNumber),
|
|
3582
|
+
skillId: readString$1(value, "skillId", lineNumber)
|
|
3547
3583
|
};
|
|
3548
3584
|
}
|
|
3549
3585
|
function readToolExecutionIdentity(value, lineNumber) {
|
|
3550
3586
|
return {
|
|
3551
|
-
runId: readString(value, "runId", lineNumber),
|
|
3552
|
-
toolCallId: readString(value, "toolCallId", lineNumber),
|
|
3553
|
-
toolName: readString(value, "toolName", lineNumber)
|
|
3587
|
+
runId: readString$1(value, "runId", lineNumber),
|
|
3588
|
+
toolCallId: readString$1(value, "toolCallId", lineNumber),
|
|
3589
|
+
toolName: readString$1(value, "toolName", lineNumber)
|
|
3554
3590
|
};
|
|
3555
3591
|
}
|
|
3556
|
-
function readString(value, field, lineNumber) {
|
|
3592
|
+
function readString$1(value, field, lineNumber) {
|
|
3557
3593
|
const fieldValue = value[field];
|
|
3558
3594
|
if (typeof fieldValue !== "string") throw new Error(`Invalid agent event field '${field}' at line ${lineNumber}.`);
|
|
3559
3595
|
return fieldValue;
|
|
@@ -4724,7 +4760,7 @@ function unwrapResult(value) {
|
|
|
4724
4760
|
}
|
|
4725
4761
|
function presentResult(toolName, value) {
|
|
4726
4762
|
if (isTruncated(value)) return "结果过长,已省略。";
|
|
4727
|
-
if (!isRecord(value)) return truncate(presentValue(value) ?? "工具执行完成。", 2e3);
|
|
4763
|
+
if (!isRecord(value)) return truncate$1(presentValue(value) ?? "工具执行完成。", 2e3);
|
|
4728
4764
|
const tool = resolveTool(toolName);
|
|
4729
4765
|
const orderedKeys = tool.fields?.map(([key]) => key) ?? [];
|
|
4730
4766
|
const lines = Object.entries(value).sort(([left], [right]) => {
|
|
@@ -4736,7 +4772,7 @@ function presentResult(toolName, value) {
|
|
|
4736
4772
|
const presented = presentValue(item, field?.[2]);
|
|
4737
4773
|
return presented ? [`${clean(field?.[1] ?? humanizeKey(key), 80)}:${presented}`] : [];
|
|
4738
4774
|
}).slice(0, 12);
|
|
4739
|
-
return truncate(lines.length > 0 ? lines.join("\n") : "工具执行完成。", 2e3);
|
|
4775
|
+
return truncate$1(lines.length > 0 ? lines.join("\n") : "工具执行完成。", 2e3);
|
|
4740
4776
|
}
|
|
4741
4777
|
function presentValue(value, unit) {
|
|
4742
4778
|
if (value === REDACTED || value === TRUNCATED || value === void 0) return void 0;
|
|
@@ -4760,9 +4796,9 @@ function humanizeKey(key) {
|
|
|
4760
4796
|
return `${text.slice(0, 1).toUpperCase()}${text.slice(1)}` || "结果";
|
|
4761
4797
|
}
|
|
4762
4798
|
function clean(value, maxLength) {
|
|
4763
|
-
return truncate(value.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/gu, " ").trim(), maxLength);
|
|
4799
|
+
return truncate$1(value.replace(/[\p{Cc}\p{Cf}]+/gu, " ").replace(/\s+/gu, " ").trim(), maxLength);
|
|
4764
4800
|
}
|
|
4765
|
-
function truncate(value, maxLength) {
|
|
4801
|
+
function truncate$1(value, maxLength) {
|
|
4766
4802
|
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}…`;
|
|
4767
4803
|
}
|
|
4768
4804
|
function isRecord(value) {
|
|
@@ -5310,7 +5346,14 @@ function createToolBroker(options) {
|
|
|
5310
5346
|
runId: authority.runId,
|
|
5311
5347
|
sessionKey: authority.sessionKey,
|
|
5312
5348
|
toolId: tool.id,
|
|
5313
|
-
toolVersion: tool.version
|
|
5349
|
+
toolVersion: tool.version,
|
|
5350
|
+
...authority.endpointId ? { origin: Object.freeze({
|
|
5351
|
+
...authority.allowedActorOpenIds ? { allowedActorOpenIds: authority.allowedActorOpenIds } : { allowedActorOpenIds: Object.freeze([]) },
|
|
5352
|
+
endpointId: authority.endpointId,
|
|
5353
|
+
tenantKey: authority.tenantKey,
|
|
5354
|
+
...authority.conversationId ? { conversationId: authority.conversationId } : {}
|
|
5355
|
+
}) } : {},
|
|
5356
|
+
...authority.sourceMessageId ? { sourceMessageId: authority.sourceMessageId } : {}
|
|
5314
5357
|
});
|
|
5315
5358
|
const replayCompleted = (result) => "replayCompleted" in tool && tool.replayCompleted ? tool.replayCompleted(request.input, result, context) : result;
|
|
5316
5359
|
if (request.operationId) {
|
|
@@ -5845,6 +5888,1483 @@ function isVisibleMemory(record) {
|
|
|
5845
5888
|
return record.scope !== "shared-user-profile" && record.scope !== "project" || record.state === "confirmed";
|
|
5846
5889
|
}
|
|
5847
5890
|
//#endregion
|
|
5891
|
+
//#region src/domain/background-agent-session.ts
|
|
5892
|
+
const BACKGROUND_SESSION_TERMINAL_PHASES = /* @__PURE__ */ new Set([
|
|
5893
|
+
"stopped",
|
|
5894
|
+
"completed",
|
|
5895
|
+
"failed",
|
|
5896
|
+
"reconciliation-required"
|
|
5897
|
+
]);
|
|
5898
|
+
function isBackgroundSessionTerminalPhase(phase) {
|
|
5899
|
+
return BACKGROUND_SESSION_TERMINAL_PHASES.has(phase);
|
|
5900
|
+
}
|
|
5901
|
+
var BackgroundSessionTransitionDenied = class extends Error {
|
|
5902
|
+
sessionId;
|
|
5903
|
+
name = "BackgroundSessionTransitionDenied";
|
|
5904
|
+
constructor(sessionId, message) {
|
|
5905
|
+
super(message);
|
|
5906
|
+
this.sessionId = sessionId;
|
|
5907
|
+
}
|
|
5908
|
+
};
|
|
5909
|
+
function createBackgroundSession(input) {
|
|
5910
|
+
if (!input.sessionId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session id must not be empty");
|
|
5911
|
+
if (!input.authority.sessionKey.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session key must not be empty");
|
|
5912
|
+
if (!input.authority.agentId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session authority requires an agent id");
|
|
5913
|
+
if (!input.sourceMessageId.trim()) throw new BackgroundSessionTransitionDenied(input.sessionId, "background session requires a trusted source message id");
|
|
5914
|
+
return Object.freeze({
|
|
5915
|
+
authority: Object.freeze({
|
|
5916
|
+
...input.authority,
|
|
5917
|
+
memoryScopes: Object.freeze([...input.authority.memoryScopes])
|
|
5918
|
+
}),
|
|
5919
|
+
consecutiveFailures: 0,
|
|
5920
|
+
createdAt: input.now,
|
|
5921
|
+
displayName: input.displayName,
|
|
5922
|
+
origin: Object.freeze({
|
|
5923
|
+
...input.origin,
|
|
5924
|
+
allowedActorOpenIds: Object.freeze([...input.origin.allowedActorOpenIds])
|
|
5925
|
+
}),
|
|
5926
|
+
parentRunId: input.parentRunId,
|
|
5927
|
+
pendingInput: Object.freeze([]),
|
|
5928
|
+
phase: "queued",
|
|
5929
|
+
prompt: input.prompt,
|
|
5930
|
+
revision: 1,
|
|
5931
|
+
sessionId: input.sessionId,
|
|
5932
|
+
sourceMessageId: input.sourceMessageId,
|
|
5933
|
+
stepCount: 0,
|
|
5934
|
+
updatedAt: input.now,
|
|
5935
|
+
wakeCount: 0
|
|
5936
|
+
});
|
|
5937
|
+
}
|
|
5938
|
+
function claimBackgroundSession(state, input) {
|
|
5939
|
+
if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot run again`);
|
|
5940
|
+
if (state.phase !== "queued" && state.phase !== "waiting") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot start a step while ${state.phase}`);
|
|
5941
|
+
if (state.wakeAt !== void 0 && state.wakeAt > input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "background session is not due");
|
|
5942
|
+
return next(state, {
|
|
5943
|
+
currentStepRunId: input.stepRunId,
|
|
5944
|
+
lastWakeText: input.wakeText,
|
|
5945
|
+
lease: input.lease,
|
|
5946
|
+
pendingInput: Object.freeze([]),
|
|
5947
|
+
phase: "running",
|
|
5948
|
+
stepCount: state.stepCount + 1,
|
|
5949
|
+
updatedAt: input.now,
|
|
5950
|
+
wakeAt: void 0,
|
|
5951
|
+
wakeCount: state.wakeCount + 1
|
|
5952
|
+
});
|
|
5953
|
+
}
|
|
5954
|
+
function renewBackgroundSessionLease(state, lease) {
|
|
5955
|
+
if (state.lease === void 0) throw new BackgroundSessionTransitionDenied(state.sessionId, "cannot renew a lease that is not held");
|
|
5956
|
+
if (state.lease.owner !== lease.owner || state.lease.epoch !== lease.epoch) throw new BackgroundSessionTransitionDenied(state.sessionId, "cannot renew a lease from another worker");
|
|
5957
|
+
return next(state, { lease });
|
|
5958
|
+
}
|
|
5959
|
+
function releaseBackgroundSessionLease(state) {
|
|
5960
|
+
if (state.lease === void 0) throw new BackgroundSessionTransitionDenied(state.sessionId, "no lease to release");
|
|
5961
|
+
return next(state, { lease: void 0 });
|
|
5962
|
+
}
|
|
5963
|
+
function suspendBackgroundSession(state, input) {
|
|
5964
|
+
if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot wait while ${state.phase}`);
|
|
5965
|
+
if (input.wakeAt !== void 0 && input.wakeAt <= input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "background session wake time must be in the future");
|
|
5966
|
+
return next(state, {
|
|
5967
|
+
currentStepRunId: void 0,
|
|
5968
|
+
phase: input.wakeAt === void 0 ? "input-required" : "waiting",
|
|
5969
|
+
updatedAt: input.now,
|
|
5970
|
+
...input.wakeAt === void 0 ? {} : { wakeAt: input.wakeAt }
|
|
5971
|
+
});
|
|
5972
|
+
}
|
|
5973
|
+
function completeBackgroundSessionStep(state, input) {
|
|
5974
|
+
if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot complete while ${state.phase}`);
|
|
5975
|
+
return next(state, {
|
|
5976
|
+
consecutiveFailures: 0,
|
|
5977
|
+
currentStepRunId: void 0,
|
|
5978
|
+
lease: void 0,
|
|
5979
|
+
phase: "completed",
|
|
5980
|
+
result: {
|
|
5981
|
+
completedAt: input.now,
|
|
5982
|
+
stepRunId: input.stepRunId,
|
|
5983
|
+
text: input.text
|
|
5984
|
+
},
|
|
5985
|
+
updatedAt: input.now
|
|
5986
|
+
});
|
|
5987
|
+
}
|
|
5988
|
+
function failBackgroundSessionStep(state, input) {
|
|
5989
|
+
if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot fail while ${state.phase}`);
|
|
5990
|
+
if (input.retryable) {
|
|
5991
|
+
if (input.wakeAt === void 0 || input.wakeAt <= input.now) throw new BackgroundSessionTransitionDenied(state.sessionId, "retryable failure requires a future wake time");
|
|
5992
|
+
return next(state, {
|
|
5993
|
+
consecutiveFailures: state.consecutiveFailures + 1,
|
|
5994
|
+
currentStepRunId: void 0,
|
|
5995
|
+
errorMessage: input.errorMessage,
|
|
5996
|
+
lease: void 0,
|
|
5997
|
+
phase: "queued",
|
|
5998
|
+
updatedAt: input.now,
|
|
5999
|
+
wakeAt: input.wakeAt
|
|
6000
|
+
});
|
|
6001
|
+
}
|
|
6002
|
+
return next(state, {
|
|
6003
|
+
consecutiveFailures: state.consecutiveFailures + 1,
|
|
6004
|
+
currentStepRunId: void 0,
|
|
6005
|
+
errorMessage: input.errorMessage,
|
|
6006
|
+
lease: void 0,
|
|
6007
|
+
phase: "failed",
|
|
6008
|
+
updatedAt: input.now
|
|
6009
|
+
});
|
|
6010
|
+
}
|
|
6011
|
+
function requestBackgroundSessionStop(state, input) {
|
|
6012
|
+
if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot stop`);
|
|
6013
|
+
if (state.phase === "stopping") throw new BackgroundSessionTransitionDenied(state.sessionId, "background session is already stopping");
|
|
6014
|
+
if (state.phase === "running") return next(state, {
|
|
6015
|
+
cancellation: {
|
|
6016
|
+
reason: input.reason,
|
|
6017
|
+
requestedAt: input.now
|
|
6018
|
+
},
|
|
6019
|
+
phase: "stopping",
|
|
6020
|
+
updatedAt: input.now
|
|
6021
|
+
});
|
|
6022
|
+
return next(state, {
|
|
6023
|
+
cancellation: {
|
|
6024
|
+
reason: input.reason,
|
|
6025
|
+
requestedAt: input.now
|
|
6026
|
+
},
|
|
6027
|
+
currentStepRunId: void 0,
|
|
6028
|
+
lease: void 0,
|
|
6029
|
+
phase: "stopped",
|
|
6030
|
+
updatedAt: input.now,
|
|
6031
|
+
wakeAt: void 0
|
|
6032
|
+
});
|
|
6033
|
+
}
|
|
6034
|
+
function completeBackgroundSessionStop(state, input) {
|
|
6035
|
+
if (state.phase !== "stopping") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot finish stopping while ${state.phase}`);
|
|
6036
|
+
return next(state, {
|
|
6037
|
+
currentStepRunId: void 0,
|
|
6038
|
+
lease: void 0,
|
|
6039
|
+
phase: "stopped",
|
|
6040
|
+
updatedAt: input.now,
|
|
6041
|
+
wakeAt: void 0
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
function parkBackgroundSessionForReconciliation(state, input) {
|
|
6045
|
+
if (isBackgroundSessionTerminalPhase(state.phase) && state.phase !== "reconciliation-required") throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot be reconciled`);
|
|
6046
|
+
return next(state, {
|
|
6047
|
+
currentStepRunId: void 0,
|
|
6048
|
+
errorMessage: input.reason,
|
|
6049
|
+
lease: void 0,
|
|
6050
|
+
phase: "reconciliation-required",
|
|
6051
|
+
reconciliationNote: input.reason,
|
|
6052
|
+
updatedAt: input.now,
|
|
6053
|
+
wakeAt: void 0
|
|
6054
|
+
});
|
|
6055
|
+
}
|
|
6056
|
+
function resolveBackgroundSessionReconciliation(state, input) {
|
|
6057
|
+
if (state.phase !== "reconciliation-required") throw new BackgroundSessionTransitionDenied(state.sessionId, `background session cannot resolve while ${state.phase}`);
|
|
6058
|
+
if (input.outcome === "continue") return next(state, {
|
|
6059
|
+
errorMessage: void 0,
|
|
6060
|
+
phase: "queued",
|
|
6061
|
+
reconciliationNote: void 0,
|
|
6062
|
+
updatedAt: input.now,
|
|
6063
|
+
wakeAt: input.now
|
|
6064
|
+
});
|
|
6065
|
+
return next(state, {
|
|
6066
|
+
errorMessage: void 0,
|
|
6067
|
+
phase: "stopped",
|
|
6068
|
+
reconciliationNote: void 0,
|
|
6069
|
+
updatedAt: input.now,
|
|
6070
|
+
wakeAt: void 0
|
|
6071
|
+
});
|
|
6072
|
+
}
|
|
6073
|
+
function appendBackgroundSessionInput(state, input) {
|
|
6074
|
+
if (isBackgroundSessionTerminalPhase(state.phase)) throw new BackgroundSessionTransitionDenied(state.sessionId, `terminal ${state.phase} session cannot accept input`);
|
|
6075
|
+
return next(state, {
|
|
6076
|
+
pendingInput: Object.freeze([...state.pendingInput, input.message]),
|
|
6077
|
+
updatedAt: input.now,
|
|
6078
|
+
...state.phase === "waiting" || state.phase === "input-required" ? {
|
|
6079
|
+
phase: "queued",
|
|
6080
|
+
wakeAt: input.now
|
|
6081
|
+
} : {}
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
6084
|
+
function requeueInterruptedBackgroundSessionStep(state, input) {
|
|
6085
|
+
if (state.phase !== "running") throw new BackgroundSessionTransitionDenied(state.sessionId, "interrupted step recovery requires a running session");
|
|
6086
|
+
return next(state, {
|
|
6087
|
+
currentStepRunId: void 0,
|
|
6088
|
+
lastWakeText: input.wakeText,
|
|
6089
|
+
lease: void 0,
|
|
6090
|
+
phase: "queued",
|
|
6091
|
+
updatedAt: input.now,
|
|
6092
|
+
wakeAt: input.now
|
|
6093
|
+
});
|
|
6094
|
+
}
|
|
6095
|
+
function isBackgroundSessionLeaseExpired(state, now) {
|
|
6096
|
+
return state.lease !== void 0 && state.lease.expiresAt <= now;
|
|
6097
|
+
}
|
|
6098
|
+
function isBackgroundSessionDue(state, now) {
|
|
6099
|
+
if (state.phase !== "queued" && state.phase !== "waiting") return false;
|
|
6100
|
+
return state.wakeAt === void 0 || state.wakeAt <= now;
|
|
6101
|
+
}
|
|
6102
|
+
function next(state, changes) {
|
|
6103
|
+
const merged = {
|
|
6104
|
+
...state,
|
|
6105
|
+
...changes,
|
|
6106
|
+
revision: state.revision + 1
|
|
6107
|
+
};
|
|
6108
|
+
for (const key of Object.keys(merged)) if (merged[key] === void 0) delete merged[key];
|
|
6109
|
+
return Object.freeze(merged);
|
|
6110
|
+
}
|
|
6111
|
+
function isValidBackgroundSessionTransition(previous, nextState) {
|
|
6112
|
+
if (nextState.sessionId !== previous.sessionId) return "session id must not change";
|
|
6113
|
+
if (nextState.revision !== previous.revision + 1) return "revision must increase by exactly one";
|
|
6114
|
+
if (nextState.authority.agentId !== previous.authority.agentId) return "authority agent must not change";
|
|
6115
|
+
if (nextState.authority.profileRevision !== previous.authority.profileRevision) return "authority profile revision must not change";
|
|
6116
|
+
if (nextState.authority.toolGrantRevision !== previous.authority.toolGrantRevision) return "authority tool grant revision must not change";
|
|
6117
|
+
if (nextState.authority.sessionKey !== previous.authority.sessionKey) return "authority session key must not change";
|
|
6118
|
+
if (nextState.origin.endpointId !== previous.origin.endpointId) return "origin endpoint must not change";
|
|
6119
|
+
if (nextState.origin.tenantKey !== previous.origin.tenantKey) return "origin tenant must not change";
|
|
6120
|
+
if (nextState.origin.conversationId !== previous.origin.conversationId) return "origin conversation must not change";
|
|
6121
|
+
if (nextState.parentRunId !== previous.parentRunId) return "parent run must not change";
|
|
6122
|
+
if (nextState.sourceMessageId !== previous.sourceMessageId) return "source message must not change";
|
|
6123
|
+
if (nextState.prompt !== previous.prompt) return "prompt must not change";
|
|
6124
|
+
if (nextState.stepCount < previous.stepCount) return "step count must not decrease";
|
|
6125
|
+
if (nextState.wakeCount < previous.wakeCount) return "wake count must not decrease";
|
|
6126
|
+
if (nextState.consecutiveFailures < previous.consecutiveFailures) {
|
|
6127
|
+
if (previous.consecutiveFailures > 0 && nextState.phase === "completed" && nextState.consecutiveFailures === 0) return;
|
|
6128
|
+
return "failure count must not decrease";
|
|
6129
|
+
}
|
|
6130
|
+
if (nextState.createdAt !== previous.createdAt) return "created time must not change";
|
|
6131
|
+
if (previous.phase === "running") {
|
|
6132
|
+
if (nextState.phase === "running") return holdsSameLease(previous, nextState) ? void 0 : "running lease owner or epoch must not change";
|
|
6133
|
+
if (nextState.phase === "waiting" || nextState.phase === "input-required") return holdsSameLease(previous, nextState) ? void 0 : "suspension requires the same lease";
|
|
6134
|
+
if (nextState.phase === "completed" && nextState.result !== void 0 && nextState.lease === void 0) return;
|
|
6135
|
+
if (nextState.phase === "failed" && nextState.errorMessage !== void 0 && nextState.lease === void 0) return;
|
|
6136
|
+
if (nextState.phase === "stopping" && nextState.cancellation !== void 0) return void 0;
|
|
6137
|
+
if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
|
|
6138
|
+
if (nextState.phase === "queued" && nextState.lease === void 0 && nextState.wakeAt !== void 0) return;
|
|
6139
|
+
return `running session cannot transition to ${nextState.phase}`;
|
|
6140
|
+
}
|
|
6141
|
+
if (previous.phase === "stopping") {
|
|
6142
|
+
if (nextState.phase === "stopped" && nextState.lease === void 0 && nextState.cancellation !== void 0) return;
|
|
6143
|
+
if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
|
|
6144
|
+
return `stopping session cannot transition to ${nextState.phase}`;
|
|
6145
|
+
}
|
|
6146
|
+
if (previous.phase === "queued") {
|
|
6147
|
+
if (nextState.phase === "running" && nextState.lease !== void 0) return void 0;
|
|
6148
|
+
if (nextState.phase === "queued" && nextState.lease === void 0 && nextState.wakeAt === previous.wakeAt) return;
|
|
6149
|
+
if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
|
|
6150
|
+
if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
|
|
6151
|
+
return `queued session cannot transition to ${nextState.phase}`;
|
|
6152
|
+
}
|
|
6153
|
+
if (previous.phase === "waiting") {
|
|
6154
|
+
if (nextState.phase === "running" && nextState.lease !== void 0) return void 0;
|
|
6155
|
+
if (nextState.phase === "waiting" && nextState.lease === void 0 && nextState.wakeAt === previous.wakeAt) return;
|
|
6156
|
+
if (nextState.phase === "queued" && nextState.wakeAt !== void 0) return void 0;
|
|
6157
|
+
if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
|
|
6158
|
+
if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
|
|
6159
|
+
return `waiting session cannot transition to ${nextState.phase}`;
|
|
6160
|
+
}
|
|
6161
|
+
if (previous.phase === "input-required") {
|
|
6162
|
+
if (nextState.phase === "input-required" && nextState.lease === void 0) return void 0;
|
|
6163
|
+
if (nextState.phase === "queued" && nextState.wakeAt !== void 0) return void 0;
|
|
6164
|
+
if (nextState.phase === "stopped" && nextState.lease === void 0) return void 0;
|
|
6165
|
+
if (nextState.phase === "reconciliation-required" && nextState.lease === void 0) return void 0;
|
|
6166
|
+
return `input-required session cannot transition to ${nextState.phase}`;
|
|
6167
|
+
}
|
|
6168
|
+
if (isBackgroundSessionTerminalPhase(previous.phase)) {
|
|
6169
|
+
if (previous.phase === "reconciliation-required") {
|
|
6170
|
+
if (nextState.phase !== "queued" && nextState.phase !== "stopped") return "reconciliation-required session can only continue or stop";
|
|
6171
|
+
return;
|
|
6172
|
+
}
|
|
6173
|
+
return `terminal ${previous.phase} session cannot transition`;
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
function holdsSameLease(previous, nextState) {
|
|
6177
|
+
return nextState.lease !== void 0 && previous.lease !== void 0 && nextState.lease.owner === previous.lease.owner && nextState.lease.epoch === previous.lease.epoch;
|
|
6178
|
+
}
|
|
6179
|
+
//#endregion
|
|
6180
|
+
//#region src/application/background-session/background-session-service.ts
|
|
6181
|
+
var BackgroundSessionCallerDenied = class extends Error {
|
|
6182
|
+
name = "BackgroundSessionCallerDenied";
|
|
6183
|
+
constructor(message) {
|
|
6184
|
+
super(message);
|
|
6185
|
+
}
|
|
6186
|
+
};
|
|
6187
|
+
function createBackgroundSessionService(options) {
|
|
6188
|
+
const serial = createSerialExecutor();
|
|
6189
|
+
const start = async (input) => {
|
|
6190
|
+
const now = options.clock.now();
|
|
6191
|
+
const origin = requireOrigin(input.context);
|
|
6192
|
+
const state = createBackgroundSession({
|
|
6193
|
+
authority: input.authority,
|
|
6194
|
+
displayName: input.displayName?.trim() || input.sessionId,
|
|
6195
|
+
now,
|
|
6196
|
+
origin: {
|
|
6197
|
+
allowedActorOpenIds: origin.allowedActorOpenIds,
|
|
6198
|
+
...origin.conversationId === void 0 ? {} : { conversationId: origin.conversationId },
|
|
6199
|
+
endpointId: origin.endpointId,
|
|
6200
|
+
...input.context.memory ? { memory: {
|
|
6201
|
+
audience: input.context.memory.audience,
|
|
6202
|
+
...input.context.memory.conversationId ? { conversationId: input.context.memory.conversationId } : {},
|
|
6203
|
+
...input.context.memory.projectId ? { projectId: input.context.memory.projectId } : {},
|
|
6204
|
+
subjectId: input.context.memory.subjectId,
|
|
6205
|
+
tenantId: input.context.memory.tenantId
|
|
6206
|
+
} } : {},
|
|
6207
|
+
tenantKey: origin.tenantKey
|
|
6208
|
+
},
|
|
6209
|
+
parentRunId: input.context.runId,
|
|
6210
|
+
prompt: input.prompt,
|
|
6211
|
+
sessionId: input.sessionId,
|
|
6212
|
+
sourceMessageId: requireSourceMessageId(input.context)
|
|
6213
|
+
});
|
|
6214
|
+
return toSummary(await options.repository.create(state));
|
|
6215
|
+
};
|
|
6216
|
+
const wait = async (input) => {
|
|
6217
|
+
const sessionId = sessionIdFromSessionKey(input.context.sessionKey);
|
|
6218
|
+
if (!sessionId) throw new BackgroundSessionCallerDenied("background.wait is only available inside a background session step");
|
|
6219
|
+
if (input.delayMs !== void 0 && input.until !== void 0) throw new BackgroundSessionCallerDenied("background.wait cannot combine delayMs and until");
|
|
6220
|
+
const now = options.clock.now();
|
|
6221
|
+
let wakeAt;
|
|
6222
|
+
if (input.delayMs !== void 0) {
|
|
6223
|
+
if (!Number.isSafeInteger(input.delayMs) || input.delayMs < 1) throw new BackgroundSessionCallerDenied("background.wait delayMs must be a positive integer");
|
|
6224
|
+
wakeAt = new Date(Date.parse(now) + input.delayMs).toISOString();
|
|
6225
|
+
} else if (input.until !== void 0) {
|
|
6226
|
+
const parsed = Date.parse(input.until);
|
|
6227
|
+
if (!Number.isFinite(parsed)) throw new BackgroundSessionCallerDenied("background.wait until must be an ISO 8601 timestamp");
|
|
6228
|
+
wakeAt = new Date(parsed).toISOString();
|
|
6229
|
+
}
|
|
6230
|
+
return toSummary(requireState(await serial.run(async () => {
|
|
6231
|
+
const state = await options.repository.get(sessionId);
|
|
6232
|
+
if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
|
|
6233
|
+
requireMatchingOrigin(state, input.context);
|
|
6234
|
+
const nextState = suspendBackgroundSession(state, {
|
|
6235
|
+
now,
|
|
6236
|
+
...input.reason === void 0 ? {} : { reason: input.reason },
|
|
6237
|
+
...wakeAt ? { wakeAt } : {}
|
|
6238
|
+
});
|
|
6239
|
+
return options.repository.update({
|
|
6240
|
+
build: () => nextState,
|
|
6241
|
+
expectedRevision: state.revision,
|
|
6242
|
+
sessionId
|
|
6243
|
+
});
|
|
6244
|
+
}), sessionId));
|
|
6245
|
+
};
|
|
6246
|
+
const list = async (input) => {
|
|
6247
|
+
return (await options.repository.list(input.phase === void 0 ? void 0 : { phase: input.phase })).filter((state) => matchesOrigin(state, input.context)).sort((left, right) => right.createdAt.localeCompare(left.createdAt)).slice(0, input.limit).map(toSummary);
|
|
6248
|
+
};
|
|
6249
|
+
const status = async (input) => {
|
|
6250
|
+
return toDetail(await requireOwnedState(input.sessionId, input.context));
|
|
6251
|
+
};
|
|
6252
|
+
const send = async (input) => {
|
|
6253
|
+
return toSummary(requireState(await serial.run(async () => {
|
|
6254
|
+
const state = await requireOwnedState(input.sessionId, input.context);
|
|
6255
|
+
const nextState = appendBackgroundSessionInput(state, {
|
|
6256
|
+
message: input.message,
|
|
6257
|
+
now: options.clock.now()
|
|
6258
|
+
});
|
|
6259
|
+
return options.repository.update({
|
|
6260
|
+
build: () => nextState,
|
|
6261
|
+
expectedRevision: state.revision,
|
|
6262
|
+
sessionId: input.sessionId
|
|
6263
|
+
});
|
|
6264
|
+
}), input.sessionId));
|
|
6265
|
+
};
|
|
6266
|
+
const stop = async (input) => {
|
|
6267
|
+
return toSummary(requireState(await serial.run(async () => {
|
|
6268
|
+
const state = await requireOwnedState(input.sessionId, input.context);
|
|
6269
|
+
const nextState = requestBackgroundSessionStop(state, {
|
|
6270
|
+
now: options.clock.now(),
|
|
6271
|
+
reason: input.reason ?? "stopped by conversation"
|
|
6272
|
+
});
|
|
6273
|
+
return updateAndEnqueueStopped(input.sessionId, state.revision, nextState);
|
|
6274
|
+
}), input.sessionId));
|
|
6275
|
+
};
|
|
6276
|
+
const requireOwnedState = async (sessionId, context) => {
|
|
6277
|
+
const state = await options.repository.get(sessionId);
|
|
6278
|
+
if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
|
|
6279
|
+
requireMatchingOrigin(state, context);
|
|
6280
|
+
return state;
|
|
6281
|
+
};
|
|
6282
|
+
const updateAndEnqueueStopped = async (sessionId, expectedRevision, nextState) => {
|
|
6283
|
+
const stored = await options.repository.update({
|
|
6284
|
+
build: () => nextState,
|
|
6285
|
+
expectedRevision,
|
|
6286
|
+
sessionId
|
|
6287
|
+
});
|
|
6288
|
+
if (stored?.phase === "stopped") await options.deliveries.enqueue({
|
|
6289
|
+
createdAt: options.clock.now(),
|
|
6290
|
+
deliveryId: terminalDeliveryId(sessionId, "stopped"),
|
|
6291
|
+
kind: "stopped",
|
|
6292
|
+
sessionId,
|
|
6293
|
+
text: stopNotice$1(stored)
|
|
6294
|
+
});
|
|
6295
|
+
return stored;
|
|
6296
|
+
};
|
|
6297
|
+
const resolveReconciliation = async (input) => {
|
|
6298
|
+
return toSummary(requireState(await serial.run(async () => {
|
|
6299
|
+
const state = await options.repository.get(input.sessionId);
|
|
6300
|
+
if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${input.sessionId}`);
|
|
6301
|
+
const nextState = resolveBackgroundSessionReconciliation(state, {
|
|
6302
|
+
now: options.clock.now(),
|
|
6303
|
+
outcome: input.outcome
|
|
6304
|
+
});
|
|
6305
|
+
return updateAndEnqueueStopped(input.sessionId, state.revision, nextState);
|
|
6306
|
+
}), input.sessionId));
|
|
6307
|
+
};
|
|
6308
|
+
return {
|
|
6309
|
+
list,
|
|
6310
|
+
resolveReconciliation,
|
|
6311
|
+
send,
|
|
6312
|
+
start,
|
|
6313
|
+
status,
|
|
6314
|
+
stop,
|
|
6315
|
+
wait
|
|
6316
|
+
};
|
|
6317
|
+
}
|
|
6318
|
+
function sessionIdFromSessionKey(sessionKey) {
|
|
6319
|
+
const prefix = `${createBackgroundSessionKey("")}`;
|
|
6320
|
+
return sessionKey.startsWith(prefix) ? sessionKey.slice(prefix.length) : void 0;
|
|
6321
|
+
}
|
|
6322
|
+
function terminalDeliveryId(sessionId, kind) {
|
|
6323
|
+
return `bg-final:${sessionId}:${kind}`;
|
|
6324
|
+
}
|
|
6325
|
+
function progressDeliveryId(sessionId, stepCount) {
|
|
6326
|
+
return `bg-progress:${sessionId}:${stepCount}`;
|
|
6327
|
+
}
|
|
6328
|
+
function requireOrigin(context) {
|
|
6329
|
+
if (!context.origin || !context.origin.endpointId || !context.origin.tenantKey) throw new BackgroundSessionCallerDenied("background tools require a trusted invocation origin; this run has none");
|
|
6330
|
+
return context.origin;
|
|
6331
|
+
}
|
|
6332
|
+
function requireSourceMessageId(context) {
|
|
6333
|
+
if (!context.sourceMessageId?.trim()) throw new BackgroundSessionCallerDenied("background.start requires a trusted source message id");
|
|
6334
|
+
return context.sourceMessageId;
|
|
6335
|
+
}
|
|
6336
|
+
function requireMatchingOrigin(state, context) {
|
|
6337
|
+
if (!matchesOrigin(state, context)) throw new BackgroundSessionCallerDenied("background session is not owned by this conversation");
|
|
6338
|
+
}
|
|
6339
|
+
function matchesOrigin(state, context) {
|
|
6340
|
+
const origin = context.origin;
|
|
6341
|
+
if (!origin) return false;
|
|
6342
|
+
if (context.agentId !== state.authority.agentId) return false;
|
|
6343
|
+
if (state.origin.endpointId !== origin.endpointId) return false;
|
|
6344
|
+
if (state.origin.tenantKey !== origin.tenantKey) return false;
|
|
6345
|
+
if ((state.origin.conversationId ?? void 0) !== (origin.conversationId ?? void 0)) return false;
|
|
6346
|
+
return true;
|
|
6347
|
+
}
|
|
6348
|
+
function requireState(state, sessionId) {
|
|
6349
|
+
if (!state) throw new BackgroundSessionCallerDenied(`background session not found: ${sessionId}`);
|
|
6350
|
+
return state;
|
|
6351
|
+
}
|
|
6352
|
+
function toSummary(state) {
|
|
6353
|
+
return {
|
|
6354
|
+
createdAt: state.createdAt,
|
|
6355
|
+
displayName: state.displayName,
|
|
6356
|
+
phase: state.phase,
|
|
6357
|
+
sessionId: state.sessionId,
|
|
6358
|
+
stepCount: state.stepCount,
|
|
6359
|
+
updatedAt: state.updatedAt,
|
|
6360
|
+
...state.wakeAt ? { wakeAt: state.wakeAt } : {}
|
|
6361
|
+
};
|
|
6362
|
+
}
|
|
6363
|
+
function toDetail(state) {
|
|
6364
|
+
return {
|
|
6365
|
+
...toSummary(state),
|
|
6366
|
+
...state.cancellation ? { cancellationReason: state.cancellation.reason } : {},
|
|
6367
|
+
...state.errorMessage ? { errorMessage: state.errorMessage } : {},
|
|
6368
|
+
parentRunId: state.parentRunId,
|
|
6369
|
+
pendingInput: state.pendingInput,
|
|
6370
|
+
...state.reconciliationNote ? { reconciliationNote: state.reconciliationNote } : {},
|
|
6371
|
+
...state.result ? { result: state.result } : {}
|
|
6372
|
+
};
|
|
6373
|
+
}
|
|
6374
|
+
function stopNotice$1(state) {
|
|
6375
|
+
return `后台会话「${state.displayName}」已停止。${state.cancellation ? `原因:${state.cancellation.reason}` : ""}`.trim();
|
|
6376
|
+
}
|
|
6377
|
+
//#endregion
|
|
6378
|
+
//#region src/application/background-session/background-session-repository.ts
|
|
6379
|
+
var BackgroundSessionRepositoryConflict = class extends Error {
|
|
6380
|
+
name = "BackgroundSessionRepositoryConflict";
|
|
6381
|
+
constructor(sessionId, message) {
|
|
6382
|
+
super(`${sessionId}: ${message}`);
|
|
6383
|
+
}
|
|
6384
|
+
};
|
|
6385
|
+
var BackgroundSessionRepositoryCorrupted = class extends Error {
|
|
6386
|
+
name = "BackgroundSessionRepositoryCorrupted";
|
|
6387
|
+
};
|
|
6388
|
+
function createBackgroundSessionRepository(options = {}) {
|
|
6389
|
+
const serial = createSerialExecutor();
|
|
6390
|
+
const states = /* @__PURE__ */ new Map();
|
|
6391
|
+
for (const state of options.initial ?? []) states.set(state.sessionId, structuredClone(state));
|
|
6392
|
+
const persist = async (state) => {
|
|
6393
|
+
if (options.persist) await options.persist(state);
|
|
6394
|
+
};
|
|
6395
|
+
return {
|
|
6396
|
+
create: (state) => serial.run(async () => {
|
|
6397
|
+
if (states.has(state.sessionId)) throw new BackgroundSessionRepositoryConflict(state.sessionId, "duplicate background session id");
|
|
6398
|
+
states.set(state.sessionId, structuredClone(state));
|
|
6399
|
+
await persist(state);
|
|
6400
|
+
return structuredClone(state);
|
|
6401
|
+
}),
|
|
6402
|
+
get: (sessionId) => serial.run(async () => {
|
|
6403
|
+
const state = states.get(sessionId);
|
|
6404
|
+
return state ? structuredClone(state) : void 0;
|
|
6405
|
+
}),
|
|
6406
|
+
list: (options) => serial.run(async () => {
|
|
6407
|
+
let results = [...states.values()];
|
|
6408
|
+
if (options?.phase !== void 0) results = results.filter((state) => state.phase === options.phase);
|
|
6409
|
+
results.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
6410
|
+
if (options?.limit !== void 0) results = results.slice(0, options.limit);
|
|
6411
|
+
return results.map((state) => structuredClone(state));
|
|
6412
|
+
}),
|
|
6413
|
+
update: (input) => serial.run(async () => {
|
|
6414
|
+
const previous = states.get(input.sessionId);
|
|
6415
|
+
if (!previous) return void 0;
|
|
6416
|
+
if (previous.revision !== input.expectedRevision) throw new BackgroundSessionRepositoryConflict(input.sessionId, `revision conflict: expected ${input.expectedRevision}, found ${previous.revision}`);
|
|
6417
|
+
if (input.expectedLease !== void 0) {
|
|
6418
|
+
const leaseMismatch = leaseConflict(input.sessionId, previous.lease, input.expectedLease, input.now);
|
|
6419
|
+
if (leaseMismatch) throw new BackgroundSessionRepositoryConflict(input.sessionId, leaseMismatch);
|
|
6420
|
+
}
|
|
6421
|
+
const nextState = input.build(previous);
|
|
6422
|
+
const invalid = isValidBackgroundSessionTransition(previous, nextState);
|
|
6423
|
+
if (invalid) throw new BackgroundSessionRepositoryConflict(input.sessionId, `invalid transition: ${invalid}`);
|
|
6424
|
+
states.set(input.sessionId, structuredClone(nextState));
|
|
6425
|
+
await persist(nextState);
|
|
6426
|
+
return structuredClone(nextState);
|
|
6427
|
+
})
|
|
6428
|
+
};
|
|
6429
|
+
}
|
|
6430
|
+
function leaseConflict(_sessionId, lease, expected, now) {
|
|
6431
|
+
if (expected.kind === "absent") {
|
|
6432
|
+
if (lease !== void 0) return `lease conflict: expected no lease, found owner ${lease.owner} epoch ${lease.epoch}`;
|
|
6433
|
+
return;
|
|
6434
|
+
}
|
|
6435
|
+
if (expected.kind === "absent-or-stale") {
|
|
6436
|
+
if (lease !== void 0 && !isExpired(lease, now)) return `lease conflict: expected absent or stale lease, found live lease owner ${lease.owner} epoch ${lease.epoch}`;
|
|
6437
|
+
return;
|
|
6438
|
+
}
|
|
6439
|
+
if (lease === void 0 || lease.owner !== expected.owner || lease.epoch !== expected.epoch) return `lease conflict: expected owner ${expected.owner} epoch ${expected.epoch}`;
|
|
6440
|
+
}
|
|
6441
|
+
function isExpired(lease, now) {
|
|
6442
|
+
return lease !== void 0 && lease.expiresAt <= (now ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
6443
|
+
}
|
|
6444
|
+
//#endregion
|
|
6445
|
+
//#region src/application/background-session/background-session-delivery-store.ts
|
|
6446
|
+
var BackgroundSessionDeliveryConflict = class extends Error {
|
|
6447
|
+
name = "BackgroundSessionDeliveryConflict";
|
|
6448
|
+
constructor(deliveryId, message) {
|
|
6449
|
+
super(`${deliveryId}: ${message}`);
|
|
6450
|
+
}
|
|
6451
|
+
};
|
|
6452
|
+
function createBackgroundSessionDeliveryStore(options = {}) {
|
|
6453
|
+
const serial = createSerialExecutor();
|
|
6454
|
+
const records = /* @__PURE__ */ new Map();
|
|
6455
|
+
for (const record of options.initial ?? []) records.set(record.deliveryId, structuredClone(record));
|
|
6456
|
+
const persist = async (record) => {
|
|
6457
|
+
if (options.persist) await options.persist(record);
|
|
6458
|
+
};
|
|
6459
|
+
return {
|
|
6460
|
+
enqueue: (input) => serial.run(async () => {
|
|
6461
|
+
const existing = records.get(input.deliveryId);
|
|
6462
|
+
if (existing) return structuredClone(existing);
|
|
6463
|
+
const record = {
|
|
6464
|
+
createdAt: input.createdAt,
|
|
6465
|
+
deliveryId: input.deliveryId,
|
|
6466
|
+
kind: input.kind,
|
|
6467
|
+
revision: 1,
|
|
6468
|
+
sessionId: input.sessionId,
|
|
6469
|
+
state: "pending",
|
|
6470
|
+
text: input.text
|
|
6471
|
+
};
|
|
6472
|
+
records.set(input.deliveryId, structuredClone(record));
|
|
6473
|
+
await persist(record);
|
|
6474
|
+
return structuredClone(record);
|
|
6475
|
+
}),
|
|
6476
|
+
pending: () => serial.run(async () => {
|
|
6477
|
+
return [...records.values()].filter((record) => record.state === "pending").sort((left, right) => left.createdAt.localeCompare(right.createdAt)).map((record) => structuredClone(record));
|
|
6478
|
+
}),
|
|
6479
|
+
markDelivered: (deliveryId, providerMessageId) => serial.run(async () => {
|
|
6480
|
+
const previous = records.get(deliveryId);
|
|
6481
|
+
if (!previous) return void 0;
|
|
6482
|
+
if (previous.state !== "pending") return structuredClone(previous);
|
|
6483
|
+
const nextState = {
|
|
6484
|
+
...previous,
|
|
6485
|
+
providerMessageId,
|
|
6486
|
+
revision: previous.revision + 1,
|
|
6487
|
+
state: "delivered"
|
|
6488
|
+
};
|
|
6489
|
+
records.set(deliveryId, structuredClone(nextState));
|
|
6490
|
+
await persist(nextState);
|
|
6491
|
+
return structuredClone(nextState);
|
|
6492
|
+
})
|
|
6493
|
+
};
|
|
6494
|
+
}
|
|
6495
|
+
//#endregion
|
|
6496
|
+
//#region src/application/background-session/background-session-supervisor.ts
|
|
6497
|
+
function createBackgroundSessionSupervisor(options) {
|
|
6498
|
+
const owner = `supervisor:${randomUUID()}`;
|
|
6499
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
6500
|
+
const stepControllers = /* @__PURE__ */ new Map();
|
|
6501
|
+
const counters = {
|
|
6502
|
+
completed: 0,
|
|
6503
|
+
deliveriesDelivered: 0,
|
|
6504
|
+
failed: 0,
|
|
6505
|
+
started: 0,
|
|
6506
|
+
staleLeaseEventsDropped: 0,
|
|
6507
|
+
stopped: 0
|
|
6508
|
+
};
|
|
6509
|
+
let repositoryHealthy = true;
|
|
6510
|
+
let snapshot = emptyPhaseCounts();
|
|
6511
|
+
let oldestDueAt;
|
|
6512
|
+
const requeuedInPass = /* @__PURE__ */ new Set();
|
|
6513
|
+
const refreshSnapshot = async () => {
|
|
6514
|
+
const counts = {
|
|
6515
|
+
completed: 0,
|
|
6516
|
+
failed: 0,
|
|
6517
|
+
"input-required": 0,
|
|
6518
|
+
queued: 0,
|
|
6519
|
+
"reconciliation-required": 0,
|
|
6520
|
+
running: 0,
|
|
6521
|
+
stopped: 0,
|
|
6522
|
+
stopping: 0,
|
|
6523
|
+
waiting: 0
|
|
6524
|
+
};
|
|
6525
|
+
let oldest;
|
|
6526
|
+
const now = options.clock.now();
|
|
6527
|
+
for (const session of await options.repository.list()) {
|
|
6528
|
+
counts[session.phase] += 1;
|
|
6529
|
+
if (isBackgroundSessionDue(session, now)) {
|
|
6530
|
+
const dueAt = session.wakeAt ?? session.createdAt;
|
|
6531
|
+
if (oldest === void 0 || dueAt < oldest) oldest = dueAt;
|
|
6532
|
+
}
|
|
6533
|
+
}
|
|
6534
|
+
snapshot = Object.freeze(counts);
|
|
6535
|
+
oldestDueAt = oldest;
|
|
6536
|
+
};
|
|
6537
|
+
const supervisionPass = async () => {
|
|
6538
|
+
try {
|
|
6539
|
+
await resolveStaleLeases();
|
|
6540
|
+
await expireLifetime();
|
|
6541
|
+
await stopStoppingSessions();
|
|
6542
|
+
await drainDeliveries();
|
|
6543
|
+
await startDueSessions();
|
|
6544
|
+
await refreshSnapshot();
|
|
6545
|
+
repositoryHealthy = true;
|
|
6546
|
+
} catch (error) {
|
|
6547
|
+
repositoryHealthy = false;
|
|
6548
|
+
throw error;
|
|
6549
|
+
}
|
|
6550
|
+
};
|
|
6551
|
+
const resolveStaleLeases = async () => {
|
|
6552
|
+
const now = options.clock.now();
|
|
6553
|
+
for (const session of await options.repository.list()) {
|
|
6554
|
+
if (session.lease === void 0 || inFlight.has(session.sessionId)) continue;
|
|
6555
|
+
if (!(session.lease.owner !== owner || isBackgroundSessionLeaseExpired(session, now))) continue;
|
|
6556
|
+
counters.staleLeaseEventsDropped += 1;
|
|
6557
|
+
const expectedLease = {
|
|
6558
|
+
kind: "held",
|
|
6559
|
+
owner: session.lease.owner,
|
|
6560
|
+
epoch: session.lease.epoch
|
|
6561
|
+
};
|
|
6562
|
+
if (session.phase === "running") {
|
|
6563
|
+
await options.repository.update({
|
|
6564
|
+
build: (state) => requeueInterruptedBackgroundSessionStep(state, {
|
|
6565
|
+
now,
|
|
6566
|
+
wakeText: interruptedStepWakeText(state)
|
|
6567
|
+
}),
|
|
6568
|
+
expectedLease,
|
|
6569
|
+
expectedRevision: session.revision,
|
|
6570
|
+
sessionId: session.sessionId
|
|
6571
|
+
});
|
|
6572
|
+
requeuedInPass.add(session.sessionId);
|
|
6573
|
+
continue;
|
|
6574
|
+
}
|
|
6575
|
+
if (session.phase === "stopping") {
|
|
6576
|
+
await completeStop(session, expectedLease);
|
|
6577
|
+
continue;
|
|
6578
|
+
}
|
|
6579
|
+
await options.repository.update({
|
|
6580
|
+
build: (state) => releaseBackgroundSessionLease(state),
|
|
6581
|
+
expectedLease,
|
|
6582
|
+
expectedRevision: session.revision,
|
|
6583
|
+
sessionId: session.sessionId
|
|
6584
|
+
});
|
|
6585
|
+
}
|
|
6586
|
+
};
|
|
6587
|
+
const expireLifetime = async () => {
|
|
6588
|
+
const now = options.clock.now();
|
|
6589
|
+
const cutoff = Date.parse(now) - options.config.sessionLifetimeMs;
|
|
6590
|
+
for (const session of await options.repository.list()) {
|
|
6591
|
+
if (isBackgroundSessionTerminalPhase(session.phase)) continue;
|
|
6592
|
+
if (Date.parse(session.createdAt) > cutoff) continue;
|
|
6593
|
+
const nextState = requestBackgroundSessionStop(session, {
|
|
6594
|
+
now,
|
|
6595
|
+
reason: "background session lifetime exceeded"
|
|
6596
|
+
});
|
|
6597
|
+
const stored = await options.repository.update({
|
|
6598
|
+
build: () => nextState,
|
|
6599
|
+
expectedRevision: session.revision,
|
|
6600
|
+
sessionId: session.sessionId
|
|
6601
|
+
});
|
|
6602
|
+
if (stored?.phase === "stopped") {
|
|
6603
|
+
counters.stopped += 1;
|
|
6604
|
+
await enqueueDelivery(stored.sessionId, "stopped", stopNotice(stored), stored);
|
|
6605
|
+
} else stepControllers.get(session.sessionId)?.abort(/* @__PURE__ */ new Error("background session lifetime exceeded"));
|
|
6606
|
+
}
|
|
6607
|
+
};
|
|
6608
|
+
const stopStoppingSessions = async () => {
|
|
6609
|
+
for (const session of await options.repository.list()) {
|
|
6610
|
+
if (session.phase !== "stopping") continue;
|
|
6611
|
+
if (inFlight.has(session.sessionId)) {
|
|
6612
|
+
stepControllers.get(session.sessionId)?.abort(/* @__PURE__ */ new Error("background session stop requested"));
|
|
6613
|
+
continue;
|
|
6614
|
+
}
|
|
6615
|
+
const expectedLease = session.lease ? {
|
|
6616
|
+
kind: "held",
|
|
6617
|
+
owner: session.lease.owner,
|
|
6618
|
+
epoch: session.lease.epoch
|
|
6619
|
+
} : { kind: "absent" };
|
|
6620
|
+
await completeStop(session, expectedLease);
|
|
6621
|
+
}
|
|
6622
|
+
};
|
|
6623
|
+
const completeStop = async (state, expectedLease) => {
|
|
6624
|
+
const stopped = await options.repository.update({
|
|
6625
|
+
build: (current) => completeBackgroundSessionStop(current, { now: options.clock.now() }),
|
|
6626
|
+
expectedLease,
|
|
6627
|
+
expectedRevision: state.revision,
|
|
6628
|
+
sessionId: state.sessionId
|
|
6629
|
+
});
|
|
6630
|
+
if (stopped) {
|
|
6631
|
+
counters.stopped += 1;
|
|
6632
|
+
await enqueueDelivery(stopped.sessionId, "stopped", stopNotice(stopped), stopped);
|
|
6633
|
+
}
|
|
6634
|
+
};
|
|
6635
|
+
const startDueSessions = async () => {
|
|
6636
|
+
const now = options.clock.now();
|
|
6637
|
+
for (const session of await options.repository.list()) {
|
|
6638
|
+
if (inFlight.size >= options.config.maxConcurrentSessions) break;
|
|
6639
|
+
if (inFlight.has(session.sessionId)) continue;
|
|
6640
|
+
if (!isBackgroundSessionDue(session, now)) continue;
|
|
6641
|
+
if (session.lease !== void 0 && !isBackgroundSessionLeaseExpired(session, now)) continue;
|
|
6642
|
+
const step = runStep(session).finally(() => {
|
|
6643
|
+
inFlight.delete(session.sessionId);
|
|
6644
|
+
});
|
|
6645
|
+
inFlight.set(session.sessionId, step);
|
|
6646
|
+
step.catch((error) => {
|
|
6647
|
+
options.onError?.(error);
|
|
6648
|
+
});
|
|
6649
|
+
}
|
|
6650
|
+
};
|
|
6651
|
+
const runStep = async (session) => {
|
|
6652
|
+
const now = options.clock.now();
|
|
6653
|
+
const stepCount = session.stepCount + 1;
|
|
6654
|
+
const wakeText = requeuedInPass.has(session.sessionId) ? interruptedStepWakeText(session) : buildWakeText(session);
|
|
6655
|
+
requeuedInPass.delete(session.sessionId);
|
|
6656
|
+
const lease = {
|
|
6657
|
+
epoch: (session.lease?.epoch ?? 0) + 1,
|
|
6658
|
+
expiresAt: toIso(Date.parse(now) + options.config.leaseMs),
|
|
6659
|
+
owner
|
|
6660
|
+
};
|
|
6661
|
+
const claimed = await options.repository.update({
|
|
6662
|
+
build: (state) => claimBackgroundSession(state, {
|
|
6663
|
+
lease,
|
|
6664
|
+
now,
|
|
6665
|
+
stepRunId: `bg-step:${session.sessionId}:${stepCount}`,
|
|
6666
|
+
wakeText
|
|
6667
|
+
}),
|
|
6668
|
+
expectedLease: { kind: "absent-or-stale" },
|
|
6669
|
+
expectedRevision: session.revision,
|
|
6670
|
+
now,
|
|
6671
|
+
sessionId: session.sessionId
|
|
6672
|
+
});
|
|
6673
|
+
if (!claimed) return;
|
|
6674
|
+
counters.started += 1;
|
|
6675
|
+
const controller = new AbortController();
|
|
6676
|
+
stepControllers.set(session.sessionId, controller);
|
|
6677
|
+
let outcome;
|
|
6678
|
+
try {
|
|
6679
|
+
let settled = false;
|
|
6680
|
+
const renewal = (async () => {
|
|
6681
|
+
while (!settled) {
|
|
6682
|
+
await Effect.runPromise(options.sleep(options.config.leaseRenewalIntervalMs)).catch(() => void 0);
|
|
6683
|
+
if (settled) break;
|
|
6684
|
+
try {
|
|
6685
|
+
const current = await options.repository.get(session.sessionId);
|
|
6686
|
+
if (!current || current.lease === void 0 || current.lease.owner !== owner || current.lease.epoch !== lease.epoch) {
|
|
6687
|
+
controller.abort(/* @__PURE__ */ new Error("background session lease was lost"));
|
|
6688
|
+
break;
|
|
6689
|
+
}
|
|
6690
|
+
if (!await options.repository.update({
|
|
6691
|
+
build: (state) => renewBackgroundSessionLease(state, {
|
|
6692
|
+
expiresAt: toIso(Date.parse(options.clock.now()) + options.config.leaseMs),
|
|
6693
|
+
epoch: lease.epoch,
|
|
6694
|
+
owner
|
|
6695
|
+
}),
|
|
6696
|
+
expectedLease: {
|
|
6697
|
+
kind: "held",
|
|
6698
|
+
owner,
|
|
6699
|
+
epoch: lease.epoch
|
|
6700
|
+
},
|
|
6701
|
+
expectedRevision: current.revision,
|
|
6702
|
+
sessionId: session.sessionId
|
|
6703
|
+
})) controller.abort(/* @__PURE__ */ new Error("background session lease renewal was denied"));
|
|
6704
|
+
} catch {
|
|
6705
|
+
controller.abort(/* @__PURE__ */ new Error("background session lease renewal failed"));
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
})();
|
|
6709
|
+
outcome = await options.runStep({
|
|
6710
|
+
session: claimed,
|
|
6711
|
+
signal: controller.signal,
|
|
6712
|
+
wakeText
|
|
6713
|
+
}).then((result) => ({
|
|
6714
|
+
ok: true,
|
|
6715
|
+
result
|
|
6716
|
+
}), (error) => ({
|
|
6717
|
+
error,
|
|
6718
|
+
ok: false
|
|
6719
|
+
}));
|
|
6720
|
+
settled = true;
|
|
6721
|
+
renewal.catch(() => void 0);
|
|
6722
|
+
} finally {
|
|
6723
|
+
stepControllers.delete(session.sessionId);
|
|
6724
|
+
}
|
|
6725
|
+
try {
|
|
6726
|
+
await commitStep(claimed, outcome, lease);
|
|
6727
|
+
} catch (error) {
|
|
6728
|
+
options.onError?.(error);
|
|
6729
|
+
await requeueAfterFailedCommit(session.sessionId, lease, error);
|
|
6730
|
+
}
|
|
6731
|
+
};
|
|
6732
|
+
const requeueAfterFailedCommit = async (sessionId, lease, error) => {
|
|
6733
|
+
try {
|
|
6734
|
+
const current = await options.repository.get(sessionId);
|
|
6735
|
+
if (!current || current.phase !== "running") return;
|
|
6736
|
+
await options.repository.update({
|
|
6737
|
+
build: (state) => requeueInterruptedBackgroundSessionStep(state, {
|
|
6738
|
+
now: options.clock.now(),
|
|
6739
|
+
wakeText: `步骤结果提交失败(${errorMessageOf(error)}),将重试`
|
|
6740
|
+
}),
|
|
6741
|
+
expectedLease: {
|
|
6742
|
+
kind: "held",
|
|
6743
|
+
owner: lease.owner,
|
|
6744
|
+
epoch: lease.epoch
|
|
6745
|
+
},
|
|
6746
|
+
expectedRevision: current.revision,
|
|
6747
|
+
sessionId
|
|
6748
|
+
});
|
|
6749
|
+
} catch (commitError) {
|
|
6750
|
+
options.onError?.(commitError);
|
|
6751
|
+
}
|
|
6752
|
+
};
|
|
6753
|
+
const commitStep = async (claimed, outcome, lease) => {
|
|
6754
|
+
const now = options.clock.now();
|
|
6755
|
+
const current = await options.repository.get(claimed.sessionId);
|
|
6756
|
+
if (!current) return;
|
|
6757
|
+
const expectedLease = {
|
|
6758
|
+
kind: "held",
|
|
6759
|
+
owner: lease.owner,
|
|
6760
|
+
epoch: lease.epoch
|
|
6761
|
+
};
|
|
6762
|
+
if (current.phase === "waiting" || current.phase === "input-required") {
|
|
6763
|
+
const text = outcome.ok ? outcome.result.finalText : "";
|
|
6764
|
+
await options.repository.update({
|
|
6765
|
+
build: (state) => releaseBackgroundSessionLease(state),
|
|
6766
|
+
expectedLease,
|
|
6767
|
+
expectedRevision: current.revision,
|
|
6768
|
+
sessionId: current.sessionId
|
|
6769
|
+
});
|
|
6770
|
+
if (text.trim() !== "") await enqueueDelivery(current.sessionId, "progress", text, current);
|
|
6771
|
+
return;
|
|
6772
|
+
}
|
|
6773
|
+
if (current.phase === "stopping") {
|
|
6774
|
+
await completeStop(current, expectedLease);
|
|
6775
|
+
return;
|
|
6776
|
+
}
|
|
6777
|
+
if (current.phase !== "running") return;
|
|
6778
|
+
if (outcome.ok) {
|
|
6779
|
+
const completed = await options.repository.update({
|
|
6780
|
+
build: (state) => completeBackgroundSessionStep(state, {
|
|
6781
|
+
now,
|
|
6782
|
+
stepRunId: outcome.result.runId,
|
|
6783
|
+
text: outcome.result.finalText
|
|
6784
|
+
}),
|
|
6785
|
+
expectedLease,
|
|
6786
|
+
expectedRevision: current.revision,
|
|
6787
|
+
sessionId: current.sessionId
|
|
6788
|
+
});
|
|
6789
|
+
if (completed) {
|
|
6790
|
+
counters.completed += 1;
|
|
6791
|
+
await enqueueDelivery(completed.sessionId, "final", completed.result?.text ?? "", completed);
|
|
6792
|
+
}
|
|
6793
|
+
return;
|
|
6794
|
+
}
|
|
6795
|
+
const errorMessage = errorMessageOf(outcome.error);
|
|
6796
|
+
if (isFencedOperationError(outcome.error)) {
|
|
6797
|
+
const parked = await options.repository.update({
|
|
6798
|
+
build: (state) => parkBackgroundSessionForReconciliation(state, {
|
|
6799
|
+
now,
|
|
6800
|
+
reason: `step failed on a fenced operation: ${errorMessage}`
|
|
6801
|
+
}),
|
|
6802
|
+
expectedLease,
|
|
6803
|
+
expectedRevision: current.revision,
|
|
6804
|
+
sessionId: current.sessionId
|
|
6805
|
+
});
|
|
6806
|
+
if (parked) await enqueueDelivery(parked.sessionId, "reconciliation", `后台会话「${parked.displayName}」需要人工对账:${parked.reconciliationNote ?? errorMessage}`, parked);
|
|
6807
|
+
return;
|
|
6808
|
+
}
|
|
6809
|
+
if (isRetryableStepError(outcome.error) && current.consecutiveFailures + 1 < options.config.maxConsecutiveFailures) {
|
|
6810
|
+
const backoff = options.config.retryBackoffMs * 2 ** current.consecutiveFailures;
|
|
6811
|
+
await options.repository.update({
|
|
6812
|
+
build: (state) => failBackgroundSessionStep(state, {
|
|
6813
|
+
errorMessage,
|
|
6814
|
+
now,
|
|
6815
|
+
retryable: true,
|
|
6816
|
+
wakeAt: toIso(Date.parse(now) + backoff)
|
|
6817
|
+
}),
|
|
6818
|
+
expectedLease,
|
|
6819
|
+
expectedRevision: current.revision,
|
|
6820
|
+
sessionId: current.sessionId
|
|
6821
|
+
});
|
|
6822
|
+
return;
|
|
6823
|
+
}
|
|
6824
|
+
const failed = await options.repository.update({
|
|
6825
|
+
build: (state) => failBackgroundSessionStep(state, {
|
|
6826
|
+
errorMessage,
|
|
6827
|
+
now,
|
|
6828
|
+
retryable: false
|
|
6829
|
+
}),
|
|
6830
|
+
expectedLease,
|
|
6831
|
+
expectedRevision: current.revision,
|
|
6832
|
+
sessionId: current.sessionId
|
|
6833
|
+
});
|
|
6834
|
+
if (failed) {
|
|
6835
|
+
counters.failed += 1;
|
|
6836
|
+
await enqueueDelivery(failed.sessionId, "failed", `后台会话「${failed.displayName}」失败:${failed.errorMessage ?? errorMessage}`, failed);
|
|
6837
|
+
}
|
|
6838
|
+
};
|
|
6839
|
+
const drainDeliveries = async () => {
|
|
6840
|
+
const pending = await options.deliveries.pending();
|
|
6841
|
+
for (const record of pending) try {
|
|
6842
|
+
const { providerMessageId } = await options.deliver(record);
|
|
6843
|
+
if (await options.deliveries.markDelivered(record.deliveryId, providerMessageId)) counters.deliveriesDelivered += 1;
|
|
6844
|
+
} catch (error) {
|
|
6845
|
+
options.onError?.(error);
|
|
6846
|
+
}
|
|
6847
|
+
};
|
|
6848
|
+
const enqueueDelivery = async (sessionId, kind, text, session) => {
|
|
6849
|
+
if (text.trim() === "") return;
|
|
6850
|
+
const deliveryId = kind === "progress" ? progressDeliveryId(sessionId, session.stepCount) : terminalDeliveryId(sessionId, kind);
|
|
6851
|
+
await options.deliveries.enqueue({
|
|
6852
|
+
createdAt: options.clock.now(),
|
|
6853
|
+
deliveryId,
|
|
6854
|
+
kind,
|
|
6855
|
+
sessionId,
|
|
6856
|
+
text
|
|
6857
|
+
});
|
|
6858
|
+
};
|
|
6859
|
+
const buildWakeText = (session) => {
|
|
6860
|
+
if (session.wakeCount === 0) return `你在一个后台会话中开始工作。任务:\n${session.prompt}\n\n继续工作直到给出最终结果;如果必须等待外部变化或用户输入,调用 background.wait。`;
|
|
6861
|
+
const parts = [`后台会话第 ${session.stepCount + 1} 步。任务:\n${session.prompt}`, "继续先前的工作(历史在会话记录中)。"];
|
|
6862
|
+
if (session.pendingInput.length > 0) parts.push(`用户新的输入:\n${session.pendingInput.join("\n---\n")}`);
|
|
6863
|
+
parts.push("完成后给出最终结果;需要等待时调用 background.wait。");
|
|
6864
|
+
return parts.join("\n\n");
|
|
6865
|
+
};
|
|
6866
|
+
const interruptedStepWakeText = (session) => `你的上一步在后台会话中被守护进程重启打断,未产生确定的工具副作用(副作用由工具账本保护)。继续先前的工作(历史在会话记录中)。` + (session.pendingInput.length > 0 ? `\n\n用户新的输入:\n${session.pendingInput.join("\n---\n")}` : "");
|
|
6867
|
+
const tick = () => Effect.promise(() => supervisionPass());
|
|
6868
|
+
const loop = createPeriodicEffectLoop({
|
|
6869
|
+
intervalMs: options.config.intervalMs,
|
|
6870
|
+
onError: (error) => {
|
|
6871
|
+
options.onError?.(error);
|
|
6872
|
+
},
|
|
6873
|
+
run: tick,
|
|
6874
|
+
sleep: options.sleep
|
|
6875
|
+
});
|
|
6876
|
+
const statusOf = () => Object.freeze({
|
|
6877
|
+
capacity: options.config.maxConcurrentSessions,
|
|
6878
|
+
counters: Object.freeze({ ...counters }),
|
|
6879
|
+
inFlightSteps: inFlight.size,
|
|
6880
|
+
...oldestDueAt === void 0 ? {} : { oldestDueAt },
|
|
6881
|
+
owner,
|
|
6882
|
+
phaseCounts: snapshot,
|
|
6883
|
+
reconciliationCount: snapshot["reconciliation-required"],
|
|
6884
|
+
repositoryHealthy,
|
|
6885
|
+
running: loop.running()
|
|
6886
|
+
});
|
|
6887
|
+
return {
|
|
6888
|
+
recover: tick,
|
|
6889
|
+
start: () => Effect.sync(() => loop.start()),
|
|
6890
|
+
status: statusOf,
|
|
6891
|
+
stop: () => Effect.promise(async () => {
|
|
6892
|
+
for (const controller of stepControllers.values()) controller.abort(/* @__PURE__ */ new Error("background session supervisor is stopping"));
|
|
6893
|
+
await Promise.allSettled(inFlight.values());
|
|
6894
|
+
await loop.stop();
|
|
6895
|
+
}),
|
|
6896
|
+
tick
|
|
6897
|
+
};
|
|
6898
|
+
}
|
|
6899
|
+
function isFencedOperationError(error) {
|
|
6900
|
+
const message = errorMessageOf(error).toLowerCase();
|
|
6901
|
+
return message.includes("fenced") || message.includes("reconciliation-required");
|
|
6902
|
+
}
|
|
6903
|
+
function isRetryableStepError(error) {
|
|
6904
|
+
if (!(error instanceof Error)) return false;
|
|
6905
|
+
if (error.name === "AgentRunCancelled") return true;
|
|
6906
|
+
if (error.name === "AgentHarnessBusy" || error.name === "AgentInstanceBusy") return true;
|
|
6907
|
+
return false;
|
|
6908
|
+
}
|
|
6909
|
+
function errorMessageOf(error) {
|
|
6910
|
+
return error instanceof Error ? error.message : String(error);
|
|
6911
|
+
}
|
|
6912
|
+
function toIso(timeMs) {
|
|
6913
|
+
return new Date(timeMs).toISOString();
|
|
6914
|
+
}
|
|
6915
|
+
function stopNotice(session) {
|
|
6916
|
+
return `后台会话「${session.displayName}」已停止。${session.cancellation ? `原因:${session.cancellation.reason}` : ""}`.trim();
|
|
6917
|
+
}
|
|
6918
|
+
function emptyPhaseCounts() {
|
|
6919
|
+
return Object.freeze({
|
|
6920
|
+
completed: 0,
|
|
6921
|
+
failed: 0,
|
|
6922
|
+
"input-required": 0,
|
|
6923
|
+
queued: 0,
|
|
6924
|
+
"reconciliation-required": 0,
|
|
6925
|
+
running: 0,
|
|
6926
|
+
stopped: 0,
|
|
6927
|
+
stopping: 0,
|
|
6928
|
+
waiting: 0
|
|
6929
|
+
});
|
|
6930
|
+
}
|
|
6931
|
+
//#endregion
|
|
6932
|
+
//#region src/application/background-session/background-session-tools.ts
|
|
6933
|
+
function createBackgroundSessionHostTools(options) {
|
|
6934
|
+
const contracts = createBackgroundSessionToolContracts();
|
|
6935
|
+
const executors = new Map(contracts.map((contract) => [contract.id, createExecutor(contract, options)]));
|
|
6936
|
+
return contracts.map((contract) => Object.freeze({
|
|
6937
|
+
...contract,
|
|
6938
|
+
createExecutor: () => ({ execute: (input, context) => asToolInputRejection$1(() => executors.get(contract.id)(input, context)) }),
|
|
6939
|
+
replayCompleted: (input, completedResult, context) => asToolInputRejection$1(() => replayCompleted(contract.id, input, completedResult, context, options))
|
|
6940
|
+
}));
|
|
6941
|
+
}
|
|
6942
|
+
function createExecutor(contract, options) {
|
|
6943
|
+
switch (contract.id) {
|
|
6944
|
+
case "background.start": return (input, context) => {
|
|
6945
|
+
const { prompt, displayName } = readObject(input, ["prompt", "displayName"]);
|
|
6946
|
+
if (typeof prompt !== "string" || prompt.trim() === "") throw new BackgroundSessionCallerDenied("background.start requires a non-empty prompt");
|
|
6947
|
+
if (displayName !== void 0 && typeof displayName !== "string") throw new BackgroundSessionCallerDenied("background.start displayName must be a string");
|
|
6948
|
+
const sessionId = options.createSessionId();
|
|
6949
|
+
return options.service.start({
|
|
6950
|
+
authority: buildAuthority(options.definition, context, sessionId),
|
|
6951
|
+
context,
|
|
6952
|
+
sessionId,
|
|
6953
|
+
...displayName === void 0 ? {} : { displayName },
|
|
6954
|
+
prompt
|
|
6955
|
+
});
|
|
6956
|
+
};
|
|
6957
|
+
case "background.wait": return (input, context) => {
|
|
6958
|
+
const { delayMs, reason, until } = readObject(input, [
|
|
6959
|
+
"delayMs",
|
|
6960
|
+
"reason",
|
|
6961
|
+
"until"
|
|
6962
|
+
]);
|
|
6963
|
+
return options.service.wait({
|
|
6964
|
+
context,
|
|
6965
|
+
...delayMs === void 0 ? {} : { delayMs: readInteger(delayMs, "delayMs") },
|
|
6966
|
+
...reason === void 0 ? {} : { reason: readString(reason, "reason") },
|
|
6967
|
+
...until === void 0 ? {} : { until: readString(until, "until") }
|
|
6968
|
+
});
|
|
6969
|
+
};
|
|
6970
|
+
case "background.list": return async (input, context) => {
|
|
6971
|
+
const { limit, phase } = readObject(input, ["limit", "phase"]);
|
|
6972
|
+
return { sessions: await options.service.list({
|
|
6973
|
+
context,
|
|
6974
|
+
...limit === void 0 ? {} : { limit: readInteger(limit, "limit") },
|
|
6975
|
+
...phase === void 0 ? {} : { phase: readPhase(phase) }
|
|
6976
|
+
}) };
|
|
6977
|
+
};
|
|
6978
|
+
case "background.status": return (input, context) => {
|
|
6979
|
+
const { sessionId } = readObject(input, ["sessionId"]);
|
|
6980
|
+
return options.service.status({
|
|
6981
|
+
context,
|
|
6982
|
+
sessionId: readString(sessionId, "sessionId")
|
|
6983
|
+
});
|
|
6984
|
+
};
|
|
6985
|
+
case "background.send": return (input, context) => {
|
|
6986
|
+
const { message, sessionId } = readObject(input, ["message", "sessionId"]);
|
|
6987
|
+
return options.service.send({
|
|
6988
|
+
context,
|
|
6989
|
+
message: readString(message, "message"),
|
|
6990
|
+
sessionId: readString(sessionId, "sessionId")
|
|
6991
|
+
});
|
|
6992
|
+
};
|
|
6993
|
+
case "background.stop": return (input, context) => {
|
|
6994
|
+
const { reason, sessionId } = readObject(input, ["reason", "sessionId"]);
|
|
6995
|
+
return options.service.stop({
|
|
6996
|
+
context,
|
|
6997
|
+
...reason === void 0 ? {} : { reason: readString(reason, "reason") },
|
|
6998
|
+
sessionId: readString(sessionId, "sessionId")
|
|
6999
|
+
});
|
|
7000
|
+
};
|
|
7001
|
+
default: throw new BackgroundSessionCallerDenied(`unknown background tool: ${contract.id}`);
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
function replayCompleted(toolId, input, completedResult, context, options) {
|
|
7005
|
+
switch (toolId) {
|
|
7006
|
+
case "background.start": {
|
|
7007
|
+
const sessionId = readReplayedSessionId(completedResult);
|
|
7008
|
+
return options.service.status({
|
|
7009
|
+
context,
|
|
7010
|
+
sessionId
|
|
7011
|
+
});
|
|
7012
|
+
}
|
|
7013
|
+
case "background.wait": return completedResult;
|
|
7014
|
+
case "background.list": return completedResult;
|
|
7015
|
+
case "background.status": {
|
|
7016
|
+
const sessionId = readReplayedSessionId(input);
|
|
7017
|
+
return options.service.status({
|
|
7018
|
+
context,
|
|
7019
|
+
sessionId
|
|
7020
|
+
});
|
|
7021
|
+
}
|
|
7022
|
+
case "background.send": {
|
|
7023
|
+
const sessionId = readReplayedSessionId(input);
|
|
7024
|
+
return options.service.status({
|
|
7025
|
+
context,
|
|
7026
|
+
sessionId
|
|
7027
|
+
});
|
|
7028
|
+
}
|
|
7029
|
+
case "background.stop": {
|
|
7030
|
+
const sessionId = readReplayedSessionId(input);
|
|
7031
|
+
return options.service.status({
|
|
7032
|
+
context,
|
|
7033
|
+
sessionId
|
|
7034
|
+
});
|
|
7035
|
+
}
|
|
7036
|
+
default: return completedResult;
|
|
7037
|
+
}
|
|
7038
|
+
}
|
|
7039
|
+
function buildAuthority(definition, context, sessionId) {
|
|
7040
|
+
return Object.freeze({
|
|
7041
|
+
agentId: definition.agentId,
|
|
7042
|
+
memoryScopes: definition.memory.scopes,
|
|
7043
|
+
policyEpoch: context.policyEpoch,
|
|
7044
|
+
profileRevision: definition.profileRevision,
|
|
7045
|
+
...definition.projectSpaceId === void 0 ? {} : { projectSpaceId: definition.projectSpaceId },
|
|
7046
|
+
sessionKey: createBackgroundSessionKey(sessionId),
|
|
7047
|
+
toolGrantRevision: definition.toolGrantSet.revision
|
|
7048
|
+
});
|
|
7049
|
+
}
|
|
7050
|
+
function readObject(input, allowed) {
|
|
7051
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) throw new BackgroundSessionCallerDenied("background tool input must be an object");
|
|
7052
|
+
const record = input;
|
|
7053
|
+
const unknown = Object.keys(record).find((key) => !allowed.includes(key));
|
|
7054
|
+
if (unknown) throw new BackgroundSessionCallerDenied(`field is not allowed: ${unknown}`);
|
|
7055
|
+
return record;
|
|
7056
|
+
}
|
|
7057
|
+
function readString(value, name) {
|
|
7058
|
+
if (typeof value !== "string" || value.trim() === "") throw new BackgroundSessionCallerDenied(`${name} must be a non-empty string`);
|
|
7059
|
+
return value;
|
|
7060
|
+
}
|
|
7061
|
+
function readInteger(value, name) {
|
|
7062
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new BackgroundSessionCallerDenied(`${name} must be a positive integer`);
|
|
7063
|
+
return value;
|
|
7064
|
+
}
|
|
7065
|
+
function readPhase(value) {
|
|
7066
|
+
const phases = [
|
|
7067
|
+
"queued",
|
|
7068
|
+
"running",
|
|
7069
|
+
"waiting",
|
|
7070
|
+
"input-required",
|
|
7071
|
+
"stopping",
|
|
7072
|
+
"stopped",
|
|
7073
|
+
"completed",
|
|
7074
|
+
"failed",
|
|
7075
|
+
"reconciliation-required"
|
|
7076
|
+
];
|
|
7077
|
+
if (typeof value !== "string" || !phases.includes(value)) throw new BackgroundSessionCallerDenied(`phase must be one of ${phases.join(", ")}`);
|
|
7078
|
+
return value;
|
|
7079
|
+
}
|
|
7080
|
+
function readReplayedSessionId(value) {
|
|
7081
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) throw new BackgroundSessionCallerDenied("replayed background tool result is invalid");
|
|
7082
|
+
return readString(value.sessionId, "sessionId");
|
|
7083
|
+
}
|
|
7084
|
+
async function asToolInputRejection$1(operation) {
|
|
7085
|
+
try {
|
|
7086
|
+
return await operation();
|
|
7087
|
+
} catch (error) {
|
|
7088
|
+
if (error instanceof BackgroundSessionCallerDenied || error instanceof BackgroundSessionTransitionDenied) throw new RivusToolInputRejected(error.message);
|
|
7089
|
+
throw error;
|
|
7090
|
+
}
|
|
7091
|
+
}
|
|
7092
|
+
//#endregion
|
|
7093
|
+
//#region src/infrastructure/persistence/jsonl-snapshot-store.ts
|
|
7094
|
+
var JsonlSnapshotStoreCorrupted = class extends Error {
|
|
7095
|
+
name = "JsonlSnapshotStoreCorrupted";
|
|
7096
|
+
};
|
|
7097
|
+
async function loadJsonlSnapshotStore(options) {
|
|
7098
|
+
const raw = await readPersistenceFile(options.filePath);
|
|
7099
|
+
if (raw === void 0) return [];
|
|
7100
|
+
const lines = raw.split("\n");
|
|
7101
|
+
const latest = /* @__PURE__ */ new Map();
|
|
7102
|
+
let lineCount = 0;
|
|
7103
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
7104
|
+
const line = lines[index] ?? "";
|
|
7105
|
+
if (!line.trim()) continue;
|
|
7106
|
+
lineCount += 1;
|
|
7107
|
+
let parsed;
|
|
7108
|
+
try {
|
|
7109
|
+
parsed = JSON.parse(line);
|
|
7110
|
+
} catch {
|
|
7111
|
+
if (index === lines.length - 1) {
|
|
7112
|
+
await truncateAtLine(options.filePath, lines, index);
|
|
7113
|
+
break;
|
|
7114
|
+
}
|
|
7115
|
+
throw new JsonlSnapshotStoreCorrupted(`${options.errorLabel} JSONL at line ${index + 1} is not valid JSON`);
|
|
7116
|
+
}
|
|
7117
|
+
if (!isRecord$4(parsed) || parsed.version !== options.version) throw new JsonlSnapshotStoreCorrupted(`unsupported ${options.errorLabel} version at line ${index + 1}`);
|
|
7118
|
+
if (isRecord$4(parsed.snapshot)) {
|
|
7119
|
+
if (!Array.isArray(parsed.snapshot.records)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
|
|
7120
|
+
latest.clear();
|
|
7121
|
+
for (const value of parsed.snapshot.records) {
|
|
7122
|
+
if (!options.isRecord(value)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} snapshot at line ${index + 1}`);
|
|
7123
|
+
latest.set(options.keyOf(value), structuredClone(value));
|
|
7124
|
+
}
|
|
7125
|
+
continue;
|
|
7126
|
+
}
|
|
7127
|
+
if (!options.isRecord(parsed.record)) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} record at line ${index + 1}`);
|
|
7128
|
+
const record = parsed.record;
|
|
7129
|
+
const previous = latest.get(options.keyOf(record));
|
|
7130
|
+
const invalid = options.validateSequence(previous, record, index + 1);
|
|
7131
|
+
if (invalid) throw new JsonlSnapshotStoreCorrupted(`invalid ${options.errorLabel} ${invalid} at line ${index + 1}`);
|
|
7132
|
+
latest.set(options.keyOf(record), structuredClone(record));
|
|
7133
|
+
}
|
|
7134
|
+
if (lineCount >= options.compactThreshold) await writeJsonlSnapshot(options.filePath, options.version, [...latest.values()]);
|
|
7135
|
+
return [...latest.values()];
|
|
7136
|
+
}
|
|
7137
|
+
async function appendJsonlRecord(filePath, version, record) {
|
|
7138
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
7139
|
+
await appendFile(filePath, `${JSON.stringify({
|
|
7140
|
+
record,
|
|
7141
|
+
version
|
|
7142
|
+
})}\n`, "utf8");
|
|
7143
|
+
}
|
|
7144
|
+
async function writeJsonlSnapshot(filePath, version, records) {
|
|
7145
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
7146
|
+
await writeFile(filePath, `${JSON.stringify({
|
|
7147
|
+
snapshot: { records },
|
|
7148
|
+
version
|
|
7149
|
+
})}\n`, "utf8");
|
|
7150
|
+
}
|
|
7151
|
+
async function truncateAtLine(filePath, lines, lineIndex) {
|
|
7152
|
+
let offset = 0;
|
|
7153
|
+
for (let index = 0; index < lineIndex; index += 1) offset += (lines[index] ?? "").length + 1;
|
|
7154
|
+
await truncate(filePath, offset);
|
|
7155
|
+
}
|
|
7156
|
+
//#endregion
|
|
7157
|
+
//#region src/infrastructure/persistence/jsonl-background-session-repository.ts
|
|
7158
|
+
const BACKGROUND_SESSION_JSONL_VERSION = 1;
|
|
7159
|
+
async function openJsonlBackgroundSessionRepository(options) {
|
|
7160
|
+
return createBackgroundSessionRepository({
|
|
7161
|
+
initial: await loadJsonlSnapshotStore({
|
|
7162
|
+
compactThreshold: options.compactThreshold ?? 5e3,
|
|
7163
|
+
errorLabel: "background session",
|
|
7164
|
+
filePath: options.filePath,
|
|
7165
|
+
isRecord: isBackgroundSessionState,
|
|
7166
|
+
keyOf: (record) => record.sessionId,
|
|
7167
|
+
validateSequence: validateSessionSequence,
|
|
7168
|
+
version: 1
|
|
7169
|
+
}),
|
|
7170
|
+
persist: (record) => appendJsonlRecord(options.filePath, 1, record)
|
|
7171
|
+
});
|
|
7172
|
+
}
|
|
7173
|
+
function validateSessionSequence(previous, record, _lineNumber) {
|
|
7174
|
+
if (previous === void 0) {
|
|
7175
|
+
if (record.revision !== 1 || record.phase !== "queued") return "first record";
|
|
7176
|
+
return;
|
|
7177
|
+
}
|
|
7178
|
+
if (record.revision !== previous.revision + 1) return "revision";
|
|
7179
|
+
return isValidBackgroundSessionTransition(previous, record);
|
|
7180
|
+
}
|
|
7181
|
+
function isBackgroundSessionState(value) {
|
|
7182
|
+
if (!isRecord$4(value)) return false;
|
|
7183
|
+
const phases = [
|
|
7184
|
+
"queued",
|
|
7185
|
+
"running",
|
|
7186
|
+
"waiting",
|
|
7187
|
+
"input-required",
|
|
7188
|
+
"stopping",
|
|
7189
|
+
"stopped",
|
|
7190
|
+
"completed",
|
|
7191
|
+
"failed",
|
|
7192
|
+
"reconciliation-required"
|
|
7193
|
+
];
|
|
7194
|
+
if (typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
|
|
7195
|
+
if (typeof value.displayName !== "string") return false;
|
|
7196
|
+
if (!phases.includes(value.phase)) return false;
|
|
7197
|
+
if (!Number.isInteger(value.revision) || value.revision < 1) return false;
|
|
7198
|
+
if (typeof value.parentRunId !== "string" || value.parentRunId.trim() === "") return false;
|
|
7199
|
+
if (typeof value.sourceMessageId !== "string" || value.sourceMessageId.trim() === "") return false;
|
|
7200
|
+
if (!isRecord$4(value.origin)) return false;
|
|
7201
|
+
const origin = value.origin;
|
|
7202
|
+
if (typeof origin.endpointId !== "string" || origin.endpointId.trim() === "") return false;
|
|
7203
|
+
if (typeof origin.tenantKey !== "string") return false;
|
|
7204
|
+
if (!Array.isArray(origin.allowedActorOpenIds) || origin.allowedActorOpenIds.some((id) => typeof id !== "string")) return false;
|
|
7205
|
+
if (origin.conversationId !== void 0 && typeof origin.conversationId !== "string") return false;
|
|
7206
|
+
if (origin.memory !== void 0 && !isRecord$4(origin.memory)) return false;
|
|
7207
|
+
if (!isRecord$4(value.authority)) return false;
|
|
7208
|
+
const authority = value.authority;
|
|
7209
|
+
if (typeof authority.agentId !== "string" || authority.agentId.trim() === "") return false;
|
|
7210
|
+
if (typeof authority.profileRevision !== "string" || authority.profileRevision.trim() === "") return false;
|
|
7211
|
+
if (typeof authority.toolGrantRevision !== "string" || authority.toolGrantRevision.trim() === "") return false;
|
|
7212
|
+
if (!Number.isInteger(authority.policyEpoch) || authority.policyEpoch < 0) return false;
|
|
7213
|
+
if (!Array.isArray(authority.memoryScopes) || authority.memoryScopes.some((scope) => typeof scope !== "string")) return false;
|
|
7214
|
+
if (typeof authority.sessionKey !== "string" || authority.sessionKey.trim() === "") return false;
|
|
7215
|
+
if (authority.projectSpaceId !== void 0 && typeof authority.projectSpaceId !== "string") return false;
|
|
7216
|
+
if (typeof value.prompt !== "string") return false;
|
|
7217
|
+
if (!Number.isInteger(value.stepCount) || value.stepCount < 0) return false;
|
|
7218
|
+
if (!Number.isInteger(value.wakeCount) || value.wakeCount < 0) return false;
|
|
7219
|
+
if (!Number.isInteger(value.consecutiveFailures) || value.consecutiveFailures < 0) return false;
|
|
7220
|
+
if (!Array.isArray(value.pendingInput) || value.pendingInput.some((entry) => typeof entry !== "string")) return false;
|
|
7221
|
+
if (value.currentStepRunId !== void 0 && typeof value.currentStepRunId !== "string") return false;
|
|
7222
|
+
if (value.lastWakeText !== void 0 && typeof value.lastWakeText !== "string") return false;
|
|
7223
|
+
if (value.wakeAt !== void 0 && typeof value.wakeAt !== "string") return false;
|
|
7224
|
+
if (value.errorMessage !== void 0 && typeof value.errorMessage !== "string") return false;
|
|
7225
|
+
if (value.reconciliationNote !== void 0 && typeof value.reconciliationNote !== "string") return false;
|
|
7226
|
+
if (typeof value.createdAt !== "string" || !isIso(value.createdAt)) return false;
|
|
7227
|
+
if (typeof value.updatedAt !== "string" || !isIso(value.updatedAt)) return false;
|
|
7228
|
+
if (value.lease !== void 0 && !isLease(value.lease)) return false;
|
|
7229
|
+
if (value.cancellation !== void 0) {
|
|
7230
|
+
if (!isRecord$4(value.cancellation)) return false;
|
|
7231
|
+
if (typeof value.cancellation.reason !== "string" || !isIso(value.cancellation.requestedAt)) return false;
|
|
7232
|
+
}
|
|
7233
|
+
if (value.result !== void 0) {
|
|
7234
|
+
if (!isRecord$4(value.result)) return false;
|
|
7235
|
+
if (typeof value.result.text !== "string" || typeof value.result.stepRunId !== "string" || !isIso(value.result.completedAt)) return false;
|
|
7236
|
+
}
|
|
7237
|
+
return true;
|
|
7238
|
+
}
|
|
7239
|
+
function isLease(value) {
|
|
7240
|
+
if (!isRecord$4(value)) return false;
|
|
7241
|
+
return typeof value.owner === "string" && value.owner.trim() !== "" && Number.isInteger(value.epoch) && value.epoch >= 0 && isIso(value.expiresAt);
|
|
7242
|
+
}
|
|
7243
|
+
function isIso(value) {
|
|
7244
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
7245
|
+
}
|
|
7246
|
+
//#endregion
|
|
7247
|
+
//#region src/infrastructure/persistence/jsonl-background-session-delivery-store.ts
|
|
7248
|
+
const BACKGROUND_SESSION_DELIVERY_JSONL_VERSION = 1;
|
|
7249
|
+
async function openJsonlBackgroundSessionDeliveryStore(options) {
|
|
7250
|
+
return createBackgroundSessionDeliveryStore({
|
|
7251
|
+
initial: await loadJsonlSnapshotStore({
|
|
7252
|
+
compactThreshold: options.compactThreshold ?? 2e3,
|
|
7253
|
+
errorLabel: "background session delivery",
|
|
7254
|
+
filePath: options.filePath,
|
|
7255
|
+
isRecord: isDeliveryRecord,
|
|
7256
|
+
keyOf: (record) => record.deliveryId,
|
|
7257
|
+
validateSequence: validateDeliverySequence,
|
|
7258
|
+
version: 1
|
|
7259
|
+
}),
|
|
7260
|
+
persist: (record) => appendJsonlRecord(options.filePath, 1, record)
|
|
7261
|
+
});
|
|
7262
|
+
}
|
|
7263
|
+
function validateDeliverySequence(previous, record, _lineNumber) {
|
|
7264
|
+
if (previous === void 0) return record.revision === 1 && record.state === "pending" ? void 0 : "first record";
|
|
7265
|
+
if (record.revision !== previous.revision + 1) return "revision";
|
|
7266
|
+
return previous.state === "pending" && record.state === "delivered" ? void 0 : "transition";
|
|
7267
|
+
}
|
|
7268
|
+
function isDeliveryRecord(value) {
|
|
7269
|
+
if (!isRecord$4(value)) return false;
|
|
7270
|
+
if (typeof value.deliveryId !== "string" || value.deliveryId.trim() === "" || typeof value.sessionId !== "string" || value.sessionId.trim() === "") return false;
|
|
7271
|
+
if (typeof value.kind !== "string" || ![
|
|
7272
|
+
"progress",
|
|
7273
|
+
"final",
|
|
7274
|
+
"stopped",
|
|
7275
|
+
"failed",
|
|
7276
|
+
"reconciliation"
|
|
7277
|
+
].includes(value.kind)) return false;
|
|
7278
|
+
if (typeof value.text !== "string") return false;
|
|
7279
|
+
if (value.state !== "pending" && value.state !== "delivered") return false;
|
|
7280
|
+
if (!Number.isInteger(value.revision) || value.revision < 1) return false;
|
|
7281
|
+
if (value.providerMessageId !== void 0 && typeof value.providerMessageId !== "string") return false;
|
|
7282
|
+
return typeof value.createdAt === "string" && Number.isFinite(Date.parse(value.createdAt));
|
|
7283
|
+
}
|
|
7284
|
+
//#endregion
|
|
7285
|
+
//#region src/infrastructure/feishu/feishu-interactive-card-send.ts
|
|
7286
|
+
function sendFeishuInteractiveCard(client, request, errorMessage) {
|
|
7287
|
+
return Effect.gen(function* () {
|
|
7288
|
+
const response = yield* client.request(request, errorMessage);
|
|
7289
|
+
const providerMessageId = readMessageId(response.body.data);
|
|
7290
|
+
if (!providerMessageId) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, "Feishu send response missing message_id"));
|
|
7291
|
+
return Object.freeze({ providerMessageId });
|
|
7292
|
+
});
|
|
7293
|
+
}
|
|
7294
|
+
function readMessageId(data) {
|
|
7295
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return void 0;
|
|
7296
|
+
const value = data.message_id;
|
|
7297
|
+
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
7298
|
+
}
|
|
7299
|
+
//#endregion
|
|
7300
|
+
//#region src/infrastructure/feishu/feishu-background-session-delivery.ts
|
|
7301
|
+
function createConfiguredFeishuBackgroundSessionDelivery(options) {
|
|
7302
|
+
const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
|
|
7303
|
+
return { deliver: (input) => Effect.runPromise(sendFeishuInteractiveCard(options.client, {
|
|
7304
|
+
body: {
|
|
7305
|
+
content: JSON.stringify(createBackgroundSessionCard(input)),
|
|
7306
|
+
msg_type: "interactive",
|
|
7307
|
+
receive_id: input.chatId,
|
|
7308
|
+
uuid: deliveryUuid$1(input.deliveryId)
|
|
7309
|
+
},
|
|
7310
|
+
method: "POST",
|
|
7311
|
+
url: `${baseUrl}/open-apis/im/v1/messages?receive_id_type=chat_id`
|
|
7312
|
+
}, "Feishu background session delivery failed")) };
|
|
7313
|
+
}
|
|
7314
|
+
function createBackgroundSessionCard(input) {
|
|
7315
|
+
const title = cardTitle(input.kind);
|
|
7316
|
+
const headerTemplate = cardHeaderTemplate(input.kind);
|
|
7317
|
+
return {
|
|
7318
|
+
body: { elements: [
|
|
7319
|
+
{
|
|
7320
|
+
content: `**${escapeCardText(title)}** · ${escapeCardText(input.displayName)}`,
|
|
7321
|
+
tag: "markdown"
|
|
7322
|
+
},
|
|
7323
|
+
{
|
|
7324
|
+
content: input.text,
|
|
7325
|
+
tag: "markdown"
|
|
7326
|
+
},
|
|
7327
|
+
{
|
|
7328
|
+
content: `会话 ${escapeCardText(input.sessionId)} · _由 Rivus 后台会话生成_`,
|
|
7329
|
+
tag: "markdown"
|
|
7330
|
+
}
|
|
7331
|
+
] },
|
|
7332
|
+
config: { update_multi: true },
|
|
7333
|
+
header: {
|
|
7334
|
+
template: headerTemplate,
|
|
7335
|
+
title: {
|
|
7336
|
+
content: title,
|
|
7337
|
+
tag: "plain_text"
|
|
7338
|
+
}
|
|
7339
|
+
},
|
|
7340
|
+
schema: "2.0"
|
|
7341
|
+
};
|
|
7342
|
+
}
|
|
7343
|
+
function cardTitle(kind) {
|
|
7344
|
+
switch (kind) {
|
|
7345
|
+
case "progress": return "后台会话进展";
|
|
7346
|
+
case "final": return "后台会话完成";
|
|
7347
|
+
case "failed": return "后台会话失败";
|
|
7348
|
+
case "stopped": return "后台会话已停止";
|
|
7349
|
+
case "reconciliation": return "后台会话需对账";
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
function cardHeaderTemplate(kind) {
|
|
7353
|
+
switch (kind) {
|
|
7354
|
+
case "final": return "green";
|
|
7355
|
+
case "failed":
|
|
7356
|
+
case "reconciliation": return "red";
|
|
7357
|
+
case "stopped": return "orange";
|
|
7358
|
+
case "progress": return "blue";
|
|
7359
|
+
}
|
|
7360
|
+
}
|
|
7361
|
+
function escapeCardText(value) {
|
|
7362
|
+
return value.replace(/([*_~`|<>])/g, "\\$1");
|
|
7363
|
+
}
|
|
7364
|
+
function deliveryUuid$1(deliveryId) {
|
|
7365
|
+
return `rivus-${createHash("sha256").update(`background-session:${deliveryId}`).digest("hex").slice(0, 32)}`;
|
|
7366
|
+
}
|
|
7367
|
+
//#endregion
|
|
5848
7368
|
//#region src/application/memory/rivus-memory-tool.ts
|
|
5849
7369
|
function createRivusMemoryTool(memory) {
|
|
5850
7370
|
const handles = Array.isArray(memory) ? [...memory] : [memory];
|
|
@@ -7015,21 +8535,16 @@ function createConfiguredFeishuTextReplySender(options) {
|
|
|
7015
8535
|
//#region src/infrastructure/feishu/feishu-automation-card-sender.ts
|
|
7016
8536
|
function createConfiguredFeishuAutomationCardSender(options) {
|
|
7017
8537
|
const baseUrl = options.config.feishu.baseUrl.replace(/\/$/, "");
|
|
7018
|
-
return { send: (input) =>
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
7024
|
-
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
}, "Feishu send failed");
|
|
7029
|
-
const providerMessageId = readMessageId(response.body.data);
|
|
7030
|
-
if (!providerMessageId) return yield* Effect.fail(new FeishuOpenApiError(response.status, response.body.code, "Feishu send response missing message_id"));
|
|
7031
|
-
return Object.freeze({ providerMessageId });
|
|
7032
|
-
}) };
|
|
8538
|
+
return { send: (input) => sendFeishuInteractiveCard(options.client, {
|
|
8539
|
+
body: {
|
|
8540
|
+
content: JSON.stringify(createAutomationCard(input.markdown)),
|
|
8541
|
+
msg_type: "interactive",
|
|
8542
|
+
receive_id: input.receiveId,
|
|
8543
|
+
uuid: deliveryUuid(input.idempotencyKey)
|
|
8544
|
+
},
|
|
8545
|
+
method: "POST",
|
|
8546
|
+
url: `${baseUrl}/open-apis/im/v1/messages?receive_id_type=${encodeURIComponent(input.receiveIdType)}`
|
|
8547
|
+
}, "Feishu send failed") };
|
|
7033
8548
|
}
|
|
7034
8549
|
function createAutomationCard(markdown) {
|
|
7035
8550
|
const presentation = splitPresentation(markdown);
|
|
@@ -7068,11 +8583,6 @@ function splitPresentation(markdown) {
|
|
|
7068
8583
|
function deliveryUuid(idempotencyKey) {
|
|
7069
8584
|
return `rivus-${createHash("sha256").update(idempotencyKey).digest("hex").slice(0, 32)}`;
|
|
7070
8585
|
}
|
|
7071
|
-
function readMessageId(data) {
|
|
7072
|
-
if (data === null || typeof data !== "object" || Array.isArray(data)) return void 0;
|
|
7073
|
-
const value = data.message_id;
|
|
7074
|
-
return typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
7075
|
-
}
|
|
7076
8586
|
//#endregion
|
|
7077
8587
|
//#region src/application/interaction/human-interaction-repository.ts
|
|
7078
8588
|
var HumanInteractionRepositoryError = class extends Error {
|
|
@@ -7426,4 +8936,4 @@ function validateDecisionInput(input) {
|
|
|
7426
8936
|
if (input.recommendedOptionId && !optionIds.includes(input.recommendedOptionId)) throw new Error("recommended user decision option must be available");
|
|
7427
8937
|
}
|
|
7428
8938
|
//#endregion
|
|
7429
|
-
export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, CardPresentationTransitionDenied, CompactionError, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, assembleAgentContext, assertRivusPluginConforms, commitAutomationOutcome, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, normalizeStableJson, openJsonAutomationTickRepository, openJsonlAgentMemoryService, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, replayAgentHistory, replayAgentTranscript, requiresToolApproval, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, shouldAcceptFeishuEndpointMessage, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };
|
|
8939
|
+
export { AgentContextBudgetExceeded, AgentEventHandlerFailed, AgentEventLogStoreError, AgentEventSinkFailed, AgentHarnessBusy, AgentInstanceBusy, AgentInstanceConflict, AgentLoopFailed, AgentMemoryError, AgentRunCancelled, AgentRuntimeDisposed, AutomationMandateError, BACKGROUND_SESSION_DELIVERY_JSONL_VERSION, BACKGROUND_SESSION_JSONL_VERSION, BACKGROUND_SESSION_SESSION_KEY_PREFIX, BACKGROUND_SESSION_START_TOOL_ID, BACKGROUND_SESSION_TOOL_IDS, BACKGROUND_SESSION_TOOL_PLUGIN_ID, BACKGROUND_SESSION_TOOL_VERSION, BackgroundSessionCallerDenied, BackgroundSessionDeliveryConflict, BackgroundSessionRepositoryConflict, BackgroundSessionRepositoryCorrupted, BackgroundSessionTransitionDenied, CardPresentationTransitionDenied, CompactionError, DEFAULT_BACKGROUND_SESSION_LEASE_MS, DEFAULT_BACKGROUND_SESSION_LEASE_RENEWAL_INTERVAL_MS, DEFAULT_BACKGROUND_SESSION_LIFETIME_MS, DEFAULT_BACKGROUND_SESSION_MAX_CONCURRENT_SESSIONS, DEFAULT_BACKGROUND_SESSION_MAX_CONSECUTIVE_FAILURES, DEFAULT_BACKGROUND_SESSION_RETRY_BACKOFF_MS, DEFAULT_BACKGROUND_SESSION_STEP_TIMEOUT_MS, DEFAULT_BACKGROUND_SESSION_SUPERVISOR_INTERVAL_MS, DEFAULT_CARD_STREAM_LEASE_MS, DelegationDenied, DeliveryOutboxError, FeishuCardPresentationNotFound, FeishuCardTargetNotFound, FeishuCardTargetRegistryStoreError, FeishuCotProtocolError, FeishuEndpointCredentialError, FeishuOpenApiError, FeishuTenantAccessTokenError, HumanInteractionRepositoryError, HumanInteractionTransitionDenied, InvalidFeishuCardAction, InvalidFeishuMessageContent, InvalidFeishuSessionReference, InvalidInvocationAuthority, InvalidProjectSkillCatalog, InvalidRecoveryAction, InvalidRivusEndpointBinding, InvalidRivusPlugin, InvalidRivusProjectSpace, InvalidStableJson, InvalidToolInput, InvalidWorkspaceRoot, LangfuseTelemetryConfigError, MEMORY_SCOPES, OpenClawEnvImportError, PluginStateConflict, RIVUS_MEMORY_TOOL_ID, RIVUS_MEMORY_TOOL_PLUGIN_ID, RIVUS_MEMORY_TOOL_VERSION, RIVUS_PLUGIN_API_VERSION, RivusDaemonConfigError, RivusDeploymentAutomationReadinessError, RivusDeploymentDaemonLifecycleError, RivusDeploymentManifestError, RivusDeploymentReadinessError, RivusPluginConformanceError, RivusPluginLoadError, RivusToolInputRejected, SessionSchedulerCapacityExceeded, SessionSchedulerDisposed, ToolInvocationDenied, UnsupportedFeishuCardAction, UnsupportedFeishuMessage, WorkspaceInstructionsSourceError, acceptsCardPresentationProgress, activeCardPresentation, appendBackgroundSessionInput, assembleAgentContext, assertRivusPluginConforms, backgroundSessionToolIds, claimBackgroundSession, commitAutomationOutcome, completeBackgroundSessionStep, completeBackgroundSessionStop, createAgentCommandFromFeishuCardAction, createAgentCommandFromFeishuMessage, createAgentConversationMessages, createAgentDomainEventHandler, createAgentDomainEventSink, createAgentDomainEventSinkFromCallback, createAgentHarness, createAgentHarnessClient, createAgentHarnessPooledRuntime, createAgentInstanceRegistry, createAgentLoopFromCallback, createAgentLoopModelExecutionEnd, createAgentLoopModelExecutionStart, createAgentLoopPromptTransformer, createAgentLoopSkillExecutionEnd, createAgentLoopSkillExecutionStart, createAgentLoopTextDelta, createAgentLoopThinkingDelta, createAgentLoopToolExecutionEnd, createAgentLoopToolExecutionStart, createAgentLoopToolExecutionUpdate, createAgentMemoryService, createAgentRunUpdateHandler, createAgentRuntime, createAgentRuntimePool, createAgentTranscriptMessages, createAgentTranscriptTurn, createAgentsMdInstructionsProvider, createAsyncIterableAgentLoop, createAutomationMandateStore, createBackgroundSession, createBackgroundSessionCard, createBackgroundSessionDeliveryStore, createBackgroundSessionHostTools, createBackgroundSessionKey, createBackgroundSessionRepository, createBackgroundSessionService, createBackgroundSessionStepSourceMessageId, createBackgroundSessionSupervisor, createBackgroundSessionToolContracts, createCoalescingFeishuPublisher, createCompactionService, createCompositeRivusDaemonTransport, createConfiguredFeishuAutomationCardSender, createConfiguredFeishuBackgroundSessionDelivery, createConfiguredFeishuCardKitPublisher, createConfiguredFeishuCardKitTargetCreator, createConfiguredFeishuCardRolloverRuntime, createConfiguredFeishuHumanInteractionPresenter, createConfiguredFeishuOpenApiClient, createConfiguredFeishuTextReplySender, createConfiguredRivusDaemonBootstrap, createConfiguredRivusDeploymentDaemon, createDailyAutomationSchedule, createDefaultAgentHarness, createDefaultAgentHarnessClient, createDefaultAgentHarnessClientFromCallback, createDefaultAgentHarnessClientFromTextCallback, createDefaultAgentHarnessFromCallback, createDefaultAgentHarnessFromTextCallback, createDefaultAgentRuntime, createDefaultAgentRuntimeFromCallback, createDefaultAgentRuntimeFromTextCallback, createDelegationService, createDeliveryOutbox, createEventAgentLoop, createFakeRivusPlugin, createFeishuAgentDaemon, createFeishuAgentRunCard, createFeishuAgentRuntime, createFeishuCardActionCallbackResponse, createFeishuCardActionErrorResponse, createFeishuCardDeliveryLedger, createFeishuCardDeliveryReconciler, createFeishuCardKitOpenApiClient, createFeishuCardKitOpenApiTargetCreator, createFeishuCardKitPublisher, createFeishuCardPresentationStore, createFeishuCardRollover, createFeishuCardRolloverSupervisor, createFeishuCardTargetPreparation, createFeishuConversationId, createFeishuCotPublisher, createFeishuDeploymentEndpoint, createFeishuEventHandlers, createFeishuHumanInteractionCard, createFeishuHumanInteractionPresenter, createFeishuInboxRepository, createFeishuMessageQueue, createFeishuMessageWorker, createFeishuOpenApiClient, createFeishuPeriodicFlush, createFeishuPresentationPreparation, createFeishuSessionKey, createFeishuStreamProjector, createFeishuTenantAccessTokenProvider, createFeishuWebSocketDaemon, createFeishuWorkerLoop, createFixedClock, createHumanInteractionEndpointRegistry, createHumanInteractionService, createHumanInteractionToolApprovalGateway, createInMemoryFeishuCardTargetRegistry, createInMemoryHumanInteractionRepository, createInvocationAuthority, createJsonFetchRequest, createJsonFileFeishuCardTargetRegistry, createJsonlAgentEventLog, createJsonlHumanInteractionRepository, createLangfuseAgentTelemetry, createLazyFeishuWebSocketEventDispatcher, createMemoryNamespace, createOpenTelemetryAgentEventSink, createOpenTelemetryAgentTelemetry, createPiAgentLoop, createPiSdkAgentLoop, createPiSessionRegistry, createPluginStateStore, createProjectMemoryPromptPreparer, createRateLimitedFeishuPublisher, createRecoveryAction, createRecoveryControl, createRivusAgentHost, createRivusDaemonProcess, createRivusDaemonShutdownController, createRivusDaemonStatusHttpServer, createRivusDaemonStatusReporter, createRivusDeploymentCliProcess, createRivusDeploymentDaemon, createRivusEnvFromOpenClawConfig, createRivusMemoryTool, createRivusMemoryToolContract, createRivusMemoryToolDescriptor, createRivusPluginCatalog, createRoutedHumanInteractionToolApprovalService, createScheduledAutomation, createSequenceRunIds, createSessionScheduler, createSubagentCoordinator, createSystemClock, createTelemetryContentRedactor, createTestClock, createTextAgentLoop, createTextAgentLoopFromCallback, createToolBroker, createToolInputDigest, createToolOperationLedger, createUuidRunIds, createWorkspaceRootHandle, describeFeishuMessageIntake, evolveAgentRun, extendBackgroundSessionDefinition, failBackgroundSessionStep, formatRivusEnvFile, initialAgentRunState, intersectToolIds, isAgentToolExecutionEvent, isAssistantTextDeltaEvent, isAssistantThinkingDeltaEvent, isBackgroundSessionDue, isBackgroundSessionLeaseExpired, isBackgroundSessionState, isBackgroundSessionTerminalPhase, isBackgroundSessionToolId, isCardPresentationHandoffDue, isTerminalAgentDomainEvent, isTerminalAgentRunPhase, loadNodeRivusPluginModule, loadRivusDaemonConfig, loadRivusDeployment, loadRivusDeploymentManifest, narrowBackgroundSessionDefinition, normalizeStableJson, openJsonAutomationTickRepository, openJsonlAgentMemoryService, openJsonlBackgroundSessionDeliveryStore, openJsonlBackgroundSessionRepository, openJsonlFeishuCardDeliveryLedger, openJsonlFeishuInboxRepository, openJsonlRecoveryControl, openJsonlToolOperationLedger, parkBackgroundSessionForReconciliation, progressDeliveryId, releaseBackgroundSessionLease, renewBackgroundSessionLease, replayAgentHistory, replayAgentTranscript, requestBackgroundSessionStop, requeueInterruptedBackgroundSessionStep, requiresToolApproval, resolveBackgroundSessionReconciliation, resolveBackgroundSessionSupervisorIntervalMs, resolveFeishuEndpointCredentials, resolveLangfuseTelemetryConfig, resolveRivusAgentDefinition, resolveRivusProjectSpace, restoreAgentHistory, restoreConfiguredRivusDaemonBootstrap, restrictMemoryScopesForAudience, runRivusDaemonCli, sessionIdFromSessionKey, shouldAcceptFeishuEndpointMessage, suspendBackgroundSession, terminalDeliveryId, transitionHumanInteraction, validateProjectSkillCatalog, validateProjectSkillCommand, validateRivusDeploymentManifest };
|