@zq-silk/yui 2.6.0 → 2.7.1
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 +23 -0
- package/dist/cli/commandCatalog.js +28 -2
- package/dist/commands/globalRoleCommands.js +22 -2
- package/dist/commands/inboxCommands.js +352 -0
- package/dist/commands/roleSkillValidation.js +2 -0
- package/dist/commands/taskCommands.js +32 -4
- package/dist/context/currentSkills.js +48 -0
- package/dist/context/roleSessionContext.js +13 -16
- package/dist/context/runContextPack.js +2 -0
- package/dist/context/runInputContract.js +1 -1
- package/dist/context/sessionBootstrapManifest.js +27 -4
- package/dist/context/skillCatalog.js +2 -3
- package/dist/context/taskContext.js +2 -0
- package/dist/controlPlaneCli.js +18 -1
- package/dist/controller/controller.js +3 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +19 -4
- package/dist/controller/globalInputDelivery.js +73 -15
- package/dist/controller/globalRuntimeAttention.js +1 -1
- package/dist/controller/operatorNotification.js +6 -1
- package/dist/controller/providerRetryDelivery.js +3 -5
- package/dist/controller/runtime.js +2 -2
- package/dist/doctor/doctor.js +25 -2
- package/dist/executor/effectiveLaunch.js +2 -2
- package/dist/executor/fileRoleLaunchPlanner.js +4 -6
- package/dist/interaction/notificationPresentation.js +2 -2
- package/dist/interaction/operatorPresentation.js +1 -1
- package/dist/message/globalInputBacklog.js +69 -0
- package/dist/message/globalMessageQuery.js +1 -0
- package/dist/message/inboxReceipt.js +32 -0
- package/dist/message/message.js +6 -1
- package/dist/message/messageContinuation.js +34 -13
- package/dist/message/taskInboxOwnership.js +21 -0
- package/dist/nativeAgent/session/backends.js +1 -1
- package/dist/observability/runtimeIdentity.js +1 -1
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/runtime/agentHost.js +105 -16
- package/dist/scheduler/operatorEvent.js +49 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +5 -4
- package/dist/scheduler/taskWake.js +5 -0
- package/dist/sqlite/driver.js +108 -0
- package/dist/storage/sqliteSchema.js +11 -1
- package/dist/storage/sqliteStore.js +60 -1
- package/dist/storage/storageSchema.js +1 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +1 -1
- package/dist/task/completionReadiness.js +11 -4
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/docs/managed-turn-and-session-runtime.md +81 -5
- package/docs/managed-turn-and-session-runtime.zh-CN.md +51 -3
- package/docs/release-workflow.md +47 -2
- package/docs/release-workflow.zh-CN.md +34 -1
- package/docs/storage-baseline.md +17 -7
- package/docs/storage-baseline.zh-CN.md +11 -5
- package/docs/task-delivery.md +17 -8
- package/docs/task-delivery.zh-CN.md +11 -5
- package/docs/testing/verification-levels.md +22 -4
- package/docs/testing/verification-levels.zh-CN.md +8 -1
- package/i18n/README.zh-CN.md +19 -0
- package/package.json +5 -2
- package/skills/yui-leader/SKILL.md +28 -11
- package/skills/yui-leader/VERSION +1 -0
- package/skills/yui-operator/SKILL.md +10 -2
- package/skills/yui-operator/VERSION +1 -0
- package/skills/yui-operator/references/task-delivery.md +2 -0
- package/skills/yui-reviewer/SKILL.md +8 -1
- package/skills/yui-reviewer/VERSION +1 -0
- package/skills/yui-runtime/SKILL.md +94 -11
- package/skills/yui-runtime/VERSION +1 -0
- package/skills/yui-runtime/references/publication.md +16 -5
- package/skills/yui-worker/SKILL.md +8 -1
- package/skills/yui-worker/VERSION +1 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { accessSync, constants, lstatSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
export const BUILTIN_YUI_SKILLS = new Set([
|
|
5
|
+
"yui-runtime", "yui-operator", "yui-leader", "yui-worker", "yui-reviewer"
|
|
6
|
+
]);
|
|
7
|
+
export function currentSkillPath(home, id) {
|
|
8
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]*$/u.test(id))
|
|
9
|
+
throw new Error(`Invalid configured Skill id: ${id}.`);
|
|
10
|
+
if (BUILTIN_YUI_SKILLS.has(id))
|
|
11
|
+
return fileURLToPath(new URL(`../../skills/${id}`, import.meta.url));
|
|
12
|
+
if (home === undefined)
|
|
13
|
+
throw new Error("YUI_HOME is required to load configured Role skills.");
|
|
14
|
+
return resolve(home, "skills", id);
|
|
15
|
+
}
|
|
16
|
+
/** Per-turn metadata only: never read the body, traverse resources, snapshot a
|
|
17
|
+
* package, or claim that the model still has instructions in its context. */
|
|
18
|
+
export function checkCurrentSkills(home, ids) {
|
|
19
|
+
return [...new Set(ids)].map(id => {
|
|
20
|
+
const path = currentSkillPath(home, id);
|
|
21
|
+
const versionPath = join(path, "VERSION");
|
|
22
|
+
try {
|
|
23
|
+
for (const file of [join(path, "SKILL.md"), versionPath]) {
|
|
24
|
+
if (realpathSync(file) !== resolve(file) || !lstatSync(file).isFile()) {
|
|
25
|
+
throw new Error(`Skill files must be regular files without symlinks: ${file}.`);
|
|
26
|
+
}
|
|
27
|
+
accessSync(file, constants.R_OK);
|
|
28
|
+
}
|
|
29
|
+
if (lstatSync(versionPath).size > 128)
|
|
30
|
+
throw new Error("VERSION exceeds 128 bytes.");
|
|
31
|
+
const version = readFileSync(versionPath, "utf8").trim();
|
|
32
|
+
if (!/^[1-9][0-9]{0,8}$/u.test(version))
|
|
33
|
+
throw new Error("VERSION must contain a positive internal integer.");
|
|
34
|
+
return { id, path, version, versionPath };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new Error(`Current Skill ${id} is missing, unreadable or invalid at ${path}. `
|
|
38
|
+
+ `${error instanceof Error ? error.message : String(error)} `
|
|
39
|
+
+ "Restore the current installation/configured Skill (including VERSION) in this Home, then check again; "
|
|
40
|
+
+ "do not replace the Session or replay input to repair Skill loading.", { cause: error });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export const SKILL_CHECK_COMMAND = "yui session skills --json";
|
|
45
|
+
export const SKILL_REUSE_RULE = "At the start of every turn, check current Skill versions and availability. "
|
|
46
|
+
+ "Read full SKILL.md and necessary references on first entry, version change, recovery, "
|
|
47
|
+
+ "or missing/compacted/uncertain context; reuse only unchanged full instructions available now. "
|
|
48
|
+
+ "A version check is not a loading receipt or authority.";
|
|
@@ -3,29 +3,18 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { snapshotSkillPackage, verifySkillPackage } from "./skillPackage.js";
|
|
6
|
+
import { BUILTIN_YUI_SKILLS, checkCurrentSkills } from "./currentSkills.js";
|
|
7
|
+
export { BUILTIN_YUI_SKILLS } from "./currentSkills.js";
|
|
6
8
|
import { SYSTEM_LEADER_ROLE, SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
7
|
-
export const BUILTIN_YUI_SKILLS = new Set([
|
|
8
|
-
"yui-runtime",
|
|
9
|
-
"yui-operator",
|
|
10
|
-
"yui-leader",
|
|
11
|
-
"yui-worker",
|
|
12
|
-
"yui-reviewer"
|
|
13
|
-
]);
|
|
14
9
|
/**
|
|
15
10
|
* Compiles stable Role policy for an Agent-native instruction channel.
|
|
16
11
|
* Dynamic wakeups and AgentRun assignments deliberately remain outside this value.
|
|
17
12
|
*/
|
|
18
13
|
export function compileRoleSessionContext(yuiHome, role, owner, options = {}) {
|
|
19
14
|
const kind = roleSessionKind(role, owner, options.purpose ?? "execution");
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
...(builtInSkillId === undefined ? [] : [builtInSkillId]),
|
|
24
|
-
...(role.skills ?? [])
|
|
25
|
-
]);
|
|
26
|
-
const skills = options.skillPackages === undefined
|
|
27
|
-
? loadYuiSkillContexts(yuiHome, skillIds)
|
|
28
|
-
: options.skillPackages.map(loadSkillPackageContext);
|
|
15
|
+
const skills = checkCurrentSkills(yuiHome, roleSkillIds(role, kind)).map(skill => ({
|
|
16
|
+
...skill, content: readFileSync(join(skill.path, "SKILL.md"), "utf8").trim()
|
|
17
|
+
}));
|
|
29
18
|
return {
|
|
30
19
|
developerInstructions: renderDeveloperInstructions(kind, role, owner),
|
|
31
20
|
skills,
|
|
@@ -34,6 +23,14 @@ export function compileRoleSessionContext(yuiHome, role, owner, options = {}) {
|
|
|
34
23
|
: { managedContextFile: roleContextFile(yuiHome, role, owner, kind) })
|
|
35
24
|
};
|
|
36
25
|
}
|
|
26
|
+
export function roleSkillIds(role, kind) {
|
|
27
|
+
const builtInSkillId = kind === "global" ? undefined : `yui-${kind}`;
|
|
28
|
+
return unique([
|
|
29
|
+
"yui-runtime",
|
|
30
|
+
...(builtInSkillId === undefined ? [] : [builtInSkillId]),
|
|
31
|
+
...(role.skills ?? [])
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
37
34
|
function renderDeveloperInstructions(kind, role, owner) {
|
|
38
35
|
const core = renderRoleCore(kind, role, owner);
|
|
39
36
|
const profile = [
|
|
@@ -344,6 +344,8 @@ export function buildRunContextPack(store, taskId, runId) {
|
|
|
344
344
|
const preliminaryBytes = Buffer.byteLength(JSON.stringify({ ...body, digest }), "utf8");
|
|
345
345
|
const pack = Object.freeze({
|
|
346
346
|
...body,
|
|
347
|
+
// Current loading guidance is not part of the frozen Assignment digest.
|
|
348
|
+
skillCheckCommand: "yui session skills --json",
|
|
347
349
|
inventory: Object.freeze({
|
|
348
350
|
refs: pointers.length,
|
|
349
351
|
frozenBytes: preliminaryBytes,
|
|
@@ -79,7 +79,7 @@ export function serializeRunInputEnvelope(value) {
|
|
|
79
79
|
.join(",");
|
|
80
80
|
const snapshot = normalized.contextSnapshotRef;
|
|
81
81
|
const lines = [
|
|
82
|
-
"Yui AgentRun input. Follow the Session Manifest and
|
|
82
|
+
"Yui AgentRun input. Check current Skills: yui session skills --json. Follow the Session Manifest profile and current Skills.",
|
|
83
83
|
`${normalized.subject.taskId === undefined ? "" : `task=${normalized.subject.taskId} `}run=${normalized.runId} role=${normalized.roleName}`,
|
|
84
84
|
`source=${normalized.source.type}/${normalized.source.channel} purpose=${normalized.purpose} subject=${subject || "global"} snapshot=${snapshot === undefined ? "none" : `${snapshot.id}@${snapshot.digest}`}`,
|
|
85
85
|
normalized.deltaRefIds.length === 0
|
|
@@ -3,6 +3,7 @@ import { chmodSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
4
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
5
5
|
import { validateSkillPackageRef } from "./skillPackage.js";
|
|
6
|
+
import { checkCurrentSkills, SKILL_CHECK_COMMAND, SKILL_REUSE_RULE } from "./currentSkills.js";
|
|
6
7
|
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
7
8
|
export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
8
9
|
/** Portable read pointers accompany every managed input, including the first
|
|
@@ -20,9 +21,22 @@ export function withSessionContextPointer(text, environment) {
|
|
|
20
21
|
/** No remembered/sent flag can prove instructions survived context compaction. */
|
|
21
22
|
export function sessionContextInstructions(manifestPath, digest) {
|
|
22
23
|
return `Yui Session Manifest: ${manifestPath}${digest === undefined ? "" : ` (digest ${digest})`}. `
|
|
23
|
-
+
|
|
24
|
-
+
|
|
25
|
-
+ "
|
|
24
|
+
+ `Before work on every turn run the Manifest's contextProtocol.skillCheckCommand `
|
|
25
|
+
+ `(older Manifests: ${SKILL_CHECK_COMMAND}; Global loadCommand also checks current Skills). `
|
|
26
|
+
+ "Use its current stable Skill paths instead of historical frozen-package paths; preserve the Manifest's Role profile. "
|
|
27
|
+
+ SKILL_REUSE_RULE;
|
|
28
|
+
}
|
|
29
|
+
/** The caller must already pass the ordinary managed Session/CLI/Home checks.
|
|
30
|
+
* Old manifests remain immutable audit records, while this read resolves their
|
|
31
|
+
* Skill IDs against the currently executing installation and Home. */
|
|
32
|
+
export function checkSessionSkills(home, manifestPath) {
|
|
33
|
+
const manifest = readSessionBootstrapManifest(manifestPath);
|
|
34
|
+
return {
|
|
35
|
+
skills: checkCurrentSkills(home, manifest.skills.map(skill => skill.id)),
|
|
36
|
+
roleProfileRef: manifest.roleProfileRef,
|
|
37
|
+
rule: SKILL_REUSE_RULE,
|
|
38
|
+
historicalPackagesAreAuditOnly: true
|
|
39
|
+
};
|
|
26
40
|
}
|
|
27
41
|
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
28
42
|
export function readSessionBootstrapManifest(path) {
|
|
@@ -76,6 +90,10 @@ export function readSessionBootstrapManifest(path) {
|
|
|
76
90
|
requireText(entry.id, "Session Manifest Skill id");
|
|
77
91
|
requireText(entry.path, "Session Manifest Skill path");
|
|
78
92
|
requireDigest(entry.digest, "Session Manifest Skill digest");
|
|
93
|
+
if (entry.version !== undefined) {
|
|
94
|
+
requireText(entry.version, "Session Manifest Skill version");
|
|
95
|
+
requireText(entry.versionPath, "Session Manifest Skill version path");
|
|
96
|
+
}
|
|
79
97
|
if (entry.package !== undefined) {
|
|
80
98
|
const ref = entry.package;
|
|
81
99
|
validateSkillPackageRef(ref);
|
|
@@ -121,6 +139,7 @@ export function materializeSessionBootstrap(input) {
|
|
|
121
139
|
id: skill.id,
|
|
122
140
|
path: skill.path,
|
|
123
141
|
digest: skill.package?.digest ?? digest(skill.content),
|
|
142
|
+
...(skill.version === undefined ? {} : { version: skill.version, versionPath: skill.versionPath }),
|
|
124
143
|
...(skill.package === undefined ? {} : { package: skill.package })
|
|
125
144
|
})),
|
|
126
145
|
roleProfileRef: { digest: profileDigest, path: roleProfilePath },
|
|
@@ -129,11 +148,15 @@ export function materializeSessionBootstrap(input) {
|
|
|
129
148
|
// A Global Session may move between Yui's remote TUI and Desktop.
|
|
130
149
|
// Carry its read entry in the Thread-visible Manifest instead of
|
|
131
150
|
// depending on the client process that happened to create it.
|
|
132
|
-
loadCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name)
|
|
151
|
+
loadCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name),
|
|
152
|
+
// Same self-contained read remains usable from Desktop without the
|
|
153
|
+
// launching shell's managed environment or PATH.
|
|
154
|
+
skillCheckCommand: renderGlobalContextCommand(input.entryPoint, home, input.role.name)
|
|
133
155
|
}
|
|
134
156
|
: {
|
|
135
157
|
loadCommand: `yui task run context ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} --json`,
|
|
136
158
|
expandCommand: `yui task run context expand ${quoteShellWord(`${input.owner.taskId}/<run-id>`)} <ref-id> --store <store> --mode full --json`,
|
|
159
|
+
skillCheckCommand: SKILL_CHECK_COMMAND,
|
|
137
160
|
...(input.role.name === "leader" ? {
|
|
138
161
|
currentCommand: `yui task context ${quoteShellWord(input.owner.taskId)} --json`
|
|
139
162
|
} : {})
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { lstatSync, readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
4
|
import { verifySkillPackage } from "./skillPackage.js";
|
|
6
|
-
import { BUILTIN_YUI_SKILLS } from "./
|
|
5
|
+
import { BUILTIN_YUI_SKILLS, currentSkillPath } from "./currentSkills.js";
|
|
7
6
|
const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/u;
|
|
8
7
|
const MAX_TEXT = 256 * 1024;
|
|
9
8
|
const digest = (data) => createHash("sha256").update(data).digest("hex");
|
|
@@ -13,7 +12,7 @@ export function skillSource(home, id) {
|
|
|
13
12
|
throw new Error("Invalid Skill id.");
|
|
14
13
|
const builtin = BUILTIN_YUI_SKILLS.has(id);
|
|
15
14
|
return { kind: builtin ? "builtin" : "configured",
|
|
16
|
-
path:
|
|
15
|
+
path: currentSkillPath(home, id) };
|
|
17
16
|
}
|
|
18
17
|
function physical(path) {
|
|
19
18
|
if (realpathSync(path) !== resolve(path))
|
|
@@ -67,6 +67,8 @@ export function readTaskContext(store, taskId, environment = {}) {
|
|
|
67
67
|
discovery: `task context list ${taskId} --store <store>`,
|
|
68
68
|
commandDiscovery: {
|
|
69
69
|
help: "yui help", describe: "yui --json help",
|
|
70
|
+
skillCheck: "yui session skills --json",
|
|
71
|
+
ownInbox: "yui inbox list --json",
|
|
70
72
|
note: "Help and completion share the current caller's offline command view. Follow that view for operation-specific discovery; execution always revalidates authority."
|
|
71
73
|
},
|
|
72
74
|
detail: `task context inspect ${taskId} --store <store> --ref <refId> --digest <digest>`,
|
package/dist/controlPlaneCli.js
CHANGED
|
@@ -38,6 +38,7 @@ import { runDurableJobCommand } from "./commands/durableJobCommands.js";
|
|
|
38
38
|
import { parseExecutionAuditOptions, runExecutionAuditCommand } from "./commands/executionAuditCommands.js";
|
|
39
39
|
import { runGlobalRoleCommand, previewGlobalRoleAgentConfigurationMutation } from "./commands/globalRoleCommands.js";
|
|
40
40
|
import { runJobCommand } from "./commands/jobCommands.js";
|
|
41
|
+
import { runInboxCommand } from "./commands/inboxCommands.js";
|
|
41
42
|
import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
|
|
42
43
|
import { previewProfileAgentConfigurationMutation, runProfileCommand } from "./commands/profileCommands.js";
|
|
43
44
|
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
@@ -61,7 +62,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
|
|
|
61
62
|
import { FileCompletionManager } from "./completion/fileCompletionManager.js";
|
|
62
63
|
import { CONFIG_DOMAINS } from "./config/configCatalog.js";
|
|
63
64
|
import { resolveTmuxBin, resolveTmuxHistoryLimit } from "./config/yuiConfig.js";
|
|
64
|
-
import { readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
|
|
65
|
+
import { checkSessionSkills, readSessionBootstrapManifest } from "./context/sessionBootstrapManifest.js";
|
|
65
66
|
import { assertFileTaskControllerStorageCompatible, ensureFileTaskController, FileTaskWorkflowRuntime, refreshRunningFileTaskControllerConfiguration, refreshRunningFileTaskControllerEnvironment, restartFileTaskController, stopFileTaskController } from "./controller/clientRuntime.js";
|
|
66
67
|
import { FileSchedulerStoreAdapter } from "./controller/fileSchedulerStoreAdapter.js";
|
|
67
68
|
import { createControllerIntegrationJobPort } from "./controller/jobClient.js";
|
|
@@ -632,6 +633,11 @@ export async function main() {
|
|
|
632
633
|
return;
|
|
633
634
|
}
|
|
634
635
|
assertTaskInvocationScope(resolved, process.env);
|
|
636
|
+
if (resolved[0] === "inbox") {
|
|
637
|
+
const result = runInboxCommand(resolved.slice(1), store, process.env);
|
|
638
|
+
emit(JSON.stringify(result, null, 2), false, result);
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
635
641
|
if (resolved[0] === "task" && resolved[1] === "role" && resolved[2] === "capabilities") {
|
|
636
642
|
const query = parseTaskAgentCapabilityQuery(resolved.slice(3));
|
|
637
643
|
const result = await callController(home, "task.role-capabilities", {
|
|
@@ -789,6 +795,17 @@ export async function main() {
|
|
|
789
795
|
return;
|
|
790
796
|
}
|
|
791
797
|
if (resolved[0] === "session") {
|
|
798
|
+
if (resolved[1] === "skills") {
|
|
799
|
+
if (resolved.length !== 2)
|
|
800
|
+
throw usageError("Usage: yui session skills --json");
|
|
801
|
+
const path = process.env.YUI_SESSION_MANIFEST;
|
|
802
|
+
if (!["task", "global"].includes(process.env.YUI_SESSION_SCOPE ?? "") || path === undefined) {
|
|
803
|
+
throw usageError("Skill checks require the current managed Session and its Manifest.");
|
|
804
|
+
}
|
|
805
|
+
const data = checkSessionSkills(home, path);
|
|
806
|
+
emit(JSON.stringify(data, null, 2), false, data);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
792
809
|
if (resolved[1] === "stop") {
|
|
793
810
|
const options = parseSessionStopOptions(resolved.slice(2));
|
|
794
811
|
const result = await runSessionStopCommand({
|
|
@@ -996,7 +996,7 @@ export class FileTaskController {
|
|
|
996
996
|
}
|
|
997
997
|
const firstRuntimeDrain = await this.#drainRuntimeEvents();
|
|
998
998
|
await this.#providerRetry?.reconcile();
|
|
999
|
-
await this.#globalInputDelivery?.();
|
|
999
|
+
await this.#globalInputDelivery?.({ reconcile: scope.kind === "full", now: this.#now() });
|
|
1000
1000
|
for (const taskId of runtimeTaskFailureIds(firstRuntimeDrain)) {
|
|
1001
1001
|
runtimeFailedTaskIds.add(taskId);
|
|
1002
1002
|
}
|
|
@@ -1076,7 +1076,8 @@ export class FileTaskController {
|
|
|
1076
1076
|
if (scope.kind === "full"
|
|
1077
1077
|
|| scope.keys.some((key) => key !== "operator")
|
|
1078
1078
|
|| runtimeAdvanced) {
|
|
1079
|
-
this.#signalOperatorMailbox(runtimeAdvanced
|
|
1079
|
+
this.#signalOperatorMailbox(runtimeAdvanced || scope.kind === "full"
|
|
1080
|
+
|| scope.kind === "dirty" && scope.keys.some(key => parseMailboxKey(key).kind === "global-role"));
|
|
1080
1081
|
}
|
|
1081
1082
|
}
|
|
1082
1083
|
catch (error) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { taskMessageInboxReceipt, unfinishedLeaderInbox } from "../message/taskInboxOwnership.js";
|
|
3
4
|
import { contextSnapshotRef } from "../context/contextSnapshot.js";
|
|
4
5
|
import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
5
6
|
import { taskNotificationPresentation } from "../interaction/notificationPresentation.js";
|
|
@@ -30,6 +31,7 @@ import { standardAgentError } from "../runtime/agentError.js";
|
|
|
30
31
|
import { recordTaskAgentError } from "./taskAgentError.js";
|
|
31
32
|
import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure } from "./globalRuntimeAttention.js";
|
|
32
33
|
import { queueOperatorNotification } from "./operatorNotification.js";
|
|
34
|
+
import { claimOperatorAttention } from "../scheduler/operatorEvent.js";
|
|
33
35
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
34
36
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
35
37
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
@@ -882,6 +884,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
882
884
|
return [...this.store.listPendingActivationRequestTaskIds()].sort((left, right) => (left.localeCompare(right, undefined, { numeric: true })));
|
|
883
885
|
}
|
|
884
886
|
getTask(taskId) { return this.store.getTask(taskId); }
|
|
887
|
+
getEvent(taskId, eventId) {
|
|
888
|
+
return this.store.queryContextRecords(taskId, {
|
|
889
|
+
family: "task-event", ids: [eventId], limit: 1
|
|
890
|
+
}).records[0]?.value ?? null;
|
|
891
|
+
}
|
|
885
892
|
getTaskWorkspace(taskId) { return this.store.getTaskWorkspace(taskId); }
|
|
886
893
|
getTaskBrief(taskId) { return this.store.getTaskBrief(taskId); }
|
|
887
894
|
listDecisions(taskId) { return this.store.listDecisions(taskId); }
|
|
@@ -1146,7 +1153,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1146
1153
|
throw new AgentHostProviderSessionBusyError("The next Global input belongs to an explicit then handoff.");
|
|
1147
1154
|
}
|
|
1148
1155
|
if (message !== undefined) {
|
|
1149
|
-
if (
|
|
1156
|
+
if (store.queryGlobalRoleMessages(input.roleName, { handling: true, limit: 0 }).count > 0
|
|
1157
|
+
|| message.inbox !== undefined || message.delivery !== undefined || message.notDelivered !== undefined
|
|
1150
1158
|
|| message.control !== undefined && message.control.outcome !== "rejected"
|
|
1151
1159
|
|| message.deliveryTarget?.agentId !== input.agentId
|
|
1152
1160
|
|| message.deliveryTarget?.nativeSessionId !== input.nativeSessionId) {
|
|
@@ -1567,7 +1575,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1567
1575
|
if (mailbox.processing !== null) {
|
|
1568
1576
|
return { status: "processing", processing: mailbox.processing };
|
|
1569
1577
|
}
|
|
1570
|
-
let claimed = claimPending(mailbox, {
|
|
1578
|
+
let claimed = (input.target.kind === "operator" ? claimOperatorAttention : claimPending)(mailbox, {
|
|
1571
1579
|
batchId: input.batchId,
|
|
1572
1580
|
owner: input.owner,
|
|
1573
1581
|
startedAt: input.now.toISOString()
|
|
@@ -1737,7 +1745,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1737
1745
|
clearPendingWakeup(taskId) { this.store.clearPendingWakeup(taskId); }
|
|
1738
1746
|
getLeaderFailure(taskId) { return this.store.getLeaderFailure(taskId); }
|
|
1739
1747
|
prepareMessageContinuations(taskId, now) {
|
|
1740
|
-
const roles = new Set(this.store.listMessages(taskId).flatMap((message) => message.recipient?.ownerRunId !== undefined && message.continuation?.runId === undefined ? [message.recipient.roleName] : []));
|
|
1748
|
+
const roles = new Set(this.store.listMessages(taskId).flatMap((message) => message.inbox === undefined && message.recipient?.ownerRunId !== undefined && message.continuation?.runId === undefined ? [message.recipient.roleName] : []));
|
|
1741
1749
|
for (const roleName of roles) {
|
|
1742
1750
|
try {
|
|
1743
1751
|
this.store.transaction((store) => prepareMessageContinuations(store, taskId, now, roleName));
|
|
@@ -1746,7 +1754,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
1746
1754
|
const reason = `preparation-failed: ${error instanceof Error ? error.message : String(error)}`.slice(0, 1000);
|
|
1747
1755
|
this.store.transaction((store) => {
|
|
1748
1756
|
for (const message of store.listMessages(taskId)) {
|
|
1749
|
-
if (message.recipient?.roleName === roleName && message.recipient.ownerRunId !== undefined && message.continuation?.runId === undefined
|
|
1757
|
+
if (message.inbox === undefined && message.recipient?.roleName === roleName && message.recipient.ownerRunId !== undefined && message.continuation?.runId === undefined
|
|
1750
1758
|
&& message.continuation?.notDeliveredReason !== reason) {
|
|
1751
1759
|
store.updateMessage(taskId, { ...message, continuation: { notDeliveredReason: reason } });
|
|
1752
1760
|
}
|
|
@@ -1855,6 +1863,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
1855
1863
|
return { wakeId, attemptId: nextAttempt, disposition: "submit",
|
|
1856
1864
|
presentation: taskNotificationPresentation(wake, store) };
|
|
1857
1865
|
}
|
|
1866
|
+
if (unfinishedLeaderInbox(store, taskId))
|
|
1867
|
+
return null;
|
|
1858
1868
|
if (mailbox.pending === null || (provider !== null && provider !== undefined
|
|
1859
1869
|
&& ["submitting", "accepted", "delivery-unknown"].includes(provider.status)))
|
|
1860
1870
|
return null;
|
|
@@ -1864,6 +1874,11 @@ export class FileSchedulerStoreAdapter {
|
|
|
1864
1874
|
const sessions = store.getTaskRoleSessionSet(taskId, "leader");
|
|
1865
1875
|
const session = sessions?.sessions[sessions.activeAgentId];
|
|
1866
1876
|
const obsoleteIds = new Set();
|
|
1877
|
+
for (const message of store.listMessages(taskId)) {
|
|
1878
|
+
if (pendingMessageIds.has(message.id) && taskMessageInboxReceipt(store, message) !== undefined) {
|
|
1879
|
+
obsoleteIds.add(message.id);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1867
1882
|
for (const message of store.listMessages(taskId)) {
|
|
1868
1883
|
if (!pendingMessageIds.has(message.id) || message.inputControl?.expectedSessionId === undefined)
|
|
1869
1884
|
continue;
|
|
@@ -1,16 +1,18 @@
|
|
|
1
|
-
import { sendAgentHostRunControl, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
1
|
+
import { sendAgentHostRunControl, reconcileAgentHostTurn, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
2
2
|
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
3
3
|
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
4
4
|
import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
|
|
5
5
|
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
6
6
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
7
|
import { globalMessagePresentation } from "../interaction/notificationPresentation.js";
|
|
8
|
+
import { recordGlobalRuntimeAttention } from "./globalRuntimeAttention.js";
|
|
8
9
|
/** Messages are the only queue. Host's atomic begin arbitrates native writers. */
|
|
9
|
-
export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
10
|
+
export async function deliverGlobalInputs(home, store, ensure, onError, options = {}) {
|
|
10
11
|
for (const role of store.listGlobalRoles()) {
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
if (store.queryGlobalRoleMessages(role.name, { handling: true, limit: 0 }).count > 0)
|
|
14
|
+
continue;
|
|
15
|
+
let pending = store.queryGlobalRoleMessages(role.name, { pending: true, limit: 64 }).messages;
|
|
14
16
|
if (pending.length === 0 || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
|
|
15
17
|
continue;
|
|
16
18
|
let sessions = store.getGlobalRoleSessionSet(role.name);
|
|
@@ -20,15 +22,68 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
20
22
|
continue;
|
|
21
23
|
if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
|
|
22
24
|
store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
|
|
23
|
-
pending = store.
|
|
24
|
-
&& (message.inputControl?.action === "queue" || message.interruptThen !== undefined));
|
|
25
|
+
pending = store.queryGlobalRoleMessages(role.name, { pending: true, limit: 64 }).messages;
|
|
25
26
|
if (pending.length === 0)
|
|
26
27
|
continue;
|
|
27
28
|
}
|
|
28
29
|
if (providerRetryPending(sessions?.providerBinding))
|
|
29
30
|
continue;
|
|
30
|
-
|
|
31
|
-
const
|
|
31
|
+
let current = sessions?.providerBinding?.run;
|
|
32
|
+
const bindingBefore = sessions?.providerBinding;
|
|
33
|
+
if (options.reconcile && current != null
|
|
34
|
+
&& ["submitting", "accepted", "delivery-unknown"].includes(current.status)
|
|
35
|
+
&& bindingBefore?.authority.owner === "controller" && bindingBefore.authority.holderId !== undefined) {
|
|
36
|
+
const ownedTurn = current;
|
|
37
|
+
// Only ask the original Host to republish evidence it already holds.
|
|
38
|
+
// Busy, unknown, unavailable and older Hosts never authorize replay,
|
|
39
|
+
// Session creation, native reads through another client, or timeout settlement.
|
|
40
|
+
let observed;
|
|
41
|
+
try {
|
|
42
|
+
observed = await reconcileAgentHostTurn({
|
|
43
|
+
home, scope: "global", roleName: role.name,
|
|
44
|
+
control: { protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "reconcile-turn",
|
|
45
|
+
nativeSessionId: currentProviderConversation(bindingBefore).conversationId,
|
|
46
|
+
attemptId: current.attemptId, nativeTurnId: current.nativeTurnId,
|
|
47
|
+
authority: { epoch: bindingBefore.authority.epoch, owner: "controller",
|
|
48
|
+
holderId: bindingBefore.authority.holderId } }
|
|
49
|
+
});
|
|
50
|
+
if (observed.outcome === "rejected" || ["idle", "failed", "exited"].includes(observed.snapshot.state)
|
|
51
|
+
&& observed.reconciliation !== "terminal-republished") {
|
|
52
|
+
throw new Error(observed.failure?.detail ?? observed.snapshot.detail ?? "Host cannot reconcile this exact terminal.");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
store.transaction(tx => recordGlobalRuntimeAttention(tx, role.name, `terminal-reconcile:${currentProviderConversation(bindingBefore).conversationId}:${ownedTurn.attemptId}`, { attemptId: ownedTurn.attemptId, nativeTurnId: ownedTurn.nativeTurnId,
|
|
57
|
+
nativeSessionId: currentProviderConversation(bindingBefore).conversationId,
|
|
58
|
+
reason: "host-terminal-unobservable",
|
|
59
|
+
cause: observed === undefined ? "host-control-unavailable"
|
|
60
|
+
: observed.outcome === "rejected" ? "host-control-rejected" : "exact-terminal-not-retained",
|
|
61
|
+
hostState: observed?.snapshot.state ?? null,
|
|
62
|
+
endpointImplementation: observed?.snapshot.endpointImplementation ?? null,
|
|
63
|
+
eventDelivery: observed?.snapshot.eventDelivery ?? null,
|
|
64
|
+
// Endpoint generation is not a loaded Host/package version.
|
|
65
|
+
inputKind: ownedTurn.attemptId.startsWith("direct:") ? "direct" : "managed",
|
|
66
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
67
|
+
recovery: "Inspect the original Host, Session, input and raw Inbox. Controller restart preserves live Hosts; it does not upgrade their loaded observation code. Restore the supported observation path, or explicitly stop/replace with exact native quiescence evidence. Missing eventDelivery means unreported, not healthy. Do not resend unknown input.",
|
|
68
|
+
confirmed: ownedTurn.status, unknown: "Native terminal and remaining writer ownership." }, options.now ?? new Date()));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
sessions = store.getGlobalRoleSessionSet(role.name);
|
|
72
|
+
current = sessions?.providerBinding?.run;
|
|
73
|
+
if (sessions?.sessions[sessions.activeAgentId]?.status !== "active"
|
|
74
|
+
|| providerRetryPending(sessions?.providerBinding)
|
|
75
|
+
|| hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
|
|
76
|
+
continue;
|
|
77
|
+
pending = store.queryGlobalRoleMessages(role.name, { pending: true, limit: 64 }).messages;
|
|
78
|
+
if (pending.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
// Interrupt-then reserves the next legal slot even behind a large queue.
|
|
82
|
+
const reservedPage = store.queryGlobalRoleMessages(role.name, {
|
|
83
|
+
pending: true, interruptThen: true, limit: 64
|
|
84
|
+
});
|
|
85
|
+
const reserved = reservedPage.messages;
|
|
86
|
+
const obsolete = reserved.filter(message => {
|
|
32
87
|
const claim = message.interruptThen;
|
|
33
88
|
return claim !== undefined && (sessions?.activeAgentId !== claim.targetAgentId
|
|
34
89
|
|| sessions.sessions[claim.targetAgentId]?.nativeSessionId !== claim.targetNativeSessionId
|
|
@@ -37,15 +92,18 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
37
92
|
});
|
|
38
93
|
for (const message of obsolete)
|
|
39
94
|
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(message, "native-session-or-authority-changed", new Date()));
|
|
40
|
-
|
|
95
|
+
const obsoleteIds = new Set(obsolete.map(message => message.id));
|
|
96
|
+
pending = pending.filter(message => !obsoleteIds.has(message.id));
|
|
41
97
|
if (pending.length === 0)
|
|
42
98
|
continue;
|
|
99
|
+
if (reservedPage.count > reserved.length && obsolete.length === reserved.length)
|
|
100
|
+
continue;
|
|
43
101
|
if (current != null && ["submitting", "accepted", "delivery-unknown"].includes(current.status))
|
|
44
102
|
continue;
|
|
45
103
|
const matchesTarget = (message) => message.deliveryTarget === undefined
|
|
46
104
|
|| sessions?.activeAgentId === message.deliveryTarget.agentId
|
|
47
105
|
&& sessions.sessions[message.deliveryTarget.agentId]?.nativeSessionId === message.deliveryTarget.nativeSessionId;
|
|
48
|
-
const claims =
|
|
106
|
+
const claims = reserved.filter(message => !obsolete.includes(message));
|
|
49
107
|
let next;
|
|
50
108
|
if (claims.length > 0) {
|
|
51
109
|
next = claims.find(message => {
|
|
@@ -76,7 +134,7 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
76
134
|
return false;
|
|
77
135
|
});
|
|
78
136
|
}
|
|
79
|
-
if (next === undefined)
|
|
137
|
+
if (next === undefined || next.inbox !== undefined)
|
|
80
138
|
continue;
|
|
81
139
|
const attemptId = `global-input:${role.name}/${next.id}`;
|
|
82
140
|
if (current?.attemptId === attemptId)
|
|
@@ -96,8 +154,8 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
96
154
|
if (binding == null || binding.authority.owner !== "controller")
|
|
97
155
|
continue;
|
|
98
156
|
const nativeSessionId = currentProviderConversation(binding).conversationId;
|
|
99
|
-
const persisted = store.
|
|
100
|
-
if (persisted === undefined || JSON.stringify(persisted) !== JSON.stringify(next))
|
|
157
|
+
const persisted = store.queryGlobalRoleMessages(role.name, { id: next.id, limit: 1 }).messages[0];
|
|
158
|
+
if (persisted === undefined || persisted.inbox !== undefined || JSON.stringify(persisted) !== JSON.stringify(next))
|
|
101
159
|
continue;
|
|
102
160
|
const claim = next.interruptThen;
|
|
103
161
|
if (!matchesTarget(next) || claim !== undefined
|
|
@@ -125,8 +183,8 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
125
183
|
});
|
|
126
184
|
if (result.outcome === "rejected") {
|
|
127
185
|
store.transaction(tx => {
|
|
128
|
-
const latest = tx.
|
|
129
|
-
if (latest === undefined || latest.delivery !== undefined || latest.notDelivered !== undefined)
|
|
186
|
+
const latest = tx.queryGlobalRoleMessages(role.name, { id: next.id, limit: 1 }).messages[0];
|
|
187
|
+
if (latest === undefined || latest.inbox !== undefined || latest.delivery !== undefined || latest.notDelivered !== undefined)
|
|
130
188
|
return;
|
|
131
189
|
const turn = tx.getGlobalRoleSessionSet(role.name)?.providerBinding?.run;
|
|
132
190
|
if (providerRetryPending(tx.getGlobalRoleSessionSet(role.name)?.providerBinding))
|
|
@@ -19,7 +19,7 @@ export function recordGlobalRuntimeAttention(store, roleName, identity, facts, n
|
|
|
19
19
|
// diagnostic readable; never create a Role or launch a substitute Agent.
|
|
20
20
|
const recipient = store.getGlobalRole("operator") === null ? roleName : "operator";
|
|
21
21
|
const requestId = `runtime-attention:${createHash("sha256").update(`${roleName}\0${identity}`).digest("hex")}`;
|
|
22
|
-
if (store.
|
|
22
|
+
if (store.queryGlobalRoleMessages(recipient, { requestId, limit: 1 }).messages.length > 0)
|
|
23
23
|
return;
|
|
24
24
|
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), recipient, [
|
|
25
25
|
`Global Role ${roleName} needs attention. No recovery or new authority is implied.`,
|
|
@@ -8,7 +8,7 @@ import { createGlobalRoleMessage } from "../message/message.js";
|
|
|
8
8
|
export function queueOperatorNotification(store, input, now = new Date()) {
|
|
9
9
|
return store.transaction(tx => {
|
|
10
10
|
const requestId = `operator-notice:${encodeURIComponent(input.receiptId)}`;
|
|
11
|
-
const previous = tx.
|
|
11
|
+
const previous = tx.queryGlobalRoleMessages("operator", { requestId, limit: 1 }).messages[0];
|
|
12
12
|
const mailbox = tx.getWorkMailbox({ kind: "operator" });
|
|
13
13
|
const claimed = mailbox?.processing?.batchId === input.batchId;
|
|
14
14
|
if (previous !== undefined) {
|
|
@@ -18,6 +18,11 @@ export function queueOperatorNotification(store, input, now = new Date()) {
|
|
|
18
18
|
}
|
|
19
19
|
if (!claimed)
|
|
20
20
|
throw new Error(`Operator notification claim changed: ${input.batchId}`);
|
|
21
|
+
// At most one unsent engineering notice. New facts coalesce in the existing
|
|
22
|
+
// mailbox while busy, without rewriting originals or overtaking user input.
|
|
23
|
+
if (tx.queryGlobalRoleMessages("operator", { pending: true, operatorNotice: true, limit: 1 }).count > 0) {
|
|
24
|
+
return { status: "deferred" };
|
|
25
|
+
}
|
|
21
26
|
const sessions = tx.getGlobalRoleSessionSet("operator");
|
|
22
27
|
const session = sessions?.sessions[sessions.activeAgentId];
|
|
23
28
|
if (session?.status !== "active" || sessions?.providerBinding == null)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { performance } from "node:perf_hooks";
|
|
2
2
|
import { runTaskCommand } from "../commands/taskCommands.js";
|
|
3
|
-
import {
|
|
3
|
+
import { routeRoleEvent } from "../scheduler/operatorEvent.js";
|
|
4
4
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
5
5
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
6
6
|
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
@@ -57,8 +57,7 @@ export function createProviderRetryHooks(home, store, ports = {}) {
|
|
|
57
57
|
roleName: owner.roleName, chainId: binding.retry.chainId, reason
|
|
58
58
|
}, new Date(at));
|
|
59
59
|
store.saveEvent(owner.taskId, event);
|
|
60
|
-
|
|
61
|
-
: { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
|
|
60
|
+
routeRoleEvent(store, event, owner.roleName, "provider-retry-needs-attention", new Date(at));
|
|
62
61
|
}
|
|
63
62
|
});
|
|
64
63
|
};
|
|
@@ -142,8 +141,7 @@ export function createProviderRetryHooks(home, store, ports = {}) {
|
|
|
142
141
|
roleName: owner.roleName, attemptId: retry.currentAttemptId, nativeSessionId: retry.nativeSessionId, detail
|
|
143
142
|
}, new Date(at));
|
|
144
143
|
store.saveEvent(owner.taskId, event);
|
|
145
|
-
|
|
146
|
-
: { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
|
|
144
|
+
routeRoleEvent(store, event, owner.roleName, "provider-retry-needs-attention", new Date(at));
|
|
147
145
|
}
|
|
148
146
|
});
|
|
149
147
|
}
|
|
@@ -560,9 +560,9 @@ export async function startFileTaskControllerRuntime(home, options = {}) {
|
|
|
560
560
|
}
|
|
561
561
|
}),
|
|
562
562
|
jobSupervisor,
|
|
563
|
-
globalInputDelivery: () => deliverGlobalInputs(home, store, async (roleName) => {
|
|
563
|
+
globalInputDelivery: (deliveryOptions) => deliverGlobalInputs(home, store, async (roleName) => {
|
|
564
564
|
await lifecycleDispatcher("runtime.ensure-role-session", { scope: "global", roleName });
|
|
565
|
-
}, options.onError ?? (() => undefined)),
|
|
565
|
+
}, options.onError ?? (() => undefined), deliveryOptions),
|
|
566
566
|
jobControl,
|
|
567
567
|
capabilityDispatcher: createCapabilityDispatcher(kernel.capabilities),
|
|
568
568
|
...(continuationReconciler === undefined ? {} : { continuationReconciler }),
|