@zq-silk/yui 0.13.7 → 0.13.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +40 -4
- package/README.md +23 -4
- package/dist/cli/commandCatalog.js +21 -2
- package/dist/cli/updatePorts.js +4 -4
- package/dist/cli.js +43 -14
- package/dist/commands/agentCommands.js +1 -1
- package/dist/commands/configCommands.js +1 -86
- package/dist/commands/executionAuditCommands.js +17 -16
- package/dist/commands/globalRoleCommands.js +4 -4
- package/dist/commands/sessionCommands.js +2 -6
- package/dist/commands/taskActor.js +1 -2
- package/dist/commands/taskCommands.js +93 -21
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskExecutionCommands.js +0 -5
- package/dist/commands/taskOverviewCommand.js +5 -1
- package/dist/commands/taskRoleRuntimeStatus.js +3 -16
- package/dist/config/configCatalog.js +1 -6
- package/dist/config/yuiConfig.js +0 -79
- package/dist/context/sessionBootstrapManifest.js +26 -23
- package/dist/controller/clientRuntime.js +40 -3
- package/dist/controller/controller.js +29 -54
- package/dist/controller/fileSchedulerStoreAdapter.js +372 -1117
- package/dist/controller/runtime.js +12 -5
- package/dist/controller/runtimeHookRunFence.js +3 -9
- package/dist/controller/runtimeLaunchCoordinator.js +44 -67
- package/dist/controller/structuredProviderObservation.js +18 -5
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/execution/executionHealth.js +1 -1
- package/dist/executor/agentExecutor.js +103 -93
- package/dist/executor/executorRegistry.js +8 -20
- package/dist/executor/fileRoleLaunchPlanner.js +26 -93
- package/dist/executor/turnCompletion.js +5 -5
- package/dist/lifecycle/exactRunTerminalization.js +2 -4
- package/dist/observability/executionAudit.js +40 -94
- package/dist/operator/operatorSessionHistory.js +7 -5
- package/dist/output/rolePresentation.js +0 -1
- package/dist/repository/taskWorkspacePreparer.js +0 -1
- package/dist/role/role.js +13 -21
- package/dist/run/agentRun.js +4 -54
- package/dist/runtime/agentDriver.js +2 -0
- package/dist/runtime/agentError.js +114 -0
- package/dist/runtime/agentHost.js +55 -82
- package/dist/runtime/builtinAgentDrivers.js +21 -9
- package/dist/runtime/builtinAgentErrorMappers.js +150 -0
- package/dist/runtime/exactControlPlane.js +6 -12
- package/dist/runtime/index.js +1 -2
- package/dist/runtime/launchBroker.js +5 -19
- package/dist/runtime/lifecycleReservation.js +20 -4
- package/dist/runtime/providerRuntimeIdentity.js +11 -19
- package/dist/runtime/runtimeBinding.js +0 -27
- package/dist/runtime/runtimeObservation.js +7 -16
- package/dist/runtime/runtimeSessionCandidate.js +3 -10
- package/dist/runtime/sessionLaunchRequest.js +1 -2
- package/dist/runtime/sessionReconciliation.js +2 -2
- package/dist/runtime/structuredProviderHost.js +44 -79
- package/dist/runtime/taskRuntimeIsolation.js +0 -7
- package/dist/runtime/tmuxAdapters.js +6 -49
- package/dist/scheduler/activeRoleRunDelivery.js +220 -178
- package/dist/scheduler/activeTaskProgress.js +1 -4
- package/dist/scheduler/leaderWakeupProcessor.js +123 -88
- package/dist/scheduler/roleRunLiveness.js +4 -1
- package/dist/scheduler/roleRunStall.js +10 -14
- package/dist/scheduler/wakeReason.js +4 -0
- package/dist/storage/migration/productionRegistry.js +475 -0
- package/dist/storage/sqliteSchema.js +54 -2
- package/dist/storage/sqliteStore.js +11 -44
- package/dist/storage/taskStore.js +8 -36
- package/dist/web/webSnapshot.js +3 -0
- package/i18n/README.zh-CN.md +11 -3
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +30 -8
- package/skills/yui-operator/SKILL.md +14 -6
- package/skills/yui-runtime/SKILL.md +11 -8
- package/dist/lifecycle/providerErrorClass.js +0 -152
- package/dist/run/providerRetry.js +0 -226
- package/dist/run/providerRetryConfig.js +0 -27
- package/dist/runtime/providerErrorCodes.js +0 -278
- package/dist/runtime/providerRecoveryDecision.js +0 -55
|
@@ -19,7 +19,7 @@ import { renderRoleDetails } from "../output/rolePresentation.js";
|
|
|
19
19
|
import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js";
|
|
20
20
|
import { cancelInputRequest } from "../input/inputRequest.js";
|
|
21
21
|
import { retireExactActiveAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
22
|
-
import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole
|
|
22
|
+
import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole } from "../role/role.js";
|
|
23
23
|
import { agentRunDeliveryReceiptId, createAgentRun, failAgentRun, withAgentRunContextSnapshot, yieldAgentRun } from "../run/agentRun.js";
|
|
24
24
|
import { matchYieldReceipt } from "../run/yieldReceipt.js";
|
|
25
25
|
import { createRejectedYieldAttempt, rejectedYieldAttemptEventPayload, rejectedYieldAttemptFromTaskEvent, RUN_YIELD_REJECTED_EVENT } from "../run/rejectedYieldAttempt.js";
|
|
@@ -987,11 +987,6 @@ function retireTaskCommand(args, store, options) {
|
|
|
987
987
|
tx.saveInputRequest(task.id, cancelInputRequest(request, `Task retired: ${summary}`, now));
|
|
988
988
|
}
|
|
989
989
|
}
|
|
990
|
-
for (const role of tx.listRoles(task.id)) {
|
|
991
|
-
if (role.status !== "idle") {
|
|
992
|
-
tx.saveRole(task.id, updateRoleStatus(role, "idle", now));
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
990
|
for (const mailbox of tx.listWorkMailboxes()) {
|
|
996
991
|
if ((mailbox.target.kind === "task"
|
|
997
992
|
|| mailbox.target.kind === "role")
|
|
@@ -1281,6 +1276,8 @@ function taskRoleCommand(args, store, options) {
|
|
|
1281
1276
|
return output(bindTaskRole(rest, store, options));
|
|
1282
1277
|
if (command === "unbind")
|
|
1283
1278
|
return output(unbindTaskRole(rest, store, options));
|
|
1279
|
+
if (command === "session")
|
|
1280
|
+
return taskRoleSessionCommand(rest, store, options);
|
|
1284
1281
|
if (command === "view")
|
|
1285
1282
|
return viewTaskRole(rest, store);
|
|
1286
1283
|
if (command === "takeover")
|
|
@@ -1291,6 +1288,87 @@ function taskRoleCommand(args, store, options) {
|
|
|
1291
1288
|
? "Task role command is required."
|
|
1292
1289
|
: `Unknown command: task role ${command}`);
|
|
1293
1290
|
}
|
|
1291
|
+
function taskRoleSessionCommand(args, store, options) {
|
|
1292
|
+
const [command, ...rest] = args;
|
|
1293
|
+
if (command === "inspect") {
|
|
1294
|
+
exactPositionals(rest, 2, "Task Role Session inspect usage: yui task role session inspect <task> <role>.");
|
|
1295
|
+
const task = requireTask(store, rest[0]);
|
|
1296
|
+
const role = requireRole(store, task.id, rest[1]);
|
|
1297
|
+
taskActor(store, options, task.id);
|
|
1298
|
+
const sessions = store.getTaskRoleSessionSet(task.id, role.name);
|
|
1299
|
+
const active = sessions?.sessions[sessions.activeAgentId] ?? null;
|
|
1300
|
+
const binding = sessions?.providerBinding ?? null;
|
|
1301
|
+
return output(active === null
|
|
1302
|
+
? `No Session exists for ${task.id}/${role.name}.\n`
|
|
1303
|
+
: [
|
|
1304
|
+
`Session ${task.id}/${role.name}`,
|
|
1305
|
+
`Agent: ${active.agentId}/${active.adapterId}`,
|
|
1306
|
+
`Native id: ${active.nativeSessionId}`,
|
|
1307
|
+
`Host activation: ${active.launchId ?? "none"}`,
|
|
1308
|
+
`Session: ${active.status}${active.endReason === undefined ? "" : `/${active.endReason}`}`,
|
|
1309
|
+
`Turn: ${binding?.turn?.status ?? "none"}`
|
|
1310
|
+
].join("\n") + "\n", { task, role, session: active, providerBinding: binding });
|
|
1311
|
+
}
|
|
1312
|
+
if (command === "stop") {
|
|
1313
|
+
const usage = "Task Role Session stop usage: yui task role session stop <task> <role> --reason <text>.";
|
|
1314
|
+
const parsed = parseTail(rest, new Set(["--reason"]), usage);
|
|
1315
|
+
exactPositionals(parsed.positionals, 2, usage);
|
|
1316
|
+
const reason = requiredOption(parsed.options, "--reason");
|
|
1317
|
+
const now = clock(options);
|
|
1318
|
+
const request = store.transaction((tx) => {
|
|
1319
|
+
const task = requireTask(tx, parsed.positionals[0]);
|
|
1320
|
+
assertTaskOpen(task);
|
|
1321
|
+
const actor = taskActor(tx, options, task.id);
|
|
1322
|
+
const role = requireRole(tx, task.id, parsed.positionals[1]);
|
|
1323
|
+
if (actor === "leader" && role.name === LEADER_ROLE) {
|
|
1324
|
+
throw usageError("A Leader cannot stop the Session executing its own current command.", usage);
|
|
1325
|
+
}
|
|
1326
|
+
if (tx.getActiveAgentRun(task.id, role.name) !== null) {
|
|
1327
|
+
throw usageError(`Task Role has an active Run; settle or retire it before stopping the Session: ${task.id}/${role.name}.`, usage);
|
|
1328
|
+
}
|
|
1329
|
+
const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
|
|
1330
|
+
const session = sessions?.sessions[sessions.activeAgentId];
|
|
1331
|
+
if (session === undefined || session.status === "ended") {
|
|
1332
|
+
throw usageError(`Task Role has no active Session: ${task.id}/${role.name}.`, usage);
|
|
1333
|
+
}
|
|
1334
|
+
const lifecycle = tx.getWorkMailbox(runtimeLifecycleTarget({
|
|
1335
|
+
scope: "task",
|
|
1336
|
+
taskId: task.id,
|
|
1337
|
+
roleName: role.name
|
|
1338
|
+
}));
|
|
1339
|
+
if (lifecycle !== null && workMailboxHasWork(lifecycle)) {
|
|
1340
|
+
throw usageError(`Task Role Session lifecycle is busy: ${task.id}/${role.name}.`, usage);
|
|
1341
|
+
}
|
|
1342
|
+
recordTaskEvent(tx, task.id, "runtime.session-stop-requested", {
|
|
1343
|
+
roleName: role.name,
|
|
1344
|
+
agentId: session.agentId,
|
|
1345
|
+
adapterId: session.adapterId,
|
|
1346
|
+
nativeSessionId: session.nativeSessionId,
|
|
1347
|
+
launchId: session.launchId ?? "",
|
|
1348
|
+
reason,
|
|
1349
|
+
requestedBy: actor
|
|
1350
|
+
}, now);
|
|
1351
|
+
return {
|
|
1352
|
+
taskId: task.id,
|
|
1353
|
+
roleName: role.name,
|
|
1354
|
+
agentId: session.agentId,
|
|
1355
|
+
adapterId: session.adapterId,
|
|
1356
|
+
nativeSessionId: session.nativeSessionId,
|
|
1357
|
+
...(session.launchId === undefined ? {} : { launchId: session.launchId }),
|
|
1358
|
+
sessionUpdatedAt: session.updatedAt
|
|
1359
|
+
};
|
|
1360
|
+
});
|
|
1361
|
+
return {
|
|
1362
|
+
kind: "session-stop",
|
|
1363
|
+
...request,
|
|
1364
|
+
reason,
|
|
1365
|
+
output: `Stopped Session ${request.taskId}/${request.roleName}: ${reason}\n`
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
throw usageError(command === undefined
|
|
1369
|
+
? "Task Role Session command is required."
|
|
1370
|
+
: `Unknown command: task role session ${command}`);
|
|
1371
|
+
}
|
|
1294
1372
|
function addTaskRole(args, store, options) {
|
|
1295
1373
|
const usage = "Task role add usage: yui task role add <task> <name> [Role and Agent settings].";
|
|
1296
1374
|
const [taskId, roleName, ...tail] = args;
|
|
@@ -1488,7 +1566,7 @@ function removeTaskRole(args, store, options) {
|
|
|
1488
1566
|
throw usageError(`Task Role has an active Run and cannot be removed: ${task.id}/${role.name}.`);
|
|
1489
1567
|
}
|
|
1490
1568
|
const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
|
|
1491
|
-
if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status
|
|
1569
|
+
if (Object.values(sessions?.sessions ?? {}).some(({ status }) => status === "active")) {
|
|
1492
1570
|
throw usageError(`Task Role has a running native Agent and cannot be removed: ${task.id}/${role.name}.`);
|
|
1493
1571
|
}
|
|
1494
1572
|
if (!tx.removeTaskRole(task.id, role.name))
|
|
@@ -1536,7 +1614,7 @@ function bindTaskRole(args, store, options) {
|
|
|
1536
1614
|
return switchActiveRoleAgent(bound, existing, agent.id, {
|
|
1537
1615
|
activeRun: tx.getActiveAgentRun(task.id, role.name) !== null,
|
|
1538
1616
|
nativeProcessRunning: currentSession !== undefined
|
|
1539
|
-
&& currentSession.status
|
|
1617
|
+
&& currentSession.status === "active"
|
|
1540
1618
|
}, now);
|
|
1541
1619
|
}
|
|
1542
1620
|
catch (error) {
|
|
@@ -1590,7 +1668,7 @@ function viewTaskRole(args, store) {
|
|
|
1590
1668
|
}
|
|
1591
1669
|
const role = requireRole(store, task.id, args[1]);
|
|
1592
1670
|
const session = store.getRoleSession(task.id, role.name);
|
|
1593
|
-
if (session === null || session.status === "
|
|
1671
|
+
if (session === null || session.status === "ended") {
|
|
1594
1672
|
throw usageError(`Task Role has no live Provider view: ${task.id}/${role.name}.`);
|
|
1595
1673
|
}
|
|
1596
1674
|
return {
|
|
@@ -1619,7 +1697,7 @@ function transferTaskRoleAuthority(args, store, options, action) {
|
|
|
1619
1697
|
if (sessions === null || sessions === undefined || session === undefined
|
|
1620
1698
|
|| binding === null || binding === undefined
|
|
1621
1699
|
|| session.launchId === undefined
|
|
1622
|
-
|| session.status === "
|
|
1700
|
+
|| session.status === "ended") {
|
|
1623
1701
|
throw new Error(`Task Role has no live managed Provider: ${task.id}/${role.name}.`);
|
|
1624
1702
|
}
|
|
1625
1703
|
const activation = currentProviderActivation(binding);
|
|
@@ -2374,7 +2452,6 @@ function dispatchWork(args, store, options) {
|
|
|
2374
2452
|
const role = requireRole(tx, task.id, unboundRun.roleName);
|
|
2375
2453
|
tx.saveAgentRun(runWithLineage);
|
|
2376
2454
|
tx.saveActiveAgentRun(runWithLineage);
|
|
2377
|
-
tx.saveRole(task.id, updateRoleStatus(role, "running", now));
|
|
2378
2455
|
enqueueWork(tx, roleMailbox(task.id, role.name), "run-dispatched", now, [
|
|
2379
2456
|
runRef(task.id, runWithLineage.id),
|
|
2380
2457
|
workItemRef(task.id, item.id)
|
|
@@ -3065,7 +3142,7 @@ function rebindTaskFinalReviewContract(args, store, options) {
|
|
|
3065
3142
|
if (leaderSessions.inFlight !== null) {
|
|
3066
3143
|
throw usageError("Task final-review contract cannot rebind while the Leader runtime has unsettled Run state.");
|
|
3067
3144
|
}
|
|
3068
|
-
const liveLeaderSession = Object.values(leaderSessions.sessions).find(({ status }) => status
|
|
3145
|
+
const liveLeaderSession = Object.values(leaderSessions.sessions).find(({ status }) => status === "active");
|
|
3069
3146
|
if (liveLeaderSession !== undefined) {
|
|
3070
3147
|
throw usageError(`Task final-review contract cannot rebind while Leader Session ${liveLeaderSession.agentId} is ${liveLeaderSession.status}.`);
|
|
3071
3148
|
}
|
|
@@ -4499,7 +4576,6 @@ function retryRun(args, store, options) {
|
|
|
4499
4576
|
});
|
|
4500
4577
|
tx.saveAgentRun(created);
|
|
4501
4578
|
tx.saveActiveAgentRun(created);
|
|
4502
|
-
tx.saveRole(task.id, updateRoleStatus(role, "running", now));
|
|
4503
4579
|
if (previous.workItemId !== undefined && retriedItemWithGroup !== null) {
|
|
4504
4580
|
const item = retriedItemWithGroup;
|
|
4505
4581
|
const workspace = tx.getWorkItemWorkspace(task.id, item.id);
|
|
@@ -6072,7 +6148,6 @@ export function dispatchPreparedReviewRound(taskId, reviewRoundId, store, option
|
|
|
6072
6148
|
const laneReviewer = requireRole(tx, taskId, unboundRun.roleName);
|
|
6073
6149
|
tx.saveAgentRun(created);
|
|
6074
6150
|
tx.saveActiveAgentRun(created);
|
|
6075
|
-
tx.saveRole(taskId, updateRoleStatus(laneReviewer, "running", now));
|
|
6076
6151
|
enqueueWork(tx, roleMailbox(taskId, laneReviewer.name), "review-requested", now, [
|
|
6077
6152
|
runRef(taskId, created.id),
|
|
6078
6153
|
...(item === undefined ? [] : [workItemRef(taskId, item.id)])
|
|
@@ -7170,14 +7245,11 @@ function settleTaskExecutionForCompletion(store, taskId, roles, summary, now) {
|
|
|
7170
7245
|
sessions = terminalizeTaskRoleRunSession(sessions, sessions.inFlight, now);
|
|
7171
7246
|
}
|
|
7172
7247
|
const current = sessions.sessions[sessions.activeAgentId];
|
|
7173
|
-
if (current !== undefined && current.status
|
|
7174
|
-
sessions = updateRoleAgentSessionStatus(sessions, sessions.activeAgentId, "
|
|
7248
|
+
if (current !== undefined && current.status === "active") {
|
|
7249
|
+
sessions = updateRoleAgentSessionStatus(sessions, sessions.activeAgentId, "ended", now, "stopped");
|
|
7175
7250
|
}
|
|
7176
7251
|
store.saveTaskRoleSessionSet(sessions);
|
|
7177
7252
|
}
|
|
7178
|
-
if (role.status !== "idle") {
|
|
7179
|
-
store.saveRole(taskId, updateRoleStatus(role, "idle", now));
|
|
7180
|
-
}
|
|
7181
7253
|
if (!cleanupRequired)
|
|
7182
7254
|
continue;
|
|
7183
7255
|
const target = runtimeLifecycleTarget({
|
|
@@ -7191,14 +7263,14 @@ function settleTaskExecutionForCompletion(store, taskId, roles, summary, now) {
|
|
|
7191
7263
|
return targets;
|
|
7192
7264
|
}
|
|
7193
7265
|
function ownedRuntimeSessionRequiresCleanup(session) {
|
|
7194
|
-
if (session === undefined || session.status === "
|
|
7266
|
+
if (session === undefined || session.status === "ended") {
|
|
7195
7267
|
return false;
|
|
7196
7268
|
}
|
|
7197
7269
|
// Older synthetic session fixtures can represent a ready native session
|
|
7198
7270
|
// without a lifecycle generation. Do not manufacture a cleanup obligation
|
|
7199
7271
|
// for those records; coordinated launches always carry launchId, while a
|
|
7200
7272
|
// running session remains actionable even when an older record lacks it.
|
|
7201
|
-
return session.
|
|
7273
|
+
return session.launchId !== undefined;
|
|
7202
7274
|
}
|
|
7203
7275
|
function roleMailbox(taskId, roleName) {
|
|
7204
7276
|
return { kind: "role", taskId, roleName };
|
|
@@ -249,7 +249,7 @@ export function runTaskContextCommand(args, store, currentTaskReviewCandidate =
|
|
|
249
249
|
const effectiveSource = activeRun === undefined ? "Session" : "Run";
|
|
250
250
|
const creation = [...events].reverse().find((event) => (event.type === "role.added" && event.payload.role === role.name));
|
|
251
251
|
return [
|
|
252
|
-
` ${role.name} [${
|
|
252
|
+
` ${role.name} [${activeRun === undefined ? "idle" : "running"}]: ${role.activeAgentId}/${binding.adapterId}`,
|
|
253
253
|
` Desired: r${role.launchRevision}; Profile intent: ${role.defaultAccess}; Model: ${binding.config.model ?? "default"}; effort: ${binding.config.effort ?? "default"}; permission: ${binding.config.permission.strategy}`,
|
|
254
254
|
` Effective: ${effective === undefined
|
|
255
255
|
? "not started"
|
|
@@ -4,7 +4,6 @@ import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
|
4
4
|
import { usageError } from "../errors/cliError.js";
|
|
5
5
|
import { requestDurableJobCancel } from "../job/durableJob.js";
|
|
6
6
|
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
7
|
-
import { updateRoleStatus } from "../role/role.js";
|
|
8
7
|
import { failAgentRun } from "../run/agentRun.js";
|
|
9
8
|
import { queueLeaderWakeup } from "../scheduler/wakeupQueue.js";
|
|
10
9
|
import { startTaskExecution, stopTaskExecution } from "../task/task.js";
|
|
@@ -83,10 +82,6 @@ export function stopTaskExecutionCommand(request, store, options = {}) {
|
|
|
83
82
|
if (sessions !== null && sessions.inFlight !== null) {
|
|
84
83
|
tx.saveTaskRoleSessionSet(terminalizeCurrentRun(sessions, now));
|
|
85
84
|
}
|
|
86
|
-
const role = tx.getRole(task.id, roleName);
|
|
87
|
-
if (role !== null && role.status !== "idle") {
|
|
88
|
-
tx.saveRole(task.id, updateRoleStatus(role, "idle", now));
|
|
89
|
-
}
|
|
90
85
|
}
|
|
91
86
|
// A stop is allowed to discard stale pointer projections even when their
|
|
92
87
|
// historical Runs are already terminal.
|
|
@@ -78,7 +78,11 @@ function buildTaskOverviewEntry(task, store, now, runtimeHealthPolicy) {
|
|
|
78
78
|
const legacyNext = deriveNextAction(task, brief, workItems, openInputRequests, blockers, pendingWakeup);
|
|
79
79
|
const leader = {
|
|
80
80
|
role: "leader",
|
|
81
|
-
roleStatus: leaderRole
|
|
81
|
+
roleStatus: leaderRole === null
|
|
82
|
+
? "missing"
|
|
83
|
+
: agentRuns.some((run) => run.roleName === "leader" && run.status === "active")
|
|
84
|
+
? "running"
|
|
85
|
+
: roleSessions.find((session) => session.roleName === "leader")?.status ?? "idle",
|
|
82
86
|
summary: brief?.leaderSummary ?? null,
|
|
83
87
|
currentFocus: brief?.currentFocus ?? null,
|
|
84
88
|
updatedAt: brief?.updatedAt ?? null,
|
|
@@ -85,7 +85,6 @@ export function renderTaskRoleRuntimeStatus(status) {
|
|
|
85
85
|
? "-"
|
|
86
86
|
: status.launchDrift ? "pending next launch" : "none"}`,
|
|
87
87
|
` Run/session ${status.runSessionDrift ? "snapshot mismatch" : "snapshot consistent"}`,
|
|
88
|
-
` Role state ${status.role.status}`,
|
|
89
88
|
` Active work ${activeWork}`,
|
|
90
89
|
` Active run ${activeRun}`,
|
|
91
90
|
...(lastRun === undefined ? [] : [` Last run ${lastRun}`]),
|
|
@@ -254,10 +253,7 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
254
253
|
healthReason: "the native Session is unbound; verified runtime cleanup is pending"
|
|
255
254
|
};
|
|
256
255
|
}
|
|
257
|
-
if (
|
|
258
|
-
return { health: "failed", healthReason: `persisted Role state is ${role.status}` };
|
|
259
|
-
}
|
|
260
|
-
if (nativeSession?.status === "broken") {
|
|
256
|
+
if (nativeSession?.status === "ended" && nativeSession.endReason === "failed") {
|
|
261
257
|
// Issue 09: a broken Session only fails a live Run. When the last Run
|
|
262
258
|
// already yielded, the Session death is a lifecycle event, not a Run
|
|
263
259
|
// failure — surface it as attention with the persisted Run outcome.
|
|
@@ -287,12 +283,6 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
287
283
|
};
|
|
288
284
|
}
|
|
289
285
|
if (activeRun !== null) {
|
|
290
|
-
if (role.status !== "running") {
|
|
291
|
-
return {
|
|
292
|
-
health: "needs-attention",
|
|
293
|
-
healthReason: `the active Run conflicts with persisted Role state ${role.status}`
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
286
|
if (activeRun.deliveredAt !== undefined && tmux.state !== "running") {
|
|
297
287
|
return { health: "needs-attention", healthReason: "the delivered active Run has no live tmux pane" };
|
|
298
288
|
}
|
|
@@ -339,13 +329,10 @@ function calculateHealth(role, activeRun, lastRun, nativeSession, runtimeCleanup
|
|
|
339
329
|
}
|
|
340
330
|
}
|
|
341
331
|
}
|
|
342
|
-
if (
|
|
343
|
-
return { health: "needs-attention", healthReason: "the Role is running without an active Run" };
|
|
344
|
-
}
|
|
345
|
-
if (nativeSession?.status === "running" && tmux.state !== "running") {
|
|
332
|
+
if (nativeSession?.status === "active" && tmux.state !== "running") {
|
|
346
333
|
return { health: "needs-attention", healthReason: "the native session is running without a live tmux pane" };
|
|
347
334
|
}
|
|
348
|
-
if (nativeSession?.status === "
|
|
335
|
+
if (nativeSession?.status === "ended" && tmux.state === "running") {
|
|
349
336
|
return { health: "needs-attention", healthReason: "a stopped native session has a live tmux pane" };
|
|
350
337
|
}
|
|
351
338
|
if (role.name === "leader" && openInputRequestCount > 0) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
2
1
|
import { MAX_RUN_CAP } from "../telemetry/telemetryConfig.js";
|
|
3
|
-
import { MAX_CONTEXT_BUDGET_TOKENS,
|
|
2
|
+
import { MAX_CONTEXT_BUDGET_TOKENS, MIN_CONTEXT_BUDGET_TOKENS } from "./yuiConfig.js";
|
|
4
3
|
export const CONFIG_DOMAINS = ["system", "runtime", "workflow", "resources", "tools"];
|
|
5
4
|
/**
|
|
6
5
|
* Public durable configuration contract. CLI help, completion, config show,
|
|
@@ -16,10 +15,6 @@ export const CONFIG_DEFINITIONS = Object.freeze([
|
|
|
16
15
|
{ key: "runtime-health", domain: "runtime", property: "runtimeHealth", label: "Runtime health thresholds", summary: "Quiet, checkpoint-overdue, and read-only diagnostic thresholds in seconds; values must be strictly increasing (defaults: 300/900/1800).", takesEffect: "CLI and Web projections immediately; scheduler diagnostics after the Controller restarts." },
|
|
17
16
|
{ key: "agent-launch-inactivity-timeout-seconds", domain: "runtime", property: "agentLaunchInactivityTimeoutSeconds", label: "Agent launch inactivity timeout", summary: "Maximum launch silence before startup fails, 15-3600 seconds (default: 300).", takesEffect: "After the Controller restarts." },
|
|
18
17
|
{ key: "delivery-timeout-seconds", domain: "runtime", property: "deliveryTimeoutSeconds", label: "Delivery timeout", summary: "Total control-plane delivery retry budget, 5-600 seconds (default: 120).", takesEffect: "After the Controller restarts; internal retry cadence remains automatic." },
|
|
19
|
-
{ key: "provider-retry-mode", domain: "runtime", property: "providerRetryMode", label: "Provider retry mode", summary: "Provider retry mode: off, shadow, or enforce (default: enforce).", takesEffect: "The next eligible provider-failure decision." },
|
|
20
|
-
{ key: "provider-retry-adapters", domain: "runtime", property: "providerRetryAdapters", label: "Provider retry adapters", summary: `Adapters with in-place retry: all, off, or a comma-separated subset of ${supportedAgentAdapterIds().join(", ")} (default: all).`, takesEffect: "The next eligible provider-failure decision." },
|
|
21
|
-
{ key: "provider-retry-delays-seconds", domain: "runtime", property: "providerRetryDelaysSeconds", label: "Provider retry delays", summary: `Ordered comma-separated retry delays of 1-600 seconds, with 1-${MAX_PROVIDER_RETRY_ATTEMPTS} attempts (default: 2,5,15).`, takesEffect: "The next eligible provider-failure decision; attempt count is the list length." },
|
|
22
|
-
{ key: "provider-retry-max-window-seconds", domain: "runtime", property: "providerRetryMaxWindowSeconds", label: "Provider retry max window", summary: "Positive-integer total retry budget per Run lineage in seconds (default: 600).", takesEffect: "The next retry episode; an active episode keeps its existing deadline." },
|
|
23
18
|
{ key: "leader-next-action", domain: "workflow", property: "leaderNextActionMode", label: "Leader next-action mode", summary: "Leader next-action mode: display, warn, or enforce (default: display).", takesEffect: "The next Leader next-action projection or gate." },
|
|
24
19
|
{ key: "leader-semantic-budget-turns", domain: "workflow", property: "leaderSemanticBudgetTurns", label: "Leader semantic budget", summary: "Consecutive yielded Leader turns without durable delivery progress before warning, 1-20 (default: 3).", takesEffect: "The next Leader delivery guard evaluation." },
|
|
25
20
|
{ key: "context-budget", domain: "workflow", property: "contextBudget", label: "Legacy context budget (inactive)", summary: `Compatibility-only soft and hard values in ${MIN_CONTEXT_BUDGET_TOKENS}-${MAX_CONTEXT_BUDGET_TOKENS}; they are parsed but never control Session or workflow behavior.`, takesEffect: "No runtime effect; Session Token metrics are read-only observations." },
|
package/dist/config/yuiConfig.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { supportedAgentAdapterIds } from "../agent/adapterCatalog.js";
|
|
2
1
|
import { DEFAULT_RUN_CAP, DEFAULT_TERMINAL_KEEP, MAX_RUN_CAP } from "../telemetry/telemetryConfig.js";
|
|
3
2
|
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "../runtime/runtimeHealthPolicy.js";
|
|
4
3
|
export const DEFAULT_RECONCILIATION_INTERVAL_SECONDS = 120;
|
|
@@ -75,84 +74,6 @@ export function resolveLeaderNextActionMode(value) {
|
|
|
75
74
|
}
|
|
76
75
|
return normalized;
|
|
77
76
|
}
|
|
78
|
-
// ── Issue 01: Provider retry ──────────────────────────────────────────────
|
|
79
|
-
export const PROVIDER_RETRY_MODES = ["off", "shadow", "enforce"];
|
|
80
|
-
export const DEFAULT_PROVIDER_RETRY_MODE = "enforce";
|
|
81
|
-
export const DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS = Object.freeze([2, 5, 15]);
|
|
82
|
-
export const DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS = 600;
|
|
83
|
-
export const MAX_PROVIDER_RETRY_ATTEMPTS = 10;
|
|
84
|
-
export function resolveProviderRetryMode(value) {
|
|
85
|
-
if (value === undefined || value === null)
|
|
86
|
-
return DEFAULT_PROVIDER_RETRY_MODE;
|
|
87
|
-
if (typeof value !== "string") {
|
|
88
|
-
throw new TypeError("providerRetryMode must be off, shadow, or enforce.");
|
|
89
|
-
}
|
|
90
|
-
const normalized = value.trim().toLowerCase();
|
|
91
|
-
if (normalized.length === 0)
|
|
92
|
-
return DEFAULT_PROVIDER_RETRY_MODE;
|
|
93
|
-
if (!PROVIDER_RETRY_MODES.includes(normalized)) {
|
|
94
|
-
throw new TypeError("providerRetryMode must be off, shadow, or enforce.");
|
|
95
|
-
}
|
|
96
|
-
return normalized;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Resolves the adapter list. `["all"]` or undefined means every supported
|
|
100
|
-
* adapter; an empty array disables in-place retry.
|
|
101
|
-
*/
|
|
102
|
-
export function resolveProviderRetryAdapters(value) {
|
|
103
|
-
if (value === undefined || value === null) {
|
|
104
|
-
return [...supportedAgentAdapterIds()];
|
|
105
|
-
}
|
|
106
|
-
if (!Array.isArray(value)) {
|
|
107
|
-
throw new TypeError("providerRetryAdapters must be an array of adapter ids.");
|
|
108
|
-
}
|
|
109
|
-
const supported = new Set(supportedAgentAdapterIds());
|
|
110
|
-
const adapters = [];
|
|
111
|
-
for (const raw of value) {
|
|
112
|
-
if (typeof raw !== "string") {
|
|
113
|
-
throw new TypeError("providerRetryAdapters entries must be strings.");
|
|
114
|
-
}
|
|
115
|
-
const token = raw.trim().toLowerCase();
|
|
116
|
-
if (token === "all") {
|
|
117
|
-
for (const adapter of supportedAgentAdapterIds()) {
|
|
118
|
-
if (!adapters.includes(adapter))
|
|
119
|
-
adapters.push(adapter);
|
|
120
|
-
}
|
|
121
|
-
continue;
|
|
122
|
-
}
|
|
123
|
-
if (!/^[a-z0-9][a-z0-9._-]*$/u.test(token)) {
|
|
124
|
-
throw new TypeError(`Invalid Provider retry adapter: ${token}.`);
|
|
125
|
-
}
|
|
126
|
-
if (!supported.has(token)) {
|
|
127
|
-
throw new TypeError(`Unknown Provider retry adapter: ${token}.`);
|
|
128
|
-
}
|
|
129
|
-
if (!adapters.includes(token))
|
|
130
|
-
adapters.push(token);
|
|
131
|
-
}
|
|
132
|
-
return adapters;
|
|
133
|
-
}
|
|
134
|
-
export function resolveProviderRetryDelaysSeconds(value) {
|
|
135
|
-
if (value === undefined || value === null)
|
|
136
|
-
return [...DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS];
|
|
137
|
-
if (!Array.isArray(value) || value.length < 1 || value.length > MAX_PROVIDER_RETRY_ATTEMPTS) {
|
|
138
|
-
throw new TypeError(`providerRetryDelaysSeconds must contain 1-${MAX_PROVIDER_RETRY_ATTEMPTS} positive integers.`);
|
|
139
|
-
}
|
|
140
|
-
const delays = value.map((entry) => {
|
|
141
|
-
if (typeof entry !== "number" || !Number.isSafeInteger(entry) || entry < 1 || entry > 600) {
|
|
142
|
-
throw new TypeError("providerRetryDelaysSeconds entries must be integers from 1 to 600.");
|
|
143
|
-
}
|
|
144
|
-
return entry;
|
|
145
|
-
});
|
|
146
|
-
for (let index = 1; index < delays.length; index += 1) {
|
|
147
|
-
if (delays[index] < delays[index - 1]) {
|
|
148
|
-
throw new TypeError("providerRetryDelaysSeconds must be ordered from shortest to longest.");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return delays;
|
|
152
|
-
}
|
|
153
|
-
export function resolveProviderRetryMaxWindowSeconds(value) {
|
|
154
|
-
return resolveBoundedPositiveInteger(value, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, 1, Number.MAX_SAFE_INTEGER, "providerRetryMaxWindowSeconds");
|
|
155
|
-
}
|
|
156
77
|
// ── Executable paths ──────────────────────────────────────────────────────
|
|
157
78
|
export function resolveTmuxBin(value) {
|
|
158
79
|
if (value === undefined || value === null)
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { chmodSync, existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
import { exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
|
|
4
|
+
import { exactControlPlaneCommandPrefix, exactControlPlaneDigest, serializeExactDescriptor } from "../runtime/exactControlPlane.js";
|
|
5
5
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
6
6
|
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
7
7
|
export { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, SESSION_CONTEXT_PROTOCOL, sessionManifestCompatibilityDigest } from "./sessionProtocolIdentity.js";
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
const ordinarySessionCli = ["#!/bin/sh", "exec yui \"$@\"", ""].join("\n");
|
|
9
|
+
function renderSessionCli(controlPlane) {
|
|
10
|
+
return [
|
|
11
|
+
"#!/bin/sh",
|
|
12
|
+
`exec ${exactControlPlaneCommandPrefix(controlPlane)} \"$@\"`,
|
|
13
|
+
""
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
13
16
|
/** Read back one immutable Session Manifest and verify its content digest. */
|
|
14
17
|
export function readSessionBootstrapManifest(path) {
|
|
15
18
|
const source = resolve(path);
|
|
@@ -84,10 +87,11 @@ export function materializeSessionBootstrap(input) {
|
|
|
84
87
|
const controlDigest = exactControlPlaneDigest(input.controlPlane);
|
|
85
88
|
const descriptorPath = resolve(join(home, "runtime", "control-plane", `${controlDigest}.json`));
|
|
86
89
|
writeImmutableText(descriptorPath, `${serializeExactDescriptor(input.controlPlane)}\n`);
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
90
|
+
// Provider command runners may rebuild PATH independently of the managed
|
|
91
|
+
// process environment. Keep the Session entry point deterministic; the
|
|
92
|
+
// existing continuity preflight accepts compatible package replacement at
|
|
93
|
+
// this path without treating package version as Session identity.
|
|
94
|
+
const sessionCliContent = renderSessionCli(input.controlPlane);
|
|
91
95
|
const sessionCliDigest = digest(sessionCliContent);
|
|
92
96
|
const sessionCliPath = resolve(join(home, "runtime", "session-cli", `yui-${sessionCliDigest}.sh`));
|
|
93
97
|
writeImmutableText(sessionCliPath, sessionCliContent);
|
|
@@ -126,11 +130,11 @@ export function materializeSessionBootstrap(input) {
|
|
|
126
130
|
roleProfileRef: { digest: profileDigest, path: roleProfilePath },
|
|
127
131
|
contextProtocol: input.owner.scope === "global"
|
|
128
132
|
? {
|
|
129
|
-
loadCommand: "
|
|
133
|
+
loadCommand: "\"$YUI_SESSION_CLI\" session context \"$YUI_ROLE\" --json"
|
|
130
134
|
}
|
|
131
135
|
: {
|
|
132
|
-
loadCommand: "
|
|
133
|
-
expandCommand: "
|
|
136
|
+
loadCommand: "\"$YUI_SESSION_CLI\" task run context \"$YUI_TASK_ID/<run-id>\" --json",
|
|
137
|
+
expandCommand: "\"$YUI_SESSION_CLI\" task run context expand \"$YUI_TASK_ID/<run-id>\" <ref-id> --store <store> --mode full --json"
|
|
134
138
|
}
|
|
135
139
|
};
|
|
136
140
|
const manifest = Object.freeze({ ...body, digest: digest(body) });
|
|
@@ -145,14 +149,13 @@ export function materializeSessionBootstrap(input) {
|
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
151
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* Manifest and its frozen descriptor stay immutable and continue to
|
|
152
|
-
* authenticate the Session; repeated refreshes are no-ops.
|
|
152
|
+
* Retargets known managed wrappers to the current resolved control plane after
|
|
153
|
+
* a compatible update. Only a valid Session Manifest may nominate a wrapper,
|
|
154
|
+
* and only Yui's ordinary or exact two-line wrapper shapes are changed.
|
|
153
155
|
*/
|
|
154
|
-
export function refreshManagedSessionCliWrappers(homeInput) {
|
|
156
|
+
export function refreshManagedSessionCliWrappers(homeInput, controlPlane) {
|
|
155
157
|
const home = resolve(homeInput);
|
|
158
|
+
const currentSessionCli = renderSessionCli(controlPlane);
|
|
156
159
|
const manifestDirectory = resolve(join(home, "runtime", "session-manifests"));
|
|
157
160
|
const sessionCliDirectory = resolve(join(home, "runtime", "session-cli"));
|
|
158
161
|
if (!existsSync(manifestDirectory)) {
|
|
@@ -189,21 +192,21 @@ export function refreshManagedSessionCliWrappers(homeInput) {
|
|
|
189
192
|
continue;
|
|
190
193
|
}
|
|
191
194
|
const content = readFileSync(wrapperPath, "utf8");
|
|
192
|
-
if (content ===
|
|
195
|
+
if (content === currentSessionCli) {
|
|
193
196
|
current += 1;
|
|
194
197
|
continue;
|
|
195
198
|
}
|
|
196
|
-
if (!
|
|
199
|
+
if (content !== ordinarySessionCli && !isExactSessionCli(content)) {
|
|
197
200
|
skipped += 1;
|
|
198
201
|
continue;
|
|
199
202
|
}
|
|
200
|
-
writeTextFileAtomically(wrapperPath,
|
|
203
|
+
writeTextFileAtomically(wrapperPath, currentSessionCli);
|
|
201
204
|
chmodSync(wrapperPath, 0o700);
|
|
202
205
|
refreshed += 1;
|
|
203
206
|
}
|
|
204
207
|
return Object.freeze({ refreshed, current, skipped });
|
|
205
208
|
}
|
|
206
|
-
function
|
|
209
|
+
function isExactSessionCli(content) {
|
|
207
210
|
return /^#!\/bin\/sh\nexec [^\n]+ '--yui-control' '[a-f0-9]{64}' "\$@"\n$/u.test(content);
|
|
208
211
|
}
|
|
209
212
|
function writeImmutableText(path, content) {
|
|
@@ -466,11 +466,48 @@ export class FileTaskWorkflowRuntime {
|
|
|
466
466
|
throw new Error(`Role runtime did not stop: ${target.taskId}/${target.roleName}.`);
|
|
467
467
|
}
|
|
468
468
|
const session = this.store.getRoleSession(target.taskId, target.roleName);
|
|
469
|
-
if (session !== null && session.status !== "
|
|
469
|
+
if (session !== null && session.status !== "ended") {
|
|
470
470
|
throw new Error(`Role runtime session is still active: ${target.taskId}/${target.roleName}.`);
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
|
+
/** Stops only the exact idle Session observed by an Agent command. */
|
|
475
|
+
async stopExactTaskRoleSession(input) {
|
|
476
|
+
if (this.store.getActiveAgentRun(input.taskId, input.roleName) !== null) {
|
|
477
|
+
throw new Error(`Role has an active Run: ${input.taskId}/${input.roleName}.`);
|
|
478
|
+
}
|
|
479
|
+
const owner = {
|
|
480
|
+
scope: "task",
|
|
481
|
+
taskId: input.taskId,
|
|
482
|
+
roleName: input.roleName
|
|
483
|
+
};
|
|
484
|
+
const target = this.schedulerStore.enqueueRuntimeCleanup(owner, new Date(), {
|
|
485
|
+
owner,
|
|
486
|
+
agentId: input.agentId,
|
|
487
|
+
adapterId: input.adapterId,
|
|
488
|
+
nativeSessionId: input.nativeSessionId,
|
|
489
|
+
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
490
|
+
sessionUpdatedAt: input.sessionUpdatedAt
|
|
491
|
+
});
|
|
492
|
+
if (target === null) {
|
|
493
|
+
throw new Error(`Role Session changed before its exact stop was reserved: ${input.taskId}/${input.roleName}.`);
|
|
494
|
+
}
|
|
495
|
+
await callFileTaskController(this.home, "scheduler.scan", {}, {
|
|
496
|
+
...this.clientOptions,
|
|
497
|
+
requestTimeoutMs: LIFECYCLE_REQUEST_TIMEOUT_MS
|
|
498
|
+
});
|
|
499
|
+
if (hasRuntimeLifecycleWork(this.store.getWorkMailbox(target))) {
|
|
500
|
+
throw new Error(`Role runtime did not stop: ${input.taskId}/${input.roleName}.`);
|
|
501
|
+
}
|
|
502
|
+
const session = this.store.getRoleSession(input.taskId, input.roleName);
|
|
503
|
+
if (session !== null
|
|
504
|
+
&& session.status !== "ended"
|
|
505
|
+
&& session.agentId === input.agentId
|
|
506
|
+
&& session.adapterId === input.adapterId
|
|
507
|
+
&& session.nativeSessionId === input.nativeSessionId) {
|
|
508
|
+
throw new Error(`Role runtime session is still active: ${input.taskId}/${input.roleName}.`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
474
511
|
/** Wait until every cancellation requested by Task execution stop is physically settled. */
|
|
475
512
|
async stopTaskDurableJobs(taskId) {
|
|
476
513
|
const deadline = Date.now() + LIFECYCLE_REQUEST_TIMEOUT_MS;
|
|
@@ -506,7 +543,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
506
543
|
+ activeJobs.map(({ id }) => id).join(", "));
|
|
507
544
|
}
|
|
508
545
|
const liveSessions = this.store.listRoleSessionSets(taskId).flatMap((sessions) => (Object.values(sessions.sessions)
|
|
509
|
-
.filter((session) => session.status
|
|
546
|
+
.filter((session) => session.status === "active")
|
|
510
547
|
.map((session) => `${sessions.owner.roleName}/${session.agentId}/${session.status}`)));
|
|
511
548
|
if (liveSessions.length > 0) {
|
|
512
549
|
throw new WorkspaceCleanupBlockedError("physical-resource-live", `task:${taskId}`, true, `Task physical resources are not released: current Role Session state is still live: `
|
|
@@ -556,7 +593,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
556
593
|
}
|
|
557
594
|
const sessions = this.store.getGlobalRoleSessionSet(roleName);
|
|
558
595
|
const active = sessions?.sessions[sessions.activeAgentId];
|
|
559
|
-
if (active !== undefined && active.status !== "
|
|
596
|
+
if (active !== undefined && active.status !== "ended") {
|
|
560
597
|
throw new Error(`Global Role runtime session is still active: ${roleName}.`);
|
|
561
598
|
}
|
|
562
599
|
}
|