@zq-silk/yui 0.11.3 → 0.12.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/ARCHITECTURE.md +23 -4
- package/README.md +44 -22
- package/dist/cli/commandCatalog.js +13 -10
- package/dist/cli/interactionPolicy.js +5 -6
- package/dist/cli/updateCommand.js +3 -1
- package/dist/cli/updateOrchestrator.js +173 -28
- package/dist/cli/updatePorts.js +137 -8
- package/dist/cli/upgradeCommand.js +19 -9
- package/dist/cli.js +156 -57
- package/dist/commands/configCommands.js +13 -46
- package/dist/commands/executionAuditCommands.js +3 -2
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +234 -200
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +61 -13
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +55 -7
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +3 -3
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +39 -3
- package/dist/controller/controller.js +31 -27
- package/dist/controller/fileSchedulerStoreAdapter.js +390 -332
- package/dist/controller/runtime.js +58 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeHookRunFence.js +19 -4
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/controller/structuredProviderObservation.js +20 -3
- package/dist/core/controllerClient.js +20 -2
- package/dist/core/controllerServer.js +1 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +52 -50
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +129 -45
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +69 -5
- package/dist/observability/executionAudit.js +5 -0
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/release/runtimeRelease.js +20 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/run/recoveryProjection.js +45 -6
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/agentHost.js +159 -85
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/conversationSwitch.js +277 -0
- package/dist/runtime/index.js +2 -1
- package/dist/runtime/launchBroker.js +12 -0
- package/dist/runtime/processExitOutbox.js +88 -0
- package/dist/runtime/providerRuntimeIdentity.js +29 -1
- package/dist/runtime/runtimeHealthPolicy.js +5 -5
- package/dist/runtime/runtimeObservation.js +28 -0
- package/dist/runtime/runtimeProjection.js +22 -32
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/runtime/tmuxAdapters.js +4 -1
- package/dist/scheduler/activeRoleRunDelivery.js +34 -199
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +42 -138
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +53 -31
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +66 -23
- package/dist/storage/sqliteStore.js +46 -23
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/homeClassification.js +52 -0
- package/dist/storage/upgrade/offlineUpgradeInventory.js +145 -7
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +333 -12
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -58
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +7 -1
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +60 -20
- package/skills/yui-operator/SKILL.md +13 -4
- package/skills/yui-reviewer/SKILL.md +35 -11
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/lifecycle/taskRoleSessionReset.js +0 -126
- package/dist/scheduler/operatorNotification.js +0 -59
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ import { runResourcesCommand } from "./commands/resourcesCommands.js";
|
|
|
37
37
|
import { applyOperatorSessionControl, runOperatorCommand } from "./commands/operatorCommands.js";
|
|
38
38
|
import { runProjectCommand } from "./commands/projectCommands.js";
|
|
39
39
|
import { runProfileCommand } from "./commands/profileCommands.js";
|
|
40
|
-
import { dispatchPreparedReviewRound, failPendingReviewRound,
|
|
40
|
+
import { dispatchPreparedReviewRound, failPendingReviewRound, preserveReviewRoundWorkspace, parseTaskCompletionRequest, parseTaskFinalReviewContractRebindRequest, preflightTaskCompletion, runTaskCommand, normalizedExecutionLanePlan, resolvedExecutionStageRetryGroup, validateTaskArchiveRequest } from "./commands/taskCommands.js";
|
|
41
41
|
import { taskActor } from "./commands/taskActor.js";
|
|
42
42
|
import { isCurrentGlobalOperator } from "./commands/taskInputCommands.js";
|
|
43
43
|
import { runTaskIntegrationCommand } from "./commands/taskIntegrationCommands.js";
|
|
@@ -49,6 +49,7 @@ import { runWorkflowCommandAsync } from "./commands/workflowCommands.js";
|
|
|
49
49
|
import { createUpdatePorts } from "./cli/updatePorts.js";
|
|
50
50
|
import { createReleaseWorkflowPorts } from "./release/releaseWorkflowPorts.js";
|
|
51
51
|
import { acquireHandoverLock, readRuntimeIdentity } from "./release/runtimeRelease.js";
|
|
52
|
+
import { assertCliHomeReleaseFence, describeCliHomeInvocation } from "./release/cliHomeReleaseFence.js";
|
|
52
53
|
import { renderReleaseActivateResult, renderReleaseInstallResult, renderReleaseList, resolveReleaseActivationDriver, runReleaseActivate, runReleaseInstall, runReleaseList } from "./commands/releaseCommands.js";
|
|
53
54
|
import { reconcileTaskRemoteBaselines, verifyTaskCompletionPublishedTree } from "./commands/taskCompletionGate.js";
|
|
54
55
|
import { runTaskBaseStatusCommand } from "./commands/taskBaseCommands.js";
|
|
@@ -68,7 +69,7 @@ import { agentNotFound, CliError, runtimeError, usageError } from "./errors/cliE
|
|
|
68
69
|
import { FileRoleLaunchPlanner } from "./executor/fileRoleLaunchPlanner.js";
|
|
69
70
|
import { AGENT_HOST_CONTROL_PROTOCOL, runAgentHost, sendAgentHostAuthorityControl } from "./runtime/agentHost.js";
|
|
70
71
|
import { TaskWorkspaceCoordinator, WorkspaceCleanupBlockedError } from "./repository/taskWorkspaceCoordinator.js";
|
|
71
|
-
import { FileTaskWorkspacePreparer
|
|
72
|
+
import { FileTaskWorkspacePreparer } from "./repository/taskWorkspacePreparer.js";
|
|
72
73
|
import { inspectStorageSchema } from "./storage/storageSchema.js";
|
|
73
74
|
import { collectRuntimeBuildIdentity, collectStorageIdentity, countDroppedInboxEvents, createProductionRuntimeIdentityPorts, evaluateStorageHealth, resolveStatusIdentityEnabled } from "./observability/runtimeIdentity.js";
|
|
74
75
|
import { resolveYuiHome } from "./storage/taskStore.js";
|
|
@@ -83,13 +84,14 @@ import { AgentConfigurationCatalogService } from "./executor/agentConfigurationC
|
|
|
83
84
|
import { TmuxWebTerminalService } from "./web/tmuxWebTerminal.js";
|
|
84
85
|
import { listOperatorSessions, operatorSessionRef } from "./operator/operatorSessionHistory.js";
|
|
85
86
|
import { YUI_VERSION, yuiVersionIdentity } from "./version.js";
|
|
87
|
+
import { SqliteSchemaMigrationError } from "./storage/sqliteSchema.js";
|
|
86
88
|
import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR, assertCompatibleControlPlanePreflight, assertExactControlPlanePreflight, assertExactTaskRuntimeEnvironment, assertExactTaskRuntimeState, exactControlPlaneDigest, extractExactControlArgument, createExactControlPlaneDescriptor, parseExactControlPlaneDescriptor } from "./runtime/exactControlPlane.js";
|
|
87
89
|
import { readSessionBootstrapManifest, refreshManagedSessionCliWrappers } from "./context/sessionBootstrapManifest.js";
|
|
88
90
|
import { builtinAgentDriverRegistry } from "./runtime/builtinAgentDrivers.js";
|
|
89
91
|
import { createTaskFinalReviewContract, extractTaskFinalReviewRequest } from "./review/taskFinalReviewContract.js";
|
|
90
92
|
import { prepareTaskFinalReviewContractRebindProof, resolveRecordedTaskFinalReviewContract } from "./review/taskFinalReviewContractRebind.js";
|
|
91
93
|
import { assessDeltaRecheck } from "./review/deltaRecheck.js";
|
|
92
|
-
import {
|
|
94
|
+
import { isAcceptedTaskReviewBaseline } from "./review/reviewAcceptance.js";
|
|
93
95
|
import { NodeGitWorkspace } from "./repository/gitWorkspace.js";
|
|
94
96
|
import { currentWorkItemExecutionGroup, workItemExecutionGroupById } from "./workItem/workItem.js";
|
|
95
97
|
const VERSION = YUI_VERSION;
|
|
@@ -107,13 +109,47 @@ void main().catch((error) => {
|
|
|
107
109
|
process.exitCode = error.exitCode;
|
|
108
110
|
return;
|
|
109
111
|
}
|
|
110
|
-
const message =
|
|
112
|
+
const message = runtimeFailureMessage(error);
|
|
111
113
|
process.stderr.write(`${jsonOutput
|
|
112
114
|
? JSON.stringify({ ok: false, code: "RUNTIME_ERROR", message, details: {} })
|
|
113
115
|
: `RUNTIME_ERROR: ${message}`}\n`);
|
|
114
116
|
process.exitCode = 5;
|
|
115
117
|
});
|
|
118
|
+
function runtimeFailureMessage(error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
if (!(error instanceof SqliteSchemaMigrationError))
|
|
121
|
+
return message;
|
|
122
|
+
try {
|
|
123
|
+
return `${message}\n${describeCliHomeInvocation({
|
|
124
|
+
home: resolveYuiHome(process.env),
|
|
125
|
+
packageRoot: fileURLToPath(new URL("../", import.meta.url)),
|
|
126
|
+
entryPath: fileURLToPath(import.meta.url)
|
|
127
|
+
})}`;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return message;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
116
133
|
export async function main() {
|
|
134
|
+
const home = resolveYuiHome(process.env);
|
|
135
|
+
const routedForFence = args.length === 0 ? undefined : routeInvocation(args);
|
|
136
|
+
const managedInvocation = process.env.YUI_SESSION_SCOPE === "task"
|
|
137
|
+
|| process.env.YUI_SESSION_SCOPE === "global"
|
|
138
|
+
|| process.env[YUI_CONTROL_PLANE_DESCRIPTOR] !== undefined
|
|
139
|
+
|| process.env[YUI_TASK_RUNTIME_DESCRIPTOR] !== undefined;
|
|
140
|
+
const homeFreeInvocation = args.length === 0
|
|
141
|
+
|| (args[0] === "version" && args.length === 1)
|
|
142
|
+
|| routedForFence?.kind === "help"
|
|
143
|
+
|| routedForFence?.kind === "path-error"
|
|
144
|
+
|| routedForFence?.kind === "incomplete";
|
|
145
|
+
if (managedInvocation || !homeFreeInvocation) {
|
|
146
|
+
assertCliHomeReleaseFence({
|
|
147
|
+
home,
|
|
148
|
+
packageRoot: fileURLToPath(new URL("../", import.meta.url)),
|
|
149
|
+
entryPath: fileURLToPath(import.meta.url),
|
|
150
|
+
args
|
|
151
|
+
});
|
|
152
|
+
}
|
|
117
153
|
const delegatedDriver = explicitReleaseActivationDriver();
|
|
118
154
|
if (delegatedDriver !== null) {
|
|
119
155
|
const delegated = spawnSync(process.execPath, [delegatedDriver, ...process.argv.slice(2)], {
|
|
@@ -160,7 +196,6 @@ export async function main() {
|
|
|
160
196
|
process.exitCode = runUpdateCommand();
|
|
161
197
|
return;
|
|
162
198
|
}
|
|
163
|
-
const home = resolveYuiHome(process.env);
|
|
164
199
|
if (args[0] === "release") {
|
|
165
200
|
const subcommand = args[1];
|
|
166
201
|
if (subcommand === "install" && args.length === 3) {
|
|
@@ -266,19 +301,44 @@ export async function main() {
|
|
|
266
301
|
if (args[0] === "upgrade") {
|
|
267
302
|
// Mirror doctor/controller: needs a Home but self-manages the schema check,
|
|
268
303
|
// because upgrade must run against a non-current Home.
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
304
|
+
const externallyQuiesced = process.env.YUI_UPDATE_EXTERNALLY_QUIESCED === "1";
|
|
305
|
+
const ownsHandover = args.length === 1 && !externallyQuiesced;
|
|
306
|
+
const externalFenceOwner = externallyQuiesced
|
|
307
|
+
? process.env.YUI_UPDATE_HANDOVER_OWNER_PID
|
|
308
|
+
: undefined;
|
|
309
|
+
const externalUpgradeFenceOwnerPid = externalFenceOwner === undefined
|
|
310
|
+
? undefined
|
|
311
|
+
: Number(externalFenceOwner);
|
|
312
|
+
if (externalUpgradeFenceOwnerPid !== undefined
|
|
313
|
+
&& (!Number.isSafeInteger(externalUpgradeFenceOwnerPid)
|
|
314
|
+
|| externalUpgradeFenceOwnerPid < 1)) {
|
|
315
|
+
throw runtimeError("Update storage-fence owner PID is invalid.");
|
|
316
|
+
}
|
|
317
|
+
const handover = ownsHandover ? acquireHandoverLock(home) : undefined;
|
|
318
|
+
try {
|
|
319
|
+
const result = await runUpgradeCommand(args.slice(1), home, externallyQuiesced
|
|
320
|
+
? {
|
|
321
|
+
controllerLifecycle: "externally-quiesced",
|
|
322
|
+
...(externalUpgradeFenceOwnerPid === undefined
|
|
323
|
+
? {}
|
|
324
|
+
: { externalUpgradeFenceOwnerPid })
|
|
325
|
+
}
|
|
326
|
+
: {});
|
|
327
|
+
// Public execute upgrades leave the Home operational even when no
|
|
328
|
+
// Controller existed before the command. Dry-run and the staged updater's
|
|
329
|
+
// externally-quiesced preflight must remain read-only/lifecycle-neutral.
|
|
330
|
+
if (ownsHandover && result.exitCode === 0) {
|
|
331
|
+
await ensureFileTaskController(home, {
|
|
332
|
+
environment: process.env,
|
|
333
|
+
handoverOwnerPid: process.pid
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
process.exitCode = result.exitCode;
|
|
337
|
+
emit(result.output, false, result.data);
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
handover?.release();
|
|
279
341
|
}
|
|
280
|
-
process.exitCode = result.exitCode;
|
|
281
|
-
emit(result.output, false, result.data);
|
|
282
342
|
return;
|
|
283
343
|
}
|
|
284
344
|
if (args[0] === "internal") {
|
|
@@ -486,9 +546,21 @@ export async function main() {
|
|
|
486
546
|
}
|
|
487
547
|
validateCompatibleFileTaskStore(home);
|
|
488
548
|
const controllerMethod = method;
|
|
549
|
+
const updateHandoverOwner = process.env.YUI_UPDATE_HANDOVER_OWNER_PID;
|
|
550
|
+
const updateHandoverOwnerPid = updateHandoverOwner === undefined
|
|
551
|
+
? undefined
|
|
552
|
+
: Number(updateHandoverOwner);
|
|
553
|
+
if (updateHandoverOwnerPid !== undefined
|
|
554
|
+
&& (!Number.isSafeInteger(updateHandoverOwnerPid) || updateHandoverOwnerPid < 1)) {
|
|
555
|
+
throw runtimeError("Update Controller handover owner PID is invalid.");
|
|
556
|
+
}
|
|
557
|
+
const controllerOptions = {
|
|
558
|
+
environment: process.env,
|
|
559
|
+
...(updateHandoverOwnerPid === undefined ? {} : { handoverOwnerPid: updateHandoverOwnerPid })
|
|
560
|
+
};
|
|
489
561
|
const result = controllerMethod === "restart"
|
|
490
|
-
? await restartFileTaskController(home,
|
|
491
|
-
: await stopFileTaskController(home,
|
|
562
|
+
? await restartFileTaskController(home, controllerOptions)
|
|
563
|
+
: await stopFileTaskController(home, controllerOptions);
|
|
492
564
|
// The update lifecycle needs the authenticated replacement PID returned by
|
|
493
565
|
// restart/readiness. Keep stop's long-standing text envelope, while
|
|
494
566
|
// exposing restart's structured result alongside its human output.
|
|
@@ -1123,14 +1195,16 @@ export async function main() {
|
|
|
1123
1195
|
const laneSnapshotPreflight = executionLaneGitSnapshot === undefined
|
|
1124
1196
|
? undefined
|
|
1125
1197
|
: executionLaneGitSnapshot;
|
|
1126
|
-
//
|
|
1127
|
-
//
|
|
1128
|
-
//
|
|
1129
|
-
|
|
1198
|
+
// Physical preparation may precede the durable write, but Task status,
|
|
1199
|
+
// workspace identity/cwd, and ManagedWorkspace ownership are adopted by
|
|
1200
|
+
// one transaction. A failed attempt therefore leaves the Task Draft and
|
|
1201
|
+
// owning no writable workspace.
|
|
1202
|
+
let taskWorkspaceActivation;
|
|
1203
|
+
if (resolved[1] === "activate" && resolved.length === 3) {
|
|
1130
1204
|
const taskId = resolved[2];
|
|
1131
1205
|
const task = taskId === undefined ? null : store.getTask(taskId);
|
|
1132
1206
|
if (task !== null && task.status === "draft") {
|
|
1133
|
-
await workspacePreparer.
|
|
1207
|
+
taskWorkspaceActivation = await workspacePreparer.activateTaskWorkspace(task.id);
|
|
1134
1208
|
}
|
|
1135
1209
|
}
|
|
1136
1210
|
const result = runTaskCommand(resolved.slice(1), store, {
|
|
@@ -1153,6 +1227,7 @@ export async function main() {
|
|
|
1153
1227
|
? {}
|
|
1154
1228
|
: { candidateWorkspace: candidateMaterialization.workspace ?? null }),
|
|
1155
1229
|
...(executionLaneWorkspaces === undefined ? {} : { executionLaneWorkspaces }),
|
|
1230
|
+
...(taskWorkspaceActivation === undefined ? {} : { taskWorkspaceActivation }),
|
|
1156
1231
|
...(laneDispatchProjectPaths === undefined ? {} : { laneDispatchProjectPaths }),
|
|
1157
1232
|
...(directTaskMainSnapshot === undefined ? {} : { directTaskMainSnapshot }),
|
|
1158
1233
|
...(actualTaskReviewCandidate === undefined
|
|
@@ -1219,31 +1294,54 @@ export async function main() {
|
|
|
1219
1294
|
...(storedRound?.deltaRecheck === undefined
|
|
1220
1295
|
|| deltaRecheckPreflight === undefined
|
|
1221
1296
|
? {}
|
|
1222
|
-
: {
|
|
1297
|
+
: {
|
|
1298
|
+
deltaRecheckDiff: deltaRecheckPreflight.diffByProject
|
|
1299
|
+
})
|
|
1223
1300
|
});
|
|
1224
1301
|
reviewOutput = run === null
|
|
1225
1302
|
? `Review ${requestedRound.id} retained pending by Resource Broker\n`
|
|
1226
1303
|
: `Review queued as ${requestedRound.id} (${run.id})\n`;
|
|
1227
1304
|
reviewData = {
|
|
1305
|
+
reviewRequest: run === null
|
|
1306
|
+
? {
|
|
1307
|
+
kind: "busy",
|
|
1308
|
+
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1309
|
+
phase: "resource-broker",
|
|
1310
|
+
activeReviewRoundId: requestedRound.id,
|
|
1311
|
+
retryable: true,
|
|
1312
|
+
retryAfterSeconds: 5
|
|
1313
|
+
}
|
|
1314
|
+
: {
|
|
1315
|
+
kind: "started",
|
|
1316
|
+
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1317
|
+
reviewRoundId: requestedRound.id,
|
|
1318
|
+
runId: run.id
|
|
1319
|
+
},
|
|
1228
1320
|
reviewRound: store.getReviewRound(requestedRound.taskId, requestedRound.id),
|
|
1229
1321
|
...(run === null ? {} : { reviewRun: run }),
|
|
1230
1322
|
workspace
|
|
1231
1323
|
};
|
|
1232
1324
|
}
|
|
1233
1325
|
catch (error) {
|
|
1234
|
-
const currentRound = store.getReviewRound(requestedRound.taskId, requestedRound.id);
|
|
1235
|
-
if (requestedRound.resumedPendingFinalReview
|
|
1236
|
-
&& !requestedRound.terminalizedLeaderRun
|
|
1237
|
-
&& (error instanceof ReviewRoundWorkspaceEvidenceError
|
|
1238
|
-
|| error instanceof TaskFinalReviewDispatchDriftError)
|
|
1239
|
-
&& (currentRound?.scope ?? "work-item") === "task") {
|
|
1240
|
-
throw error;
|
|
1241
|
-
}
|
|
1242
1326
|
const message = error instanceof Error ? error.message : String(error);
|
|
1243
1327
|
await workspacePreparer.discardUnadoptedExecutionLaneWorkspaces(executionLaneWorkspaces);
|
|
1244
|
-
const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
|
|
1245
|
-
|
|
1246
|
-
|
|
1328
|
+
const failed = failPendingReviewRound(requestedRound.taskId, requestedRound.id, message, store, {
|
|
1329
|
+
runtime,
|
|
1330
|
+
environment: process.env,
|
|
1331
|
+
yuiHome: home
|
|
1332
|
+
});
|
|
1333
|
+
reviewOutput = `Review could not start: ${message}\n`
|
|
1334
|
+
+ "The failed ReviewRound was retained for Leader routing.\n";
|
|
1335
|
+
reviewData = {
|
|
1336
|
+
reviewRequest: {
|
|
1337
|
+
kind: "unavailable",
|
|
1338
|
+
reviewerRoleName: requestedRound.reviewerRoleName,
|
|
1339
|
+
reviewRoundId: failed.id,
|
|
1340
|
+
reason: message,
|
|
1341
|
+
retryable: true
|
|
1342
|
+
},
|
|
1343
|
+
reviewRound: failed
|
|
1344
|
+
};
|
|
1247
1345
|
}
|
|
1248
1346
|
}
|
|
1249
1347
|
if (resolved[1] === "project" && resolved[2] === "add") {
|
|
@@ -1906,6 +2004,7 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
1906
2004
|
if (args[0] !== "task")
|
|
1907
2005
|
return undefined;
|
|
1908
2006
|
let taskId;
|
|
2007
|
+
let decisionSupportRead = false;
|
|
1909
2008
|
if (args[1] === "complete" && args[2] !== undefined) {
|
|
1910
2009
|
const task = store.getTask(args[2]);
|
|
1911
2010
|
if (task === null || task.status !== "active" || task.projectBindings.length === 0) {
|
|
@@ -1963,9 +2062,21 @@ async function actualTaskReviewCandidateForTaskCommand(args, store, preparer, en
|
|
|
1963
2062
|
taskId = reference.taskId;
|
|
1964
2063
|
}
|
|
1965
2064
|
}
|
|
2065
|
+
else if ((args[1] === "context" || args[1] === "next-action")
|
|
2066
|
+
&& args[2] !== undefined) {
|
|
2067
|
+
taskId = store.getTask(args[2])?.id;
|
|
2068
|
+
decisionSupportRead = true;
|
|
2069
|
+
}
|
|
1966
2070
|
if (taskId === undefined || store.getTask(taskId)?.status !== "active")
|
|
1967
2071
|
return undefined;
|
|
1968
|
-
|
|
2072
|
+
try {
|
|
2073
|
+
return await snapshotActualTaskReviewCandidate(taskId, store, preparer);
|
|
2074
|
+
}
|
|
2075
|
+
catch (error) {
|
|
2076
|
+
if (decisionSupportRead && error instanceof CliError)
|
|
2077
|
+
return undefined;
|
|
2078
|
+
throw error;
|
|
2079
|
+
}
|
|
1969
2080
|
}
|
|
1970
2081
|
async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
|
|
1971
2082
|
const task = store.getTask(taskId);
|
|
@@ -2002,9 +2113,8 @@ async function snapshotActualTaskReviewCandidate(taskId, store, preparer) {
|
|
|
2002
2113
|
/**
|
|
2003
2114
|
* Issue 07: computes the delta-recheck assessment for `task review request
|
|
2004
2115
|
* --delta-recheck`. Runs only for that exact command; every other command
|
|
2005
|
-
* returns undefined.
|
|
2006
|
-
*
|
|
2007
|
-
* still owns the semantic equivalence decision.
|
|
2116
|
+
* returns undefined. Technical evidence boundaries fail closed here; semantic
|
|
2117
|
+
* risk remains a Leader and Project-policy decision.
|
|
2008
2118
|
*/
|
|
2009
2119
|
async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReviewCandidate) {
|
|
2010
2120
|
if (args[0] !== "task" || args[1] !== "review" || args[2] !== "request") {
|
|
@@ -2018,23 +2128,13 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
|
|
|
2018
2128
|
const task = store.getTask(taskId);
|
|
2019
2129
|
if (task === null || task.status !== "active")
|
|
2020
2130
|
return undefined;
|
|
2021
|
-
const config = store.getReviewConfig();
|
|
2022
|
-
if (!deltaRecheckEnabled(config)) {
|
|
2023
|
-
throw usageError("Delta-recheck is not enabled for this Project's review policy. "
|
|
2024
|
-
+ "Set `yui config workflow set review --role <role> --trigger final --delta-recheck enabled` "
|
|
2025
|
-
+ "or request a full Review.");
|
|
2026
|
-
}
|
|
2027
|
-
const reviewConfig = config;
|
|
2028
2131
|
if (actualTaskReviewCandidate === undefined)
|
|
2029
2132
|
return undefined;
|
|
2030
2133
|
// The previous Round is the latest completed Task-final Round that accepted
|
|
2031
2134
|
// a head (a full Review or an equivalent-and-accepted delta). A
|
|
2032
2135
|
// non-accepted delta cannot be the base for a new delta.
|
|
2033
2136
|
const previous = [...store.listReviewRounds(task.id)]
|
|
2034
|
-
.filter((round) => (
|
|
2035
|
-
&& round.status === "completed"
|
|
2036
|
-
&& (round.deltaRecheck === undefined
|
|
2037
|
-
|| round.deltaRecheck.disposition === "equivalent-and-accepted")))
|
|
2137
|
+
.filter((round) => isAcceptedTaskReviewBaseline(store, round))
|
|
2038
2138
|
.sort((left, right) => left.id.localeCompare(right.id, undefined, { numeric: true }))
|
|
2039
2139
|
.at(-1);
|
|
2040
2140
|
if (previous === undefined) {
|
|
@@ -2052,11 +2152,10 @@ async function deltaRecheckPreflightForTaskCommand(args, store, actualTaskReview
|
|
|
2052
2152
|
repositoryPaths,
|
|
2053
2153
|
previousRound: previous,
|
|
2054
2154
|
candidate: actualTaskReviewCandidate,
|
|
2055
|
-
git: new NodeGitWorkspace()
|
|
2056
|
-
config: reviewConfig
|
|
2155
|
+
git: new NodeGitWorkspace()
|
|
2057
2156
|
});
|
|
2058
2157
|
if (assessment.kind === "ineligible") {
|
|
2059
|
-
throw usageError(`Delta-recheck is
|
|
2158
|
+
throw usageError(`Delta-recheck is technically unavailable: ${assessment.reason}`);
|
|
2060
2159
|
}
|
|
2061
2160
|
return assessment.preflight;
|
|
2062
2161
|
}
|
|
@@ -2115,13 +2214,13 @@ function reviewRoundFromCommandData(data) {
|
|
|
2115
2214
|
const value = round;
|
|
2116
2215
|
return typeof value.id === "string"
|
|
2117
2216
|
&& typeof value.taskId === "string"
|
|
2217
|
+
&& typeof value.reviewerRoleName === "string"
|
|
2118
2218
|
&& typeof value.status === "string"
|
|
2119
2219
|
? {
|
|
2120
2220
|
id: value.id,
|
|
2121
2221
|
taskId: value.taskId,
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
terminalizedLeaderRun: data[TERMINALIZED_LEADER_BEFORE_FINAL_REVIEW] === true
|
|
2222
|
+
reviewerRoleName: value.reviewerRoleName,
|
|
2223
|
+
status: value.status
|
|
2125
2224
|
}
|
|
2126
2225
|
: undefined;
|
|
2127
2226
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { mkdirSync, realpathSync } from "node:fs";
|
|
2
2
|
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
3
|
import { usageError } from "../errors/cliError.js";
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_AGENT_LAUNCH_INACTIVITY_TIMEOUT_SECONDS, DEFAULT_CONTROLLER_TASK_CONCURRENCY, DEFAULT_DELIVERY_TIMEOUT_SECONDS, DEFAULT_LEADER_NEXT_ACTION_MODE, DEFAULT_LEADER_SEMANTIC_BUDGET_TURNS, DEFAULT_PROVIDER_RETRY_DELAYS_SECONDS, DEFAULT_PROVIDER_RETRY_MAX_WINDOW_SECONDS, DEFAULT_PROVIDER_RETRY_MODE, DEFAULT_RECONCILIATION_INTERVAL_SECONDS, DEFAULT_RESOURCES_GC_MODE, DEFAULT_RESOURCES_QUARANTINE_TTL_HOURS, DEFAULT_TMUX_HISTORY_LIMIT, LEADER_NEXT_ACTION_MODES, PROVIDER_RETRY_MODES, reconciliationIntervalMilliseconds, resolveAgentLaunchInactivityTimeoutSeconds, resolveControllerTaskConcurrency, resolveContextBudget, resolveDeliveryTimeoutSeconds, resolveLeaderNextActionMode, resolveLeaderSemanticBudgetTurns, resolveProviderRetryAdapters, resolveProviderRetryDelaysSeconds, resolveProviderRetryMaxWindowSeconds, resolveProviderRetryMode, resolveResourcesGcAutoQuarantine, resolveResourcesGcMode, resolveResourcesQuarantineTtlHours, resolveRuntimeHealth, resolveTelemetryEnabled, resolveTelemetryRunCap, resolveTelemetryTerminalKeep, resolveTmuxBin, resolveTmuxHistoryLimit } from "../config/yuiConfig.js";
|
|
5
5
|
import { CONFIG_DEFINITIONS, CONFIG_KEYS, configDefinition, configDefinitionsForDomain } from "../config/configCatalog.js";
|
|
6
6
|
import { resolveTimeZone } from "../output/timePresentation.js";
|
|
7
7
|
import { defaultTableWidth, renderTable } from "../output/table.js";
|
|
8
|
-
import {
|
|
8
|
+
import { REVIEW_FINDING_LEDGER_MODES, REVIEW_TRIGGERS } from "../review/reviewConfig.js";
|
|
9
9
|
/**
|
|
10
10
|
* One uniform key model for every durable setting. Each catalog domain uses
|
|
11
11
|
* the same show/set/clear contract; strategy settings such as `review` and
|
|
@@ -23,8 +23,7 @@ const RESOURCES_GC_AUTO_QUARANTINE_SET_USAGE = "Resources config set usage: yui
|
|
|
23
23
|
const LEADER_NEXT_ACTION_SET_USAGE = `Workflow config set usage: yui config workflow set leader-next-action <${LEADER_NEXT_ACTION_MODES.join("|")}>.`;
|
|
24
24
|
const CONTEXT_BUDGET_SET_USAGE = "Workflow config set usage: yui config workflow set context-budget [--soft-tokens <n>] [--hard-tokens <n>].";
|
|
25
25
|
const REVIEW_SET_USAGE = "Workflow config set usage: yui config workflow set review --role <global-role> --trigger <always|leader|final> "
|
|
26
|
-
+ "[--finding-ledger <shadow|enforce>]
|
|
27
|
-
+ "[--delta-recheck-max-lines <n>] [--delta-recheck-max-files <n>].";
|
|
26
|
+
+ "[--finding-ledger <shadow|enforce>].";
|
|
28
27
|
export function runConfigCommand(domain, args, store) {
|
|
29
28
|
const keys = configDefinitionsForDomain(domain).map(({ key }) => key);
|
|
30
29
|
const usage = `${capitalize(domain)} config usage: yui config ${domain} show | yui config ${domain} set <key> <value...> | yui config ${domain} clear <key>.`;
|
|
@@ -97,12 +96,7 @@ export function effectiveConfigData(config, domain) {
|
|
|
97
96
|
: {
|
|
98
97
|
roleName: config.review.roleName,
|
|
99
98
|
trigger: config.review.trigger,
|
|
100
|
-
findingLedger: config.review.findingLedger ?? "shadow"
|
|
101
|
-
deltaRecheck: config.review.deltaRecheck ?? "disabled",
|
|
102
|
-
deltaRecheckMaxChangedLines: config.review.deltaRecheckMaxChangedLines
|
|
103
|
-
?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_LINES,
|
|
104
|
-
deltaRecheckMaxChangedFiles: config.review.deltaRecheckMaxChangedFiles
|
|
105
|
-
?? DEFAULT_DELTA_RECHECK_MAX_CHANGED_FILES
|
|
99
|
+
findingLedger: config.review.findingLedger ?? "shadow"
|
|
106
100
|
},
|
|
107
101
|
completionInstallations: config.completionInstallations ?? {}
|
|
108
102
|
};
|
|
@@ -175,14 +169,6 @@ function parseReconciliationIntervalSeconds(value) {
|
|
|
175
169
|
function parseBooleanConfigValue(value) {
|
|
176
170
|
return value === "true" ? true : value === "false" ? false : value;
|
|
177
171
|
}
|
|
178
|
-
function parsePositiveIntegerOption(value, usage) {
|
|
179
|
-
if (value === undefined)
|
|
180
|
-
return undefined;
|
|
181
|
-
if (!/^\d+$/.test(value) || Number(value) < 1) {
|
|
182
|
-
throw usageError(usage);
|
|
183
|
-
}
|
|
184
|
-
return Number(value);
|
|
185
|
-
}
|
|
186
172
|
const CONFIG_KEY_HANDLERS = [
|
|
187
173
|
{
|
|
188
174
|
key: "default-agent",
|
|
@@ -313,10 +299,10 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
313
299
|
},
|
|
314
300
|
{
|
|
315
301
|
key: "context-budget",
|
|
316
|
-
showLabel: "
|
|
302
|
+
showLabel: "Legacy context budget (inactive)",
|
|
317
303
|
showValue: (config) => {
|
|
318
304
|
const budget = resolveContextBudget(config.contextBudget);
|
|
319
|
-
return `soft ${budget.softTokens} / hard ${budget.hardTokens} tokens`;
|
|
305
|
+
return `inactive; legacy soft ${budget.softTokens} / hard ${budget.hardTokens} tokens`;
|
|
320
306
|
},
|
|
321
307
|
set(args, store) {
|
|
322
308
|
if (args.length !== 2 && args.length !== 4)
|
|
@@ -343,14 +329,14 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
343
329
|
};
|
|
344
330
|
const resolved = resolveContextBudget({ ...current, ...next });
|
|
345
331
|
saveConfigKey(store, (config) => ({ ...config, contextBudget: resolved }));
|
|
346
|
-
return `
|
|
332
|
+
return `Legacy context budget stored as soft ${resolved.softTokens} / hard ${resolved.hardTokens} tokens (inactive)\n`;
|
|
347
333
|
},
|
|
348
334
|
clear(store) {
|
|
349
335
|
saveConfigKey(store, (config) => {
|
|
350
336
|
const { contextBudget: _removed, ...rest } = config;
|
|
351
337
|
return rest;
|
|
352
338
|
});
|
|
353
|
-
return
|
|
339
|
+
return "Legacy context budget cleared; Session Token metrics remain read-only\n";
|
|
354
340
|
}
|
|
355
341
|
},
|
|
356
342
|
{
|
|
@@ -469,7 +455,7 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
469
455
|
const { runtimeHealth: _removed, ...rest } = config;
|
|
470
456
|
return rest;
|
|
471
457
|
});
|
|
472
|
-
return "Runtime health thresholds reset to quiet 300s / diagnostic
|
|
458
|
+
return "Runtime health thresholds reset to quiet 300s / diagnostic 900s / stall 1800s\n";
|
|
473
459
|
}
|
|
474
460
|
},
|
|
475
461
|
{
|
|
@@ -696,7 +682,7 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
696
682
|
showValue: (config) => (config.review === undefined
|
|
697
683
|
? "disabled"
|
|
698
684
|
: `${config.review.roleName} (${config.review.trigger}; finding ledger: ${config.review.findingLedger ?? "shadow"}`
|
|
699
|
-
+
|
|
685
|
+
+ ")"),
|
|
700
686
|
set(args, store) {
|
|
701
687
|
if (args.length < 4 || args.length % 2 !== 0)
|
|
702
688
|
throw usageError(REVIEW_SET_USAGE);
|
|
@@ -704,8 +690,7 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
704
690
|
for (let index = 0; index < args.length; index += 2) {
|
|
705
691
|
const name = args[index];
|
|
706
692
|
const value = args[index + 1];
|
|
707
|
-
if (!["--role", "--trigger", "--finding-ledger"
|
|
708
|
-
"--delta-recheck-max-lines", "--delta-recheck-max-files"].includes(name)
|
|
693
|
+
if (!["--role", "--trigger", "--finding-ledger"].includes(name)
|
|
709
694
|
|| value === undefined
|
|
710
695
|
|| options.has(name)) {
|
|
711
696
|
throw usageError(REVIEW_SET_USAGE);
|
|
@@ -731,33 +716,15 @@ const CONFIG_KEY_HANDLERS = [
|
|
|
731
716
|
}
|
|
732
717
|
findingLedger = rawLedgerMode;
|
|
733
718
|
}
|
|
734
|
-
const rawDeltaRecheck = options.get("--delta-recheck")?.trim();
|
|
735
|
-
let deltaRecheck;
|
|
736
|
-
if (rawDeltaRecheck !== undefined) {
|
|
737
|
-
if (!REVIEW_DELTA_RECHECK_MODES.includes(rawDeltaRecheck)) {
|
|
738
|
-
throw usageError(REVIEW_SET_USAGE);
|
|
739
|
-
}
|
|
740
|
-
deltaRecheck = rawDeltaRecheck;
|
|
741
|
-
}
|
|
742
|
-
const deltaRecheckMaxChangedLines = parsePositiveIntegerOption(options.get("--delta-recheck-max-lines"), REVIEW_SET_USAGE);
|
|
743
|
-
const deltaRecheckMaxChangedFiles = parsePositiveIntegerOption(options.get("--delta-recheck-max-files"), REVIEW_SET_USAGE);
|
|
744
719
|
saveConfigKey(store, (config) => ({
|
|
745
720
|
...config,
|
|
746
721
|
review: {
|
|
747
722
|
roleName,
|
|
748
723
|
trigger,
|
|
749
|
-
...(findingLedger === undefined ? {} : { findingLedger })
|
|
750
|
-
...(deltaRecheck === undefined ? {} : { deltaRecheck }),
|
|
751
|
-
...(deltaRecheckMaxChangedLines === undefined
|
|
752
|
-
? {}
|
|
753
|
-
: { deltaRecheckMaxChangedLines }),
|
|
754
|
-
...(deltaRecheckMaxChangedFiles === undefined
|
|
755
|
-
? {}
|
|
756
|
-
: { deltaRecheckMaxChangedFiles })
|
|
724
|
+
...(findingLedger === undefined ? {} : { findingLedger })
|
|
757
725
|
}
|
|
758
726
|
}));
|
|
759
|
-
return `Review set to ${roleName} (${trigger}; finding ledger: ${findingLedger ?? "shadow"}
|
|
760
|
-
+ `delta recheck: ${deltaRecheck ?? "disabled"})\n`;
|
|
727
|
+
return `Review set to ${roleName} (${trigger}; finding ledger: ${findingLedger ?? "shadow"})\n`;
|
|
761
728
|
},
|
|
762
729
|
clear(store) {
|
|
763
730
|
saveConfigKey(store, (config) => {
|
|
@@ -139,7 +139,8 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
139
139
|
}
|
|
140
140
|
if (report.sessions.status === "ok" && report.sessions.data !== undefined) {
|
|
141
141
|
const sessions = report.sessions.data;
|
|
142
|
-
lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} ·
|
|
142
|
+
lines.push("", `Sessions: ${sessions.generations} generations · ${sessions.broken} broken · ${sessions.stopped} stopped · ${sessions.other} other`, `Resets: ${sessions.resets} · Conversation switches ${sessions.conversationSwitches}`
|
|
143
|
+
+ ` · lifecycle events ${sessions.lifecycleEvents} · stop failures ${sessions.stopFailures}`, `Terminal by Run relation: ${sessions.terminalByRunRelation.postRunYielded} post-run-yielded`
|
|
143
144
|
+ ` · ${sessions.terminalByRunRelation.runFailed} run-failed`
|
|
144
145
|
+ ` · ${sessions.terminalByRunRelation.activeRun} active-run`
|
|
145
146
|
+ ` · ${sessions.terminalByRunRelation.noRun} no-run`);
|
|
@@ -155,7 +156,7 @@ export function renderExecutionAudit(report, width = defaultTableWidth()) {
|
|
|
155
156
|
`Delta-rechecks: ${reviews.deltaRechecks.total} total · `
|
|
156
157
|
+ `${reviews.deltaRechecks.equivalentAndAccepted} accepted · `
|
|
157
158
|
+ `${reviews.deltaRechecks.finding} finding · `
|
|
158
|
-
+ `${reviews.deltaRechecks.requiresFullReview}
|
|
159
|
+
+ `${reviews.deltaRechecks.requiresFullReview} requiring Leader decision`
|
|
159
160
|
]));
|
|
160
161
|
}
|
|
161
162
|
else {
|
|
@@ -56,18 +56,14 @@ function collectJobs(store) {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
const failure = store.getLeaderFailure(task.id);
|
|
59
|
-
|
|
60
|
-
const recoveryNotification = notification?.type === "leader-recovery-failed"
|
|
61
|
-
? notification
|
|
62
|
-
: null;
|
|
63
|
-
if (failure !== null || recoveryNotification !== null) {
|
|
59
|
+
if (failure !== null) {
|
|
64
60
|
jobs.push({
|
|
65
61
|
id: `${RECOVERY_PREFIX}${task.id}`,
|
|
66
62
|
taskId: task.id,
|
|
67
63
|
kind: "leader-recovery",
|
|
68
64
|
status: "failed",
|
|
69
|
-
detail: failure
|
|
70
|
-
updatedAt: failure
|
|
65
|
+
detail: failure.message,
|
|
66
|
+
updatedAt: failure.lastFailedAt
|
|
71
67
|
});
|
|
72
68
|
}
|
|
73
69
|
}
|
|
@@ -93,12 +89,10 @@ function retryJob(args, store, options) {
|
|
|
93
89
|
if (task.status !== "active") {
|
|
94
90
|
throw usageError(`Leader recovery can only be retried for an active Task: ${task.id}.`);
|
|
95
91
|
}
|
|
96
|
-
if (tx.getLeaderFailure(task.id) === null
|
|
97
|
-
&& tx.getOperatorNotification(task.id)?.type !== "leader-recovery-failed") {
|
|
92
|
+
if (tx.getLeaderFailure(task.id) === null) {
|
|
98
93
|
throw usageError(`Job not found: ${id}.`);
|
|
99
94
|
}
|
|
100
95
|
tx.clearLeaderFailure(task.id);
|
|
101
|
-
tx.clearOperatorNotification(task.id);
|
|
102
96
|
enqueueWork(tx, { kind: "role", taskId: task.id, roleName: "leader" }, "recovery-retry", now, [{ type: "task", id: task.id }]);
|
|
103
97
|
});
|
|
104
98
|
const target = { kind: "role", taskId, roleName: "leader" };
|