@zq-silk/yui 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/ARCHITECTURE.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +211 -0
  4. package/dist/agent/adapterCatalog.js +10 -0
  5. package/dist/agent/agent.js +89 -0
  6. package/dist/agent/agentRegistry.js +10 -0
  7. package/dist/agent/argumentPolicy.js +80 -0
  8. package/dist/brief/taskBrief.js +37 -0
  9. package/dist/cli/commandCatalog.js +647 -0
  10. package/dist/cli/completion.js +111 -0
  11. package/dist/cli/completionWizard.js +143 -0
  12. package/dist/cli/dynamicCompletion.js +48 -0
  13. package/dist/cli/helpRenderer.js +32 -0
  14. package/dist/cli/interactionCandidates.js +139 -0
  15. package/dist/cli/interactionPolicy.js +389 -0
  16. package/dist/cli/interactiveSelection.js +185 -0
  17. package/dist/cli/invocationRouter.js +51 -0
  18. package/dist/cli/roleOptionCatalog.js +67 -0
  19. package/dist/cli/roleWizard.js +546 -0
  20. package/dist/cli/selectionPorts.js +1 -0
  21. package/dist/cli/updateCommand.js +22 -0
  22. package/dist/cli.js +402 -0
  23. package/dist/commands/agentCommands.js +196 -0
  24. package/dist/commands/globalRoleCommands.js +367 -0
  25. package/dist/commands/jobCommands.js +100 -0
  26. package/dist/commands/operatorCommands.js +38 -0
  27. package/dist/commands/repositoryCommands.js +86 -0
  28. package/dist/commands/roleConfiguration.js +201 -0
  29. package/dist/commands/taskCommands.js +1344 -0
  30. package/dist/commands/taskContextCommand.js +215 -0
  31. package/dist/commands/taskInputCommands.js +423 -0
  32. package/dist/commands/taskRoleRuntimeStatus.js +152 -0
  33. package/dist/completion/completionInstaller.js +168 -0
  34. package/dist/completion/completionPort.js +1 -0
  35. package/dist/completion/completionState.js +137 -0
  36. package/dist/completion/completionWizard.js +125 -0
  37. package/dist/completion/fileCompletionManager.js +51 -0
  38. package/dist/config/yuiConfig.js +17 -0
  39. package/dist/context/dispatchContext.js +74 -0
  40. package/dist/controller/clientRuntime.js +215 -0
  41. package/dist/controller/controller.js +158 -0
  42. package/dist/controller/controllerMain.js +37 -0
  43. package/dist/controller/fileSchedulerStoreAdapter.js +322 -0
  44. package/dist/controller/runtime.js +31 -0
  45. package/dist/controller/sessionNotify.js +136 -0
  46. package/dist/core/controllerClient.js +127 -0
  47. package/dist/core/controllerServer.js +269 -0
  48. package/dist/core/protocol.js +169 -0
  49. package/dist/decision/decision.js +42 -0
  50. package/dist/doctor/doctor.js +229 -0
  51. package/dist/errors/cliError.js +38 -0
  52. package/dist/event/taskEvent.js +44 -0
  53. package/dist/executor/agentAdapter.js +338 -0
  54. package/dist/executor/agentExecutor.js +144 -0
  55. package/dist/executor/executorRegistry.js +101 -0
  56. package/dist/executor/fileRoleLaunchPlanner.js +156 -0
  57. package/dist/executor/launchPlan.js +16 -0
  58. package/dist/input/inputRequest.js +326 -0
  59. package/dist/message/message.js +69 -0
  60. package/dist/milestone/milestone.js +27 -0
  61. package/dist/operator/operatorContext.js +66 -0
  62. package/dist/output/rolePresentation.js +82 -0
  63. package/dist/output/table.js +77 -0
  64. package/dist/output/terminal.js +198 -0
  65. package/dist/repository/gitWorkspace.js +210 -0
  66. package/dist/repository/repository.js +55 -0
  67. package/dist/repository/taskWorkspacePreparer.js +256 -0
  68. package/dist/role/role.js +246 -0
  69. package/dist/role/systemRoles.js +20 -0
  70. package/dist/run/agentRun.js +102 -0
  71. package/dist/scheduler/activeRoleRunDelivery.js +94 -0
  72. package/dist/scheduler/archivedTaskRuntime.js +12 -0
  73. package/dist/scheduler/leaderFailure.js +18 -0
  74. package/dist/scheduler/leaderWakeupProcessor.js +143 -0
  75. package/dist/scheduler/operatorInputNotificationProcessor.js +85 -0
  76. package/dist/scheduler/operatorNotification.js +17 -0
  77. package/dist/scheduler/pendingWakeup.js +33 -0
  78. package/dist/scheduler/ports.js +1 -0
  79. package/dist/scheduler/roleRunLiveness.js +41 -0
  80. package/dist/scheduler/wakeupQueue.js +13 -0
  81. package/dist/setup/setupCommand.js +317 -0
  82. package/dist/storage/durableFile.js +38 -0
  83. package/dist/storage/storageSchema.js +259 -0
  84. package/dist/storage/taskStore.js +1032 -0
  85. package/dist/task/task.js +216 -0
  86. package/dist/tmux/commandExecutor.js +69 -0
  87. package/dist/tmux/terminalHandoff.js +17 -0
  88. package/dist/tmux/tmuxManager.js +408 -0
  89. package/dist/workItem/workItem.js +45 -0
  90. package/dist/worktree/roleWorkspace.js +62 -0
  91. package/i18n/README.zh-CN.md +205 -0
  92. package/package.json +47 -0
  93. package/skills/yui-leader/SKILL.md +72 -0
  94. package/skills/yui-operator/SKILL.md +57 -0
  95. package/skills/yui-worker/SKILL.md +31 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Delivers durable Work AgentRuns before liveness reconciliation. Task command
