@zq-silk/yui 2.5.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 +31 -4
- 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 +40 -7
- package/dist/commands/taskContextCommand.js +3 -2
- 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 +9 -4
- 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/kernel/builtinCapabilities.js +2 -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/runtime/builtinAgentErrorMappers.js +9 -5
- package/dist/runtime/providerRetry.js +38 -0
- package/dist/runtime/taskSessions.js +240 -0
- package/dist/scheduler/operatorEvent.js +49 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +5 -4
- package/dist/scheduler/taskExecutionProjection.js +78 -6
- 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/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app/taskView.js +1 -0
- package/dist/web/assets/client/domain/records.js +70 -2
- package/dist/web/assets/client/domain/runs.js +3 -2
- package/dist/web/assets/client/domain/vocab.js +1 -1
- package/dist/web/assets/client/i18n/en.js +17 -1
- package/dist/web/assets/client/i18n/zh-CN.js +17 -1
- package/dist/web/assets/client/lib/api.js +4 -1
- package/dist/web/assets/client/lib/theme.js +31 -10
- package/dist/web/assets/client/views/task/actions.js +38 -21
- package/dist/web/assets/client/views/task/observation.js +12 -3
- package/dist/web/assets/client/views/task/overview.js +7 -40
- package/dist/web/assets/client/views/task/runtime.js +71 -34
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shell/index.js +1 -0
- package/dist/web/assets/shell/settings.js +1 -0
- package/dist/web/assets/styles/views/task.js +14 -0
- package/dist/web/webServer.js +12 -0
- package/dist/web/webSessions.js +2 -165
- package/dist/web/webSnapshot.js +9 -5
- package/dist/web/webTaskActivity.js +54 -0
- package/dist/web/webTaskSurface.js +1 -0
- package/docs/managed-turn-and-session-runtime.md +81 -5
- package/docs/managed-turn-and-session-runtime.zh-CN.md +51 -3
- package/docs/observability/README.md +25 -1
- package/docs/observability/README.zh-CN.md +18 -1
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +74 -2
- package/docs/release-workflow.zh-CN.md +56 -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-runtime/references/recovery.md +9 -0
- package/skills/yui-worker/SKILL.md +8 -1
- package/skills/yui-worker/VERSION +1 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
2
|
import { isDeepStrictEqual } from "node:util";
|
|
3
|
+
import { taskMessageInboxReceipt } from "../message/taskInboxOwnership.js";
|
|
3
4
|
import { agentExecutionComponentLabel } from "../agent/executionComponents.js";
|
|
4
5
|
import { assertLeaderArchiveAdmission } from "../task/leaderArchiveAuthority.js";
|
|
5
6
|
import { createRun, runExecutionObservation, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
@@ -45,7 +46,7 @@ import { copyGlobalRoleToTaskRole, activeRoleAgentBinding, createRole, createRol
|
|
|
45
46
|
import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
|
|
46
47
|
import { RUNTIME_SESSION_REPLACE_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
47
48
|
import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
48
|
-
import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
49
|
+
import { controlProviderRetry, providerRunRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
49
50
|
import { currentProviderConversation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
|
|
50
51
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
51
52
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
@@ -1631,9 +1632,25 @@ function taskMessageCommand(args, store, options) {
|
|
|
1631
1632
|
const message = store.transaction((tx) => {
|
|
1632
1633
|
assertTaskDeliveryAuthority(tx, options.environment, ref.taskId);
|
|
1633
1634
|
const current = tx.listMessages(ref.taskId).find((entry) => entry.id === ref.localId);
|
|
1634
|
-
if (current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
|
|
1635
|
+
if (current?.inbox?.ack !== undefined || current?.recipient?.ownerRunId === undefined || current.continuation?.runId !== undefined) {
|
|
1635
1636
|
throw usageError("Only an unassigned pending Message can be explicitly handed off.");
|
|
1636
1637
|
}
|
|
1638
|
+
if (current.inbox !== undefined) {
|
|
1639
|
+
const actor = current.inbox.claim.actor;
|
|
1640
|
+
for (const runId of new Set([actor.runId, current.recipient.ownerRunId])) {
|
|
1641
|
+
const handler = runId === undefined ? null : tx.getRun(ref.taskId, runId);
|
|
1642
|
+
const observation = handler === null ? undefined : runExecutionObservation(handler, tx.getTaskRoleSessionSet(ref.taskId, handler.roleName)?.providerBinding, tx.listEvents(ref.taskId));
|
|
1643
|
+
if (handler === null || handler.status === "active"
|
|
1644
|
+
|| handler.result?.failureReason === "delivery-unknown"
|
|
1645
|
+
|| handler.result?.provider === undefined
|
|
1646
|
+
|| runId === actor.runId && handler.result.provider.conversationId !== actor.nativeSessionId
|
|
1647
|
+
|| !["completed", "failed", "cancelled"].includes(observation?.delivery ?? "")) {
|
|
1648
|
+
throw usageError("The original and current inbox handlers must have exact stopped native execution before explicit handoff.");
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
// Preserve the original claim: the new authorized Assignment must
|
|
1652
|
+
// inspect prior effects and ack explicitly, never auto-replay the input.
|
|
1653
|
+
}
|
|
1637
1654
|
const recipient = resolveMessageRecipient(tx, ref.taskId, requiredOption(parsed.options, "--to"), {
|
|
1638
1655
|
...(current.recipient.workItemId === undefined ? {} : { workItemId: current.recipient.workItemId }),
|
|
1639
1656
|
...(current.recipient.reviewRoundId === undefined ? {} : { reviewRoundId: current.recipient.reviewRoundId })
|
|
@@ -1853,15 +1870,23 @@ function steerTaskInput(taskId, input, store, options) {
|
|
|
1853
1870
|
// and idempotent, so the Host's later settlement fold promotes it to a proven
|
|
1854
1871
|
// terminal and never rewinds it. An idempotent replay above already returned
|
|
1855
1872
|
// the saved Message, so this records the attempt exactly once.
|
|
1856
|
-
store.transaction((tx) => {
|
|
1873
|
+
const admitted = store.transaction((tx) => {
|
|
1857
1874
|
const saved = tx.listMessages(result.task.id).find((entry) => entry.id === result.message.id);
|
|
1858
1875
|
const requestId = saved?.inputControl?.requestId;
|
|
1859
|
-
|
|
1860
|
-
|
|
1876
|
+
// The recipient may claim between Message persistence and control
|
|
1877
|
+
// admission. A Leader window owns its referenced originals even after ack;
|
|
1878
|
+
// never re-submit them through a delayed steer.
|
|
1879
|
+
if (saved === undefined || requestId === undefined || taskMessageInboxReceipt(tx, saved) !== undefined)
|
|
1880
|
+
return false;
|
|
1861
1881
|
tx.updateMessage(result.task.id, recordTaskMessageControlOutcome(saved, {
|
|
1862
1882
|
requestId, receiptId, outcome: "pending", observedAt: clock(options)
|
|
1863
1883
|
}));
|
|
1884
|
+
return true;
|
|
1864
1885
|
});
|
|
1886
|
+
if (!admitted) {
|
|
1887
|
+
return output(`Steer message ${result.message.id} saved but not delivered (TARGET_CHANGED: inbox handling owns the input).\n`, { taskId: result.task.id, message: messageReceipt(result.message),
|
|
1888
|
+
steer: { state: "not-steered", code: "TARGET_CHANGED", detail: "Inbox handling already owns the original input." } });
|
|
1889
|
+
}
|
|
1865
1890
|
return {
|
|
1866
1891
|
kind: "input-steer",
|
|
1867
1892
|
taskId: result.task.id,
|
|
@@ -2407,6 +2432,9 @@ function registerInterruptThen(store, taskId, roleName, thenMessageRef, target,
|
|
|
2407
2432
|
}
|
|
2408
2433
|
// A Message that already delivered, or was already ruled undeliverable, is not
|
|
2409
2434
|
// a fresh input to reuse; reusing it would replay or resurrect a settled fact.
|
|
2435
|
+
if (taskMessageInboxReceipt(store, message) !== undefined) {
|
|
2436
|
+
return { code: "TARGET_CHANGED", detail: `Message ${thenMessageRef} is owned by active inbox handling.` };
|
|
2437
|
+
}
|
|
2410
2438
|
if (message.continuation?.runId !== undefined) {
|
|
2411
2439
|
return { code: "TARGET_CHANGED", detail: `Message ${thenMessageRef} was already delivered by ${message.continuation.runId}.` };
|
|
2412
2440
|
}
|
|
@@ -4897,7 +4925,9 @@ function retryRun(args, store, options) {
|
|
|
4897
4925
|
return output(`Retry already recorded as ${existing.payload.runId}; retry that exact successor if needed.\n`);
|
|
4898
4926
|
}
|
|
4899
4927
|
const sessions = tx.getTaskRoleSessionSet(previous.taskId, previous.roleName);
|
|
4900
|
-
const retry = sessions?.providerBinding?.retry
|
|
4928
|
+
const retry = options.providerRetryChainId !== undefined ? sessions?.providerBinding?.retry
|
|
4929
|
+
: sessions?.providerBinding == null ? undefined
|
|
4930
|
+
: providerRunRetry(sessions.providerBinding, previous.id, clock(options).getTime());
|
|
4901
4931
|
if (options.providerRetryChainId !== undefined
|
|
4902
4932
|
&& (retry?.chainId !== options.providerRetryChainId || retry.status !== "waiting"
|
|
4903
4933
|
|| retry.previousRunId !== previous.id || clock(options).getTime() < Date.parse(retry.nextEligibleAt)
|
|
@@ -4909,7 +4939,10 @@ function retryRun(args, store, options) {
|
|
|
4909
4939
|
const successor = tx.getActiveRun(previous.taskId, previous.roleName);
|
|
4910
4940
|
tx.saveTaskRoleSessionSet({
|
|
4911
4941
|
...sessions, providerBinding: {
|
|
4912
|
-
...sessions.providerBinding,
|
|
4942
|
+
...sessions.providerBinding,
|
|
4943
|
+
...(sessions.providerBinding.retry !== undefined && sessions.providerBinding.retry.chainId !== retry.chainId
|
|
4944
|
+
? { retryHistory: [...(sessions.providerBinding.retryHistory ?? []), sessions.providerBinding.retry] } : {}),
|
|
4945
|
+
retry: {
|
|
4913
4946
|
...retry, status: "waiting", successorAutomatic: options.providerRetryChainId !== undefined,
|
|
4914
4947
|
...(options.providerRetryChainId === undefined ? {
|
|
4915
4948
|
intentAt: clock(options).toISOString(),
|
|
@@ -17,12 +17,12 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
17
17
|
const action = explicit ? args[0] : "read";
|
|
18
18
|
const taskId = args[explicit ? 1 : 0];
|
|
19
19
|
if (!taskId || taskId.startsWith("--"))
|
|
20
|
-
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--status <status>] [--work-item <id>].");
|
|
20
|
+
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--view <current|all>] [--status <status>] [--work-item <id>].");
|
|
21
21
|
const options = new Map();
|
|
22
22
|
const rest = args.slice(explicit ? 2 : 1);
|
|
23
23
|
const allowed = action === "read" ? [] : action === "delta"
|
|
24
24
|
? ["--after", "--continuation", "--limit"] : action === "list"
|
|
25
|
-
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
25
|
+
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item", "--view"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
26
26
|
for (let i = 0; i < rest.length; i += 2) {
|
|
27
27
|
const flag = rest[i];
|
|
28
28
|
const value = rest[i + 1];
|
|
@@ -40,6 +40,7 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
40
40
|
throw usageError("Context list requires --store.");
|
|
41
41
|
data = listTaskContext(store, taskId, family, {
|
|
42
42
|
cursor: options.get("--cursor"),
|
|
43
|
+
view: options.get("--view"),
|
|
43
44
|
status: options.get("--status"), after: options.get("--after"), workItemId: options.get("--work-item"),
|
|
44
45
|
...(options.has("--limit") ? { limit: Number(options.get("--limit")) } : {})
|
|
45
46
|
}, environment);
|
|
@@ -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>`,
|
|
@@ -191,7 +193,10 @@ export function readTaskContextResource(store, taskId, selector, environment = {
|
|
|
191
193
|
export function listTaskContext(store, taskId, family, options = {}, environment = {}) {
|
|
192
194
|
return store.readTransaction(reader => {
|
|
193
195
|
const scope = authorizeContext(reader, taskId, environment);
|
|
194
|
-
|
|
196
|
+
if (options.view !== undefined && !["current", "all"].includes(options.view)) {
|
|
197
|
+
throw usageError("Context list view must be current or all.");
|
|
198
|
+
}
|
|
199
|
+
const sources = authorizedSources(reader, scope, options.view === "current", family, true);
|
|
195
200
|
if (!CONTEXT_FAMILIES.includes(family))
|
|
196
201
|
throw usageError(`Unknown Context store: ${family}.`);
|
|
197
202
|
if (options.after !== undefined && !Number.isFinite(Date.parse(options.after)))
|
|
@@ -216,7 +221,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
|
|
|
216
221
|
}
|
|
217
222
|
}
|
|
218
223
|
return recordPage(records, `task:${taskId}/${family}/${JSON.stringify({
|
|
219
|
-
status: options.status, after: options.after, workItemId: options.workItemId
|
|
224
|
+
view: options.view ?? "all", status: options.status, after: options.after, workItemId: options.workItemId
|
|
220
225
|
})}`, options);
|
|
221
226
|
});
|
|
222
227
|
}
|
|
@@ -447,7 +452,7 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
|
|
|
447
452
|
default: return null;
|
|
448
453
|
}
|
|
449
454
|
}
|
|
450
|
-
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily) {
|
|
455
|
+
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily, discovery = false) {
|
|
451
456
|
const taskId = task.id;
|
|
452
457
|
const sources = [];
|
|
453
458
|
const add = (family, id, value) => {
|
|
@@ -462,7 +467,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
|
|
|
462
467
|
const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
|
|
463
468
|
sources.push({ family, count, *read(offset = 0) {
|
|
464
469
|
for (const { id, value } of store.queryContextRecords(taskId, {
|
|
465
|
-
...selection, offset, limit: current ? 8 : MAX_RECORDS
|
|
470
|
+
...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
|
|
466
471
|
}).records)
|
|
467
472
|
yield materialize(family, id, value);
|
|
468
473
|
} });
|
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;
|