@zq-silk/yui 0.2.0 → 0.4.2
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/ARCHITECTURE.md +603 -133
- package/README.md +806 -31
- package/dist/agent/agent.js +2 -1
- package/dist/agent/argumentPolicy.js +3 -1
- package/dist/agent/launchEnvironment.js +106 -0
- package/dist/agent/managedRuntimeEnvironment.js +34 -0
- package/dist/brief/taskBrief.js +11 -1
- package/dist/cli/agentConfigurationPicker.js +287 -0
- package/dist/cli/commandCatalog.js +488 -60
- package/dist/cli/completion.js +146 -22
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli/interactionCandidates.js +53 -15
- package/dist/cli/interactionPolicy.js +267 -30
- package/dist/cli/interactiveSelection.js +6 -2
- package/dist/cli/invocationRouter.js +5 -1
- package/dist/cli/operatorWizard.js +87 -0
- package/dist/cli/roleOptionCatalog.js +1 -0
- package/dist/cli/roleWizard.js +185 -21
- package/dist/cli/updateCommand.js +62 -19
- package/dist/cli/updateOrchestrator.js +539 -0
- package/dist/cli/updatePorts.js +1119 -0
- package/dist/cli/upgradeCommand.js +112 -0
- package/dist/cli.js +1420 -86
- package/dist/commands/agentCommands.js +146 -3
- package/dist/commands/configCommands.js +126 -0
- package/dist/commands/controllerCommands.js +365 -0
- package/dist/commands/globalRoleCommands.js +168 -126
- package/dist/commands/jobCommands.js +18 -8
- package/dist/commands/operatorCommands.js +159 -9
- package/dist/commands/profileCommands.js +203 -0
- package/dist/commands/projectCommands.js +650 -0
- package/dist/commands/roleConfiguration.js +85 -24
- package/dist/commands/roleRuntimeGuard.js +12 -0
- package/dist/commands/roleSkillValidation.js +47 -0
- package/dist/commands/taskActor.js +127 -0
- package/dist/commands/taskCommands.js +4201 -313
- package/dist/commands/taskCompletionGate.js +131 -0
- package/dist/commands/taskContextCommand.js +244 -30
- package/dist/commands/taskInputCommands.js +177 -59
- package/dist/commands/taskIntegrationCommands.js +303 -0
- package/dist/commands/taskOverviewCommand.js +363 -0
- package/dist/commands/taskRoleRuntimeStatus.js +125 -19
- package/dist/commands/textInput.js +15 -0
- package/dist/completion/completionInstaller.js +26 -22
- package/dist/config/yuiConfig.js +4 -3
- package/dist/context/dispatchContext.js +90 -38
- package/dist/context/roleSessionContext.js +119 -0
- package/dist/controller/claudeLifecycleHook.js +203 -0
- package/dist/controller/clientRuntime.js +408 -56
- package/dist/controller/codexLifecycleHook.js +108 -0
- package/dist/controller/controller.js +1089 -32
- package/dist/controller/domainIdentity.js +505 -0
- package/dist/controller/ephemeralResourceReaper.js +131 -0
- package/dist/controller/fileSchedulerStoreAdapter.js +2153 -103
- package/dist/controller/providerHookRunFence.js +127 -0
- package/dist/controller/resourceCleanupLinux.js +286 -0
- package/dist/controller/resourceInventory.js +531 -0
- package/dist/controller/resourceInventoryLinux.js +610 -0
- package/dist/controller/runtime.js +629 -10
- package/dist/controller/runtimeEventInbox.js +564 -0
- package/dist/controller/runtimeEventProcessor.js +248 -0
- package/dist/controller/runtimeLaunchCoordinator.js +477 -0
- package/dist/controller/sessionNotify.js +121 -78
- package/dist/coordination/deadlineScheduler.js +15 -0
- package/dist/coordination/mailboxScheduler.js +108 -0
- package/dist/coordination/workMailbox.js +329 -0
- package/dist/coordination/workMailboxQueue.js +86 -0
- package/dist/core/controllerClient.js +19 -5
- package/dist/core/controllerEndpoint.js +37 -0
- package/dist/core/controllerServer.js +218 -10
- package/dist/core/protocol.js +6 -2
- package/dist/decision/decision.js +2 -1
- package/dist/doctor/doctor.js +681 -32
- package/dist/domain/validation.js +53 -0
- package/dist/errors/cliError.js +5 -3
- package/dist/event/taskEvent.js +7 -3
- package/dist/execution/codexThreadNaming.js +160 -0
- package/dist/execution/executionGroup.js +579 -0
- package/dist/executor/agentAdapter.js +255 -40
- package/dist/executor/agentConfigurationCatalog.js +326 -0
- package/dist/executor/agentConfigurationProbe.js +506 -0
- package/dist/executor/agentExecutor.js +625 -10
- package/dist/executor/codexConfigConflict.js +290 -0
- package/dist/executor/effectiveLaunch.js +340 -0
- package/dist/executor/executorRegistry.js +238 -36
- package/dist/executor/fileRoleLaunchPlanner.js +550 -40
- package/dist/executor/turnCompletion.js +126 -0
- package/dist/input/inputRequest.js +30 -9
- package/dist/integration/changeSet.js +36 -0
- package/dist/integration/checkResult.js +24 -0
- package/dist/integration/gitIntegrationService.js +695 -0
- package/dist/integration/integrationAttempt.js +142 -0
- package/dist/interaction/operatorPresentation.js +96 -0
- package/dist/lifecycle/canonicalLifecycleEvent.js +342 -0
- package/dist/lifecycle/exactRunTerminalization.js +572 -0
- package/dist/lifecycle/providerLifecycleMapping.js +190 -0
- package/dist/lifecycle/taskRoleSessionReset.js +124 -0
- package/dist/message/message.js +23 -7
- package/dist/milestone/milestone.js +2 -1
- package/dist/operator/operatorSessionHistory.js +124 -0
- package/dist/output/agentConfigurationPresentation.js +43 -0
- package/dist/output/rolePresentation.js +34 -10
- package/dist/output/terminal.js +8 -0
- package/dist/output/timePresentation.js +55 -0
- package/dist/profile/agentProfile.js +128 -0
- package/dist/repository/gitWorkspace.js +578 -24
- package/dist/repository/project.js +213 -0
- package/dist/repository/taskWorkspaceCoordinator.js +392 -0
- package/dist/repository/taskWorkspacePreparer.js +1688 -191
- package/dist/review/reviewConfig.js +11 -0
- package/dist/review/reviewRound.js +399 -0
- package/dist/review/taskFinalReviewContract.js +90 -0
- package/dist/role/role.js +124 -23
- package/dist/run/agentRun.js +155 -12
- package/dist/run/runIdentity.js +82 -0
- package/dist/runtime/exactControlPlane.js +472 -0
- package/dist/runtime/index.js +8 -0
- package/dist/runtime/lifecycleReservation.js +38 -0
- package/dist/runtime/ports.js +11 -0
- package/dist/runtime/preallocatedNativeSession.js +13 -0
- package/dist/runtime/promptEnvelope.js +30 -0
- package/dist/runtime/runtimeBinding.js +31 -0
- package/dist/runtime/runtimeOwner.js +14 -0
- package/dist/runtime/sessionLaunchRequest.js +62 -0
- package/dist/runtime/sessionTitle.js +54 -0
- package/dist/runtime/taskRuntimeIsolation.js +643 -0
- package/dist/runtime/tmuxAdapters.js +315 -0
- package/dist/runtime/turnCompletion.js +3 -0
- package/dist/runtime/validation.js +23 -0
- package/dist/scheduler/activeRoleRunDelivery.js +342 -32
- package/dist/scheduler/activeTaskProgress.js +63 -0
- package/dist/scheduler/leaderFailure.js +2 -1
- package/dist/scheduler/leaderWakeupProcessor.js +307 -66
- package/dist/scheduler/operatorInputNotificationProcessor.js +109 -46
- package/dist/scheduler/operatorNotification.js +44 -2
- package/dist/scheduler/ports.js +28 -1
- package/dist/scheduler/roleRunLiveness.js +131 -25
- package/dist/scheduler/roleRunStall.js +951 -0
- package/dist/scheduler/taskExecutionProjection.js +544 -0
- package/dist/scheduler/wakeupQueue.js +3 -0
- package/dist/setup/setupCommand.js +302 -52
- package/dist/storage/compatibleTaskStore.js +102 -0
- package/dist/storage/migration/baseline.js +78 -0
- package/dist/storage/migration/classifier.js +51 -0
- package/dist/storage/migration/compatibleCodec.js +53 -0
- package/dist/storage/migration/engine.js +147 -0
- package/dist/storage/migration/index.js +33 -0
- package/dist/storage/migration/planner.js +154 -0
- package/dist/storage/migration/productionRegistry.js +486 -0
- package/dist/storage/migration/registry.js +169 -0
- package/dist/storage/migration/report.js +54 -0
- package/dist/storage/migration/types.js +31 -0
- package/dist/storage/storageSchema.js +147 -123
- package/dist/storage/storageVersions.js +11 -0
- package/dist/storage/taskStore.js +1793 -197
- package/dist/storage/upgrade/homeClassification.js +156 -0
- package/dist/storage/upgrade/homeMigrationTarget.js +595 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +315 -0
- package/dist/storage/upgrade/productionMigrationRegistry.js +6 -0
- package/dist/storage/upgrade/recordVersionScan.js +176 -0
- package/dist/storage/upgrade/recordVersions.js +159 -0
- package/dist/storage/upgrade/switchProgress.js +80 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +948 -0
- package/dist/storage/upgrade/upgradeReceipt.js +161 -0
- package/dist/storage/upgradeCoordination.js +186 -0
- package/dist/storage/upgradeFence.js +366 -0
- package/dist/task/task.js +132 -26
- package/dist/task/taskRecordReference.js +66 -0
- package/dist/tmux/commandExecutor.js +75 -2
- package/dist/tmux/tmuxManager.js +747 -49
- package/dist/version.js +23 -0
- package/dist/web/assets/assetManifest.js +62 -0
- package/dist/web/assets/client/app.js +631 -0
- package/dist/web/assets/client/components.js +605 -0
- package/dist/web/assets/client/dom.js +14 -0
- package/dist/web/assets/client/format.js +28 -0
- package/dist/web/assets/client/i18n.js +494 -0
- package/dist/web/assets/client/markdown.js +114 -0
- package/dist/web/assets/client/theme.js +32 -0
- package/dist/web/assets/client/view.js +458 -0
- package/dist/web/assets/fontData.js +12 -0
- package/dist/web/assets/fonts.js +12 -0
- package/dist/web/assets/shell.js +114 -0
- package/dist/web/assets/styles/cards.js +135 -0
- package/dist/web/assets/styles/layout.js +47 -0
- package/dist/web/assets/styles/markdown.js +29 -0
- package/dist/web/assets/styles/responsive.js +39 -0
- package/dist/web/assets/styles/tokens.js +101 -0
- package/dist/web/assets/styles/widgets.js +147 -0
- package/dist/web/tmuxWebTerminal.js +158 -0
- package/dist/web/webServer.js +463 -0
- package/dist/web/webSnapshot.js +148 -0
- package/dist/workItem/workItem.js +642 -23
- package/dist/workspace/gitChangeSetCapture.js +86 -0
- package/dist/workspace/workItemChangeSetManager.js +445 -0
- package/dist/worktree/managedWorkspace.js +202 -0
- package/docs/task-local-identity.md +62 -0
- package/i18n/README.zh-CN.md +406 -31
- package/package.json +10 -2
- package/skills/yui-leader/SKILL.md +601 -39
- package/skills/yui-operator/SKILL.md +255 -34
- package/skills/yui-reviewer/SKILL.md +57 -0
- package/skills/yui-worker/SKILL.md +214 -17
- package/dist/commands/repositoryCommands.js +0 -86
- package/dist/operator/operatorContext.js +0 -66
- package/dist/repository/repository.js +0 -55
- package/dist/scheduler/archivedTaskRuntime.js +0 -12
- package/dist/worktree/roleWorkspace.js +0 -62
|
@@ -1,9 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { callController } from "../core/controllerClient.js";
|
|
2
|
+
import { FileRuntimeEventInbox } from "./runtimeEventInbox.js";
|
|
3
|
+
import { yuiRunBodyFromInputMessage, yuiRunIdFromInputMessages } from "../run/runIdentity.js";
|
|
4
|
+
import { runtimeLifecycleSignalKey } from "../runtime/lifecycleReservation.js";
|
|
5
|
+
import { setCodexThreadName } from "../execution/codexThreadNaming.js";
|
|
6
|
+
import { openCompatibleFileTaskStore } from "../storage/compatibleTaskStore.js";
|
|
7
|
+
const NO_FINAL_ASSISTANT_MESSAGE_SUMMARY = "Native Turn completed without a final assistant message.";
|
|
8
|
+
/** Hidden CLI entrypoint used by Codex's structured notify hook. */
|
|
9
|
+
export async function runSessionNotifyCommand(payloadArgument, environment = process.env, call, setThreadName = setCodexThreadName) {
|
|
5
10
|
const params = parseCodexSessionNotification(payloadArgument, environment);
|
|
6
|
-
|
|
11
|
+
const home = requireText(environment.YUI_HOME, "YUI_HOME");
|
|
12
|
+
const enqueued = new FileRuntimeEventInbox(home).enqueueTurnCompleted({
|
|
13
|
+
scope: params.scope,
|
|
14
|
+
...(params.scope === "task" ? { taskId: params.taskId } : {}),
|
|
15
|
+
roleName: params.roleName,
|
|
16
|
+
agentId: params.agentId,
|
|
17
|
+
adapterId: params.adapterId,
|
|
18
|
+
launchId: params.launchId,
|
|
19
|
+
nativeSessionId: params.nativeSessionId,
|
|
20
|
+
turnId: params.turnId,
|
|
21
|
+
...(params.runId === undefined ? {} : { runId: params.runId }),
|
|
22
|
+
...(params.title === undefined ? {} : { title: params.title }),
|
|
23
|
+
summary: params.lastAssistantMessage
|
|
24
|
+
});
|
|
25
|
+
// The durable write is authoritative. This short socket call is only a
|
|
26
|
+
// wake-up hint and never starts or waits for a Controller process.
|
|
27
|
+
await (call ?? callController)(home, "scheduler.signal", {
|
|
28
|
+
key: runtimeLifecycleSignalKey(params.scope === "task"
|
|
29
|
+
? {
|
|
30
|
+
scope: "task",
|
|
31
|
+
taskId: params.taskId,
|
|
32
|
+
roleName: params.roleName
|
|
33
|
+
}
|
|
34
|
+
: {
|
|
35
|
+
scope: "global",
|
|
36
|
+
roleName: params.roleName
|
|
37
|
+
})
|
|
38
|
+
}, { timeoutMs: 100 }).catch(() => { });
|
|
39
|
+
if (enqueued.created && shouldSetThreadName(home, params)) {
|
|
40
|
+
const request = threadNameRequest(params, environment);
|
|
41
|
+
if (request !== null)
|
|
42
|
+
await setThreadName(request).catch(() => { });
|
|
43
|
+
}
|
|
7
44
|
}
|
|
8
45
|
export function parseCodexSessionNotification(payloadArgument, environment) {
|
|
9
46
|
const payload = parseObject(payloadArgument, "Codex notify payload");
|
|
@@ -11,6 +48,12 @@ export function parseCodexSessionNotification(payloadArgument, environment) {
|
|
|
11
48
|
throw new Error("Codex notify payload type is invalid.");
|
|
12
49
|
}
|
|
13
50
|
const nativeSessionId = requireText(payload["thread-id"], "Codex thread-id");
|
|
51
|
+
const turnId = requireText(payload["turn-id"], "Codex turn-id");
|
|
52
|
+
const lastAssistantMessage = requireAssistantMessage(payload["last-assistant-message"]);
|
|
53
|
+
const runId = yuiRunIdFromInputMessages(payload["input-messages"]);
|
|
54
|
+
const title = environment.YUI_SESSION_TITLE === undefined
|
|
55
|
+
? sessionTitleFromInputMessages(payload["input-messages"])
|
|
56
|
+
: requireText(environment.YUI_SESSION_TITLE, "YUI_SESSION_TITLE");
|
|
14
57
|
const scope = environment.YUI_SESSION_SCOPE;
|
|
15
58
|
if (scope !== "task" && scope !== "global") {
|
|
16
59
|
throw new Error("YUI_SESSION_SCOPE must be task or global.");
|
|
@@ -20,7 +63,12 @@ export function parseCodexSessionNotification(payloadArgument, environment) {
|
|
|
20
63
|
roleName: requireText(environment.YUI_ROLE, "YUI_ROLE"),
|
|
21
64
|
agentId: requireText(environment.YUI_AGENT_ID, "YUI_AGENT_ID"),
|
|
22
65
|
adapterId: requireCodexAdapter(environment.YUI_ADAPTER_ID),
|
|
23
|
-
|
|
66
|
+
launchId: requireText(environment.YUI_LAUNCH_ID, "YUI_LAUNCH_ID"),
|
|
67
|
+
nativeSessionId,
|
|
68
|
+
turnId,
|
|
69
|
+
...(runId === undefined ? {} : { runId }),
|
|
70
|
+
...(title === undefined ? {} : { title }),
|
|
71
|
+
lastAssistantMessage
|
|
24
72
|
};
|
|
25
73
|
return scope === "task"
|
|
26
74
|
? {
|
|
@@ -29,64 +77,6 @@ export function parseCodexSessionNotification(payloadArgument, environment) {
|
|
|
29
77
|
}
|
|
30
78
|
: common;
|
|
31
79
|
}
|
|
32
|
-
/** Controller-side handler; session identity never travels through an Agent prompt. */
|
|
33
|
-
export function createSessionNotifyDispatcher(store, fallback) {
|
|
34
|
-
return async (method, params) => {
|
|
35
|
-
if (method !== SESSION_BIND_METHOD) {
|
|
36
|
-
if (fallback !== undefined)
|
|
37
|
-
return fallback(method, params);
|
|
38
|
-
throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
|
|
39
|
-
}
|
|
40
|
-
const input = parseSessionBindParams(params);
|
|
41
|
-
const recorded = input.scope === "task"
|
|
42
|
-
? store.recordRuntimeNativeSession({
|
|
43
|
-
taskId: input.taskId,
|
|
44
|
-
roleName: input.roleName,
|
|
45
|
-
agentId: input.agentId,
|
|
46
|
-
adapterId: input.adapterId,
|
|
47
|
-
nativeSessionId: input.nativeSessionId
|
|
48
|
-
})
|
|
49
|
-
: store.recordGlobalRuntimeNativeSession({
|
|
50
|
-
roleName: input.roleName,
|
|
51
|
-
agentId: input.agentId,
|
|
52
|
-
adapterId: input.adapterId,
|
|
53
|
-
nativeSessionId: input.nativeSessionId
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
recorded: true,
|
|
57
|
-
scope: input.scope,
|
|
58
|
-
roleName: input.roleName,
|
|
59
|
-
nativeSessionId: recorded.nativeSessionId
|
|
60
|
-
};
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function parseSessionBindParams(value) {
|
|
64
|
-
if (!isObject(value))
|
|
65
|
-
throw invalidParams();
|
|
66
|
-
const input = value;
|
|
67
|
-
const scope = input.scope;
|
|
68
|
-
const expected = scope === "task"
|
|
69
|
-
? ["scope", "taskId", "roleName", "agentId", "adapterId", "nativeSessionId"]
|
|
70
|
-
: ["scope", "roleName", "agentId", "adapterId", "nativeSessionId"];
|
|
71
|
-
if ((scope !== "task" && scope !== "global") || !hasExactKeys(input, expected)) {
|
|
72
|
-
throw invalidParams();
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
const common = {
|
|
76
|
-
scope,
|
|
77
|
-
roleName: requireText(input.roleName, "Role name"),
|
|
78
|
-
agentId: requireText(input.agentId, "Agent id"),
|
|
79
|
-
adapterId: requireCodexAdapter(input.adapterId),
|
|
80
|
-
nativeSessionId: requireText(input.nativeSessionId, "Native session id")
|
|
81
|
-
};
|
|
82
|
-
return scope === "task"
|
|
83
|
-
? { ...common, taskId: requireText(input.taskId, "Task id") }
|
|
84
|
-
: common;
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
throw invalidParams();
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
80
|
function parseObject(value, label) {
|
|
91
81
|
if (value === undefined || Buffer.byteLength(value) > 1_048_576) {
|
|
92
82
|
throw new Error(`${label} is invalid.`);
|
|
@@ -115,22 +105,75 @@ function requireText(value, label) {
|
|
|
115
105
|
throw new Error(`${label} is invalid.`);
|
|
116
106
|
return text;
|
|
117
107
|
}
|
|
118
|
-
function
|
|
119
|
-
|
|
108
|
+
function requireAssistantMessage(value) {
|
|
109
|
+
if (value === null)
|
|
110
|
+
return NO_FINAL_ASSISTANT_MESSAGE_SUMMARY;
|
|
111
|
+
if (typeof value !== "string" || value.includes("\0")) {
|
|
112
|
+
throw new Error("Codex last assistant message is required.");
|
|
113
|
+
}
|
|
114
|
+
const text = value.trim();
|
|
115
|
+
if (text.length === 0 || Buffer.byteLength(text) > 524_288) {
|
|
116
|
+
throw new Error("Codex last assistant message is invalid.");
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
119
|
+
}
|
|
120
|
+
function sessionTitleFromInputMessages(value) {
|
|
121
|
+
if (!Array.isArray(value))
|
|
122
|
+
return undefined;
|
|
123
|
+
for (const entry of value) {
|
|
124
|
+
if (typeof entry !== "string")
|
|
125
|
+
continue;
|
|
126
|
+
const body = yuiRunBodyFromInputMessage(entry);
|
|
127
|
+
const normalized = body.trim().replaceAll(/\s+/g, " ");
|
|
128
|
+
if (normalized.length > 0)
|
|
129
|
+
return truncateSessionText(normalized);
|
|
130
|
+
}
|
|
131
|
+
return undefined;
|
|
120
132
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
133
|
+
function shouldSetThreadName(home, params) {
|
|
134
|
+
if (params.scope !== "task"
|
|
135
|
+
|| params.title === undefined
|
|
136
|
+
|| params.runId === undefined)
|
|
137
|
+
return false;
|
|
138
|
+
try {
|
|
139
|
+
const store = openCompatibleFileTaskStore(home);
|
|
140
|
+
return store.getAgentRun(params.taskId, params.runId)?.mode === "new";
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
125
145
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
146
|
+
function threadNameRequest(params, environment) {
|
|
147
|
+
if (params.title === undefined)
|
|
148
|
+
return null;
|
|
149
|
+
const command = environment.YUI_AGENT_COMMAND;
|
|
150
|
+
const encodedBaseArgs = environment.YUI_AGENT_BASE_ARGS;
|
|
151
|
+
if (command === undefined || encodedBaseArgs === undefined)
|
|
152
|
+
return null;
|
|
153
|
+
try {
|
|
154
|
+
const parsed = JSON.parse(encodedBaseArgs);
|
|
155
|
+
if (!Array.isArray(parsed)
|
|
156
|
+
|| parsed.some((value) => typeof value !== "string" || value.includes("\0"))) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
command: requireText(command, "YUI_AGENT_COMMAND"),
|
|
161
|
+
baseArgs: parsed,
|
|
162
|
+
environment,
|
|
163
|
+
threadId: params.nativeSessionId,
|
|
164
|
+
name: params.title
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
128
170
|
}
|
|
129
|
-
function
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
171
|
+
function truncateSessionText(value) {
|
|
172
|
+
const truncated = value.slice(0, 1_024);
|
|
173
|
+
return /[\uD800-\uDBFF]$/.test(truncated)
|
|
174
|
+
? truncated.slice(0, -1)
|
|
175
|
+
: truncated;
|
|
133
176
|
}
|
|
134
|
-
function
|
|
135
|
-
return
|
|
177
|
+
function isObject(value) {
|
|
178
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
136
179
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure nearest-deadline selection shared by business deadlines and runtime
|
|
3
|
+
* recovery deadlines. Invalid timestamps are ignored and equal deadlines are
|
|
4
|
+
* coalesced into one wake batch.
|
|
5
|
+
*/
|
|
6
|
+
export function nearestDeadlineBatch(deadlines) {
|
|
7
|
+
const valid = deadlines.filter((deadline) => Number.isFinite(deadline.at));
|
|
8
|
+
if (valid.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
const at = Math.min(...valid.map((deadline) => deadline.at));
|
|
11
|
+
return {
|
|
12
|
+
at,
|
|
13
|
+
keys: [...new Set(valid.filter((deadline) => deadline.at === at).map((deadline) => deadline.key))]
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const DEFAULT_WINDOW_MS = 100;
|
|
2
|
+
/**
|
|
3
|
+
* Coalesces dirty mailbox keys over a fixed window. The first signal starts
|
|
4
|
+
* the window; later signals never extend it. A signal received during a batch
|
|
5
|
+
* belongs to a later batch, and batches are never processed concurrently.
|
|
6
|
+
*/
|
|
7
|
+
export class MailboxScheduler {
|
|
8
|
+
processBatch;
|
|
9
|
+
#windowMs;
|
|
10
|
+
#setTimer;
|
|
11
|
+
#clearTimer;
|
|
12
|
+
#onError;
|
|
13
|
+
#pendingKeys = new Set();
|
|
14
|
+
#timer;
|
|
15
|
+
#current;
|
|
16
|
+
#drainRequested = false;
|
|
17
|
+
#stopped = false;
|
|
18
|
+
constructor(processBatch, options = {}) {
|
|
19
|
+
this.processBatch = processBatch;
|
|
20
|
+
this.#windowMs = nonNegativeFinite(options.windowMs ?? DEFAULT_WINDOW_MS);
|
|
21
|
+
this.#setTimer = options.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs));
|
|
22
|
+
this.#clearTimer = options.clearTimer ?? ((timer) => {
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
});
|
|
25
|
+
this.#onError = options.onError ?? (() => { });
|
|
26
|
+
}
|
|
27
|
+
signal(key) {
|
|
28
|
+
if (this.#stopped)
|
|
29
|
+
return;
|
|
30
|
+
this.#pendingKeys.add(key);
|
|
31
|
+
this.schedule();
|
|
32
|
+
}
|
|
33
|
+
schedule() {
|
|
34
|
+
if (this.#stopped
|
|
35
|
+
|| this.#pendingKeys.size === 0
|
|
36
|
+
|| this.#timer !== undefined) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.#timer = this.#setTimer(() => {
|
|
40
|
+
this.#timer = undefined;
|
|
41
|
+
if (this.#current !== undefined) {
|
|
42
|
+
this.#drainRequested = true;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
void this.drain().catch(this.#onError);
|
|
46
|
+
}, this.#windowMs);
|
|
47
|
+
}
|
|
48
|
+
drain() {
|
|
49
|
+
if (this.#stopped)
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
this.#cancelTimer();
|
|
52
|
+
if (this.#current !== undefined) {
|
|
53
|
+
this.#drainRequested = true;
|
|
54
|
+
return this.#current;
|
|
55
|
+
}
|
|
56
|
+
if (this.#pendingKeys.size === 0)
|
|
57
|
+
return Promise.resolve();
|
|
58
|
+
// Defer invoking the callback until #current is installed. This closes the
|
|
59
|
+
// small reentrancy window where a synchronous callback could call drain()
|
|
60
|
+
// before the scheduler knew a drain was already active.
|
|
61
|
+
const running = Promise.resolve().then(() => this.#drainDueBatches());
|
|
62
|
+
this.#current = running;
|
|
63
|
+
void running.finally(() => {
|
|
64
|
+
if (this.#current !== running)
|
|
65
|
+
return;
|
|
66
|
+
this.#current = undefined;
|
|
67
|
+
if (this.#stopped || this.#pendingKeys.size === 0)
|
|
68
|
+
return;
|
|
69
|
+
if (this.#drainRequested) {
|
|
70
|
+
this.#drainRequested = false;
|
|
71
|
+
void this.drain().catch(this.#onError);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.schedule();
|
|
75
|
+
}
|
|
76
|
+
}).catch(() => { });
|
|
77
|
+
return running;
|
|
78
|
+
}
|
|
79
|
+
stop() {
|
|
80
|
+
this.#stopped = true;
|
|
81
|
+
this.#cancelTimer();
|
|
82
|
+
this.#pendingKeys.clear();
|
|
83
|
+
this.#drainRequested = false;
|
|
84
|
+
}
|
|
85
|
+
async #drainDueBatches() {
|
|
86
|
+
do {
|
|
87
|
+
this.#drainRequested = false;
|
|
88
|
+
const keys = [...this.#pendingKeys];
|
|
89
|
+
this.#pendingKeys.clear();
|
|
90
|
+
if (keys.length > 0)
|
|
91
|
+
await this.processBatch(keys);
|
|
92
|
+
} while (!this.#stopped
|
|
93
|
+
&& this.#drainRequested
|
|
94
|
+
&& this.#pendingKeys.size > 0);
|
|
95
|
+
}
|
|
96
|
+
#cancelTimer() {
|
|
97
|
+
if (this.#timer === undefined)
|
|
98
|
+
return;
|
|
99
|
+
this.#clearTimer(this.#timer);
|
|
100
|
+
this.#timer = undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function nonNegativeFinite(value) {
|
|
104
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
105
|
+
throw new Error("Mailbox scheduler window must be a non-negative finite number.");
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { validateTaskRecordReference } from "../task/taskRecordReference.js";
|
|
2
|
+
function requireText(value, label) {
|
|
3
|
+
const normalized = value.trim();
|
|
4
|
+
if (normalized.length === 0) {
|
|
5
|
+
throw new Error(`${label} must not be empty`);
|
|
6
|
+
}
|
|
7
|
+
return normalized;
|
|
8
|
+
}
|
|
9
|
+
function requireInteger(value, minimum, label) {
|
|
10
|
+
if (!Number.isInteger(value) || value < minimum) {
|
|
11
|
+
throw new Error(`${label} must be an integer greater than or equal to ${minimum}`);
|
|
12
|
+
}
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function record(value, label) {
|
|
16
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
17
|
+
throw new Error(`${label} must be an object`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
function exact(value, keys, label) {
|
|
22
|
+
const expected = new Set(keys);
|
|
23
|
+
const unknown = Object.keys(value).find((key) => !expected.has(key));
|
|
24
|
+
if (unknown !== undefined)
|
|
25
|
+
throw new Error(`${label} has unknown field: ${unknown}`);
|
|
26
|
+
const missing = keys.find((key) => !Object.hasOwn(value, key));
|
|
27
|
+
if (missing !== undefined)
|
|
28
|
+
throw new Error(`${label} is missing field: ${missing}`);
|
|
29
|
+
}
|
|
30
|
+
function requireString(value, label) {
|
|
31
|
+
if (typeof value !== "string")
|
|
32
|
+
throw new Error(`${label} must be a string`);
|
|
33
|
+
return requireText(value, label);
|
|
34
|
+
}
|
|
35
|
+
function requireTimestamp(value, label) {
|
|
36
|
+
const timestamp = requireString(value, label);
|
|
37
|
+
if (!Number.isFinite(Date.parse(timestamp)))
|
|
38
|
+
throw new Error(`${label} must be a timestamp`);
|
|
39
|
+
return timestamp;
|
|
40
|
+
}
|
|
41
|
+
function requireStringArray(value, label) {
|
|
42
|
+
if (!Array.isArray(value))
|
|
43
|
+
throw new Error(`${label} must be an array`);
|
|
44
|
+
const result = value.map((item, index) => requireString(item, `${label}[${index}]`));
|
|
45
|
+
if (new Set(result).size !== result.length)
|
|
46
|
+
throw new Error(`${label} must not contain duplicates`);
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
function copyTarget(target) {
|
|
50
|
+
switch (target.kind) {
|
|
51
|
+
case "task":
|
|
52
|
+
return { kind: "task", taskId: requireText(target.taskId, "taskId") };
|
|
53
|
+
case "role":
|
|
54
|
+
return {
|
|
55
|
+
kind: "role",
|
|
56
|
+
taskId: requireText(target.taskId, "taskId"),
|
|
57
|
+
roleName: requireText(target.roleName, "roleName")
|
|
58
|
+
};
|
|
59
|
+
case "role-runtime":
|
|
60
|
+
return {
|
|
61
|
+
kind: "role-runtime",
|
|
62
|
+
taskId: requireText(target.taskId, "taskId"),
|
|
63
|
+
roleName: requireText(target.roleName, "roleName")
|
|
64
|
+
};
|
|
65
|
+
case "global-role-runtime":
|
|
66
|
+
return {
|
|
67
|
+
kind: "global-role-runtime",
|
|
68
|
+
roleName: requireText(target.roleName, "roleName")
|
|
69
|
+
};
|
|
70
|
+
case "operator":
|
|
71
|
+
return { kind: "operator" };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function mailboxTargetKey(target) {
|
|
75
|
+
const copied = copyTarget(target);
|
|
76
|
+
switch (copied.kind) {
|
|
77
|
+
case "operator": return "operator";
|
|
78
|
+
case "task": return `task/${encodeURIComponent(copied.taskId)}`;
|
|
79
|
+
case "role": return `role/${encodeURIComponent(copied.taskId)}/${encodeURIComponent(copied.roleName)}`;
|
|
80
|
+
case "role-runtime":
|
|
81
|
+
return `role-runtime/${encodeURIComponent(copied.taskId)}/${encodeURIComponent(copied.roleName)}`;
|
|
82
|
+
case "global-role-runtime":
|
|
83
|
+
return `global-role-runtime/${encodeURIComponent(copied.roleName)}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function copyRef(ref) {
|
|
87
|
+
if (!("taskId" in ref)) {
|
|
88
|
+
if (ref.type !== "task" && ref.type !== "session") {
|
|
89
|
+
throw new Error(`entity reference taskId is required for ${ref.type}`);
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
type: ref.type,
|
|
93
|
+
id: requireText(ref.id, "entity reference id")
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const validated = validateTaskRecordReference({
|
|
97
|
+
taskId: ref.taskId,
|
|
98
|
+
localId: ref.id
|
|
99
|
+
}, mailboxTaskRecordKind(ref.type));
|
|
100
|
+
return {
|
|
101
|
+
type: ref.type,
|
|
102
|
+
taskId: validated.taskId,
|
|
103
|
+
id: validated.localId
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function mailboxEntityRefKey(ref) {
|
|
107
|
+
return !("taskId" in ref)
|
|
108
|
+
? `${ref.type}\u0000${ref.id}`
|
|
109
|
+
: `${ref.type}\u0000${ref.taskId}\u0000${ref.id}`;
|
|
110
|
+
}
|
|
111
|
+
function appendUnique(existing, incoming, keyOf) {
|
|
112
|
+
const result = [...existing];
|
|
113
|
+
const keys = new Set(existing.map(keyOf));
|
|
114
|
+
for (const value of incoming) {
|
|
115
|
+
const key = keyOf(value);
|
|
116
|
+
if (!keys.has(key)) {
|
|
117
|
+
keys.add(key);
|
|
118
|
+
result.push(value);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
function mergeBatches(earlier, later) {
|
|
124
|
+
const merged = {
|
|
125
|
+
fromSequence: earlier.fromSequence,
|
|
126
|
+
toSequence: later.toSequence,
|
|
127
|
+
reasons: appendUnique(earlier.reasons, later.reasons, (reason) => reason),
|
|
128
|
+
refs: appendUnique(earlier.refs, later.refs, mailboxEntityRefKey),
|
|
129
|
+
requestCount: earlier.requestCount + later.requestCount,
|
|
130
|
+
firstQueuedAt: earlier.firstQueuedAt,
|
|
131
|
+
lastQueuedAt: later.lastQueuedAt
|
|
132
|
+
};
|
|
133
|
+
return merged;
|
|
134
|
+
}
|
|
135
|
+
function requireProcessing(mailbox, batchId) {
|
|
136
|
+
const normalizedBatchId = requireText(batchId, "batch id");
|
|
137
|
+
if (mailbox.processing === null) {
|
|
138
|
+
throw new Error("Mailbox has no processing batch");
|
|
139
|
+
}
|
|
140
|
+
if (mailbox.processing.batchId !== normalizedBatchId) {
|
|
141
|
+
throw new Error(`Mailbox processing batch id does not match ${normalizedBatchId}`);
|
|
142
|
+
}
|
|
143
|
+
return mailbox.processing;
|
|
144
|
+
}
|
|
145
|
+
export function createWorkMailbox(target) {
|
|
146
|
+
return {
|
|
147
|
+
schemaVersion: 1,
|
|
148
|
+
target: copyTarget(target),
|
|
149
|
+
nextSequence: 1,
|
|
150
|
+
processing: null,
|
|
151
|
+
pending: null
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function validateWorkMailbox(value) {
|
|
155
|
+
const mailbox = record(value, "WorkMailbox");
|
|
156
|
+
exact(mailbox, ["schemaVersion", "target", "nextSequence", "processing", "pending"], "WorkMailbox");
|
|
157
|
+
if (mailbox.schemaVersion !== 1)
|
|
158
|
+
throw new Error("WorkMailbox must use schemaVersion 1");
|
|
159
|
+
const target = parseTarget(mailbox.target);
|
|
160
|
+
const nextSequence = requireInteger(mailbox.nextSequence, 1, "WorkMailbox nextSequence");
|
|
161
|
+
const processing = mailbox.processing === null ? null : parseProcessing(mailbox.processing);
|
|
162
|
+
const pending = mailbox.pending === null ? null : parseBatch(mailbox.pending, "WorkMailbox pending");
|
|
163
|
+
const batches = [processing?.batch, pending].filter((batch) => batch !== null && batch !== undefined);
|
|
164
|
+
for (const batch of batches) {
|
|
165
|
+
if (batch.toSequence >= nextSequence) {
|
|
166
|
+
throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (processing !== null && pending !== null && processing.batch.toSequence >= pending.fromSequence) {
|
|
170
|
+
throw new Error("WorkMailbox processing and pending sequences overlap");
|
|
171
|
+
}
|
|
172
|
+
return { schemaVersion: 1, target, nextSequence, processing, pending };
|
|
173
|
+
}
|
|
174
|
+
function parseTarget(value) {
|
|
175
|
+
const target = record(value, "WorkMailbox target");
|
|
176
|
+
switch (target.kind) {
|
|
177
|
+
case "operator":
|
|
178
|
+
exact(target, ["kind"], "WorkMailbox operator target");
|
|
179
|
+
return { kind: "operator" };
|
|
180
|
+
case "task":
|
|
181
|
+
exact(target, ["kind", "taskId"], "WorkMailbox task target");
|
|
182
|
+
return { kind: "task", taskId: requireString(target.taskId, "WorkMailbox target taskId") };
|
|
183
|
+
case "role":
|
|
184
|
+
exact(target, ["kind", "taskId", "roleName"], "WorkMailbox role target");
|
|
185
|
+
return {
|
|
186
|
+
kind: "role",
|
|
187
|
+
taskId: requireString(target.taskId, "WorkMailbox target taskId"),
|
|
188
|
+
roleName: requireString(target.roleName, "WorkMailbox target roleName")
|
|
189
|
+
};
|
|
190
|
+
case "role-runtime":
|
|
191
|
+
exact(target, ["kind", "taskId", "roleName"], "WorkMailbox role runtime target");
|
|
192
|
+
return {
|
|
193
|
+
kind: "role-runtime",
|
|
194
|
+
taskId: requireString(target.taskId, "WorkMailbox target taskId"),
|
|
195
|
+
roleName: requireString(target.roleName, "WorkMailbox target roleName")
|
|
196
|
+
};
|
|
197
|
+
case "global-role-runtime":
|
|
198
|
+
exact(target, ["kind", "roleName"], "WorkMailbox global role runtime target");
|
|
199
|
+
return {
|
|
200
|
+
kind: "global-role-runtime",
|
|
201
|
+
roleName: requireString(target.roleName, "WorkMailbox target roleName")
|
|
202
|
+
};
|
|
203
|
+
default:
|
|
204
|
+
throw new Error("WorkMailbox target kind is invalid");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function parseRef(value, label) {
|
|
208
|
+
const ref = record(value, label);
|
|
209
|
+
const types = ["task", "run", "work-item", "input", "session", "message"];
|
|
210
|
+
if (!types.includes(ref.type))
|
|
211
|
+
throw new Error(`${label} type is invalid`);
|
|
212
|
+
if (ref.type === "task" || ref.type === "session") {
|
|
213
|
+
exact(ref, ["type", "id"], label);
|
|
214
|
+
return { type: ref.type, id: requireString(ref.id, `${label} id`) };
|
|
215
|
+
}
|
|
216
|
+
exact(ref, ["type", "taskId", "id"], label);
|
|
217
|
+
return copyRef({
|
|
218
|
+
type: ref.type,
|
|
219
|
+
taskId: requireString(ref.taskId, `${label} taskId`),
|
|
220
|
+
id: requireString(ref.id, `${label} id`)
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function parseBatch(value, label) {
|
|
224
|
+
const batch = record(value, label);
|
|
225
|
+
const required = ["fromSequence", "toSequence", "reasons", "refs", "requestCount", "firstQueuedAt", "lastQueuedAt"];
|
|
226
|
+
exact(batch, required, label);
|
|
227
|
+
const fromSequence = requireInteger(batch.fromSequence, 1, `${label} fromSequence`);
|
|
228
|
+
const toSequence = requireInteger(batch.toSequence, fromSequence, `${label} toSequence`);
|
|
229
|
+
const requestCount = requireInteger(batch.requestCount, 1, `${label} requestCount`);
|
|
230
|
+
if (requestCount !== toSequence - fromSequence + 1) {
|
|
231
|
+
throw new Error(`${label} requestCount does not match its sequence range`);
|
|
232
|
+
}
|
|
233
|
+
const reasons = requireStringArray(batch.reasons, `${label} reasons`);
|
|
234
|
+
if (reasons.length === 0)
|
|
235
|
+
throw new Error(`${label} reasons must not be empty`);
|
|
236
|
+
if (!Array.isArray(batch.refs))
|
|
237
|
+
throw new Error(`${label} refs must be an array`);
|
|
238
|
+
const refs = batch.refs.map((ref, index) => parseRef(ref, `${label} refs[${index}]`));
|
|
239
|
+
const refKeys = refs.map(mailboxEntityRefKey);
|
|
240
|
+
if (new Set(refKeys).size !== refKeys.length)
|
|
241
|
+
throw new Error(`${label} refs must not contain duplicates`);
|
|
242
|
+
const result = {
|
|
243
|
+
fromSequence,
|
|
244
|
+
toSequence,
|
|
245
|
+
reasons,
|
|
246
|
+
refs,
|
|
247
|
+
requestCount,
|
|
248
|
+
firstQueuedAt: requireTimestamp(batch.firstQueuedAt, `${label} firstQueuedAt`),
|
|
249
|
+
lastQueuedAt: requireTimestamp(batch.lastQueuedAt, `${label} lastQueuedAt`)
|
|
250
|
+
};
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
function parseProcessing(value) {
|
|
254
|
+
const processing = record(value, "WorkMailbox processing");
|
|
255
|
+
const required = ["batchId", "batch", "owner", "startedAt"];
|
|
256
|
+
exact(processing, processing.executionRef === undefined ? required : [...required, "executionRef"], "WorkMailbox processing");
|
|
257
|
+
const result = {
|
|
258
|
+
batchId: requireString(processing.batchId, "WorkMailbox processing batchId"),
|
|
259
|
+
batch: parseBatch(processing.batch, "WorkMailbox processing batch"),
|
|
260
|
+
owner: requireString(processing.owner, "WorkMailbox processing owner"),
|
|
261
|
+
startedAt: requireTimestamp(processing.startedAt, "WorkMailbox processing startedAt")
|
|
262
|
+
};
|
|
263
|
+
return processing.executionRef === undefined
|
|
264
|
+
? result
|
|
265
|
+
: { ...result, executionRef: parseRef(processing.executionRef, "WorkMailbox processing executionRef") };
|
|
266
|
+
}
|
|
267
|
+
export function enqueueSignal(mailbox, signal) {
|
|
268
|
+
const reason = requireText(signal.reason, "signal reason");
|
|
269
|
+
const occurredAt = requireText(signal.occurredAt, "signal occurredAt");
|
|
270
|
+
const refs = signal.refs.map(copyRef);
|
|
271
|
+
const sequence = mailbox.nextSequence;
|
|
272
|
+
const incoming = {
|
|
273
|
+
fromSequence: sequence,
|
|
274
|
+
toSequence: sequence,
|
|
275
|
+
reasons: [reason],
|
|
276
|
+
refs: appendUnique([], refs, mailboxEntityRefKey),
|
|
277
|
+
requestCount: 1,
|
|
278
|
+
firstQueuedAt: occurredAt,
|
|
279
|
+
lastQueuedAt: occurredAt
|
|
280
|
+
};
|
|
281
|
+
return {
|
|
282
|
+
...mailbox,
|
|
283
|
+
nextSequence: sequence + 1,
|
|
284
|
+
pending: mailbox.pending === null
|
|
285
|
+
? incoming
|
|
286
|
+
: mergeBatches(mailbox.pending, incoming)
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function mailboxTaskRecordKind(type) {
|
|
290
|
+
switch (type) {
|
|
291
|
+
case "run": return "agentRun";
|
|
292
|
+
case "work-item": return "workItem";
|
|
293
|
+
case "input": return "inputRequest";
|
|
294
|
+
case "message": return "message";
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
export function claimPending(mailbox, options) {
|
|
298
|
+
if (mailbox.processing !== null) {
|
|
299
|
+
throw new Error("Mailbox is already processing a batch");
|
|
300
|
+
}
|
|
301
|
+
if (mailbox.pending === null) {
|
|
302
|
+
throw new Error("Mailbox has no pending work to claim");
|
|
303
|
+
}
|
|
304
|
+
const processing = {
|
|
305
|
+
batchId: requireText(options.batchId, "batch id"),
|
|
306
|
+
batch: mailbox.pending,
|
|
307
|
+
owner: requireText(options.owner, "claim owner"),
|
|
308
|
+
startedAt: requireText(options.startedAt, "claim startedAt")
|
|
309
|
+
};
|
|
310
|
+
return { ...mailbox, processing, pending: null };
|
|
311
|
+
}
|
|
312
|
+
export function bindExecution(mailbox, batchId, executionRef) {
|
|
313
|
+
const processing = requireProcessing(mailbox, batchId);
|
|
314
|
+
return {
|
|
315
|
+
...mailbox,
|
|
316
|
+
processing: { ...processing, executionRef: copyRef(executionRef) }
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
export function completeProcessing(mailbox, batchId) {
|
|
320
|
+
requireProcessing(mailbox, batchId);
|
|
321
|
+
return { ...mailbox, processing: null };
|
|
322
|
+
}
|
|
323
|
+
export function releaseProcessing(mailbox, batchId) {
|
|
324
|
+
const processing = requireProcessing(mailbox, batchId);
|
|
325
|
+
const released = mailbox.pending === null
|
|
326
|
+
? processing.batch
|
|
327
|
+
: mergeBatches(processing.batch, mailbox.pending);
|
|
328
|
+
return { ...mailbox, processing: null, pending: released };
|
|
329
|
+
}
|