3
+ * handlers only record intent; this Controller path is the sole automated
4
+ * route into the Agent terminal, through tmux receipt-backed delivery.
5
+ */
6
+ export async function processActiveRoleRunDeliveries(store, delivery, now) {
7
+ const results = [];
8
+ for (const task of store.listTasks()) {
9
+ if (task.status !== "active")
10
+ continue;
11
+ for (const role of store.listRoles(task.id)) {
12
+ const run = store.getActiveAgentRun(task.id, role.name);
13
+ // A crash after a Leader wake is durably claimed but before tmux input
14
+ // is recoverable through the same receipt-backed delivery path.
15
+ if (run === null || run.deliveredAt !== undefined)
16
+ continue;
17
+ if (task.repositoryId !== undefined && task.cwd === undefined) {
18
+ results.push({
19
+ taskId: task.id,
20
+ roleName: role.name,
21
+ runId: run.id,
22
+ status: "skipped",
23
+ reason: "workspace-not-ready"
24
+ });
25
+ continue;
26
+ }
27
+ const existingSession = store.getRoleSession(task.id, role.name);
28
+ const receiptId = `agent-run:${run.id}`;
29
+ try {
30
+ const nativeSessionId = run.mode === "resume"
31
+ ? requireResumeSession(role, existingSession)
32
+ : undefined;
33
+ const prepared = await delivery.prepareRoleSession({
34
+ taskId: task.id,
35
+ roleName: role.name,
36
+ agentId: role.activeAgentId,
37
+ adapterId: role.adapterId,
38
+ mode: run.mode,
39
+ ...(nativeSessionId === undefined ? {} : { nativeSessionId })
40
+ });
41
+ const existingReceipt = await delivery.findExistingReceipt?.({
42
+ delivery: prepared,
43
+ receiptId
44
+ }) ?? null;
45
+ const ready = existingReceipt ?? await delivery.waitUntilReady(prepared);
46
+ const session = validateReadySession(role, existingSession, run.mode, ready);
47
+ let status = "already-delivered";
48
+ if (existingReceipt === null) {
49
+ status = await delivery.sendOnce({
50
+ delivery: ready,
51
+ receiptId,
52
+ text: run.input
53
+ }) === "sent" ? "delivered" : "already-delivered";
54
+ }
55
+ store.saveRoleRunDelivery({ task, role, run, session, now });
56
+ results.push({ taskId: task.id, roleName: role.name, runId: run.id, status });
57
+ }
58
+ catch (error) {
59
+ results.push({
60
+ taskId: task.id,
61
+ roleName: role.name,
62
+ runId: run.id,
63
+ status: "failed",
64
+ error: error instanceof Error ? error.message : String(error)
65
+ });
66
+ }
67
+ }
68
+ }
69
+ return results;
70
+ }
71
+ function requireResumeSession(role, session) {
72
+ if (session === null || !hasText(session.nativeSessionId)) {
73
+ throw new Error(`Role resume has no fixed native session: ${role.taskId}/${role.name}.`);
74
+ }
75
+ return session.nativeSessionId;
76
+ }
77
+ function validateReadySession(role, existing, mode, ready) {
78
+ const session = ready.session;
79
+ if (mode === "new" && session === null)
80
+ return null;
81
+ if (session === null || !hasText(session.nativeSessionId)) {
82
+ throw new Error(`Ready Role session has no native session id: ${role.taskId}/${role.name}.`);
83
+ }
84
+ if (session.agentId !== role.activeAgentId || session.adapterId !== role.adapterId) {
85
+ throw new Error(`Ready Role session identity changed: ${role.taskId}/${role.name}.`);
86
+ }
87
+ if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
88
+ throw new Error(`Role resume changed the fixed native session id: ${role.taskId}/${role.name}.`);
89
+ }
90
+ return { ...session, status: "running" };
91
+ }
92
+ function hasText(value) {
93
+ return typeof value === "string" && value.trim().length > 0;
94
+ }
@@ -0,0 +1,12 @@
1
+ /** Stops archived Task processes at the tmux boundary and closes their sessions. */
2
+ export async function stopArchivedTaskRuntimes(store, delivery, now) {
3
+ const stopped = [];
4
+ for (const task of store.listTasks()) {
5
+ if (task.status !== "archived")
6
+ continue;
7
+ if (await delivery.stopTask(task.id))
8
+ stopped.push(task.id);
9
+ store.saveArchivedTaskStopped(task.id, now);
10
+ }
11
+ return stopped;
12
+ }
@@ -0,0 +1,18 @@
1
+ export function recordLeaderFailure(taskId, nativeSessionId, message, now, existing) {
2
+ const timestamp = now.toISOString();
3
+ return {
4
+ schemaVersion: 1,
5
+ taskId: requiredText(taskId, "Task id"),
6
+ nativeSessionId: requiredText(nativeSessionId, "Native session id"),
7
+ message: requiredText(message, "Leader failure message"),
8
+ attemptCount: (existing?.attemptCount ?? 0) + 1,
9
+ firstFailedAt: existing?.firstFailedAt ?? timestamp,
10
+ lastFailedAt: timestamp
11
+ };
12
+ }
13
+ function requiredText(value, label) {
14
+ const normalized = value.trim();
15
+ if (normalized.length === 0)
16
+ throw new Error(`${label} is required.`);
17
+ return normalized;
18
+ }
@@ -0,0 +1,143 @@
1
+ import { createAgentRun } from "../run/agentRun.js";
2
+ import { recordLeaderFailure } from "./leaderFailure.js";
3
+ import { createLeaderRecoveryNotification } from "./operatorNotification.js";
4
+ export async function processLeaderWakeups(store, delivery, now) {
5
+ const results = [];
6
+ for (const wakeup of store.listPendingWakeups()) {
7
+ const task = store.getTask(wakeup.taskId);
8
+ const role = store.getRole(wakeup.taskId, "leader");
9
+ if (task === null || task.status !== "active" || role === null) {
10
+ results.push({ taskId: wakeup.taskId, status: "skipped", reason: "unavailable" });
11
+ continue;
12
+ }
13
+ if (task.repositoryId !== undefined && task.cwd === undefined) {
14
+ results.push({ taskId: task.id, status: "skipped", reason: "workspace-not-ready" });
15
+ continue;
16
+ }
17
+ if (store.getLeaderFailure(task.id) !== null) {
18
+ results.push({ taskId: task.id, status: "skipped", reason: "recovery-blocked" });
19
+ continue;
20
+ }
21
+ if (store.hasOpenInputRequest(task.id)) {
22
+ results.push({ taskId: task.id, status: "skipped", reason: "waiting-input" });
23
+ continue;
24
+ }
25
+ // This check deliberately precedes every tmux operation. A pending wake is
26
+ // durable state, not text that may be injected into a busy Agent composer.
27
+ if (store.getActiveAgentRun(task.id, role.name) !== null) {
28
+ results.push({ taskId: task.id, status: "skipped", reason: "busy" });
29
+ continue;
30
+ }
31
+ const existingSession = store.getRoleSession(task.id, role.name);
32
+ let effectiveSession = existingSession;
33
+ let claimed = false;
34
+ let run = null;
35
+ try {
36
+ const mode = hasNativeSession(existingSession) ? "resume" : "new";
37
+ const input = leaderWakeupInput(task.id, wakeup.reasons, store.getTaskBrief(task.id), store.listDecisions(task.id), store.listMilestones(task.id));
38
+ run = createAgentRun(store.nextAgentRunId(task.id), task.id, role.name, mode, input, now);
39
+ const prepared = await delivery.prepareRoleSession({
40
+ taskId: task.id,
41
+ roleName: role.name,
42
+ agentId: role.activeAgentId,
43
+ adapterId: role.adapterId,
44
+ mode,
45
+ ...(mode === "resume" ? { nativeSessionId: existingSession.nativeSessionId } : {})
46
+ });
47
+ const ready = await delivery.waitUntilReady(prepared);
48
+ const latestTask = store.getTask(task.id);
49
+ if (latestTask === null || latestTask.status !== "active") {
50
+ results.push({ taskId: task.id, status: "skipped", reason: "unavailable" });
51
+ continue;
52
+ }
53
+ effectiveSession = validateReadySession(role.activeAgentId, existingSession, mode, ready.session);
54
+ const claim = store.saveLeaderDispatch({ task, role, run, session: effectiveSession, wakeup, now });
55
+ if (claim !== "claimed") {
56
+ results.push({ taskId: task.id, status: "skipped", reason: claim });
57
+ continue;
58
+ }
59
+ claimed = true;
60
+ await delivery.sendOnce({
61
+ delivery: ready,
62
+ receiptId: `agent-run:${run.id}`,
63
+ text: input
64
+ });
65
+ store.saveRoleRunDelivery({ task, role, run, session: effectiveSession, now });
66
+ results.push({ taskId: task.id, status: "dispatched" });
67
+ }
68
+ catch (error) {
69
+ const detail = error instanceof Error ? error.message : String(error);
70
+ const message = `Leader dispatch failed: ${detail}`;
71
+ store.saveLeaderDispatchFailure({
72
+ task,
73
+ role,
74
+ session: effectiveSession,
75
+ failure: recordLeaderFailure(task.id, effectiveSession?.nativeSessionId ?? "(unregistered)", message, now, store.getLeaderFailure(task.id)),
76
+ notification: createLeaderRecoveryNotification(task.id, message, now, store.getOperatorNotification(task.id)),
77
+ ...(claimed && run !== null ? { claimed: { run, wakeup } } : {}),
78
+ now
79
+ });
80
+ results.push({ taskId: task.id, status: "failed", error: message });
81
+ }
82
+ }
83
+ return results;
84
+ }
85
+ function validateReadySession(activeAgentId, existing, mode, session) {
86
+ if (mode === "new" && session === null)
87
+ return null;
88
+ if (session === null)
89
+ throw new Error("Leader resume returned no fixed native session.");
90
+ if (session.agentId !== activeAgentId) {
91
+ throw new Error(`Ready session belongs to another Agent: ${session.agentId}.`);
92
+ }
93
+ if (!hasNativeSession(session)) {
94
+ throw new Error("Ready Leader session has no native session id.");
95
+ }
96
+ if (mode === "resume" && session.nativeSessionId !== existing?.nativeSessionId) {
97
+ throw new Error("Leader resume changed the fixed native session id.");
98
+ }
99
+ return { ...session, status: "running" };
100
+ }
101
+ function hasNativeSession(session) {
102
+ return session !== null &&
103
+ typeof session.nativeSessionId === "string" &&
104
+ session.nativeSessionId.trim().length > 0;
105
+ }
106
+ function leaderWakeupInput(taskId, reasons, brief, decisions, milestones) {
107
+ const lines = [
108
+ `Yui wakeup reasons: ${reasons.join(", ")}.`
109
+ ];
110
+ if (brief !== null) {
111
+ lines.push(`Objective: ${brief.objective}`);
112
+ if (brief.boundaries.length > 0) {
113
+ lines.push("Boundaries:");
114
+ for (const boundary of brief.boundaries) {
115
+ lines.push(` - ${boundary}`);
116
+ }
117
+ }
118
+ if (brief.currentFocus.trim().length > 0) {
119
+ lines.push(`Current focus: ${brief.currentFocus}`);
120
+ }
121
+ if (brief.leaderSummary.trim().length > 0) {
122
+ lines.push(`Leader summary: ${brief.leaderSummary}`);
123
+ }
124
+ }
125
+ const activeDecisions = decisions.filter((d) => d.status === "active").slice(-3);
126
+ if (activeDecisions.length > 0) {
127
+ lines.push("Active decisions:");
128
+ for (const decision of activeDecisions) {
129
+ lines.push(` - ${decision.title}: ${decision.rationale}`);
130
+ }
131
+ }
132
+ const recentMilestones = [...milestones]
133
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
134
+ .slice(0, 3);
135
+ if (recentMilestones.length > 0) {
136
+ lines.push("Recent milestones:");
137
+ for (const milestone of recentMilestones) {
138
+ lines.push(` - ${milestone.title}`);
139
+ }
140
+ }
141
+ lines.push(`Inspect yui task context ${taskId}, which includes open and recently resolved input requests; then continue Leader stewardship. Use narrower show/list commands only when one record needs closer inspection.`);
142
+ return lines.join("\n");
143
+ }
@@ -0,0 +1,85 @@
1
+ export async function processOperatorInputNotifications(store, delivery) {
2
+ const requests = store.listOpenInputRequests();
3
+ if (requests.length === 0)
4
+ return [];
5
+ const target = store.getOperatorDeliveryTarget();
6
+ if (target === null || delivery.notifyOperatorInputOnce === undefined) {
7
+ const reason = target === null ? "operator-unavailable" : "delivery-unsupported";
8
+ return requests.map((request) => skipped(request, reason));
9
+ }
10
+ const results = [];
11
+ for (const [index, request] of requests.entries()) {
12
+ try {
13
+ const outcome = await delivery.notifyOperatorInputOnce({
14
+ ...target,
15
+ receiptId: `input-request:${request.id}`,
16
+ text: renderOperatorInputNotification(request)
17
+ });
18
+ if (outcome === "unavailable") {
19
+ results.push(skipped(request, "operator-unavailable"));
20
+ results.push(...requests.slice(index + 1).map((pending) => (skipped(pending, "operator-unavailable"))));
21
+ break;
22
+ }
23
+ else if (outcome === "not-ready") {
24
+ results.push(skipped(request, "operator-not-ready"));
25
+ results.push(...requests.slice(index + 1).map((pending) => (skipped(pending, "operator-not-ready"))));
26
+ break;
27
+ }
28
+ else {
29
+ results.push({
30
+ inputRequestId: request.id,
31
+ taskId: request.taskId,
32
+ status: outcome
33
+ });
34
+ if (outcome === "sent") {
35
+ results.push(...requests.slice(index + 1).map((pending) => (skipped(pending, "operator-not-ready"))));
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ catch (error) {
41
+ results.push({
42
+ inputRequestId: request.id,
43
+ taskId: request.taskId,
44
+ status: "failed",
45
+ error: error instanceof Error ? error.message : String(error)
46
+ });
47
+ }
48
+ }
49
+ return results;
50
+ }
51
+ function renderOperatorInputNotification(request) {
52
+ const recommendedChoiceKey = request.policy.kind === "recommended"
53
+ ? request.policy.recommendedChoiceKey
54
+ : undefined;
55
+ const recommendedChoice = recommendedChoiceKey === undefined
56
+ ? undefined
57
+ : request.choices.find((choice) => choice.key === recommendedChoiceKey);
58
+ return [
59
+ "A Task Leader is waiting for user input. Present this question to the user; do not answer it yourself.",
60
+ `Task: ${request.taskId}`,
61
+ `Input: ${request.id}`,
62
+ `Question: ${request.question}`,
63
+ ...(request.choices.length === 0
64
+ ? ["Answer type: free text"]
65
+ : ["Choices:", ...request.choices.map((choice) => ` ${choice.key}: ${choice.label}`)]),
66
+ ...(request.policy.kind === "required"
67
+ ? ["Decision policy: user response required; there is no automatic fallback."]
68
+ : [
69
+ `Agent recommendation: ${recommendedChoice.key}: ${recommendedChoice.label}`,
70
+ `Automatic fallback after: ${request.policy.timeoutAt}`
71
+ ]),
72
+ `Inspect: yui task input show ${request.id}`,
73
+ request.choices.length === 0
74
+ ? `After the user replies: yui task input answer ${request.id} --text "<answer>"`
75
+ : `After the user chooses: yui task input answer ${request.id} --choice <key>`
76
+ ].join("\n");
77
+ }
78
+ function skipped(request, reason) {
79
+ return {
80
+ inputRequestId: request.id,
81
+ taskId: request.taskId,
82
+ status: "skipped",
83
+ reason
84
+ };
85
+ }
@@ -0,0 +1,17 @@
1
+ export function createLeaderRecoveryNotification(taskId, message, now, existing) {
2
+ const timestamp = now.toISOString();
3
+ return {
4
+ schemaVersion: 1,
5
+ taskId: requiredText(taskId, "Task id"),
6
+ type: "leader-recovery-failed",
7
+ message: requiredText(message, "Operator notification message"),
8
+ createdAt: existing?.createdAt ?? timestamp,
9
+ updatedAt: timestamp
10
+ };
11
+ }
12
+ function requiredText(value, label) {
13
+ const normalized = value.trim();
14
+ if (normalized.length === 0)
15
+ throw new Error(`${label} is required.`);
16
+ return normalized;
17
+ }
@@ -0,0 +1,33 @@
1
+ export function mergePendingWakeup(taskId, reason, now, existing) {
2
+ const normalizedTaskId = requiredText(taskId, "Task id");
3
+ const normalizedReason = requiredText(reason, "Wakeup reason");
4
+ if (existing !== null && existing.taskId !== normalizedTaskId) {
5
+ throw new Error(`Pending wakeup belongs to another Task: ${existing.taskId}.`);
6
+ }
7
+ const timestamp = now.toISOString();
8
+ return {
9
+ schemaVersion: 1,
10
+ taskId: normalizedTaskId,
11
+ reasons: existing === null
12
+ ? [normalizedReason]
13
+ : [...new Set([...existing.reasons, normalizedReason])],
14
+ requestCount: (existing?.requestCount ?? 0) + 1,
15
+ firstRequestedAt: existing?.firstRequestedAt ?? timestamp,
16
+ lastRequestedAt: timestamp
17
+ };
18
+ }
19
+ export function pendingWakeupsMatch(left, right) {
20
+ return left.schemaVersion === right.schemaVersion &&
21
+ left.taskId === right.taskId &&
22
+ left.requestCount === right.requestCount &&
23
+ left.firstRequestedAt === right.firstRequestedAt &&
24
+ left.lastRequestedAt === right.lastRequestedAt &&
25
+ left.reasons.length === right.reasons.length &&
26
+ left.reasons.every((reason, index) => reason === right.reasons[index]);
27
+ }
28
+ function requiredText(value, label) {
29
+ const normalized = value.trim();
30
+ if (normalized.length === 0)
31
+ throw new Error(`${label} is required.`);
32
+ return normalized;
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,41 @@
1
+ import { queueLeaderWakeup } from "./wakeupQueue.js";
2
+ export const EXITED_ROLE_RUN_SUMMARY = "The role's tmux session exited before the run yielded.";
3
+ /**
4
+ * Lightweight liveness only: an active AgentRun whose tmux role is absent is
5
+ * failed, then the Leader is durably queued. No TTL, cooldown, or schedules.
6
+ */
7
+ export async function reconcileExitedRoleRuns(store, delivery, now) {
8
+ const failed = [];
9
+ for (const task of store.listTasks()) {
10
+ for (const role of store.listRoles(task.id)) {
11
+ const run = store.getActiveAgentRun(task.id, role.name);
12
+ if (run === null)
13
+ continue;
14
+ const session = store.getRoleSession(task.id, role.name);
15
+ const status = await delivery.inspectRole({
16
+ taskId: task.id,
17
+ roleName: role.name,
18
+ agentId: role.activeAgentId,
19
+ adapterId: role.adapterId,
20
+ ...(session?.nativeSessionId === undefined
21
+ ? {}
22
+ : { nativeSessionId: session.nativeSessionId })
23
+ });
24
+ if (status === "present")
25
+ continue;
26
+ store.saveExitedRoleRun({
27
+ task,
28
+ role,
29
+ run,
30
+ session,
31
+ summary: EXITED_ROLE_RUN_SUMMARY,
32
+ now
33
+ });
34
+ failed.push(run.id);
35
+ if (task.status === "active") {
36
+ queueLeaderWakeup(store, task.id, role.name === "leader" ? "leader-run-failed" : "role-run-failed", now);
37
+ }
38
+ }
39
+ }
40
+ return failed;
41
+ }
@@ -0,0 +1,13 @@
1
+ import { mergePendingWakeup } from "./pendingWakeup.js";
2
+ export function queueLeaderWakeup(store, taskId, reason, now) {
3
+ const pending = mergePendingWakeup(taskId, reason, now, store.getPendingWakeup(taskId));
4
+ store.savePendingWakeup(pending);
5
+ return pending;
6
+ }
7
+ export function queueLeaderWakeupAfterYield(store, task, run, now) {
8
+ if (run.taskId !== task.id)
9
+ throw new Error(`AgentRun belongs to another Task: ${run.taskId}.`);
10
+ if (task.status !== "active" || run.roleName === "leader")
11
+ return null;
12
+ return queueLeaderWakeup(store, task.id, "role-result", now);
13
+ }