@zq-silk/yui 0.15.9 → 0.15.12
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 +8 -4
- package/ARCHITECTURE.zh-CN.md +5 -2
- package/README.md +13 -5
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/cli/commandCatalog.js +64 -16
- package/dist/cli/interactionPolicy.js +7 -3
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +381 -107
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +339 -4
- package/dist/commands/projectCommands.js +50 -22
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +586 -96
- package/dist/commands/taskIntegrationCommands.js +19 -39
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskOverviewCommand.js +4 -3
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +11 -2
- package/dist/commands/taskPublicationVerifyCommand.js +23 -39
- package/dist/commands/taskRemoteDeliveryCommand.js +22 -11
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +3 -0
- package/dist/context/taskCatalog.js +187 -0
- package/dist/context/taskContext.js +55 -6
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +14 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +519 -25
- package/dist/controller/globalInputDelivery.js +132 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +117 -7
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +329 -386
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/lifecycle/exactRunTerminalization.js +4 -1
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +367 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +430 -107
- package/dist/repository/projectMaintenanceLock.js +75 -18
- package/dist/repository/taskWorkspaceCoordinator.js +182 -101
- package/dist/repository/taskWorkspacePreparer.js +205 -72
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentHost.js +179 -82
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +6 -2
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/activeRoleRunDelivery.js +12 -0
- package/dist/scheduler/leaderWakeupProcessor.js +5 -0
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +38 -6
- package/dist/scheduler/taskObservabilityProjection.js +6 -44
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +26 -8
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +167 -4
- package/dist/storage/sqliteStore.js +57 -1
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -0
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +2 -0
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +129 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/web/assets/client/app.js +147 -17
- package/dist/web/assets/client/components.js +56 -13
- package/dist/web/assets/client/i18n.js +78 -4
- package/dist/web/assets/client/taskSurface.js +108 -1
- package/dist/web/assets/client/view.js +39 -8
- package/dist/web/assets/shell.js +29 -0
- package/dist/web/assets/styles/layout.js +8 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +131 -4
- package/dist/web/webSnapshot.js +16 -6
- package/dist/web/webTaskSurface.js +222 -5
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +111 -35
- package/docs/agent-result-consumption.md +4 -0
- package/docs/agent-result-consumption.zh-CN.md +3 -0
- package/docs/agent-runtime-drivers.md +7 -0
- package/docs/agent-runtime-drivers.zh-CN.md +5 -0
- package/docs/architecture/README.md +2 -0
- package/docs/architecture/README.zh-CN.md +3 -1
- package/docs/architecture/capabilities-and-resources.md +30 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
- package/docs/managed-turn-and-session-runtime.md +47 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/release-workflow.md +39 -0
- package/docs/release-workflow.zh-CN.md +29 -0
- package/docs/sqlite-control-plane-design.md +223 -1
- package/docs/task-delivery.md +133 -13
- package/docs/task-delivery.zh-CN.md +99 -10
- package/docs/task-discovery.md +102 -0
- package/docs/task-discovery.zh-CN.md +86 -0
- package/docs/testing/verification-levels.md +40 -0
- package/docs/testing/verification-levels.zh-CN.md +23 -0
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +154 -51
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-operator/SKILL.md +19 -3
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +42 -0
- package/skills/yui-runtime/references/publication.md +42 -0
- package/skills/yui-runtime/references/recovery.md +24 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { sendAgentHostRunControl, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
2
|
+
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
3
|
+
import { hasRuntimeCleanupObligation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
4
|
+
import { markGlobalRoleMessageNotDelivered } from "../message/message.js";
|
|
5
|
+
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
6
|
+
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
7
|
+
/** Messages are the only queue. Host's atomic begin arbitrates native writers. */
|
|
8
|
+
export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
9
|
+
for (const role of store.listGlobalRoles()) {
|
|
10
|
+
try {
|
|
11
|
+
let pending = store.listGlobalRoleMessages(role.name).filter(message => message.delivery === undefined && message.notDelivered === undefined
|
|
12
|
+
&& (message.inputControl?.action === "queue" || message.interruptThen !== undefined));
|
|
13
|
+
if (pending.length === 0 || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
|
|
14
|
+
continue;
|
|
15
|
+
let sessions = store.getGlobalRoleSessionSet(role.name);
|
|
16
|
+
if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
|
|
17
|
+
store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
|
|
18
|
+
pending = store.listGlobalRoleMessages(role.name).filter(message => message.delivery === undefined && message.notDelivered === undefined
|
|
19
|
+
&& (message.inputControl?.action === "queue" || message.interruptThen !== undefined));
|
|
20
|
+
if (pending.length === 0)
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (providerRetryPending(sessions?.providerBinding))
|
|
24
|
+
continue;
|
|
25
|
+
const current = sessions?.providerBinding?.run;
|
|
26
|
+
const obsolete = pending.filter(message => {
|
|
27
|
+
const claim = message.interruptThen;
|
|
28
|
+
return claim !== undefined && (sessions?.activeAgentId !== claim.targetAgentId
|
|
29
|
+
|| sessions.sessions[claim.targetAgentId]?.nativeSessionId !== claim.targetNativeSessionId
|
|
30
|
+
|| sessions.providerBinding?.authority.epoch !== claim.targetAuthorityEpoch
|
|
31
|
+
|| sessions.providerBinding.authority.holderId !== claim.targetAuthorityHolderId);
|
|
32
|
+
});
|
|
33
|
+
for (const message of obsolete)
|
|
34
|
+
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(message, "native-session-or-authority-changed", new Date()));
|
|
35
|
+
pending = pending.filter(message => !obsolete.includes(message));
|
|
36
|
+
if (pending.length === 0)
|
|
37
|
+
continue;
|
|
38
|
+
if (current != null && ["submitting", "accepted", "delivery-unknown"].includes(current.status))
|
|
39
|
+
continue;
|
|
40
|
+
const claims = pending.filter(message => message.interruptThen !== undefined);
|
|
41
|
+
let next;
|
|
42
|
+
if (claims.length > 0) {
|
|
43
|
+
next = claims.find(message => {
|
|
44
|
+
const claim = message.interruptThen;
|
|
45
|
+
const binding = sessions?.providerBinding;
|
|
46
|
+
return current?.attemptId === claim.targetAttemptId
|
|
47
|
+
&& ["completed", "failed", "cancelled"].includes(current.status)
|
|
48
|
+
&& sessions?.activeAgentId === claim.targetAgentId
|
|
49
|
+
&& sessions.sessions[claim.targetAgentId]?.nativeSessionId === claim.targetNativeSessionId
|
|
50
|
+
&& binding?.authority.owner === "controller"
|
|
51
|
+
&& binding.authority.epoch === claim.targetAuthorityEpoch
|
|
52
|
+
&& binding.authority.holderId === claim.targetAuthorityHolderId;
|
|
53
|
+
});
|
|
54
|
+
if (next === undefined)
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
else
|
|
58
|
+
next = pending[0];
|
|
59
|
+
const attemptId = `global-input:${role.name}/${next.id}`;
|
|
60
|
+
if (current?.attemptId === attemptId)
|
|
61
|
+
continue;
|
|
62
|
+
if (next.control !== undefined && next.control.outcome !== "rejected")
|
|
63
|
+
continue;
|
|
64
|
+
const matchesTarget = () => next.deliveryTarget === undefined
|
|
65
|
+
|| sessions?.activeAgentId === next.deliveryTarget.agentId
|
|
66
|
+
&& sessions.sessions[next.deliveryTarget.agentId]?.nativeSessionId === next.deliveryTarget.nativeSessionId;
|
|
67
|
+
if (!matchesTarget()) {
|
|
68
|
+
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(next, "native-session-changed", new Date()));
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
await ensure(role.name);
|
|
72
|
+
sessions = store.getGlobalRoleSessionSet(role.name);
|
|
73
|
+
const binding = sessions?.providerBinding;
|
|
74
|
+
if (binding == null || binding.authority.owner !== "controller")
|
|
75
|
+
continue;
|
|
76
|
+
const nativeSessionId = currentProviderConversation(binding).conversationId;
|
|
77
|
+
const persisted = store.listGlobalRoleMessages(role.name).find(message => message.id === next.id);
|
|
78
|
+
if (persisted === undefined || JSON.stringify(persisted) !== JSON.stringify(next))
|
|
79
|
+
continue;
|
|
80
|
+
const claim = next.interruptThen;
|
|
81
|
+
if (!matchesTarget() || claim !== undefined
|
|
82
|
+
&& (sessions?.activeAgentId !== claim.targetAgentId
|
|
83
|
+
|| nativeSessionId !== claim.targetNativeSessionId
|
|
84
|
+
|| binding.authority.epoch !== claim.targetAuthorityEpoch
|
|
85
|
+
|| binding.authority.holderId !== claim.targetAuthorityHolderId
|
|
86
|
+
|| binding.run?.attemptId !== claim.targetAttemptId
|
|
87
|
+
|| !["completed", "failed", "cancelled"].includes(binding.run.status))) {
|
|
88
|
+
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(next, "native-session-changed", new Date()));
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (next.deliveryTarget === undefined) {
|
|
92
|
+
next = { ...next, deliveryTarget: { agentId: sessions.activeAgentId, nativeSessionId } };
|
|
93
|
+
store.updateGlobalRoleMessage(next);
|
|
94
|
+
}
|
|
95
|
+
const result = await sendAgentHostRunControl({
|
|
96
|
+
home, scope: "global", roleName: role.name,
|
|
97
|
+
control: {
|
|
98
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn", nativeSessionId,
|
|
99
|
+
authority: { epoch: binding.authority.epoch, owner: "controller",
|
|
100
|
+
holderId: binding.authority.holderId },
|
|
101
|
+
run: { attemptId, boundedText: [
|
|
102
|
+
`Yui Global Message: role=${role.name} message=${next.id}.`,
|
|
103
|
+
"Read your Session Context and the referenced Message in full, then act on its durable input."
|
|
104
|
+
].join("\n") }
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
if (result.outcome === "rejected") {
|
|
108
|
+
store.transaction(tx => {
|
|
109
|
+
const latest = tx.listGlobalRoleMessages(role.name).find(message => message.id === next.id);
|
|
110
|
+
if (latest === undefined || latest.delivery !== undefined || latest.notDelivered !== undefined)
|
|
111
|
+
return;
|
|
112
|
+
const turn = tx.getGlobalRoleSessionSet(role.name)?.providerBinding?.run;
|
|
113
|
+
if (providerRetryPending(tx.getGlobalRoleSessionSet(role.name)?.providerBinding))
|
|
114
|
+
return;
|
|
115
|
+
// A later canonical acceptance wins over transport diagnostics.
|
|
116
|
+
if (turn?.attemptId === attemptId && ["accepted", "completed", "failed", "cancelled"].includes(turn.status))
|
|
117
|
+
return;
|
|
118
|
+
const unknown = result.failure?.inputDisposition === "unknown" || result.snapshot.state === "delivery-unknown";
|
|
119
|
+
const updated = { ...latest, control: {
|
|
120
|
+
requestId: latest.inputControl?.requestId ?? latest.interruptThen.requestId,
|
|
121
|
+
receiptId: attemptId, outcome: unknown ? "delivery-unknown" : "rejected",
|
|
122
|
+
observedAt: new Date().toISOString()
|
|
123
|
+
} };
|
|
124
|
+
tx.updateGlobalRoleMessage(unknown ? updated : markGlobalRoleMessageNotDelivered(updated, result.failure?.detail ?? result.snapshot.detail ?? "provider-input-rejected", new Date()));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
onError(error);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -253,10 +253,14 @@ function validateJobTarget(store, params) {
|
|
|
253
253
|
if (attempt === null) {
|
|
254
254
|
throw jobDomainError(`Integration Attempt not found: ${params.taskId}/${params.owner.integrationAttemptId}.`);
|
|
255
255
|
}
|
|
256
|
-
if (attempt.status
|
|
257
|
-
throw jobDomainError(`DurableJob owner Integration Attempt
|
|
256
|
+
if (attempt.status !== "running") {
|
|
257
|
+
throw jobDomainError(`DurableJob owner Integration Attempt must be running: `
|
|
258
258
|
+ `${params.taskId}/${params.owner.integrationAttemptId} is ${attempt.status}.`);
|
|
259
259
|
}
|
|
260
|
+
if (attempt.candidateCommit !== params.head || attempt.checkInputDigest === undefined
|
|
261
|
+
|| attempt.checkInputDigest !== durableJobIdempotencyKey(params)) {
|
|
262
|
+
throw jobDomainError("Integration Job has no matching admitted candidate/check specification.");
|
|
263
|
+
}
|
|
260
264
|
}
|
|
261
265
|
const project = store.getProject(params.projectId);
|
|
262
266
|
if (project === null) {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { admitProviderRetry, cancelProviderRetry, providerRetryAttemptId, providerRetryPending } from "../runtime/providerRetry.js";
|
|
2
|
+
import { blockingProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
3
|
+
import { validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
|
|
4
|
+
import { isDeepStrictEqual } from "node:util";
|
|
5
|
+
export function ownedProviderInput(input) {
|
|
6
|
+
if (input.runId !== undefined && input.taskId !== undefined) {
|
|
7
|
+
return { kind: "run", taskId: input.taskId, runId: input.runId };
|
|
8
|
+
}
|
|
9
|
+
const wake = /^notification:([^/]+)\/([^/]+)\//u.exec(input.attemptId);
|
|
10
|
+
if (wake !== null && wake[1] === input.taskId) {
|
|
11
|
+
return { kind: "wake", taskId: wake[1], wakeId: wake[2] };
|
|
12
|
+
}
|
|
13
|
+
const message = /^global-input:([^/]+)\/([^/]+)$/u.exec(input.attemptId);
|
|
14
|
+
if (message !== null && message[1] === input.roleName && input.taskId === undefined) {
|
|
15
|
+
return { kind: "message", roleName: input.roleName, messageId: message[2] };
|
|
16
|
+
}
|
|
17
|
+
return input.boundedText === undefined ? undefined : { kind: "text", text: input.boundedText };
|
|
18
|
+
}
|
|
19
|
+
export function retryIntentBlocker(store, input, retry) {
|
|
20
|
+
if (input.taskId === undefined) {
|
|
21
|
+
const role = store.getGlobalRole(input.roleName);
|
|
22
|
+
const sessions = store.getGlobalRoleSessionSet(input.roleName);
|
|
23
|
+
if (role === null || sessions?.activeAgentId !== role.activeAgentId)
|
|
24
|
+
return "role-changed";
|
|
25
|
+
if (sessions.sessions[sessions.activeAgentId]?.effective.sourceDesiredRevision !== role.launchRevision)
|
|
26
|
+
return "role-configuration-changed";
|
|
27
|
+
if (store.listGlobalRoleMessages(input.roleName).some(message => Date.parse(message.createdAt) > Date.parse(retry.intentAt)
|
|
28
|
+
&& message.delivery === undefined && message.notDelivered === undefined))
|
|
29
|
+
return "new-user-input";
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const task = store.getTask(input.taskId);
|
|
33
|
+
if (task === null || !["active", "draft"].includes(task.status)
|
|
34
|
+
|| task.executionGate.state !== "enabled")
|
|
35
|
+
return "task-execution-stopped";
|
|
36
|
+
const role = store.getRole(input.taskId, input.roleName);
|
|
37
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
38
|
+
if (role === null || sessions?.activeAgentId !== role.activeAgentId)
|
|
39
|
+
return "role-changed";
|
|
40
|
+
if (sessions.sessions[sessions.activeAgentId]?.effective.sourceDesiredRevision !== role.launchRevision)
|
|
41
|
+
return "role-configuration-changed";
|
|
42
|
+
if (store.listMessages(input.taskId).some(message => (message.kind === "operator" || message.kind === "user")
|
|
43
|
+
&& Date.parse(message.createdAt) > Date.parse(retry.intentAt)))
|
|
44
|
+
return "new-user-input";
|
|
45
|
+
if (blockingProviderContinuations(store.listEvents(input.taskId)).some(c => c.roleName === input.roleName && c.identity.conversationId === retry.nativeSessionId)) {
|
|
46
|
+
return "background-work-unsettled";
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
/** Shared by automatic recovery and every ordinary Host submit. The native
|
|
51
|
+
* writer CAS remains the only grant; a scheduled successor is not a grant. */
|
|
52
|
+
export function admitOwnedProviderInput(store, binding, input) {
|
|
53
|
+
const retry = binding.retry;
|
|
54
|
+
const sameReviewSuccessor = retry?.successorReviewRoundId !== undefined && input.taskId !== undefined && input.runId !== undefined
|
|
55
|
+
&& store.getRun(input.taskId, input.runId)?.reviewRoundId === retry.successorReviewRoundId;
|
|
56
|
+
if (retry !== undefined && !providerRetryPending(binding)
|
|
57
|
+
&& (input.attemptId.startsWith("provider-retry:") || input.runId !== undefined && input.runId === retry.successorRunId
|
|
58
|
+
|| sameReviewSuccessor)) {
|
|
59
|
+
throw new Error(`Provider retry is ${retry.status}; this scheduled successor cannot regain admission.`);
|
|
60
|
+
}
|
|
61
|
+
if (!providerRetryPending(binding) || retry === undefined)
|
|
62
|
+
return binding;
|
|
63
|
+
const automatic = input.attemptId === providerRetryAttemptId(retry)
|
|
64
|
+
|| input.runId !== undefined && input.runId === retry.successorRunId
|
|
65
|
+
|| input.runId !== undefined && input.taskId !== undefined && retry.successorReviewRoundId !== undefined
|
|
66
|
+
&& store.getRun(input.taskId, input.runId)?.reviewRoundId === retry.successorReviewRoundId
|
|
67
|
+
|| retry.status === "in-flight" && input.attemptId === retry.currentAttemptId;
|
|
68
|
+
if (!automatic)
|
|
69
|
+
return cancelProviderRetry(binding, "superseded-by-explicit-input", input.now.getTime());
|
|
70
|
+
const blocker = retryIntentBlocker(store, input, retry);
|
|
71
|
+
if (blocker !== undefined)
|
|
72
|
+
throw new Error(`Provider retry admission stopped: ${blocker}.`);
|
|
73
|
+
if (input.retrySupported !== true)
|
|
74
|
+
throw new Error("This Host cannot prove safe same-Session recovery; use explicit recovery.");
|
|
75
|
+
if (binding.goal?.status === "active")
|
|
76
|
+
throw new Error("Provider still owns an autonomous Goal; inspect its exact continuation.");
|
|
77
|
+
if (input.taskId !== undefined && input.runId !== undefined) {
|
|
78
|
+
const run = store.getRun(input.taskId, input.runId);
|
|
79
|
+
const sessions = store.getTaskRoleSessionSet(input.taskId, input.roleName);
|
|
80
|
+
if (run === null || run.status !== "active"
|
|
81
|
+
|| !isDeepStrictEqual(run.effective, sessions?.sessions[input.agentId]?.effective)) {
|
|
82
|
+
throw new Error("Retry execution no longer matches its fixed native Session.");
|
|
83
|
+
}
|
|
84
|
+
if (run.workspace !== undefined && !isDeepStrictEqual(store.getManagedWorkspace(run.workspace.owner), run.workspace)) {
|
|
85
|
+
throw new Error("Retry workspace authority changed.");
|
|
86
|
+
}
|
|
87
|
+
if (run.workItemId !== undefined) {
|
|
88
|
+
const item = store.getWorkItem(input.taskId, run.workItemId);
|
|
89
|
+
if (item?.status !== "open" || run.purpose !== "review" && run.executionLaneId === undefined
|
|
90
|
+
&& item.assignee !== run.roleName)
|
|
91
|
+
throw new Error("Retry WorkItem responsibility changed.");
|
|
92
|
+
}
|
|
93
|
+
if (validateExactRunReviewRound(store, run).disposition !== "applied") {
|
|
94
|
+
throw new Error("Retry Review no longer matches its exact Round and frozen candidate.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const admitted = admitProviderRetry(binding, { attemptId: input.attemptId, at: input.now.getTime() });
|
|
98
|
+
return input.runId === undefined ? admitted
|
|
99
|
+
: { ...admitted, retry: { ...admitted.retry, successorRunId: input.runId } };
|
|
100
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { runTaskCommand } from "../commands/taskCommands.js";
|
|
3
|
+
import { retryIntentBlocker } from "./providerRetryAdmission.js";
|
|
4
|
+
import { cancelProviderRetry, providerRetryAttemptId, providerRetryPending } from "../runtime/providerRetry.js";
|
|
5
|
+
import { currentProviderConversation } from "../runtime/providerRuntimeIdentity.js";
|
|
6
|
+
import { runtimeLifecycleTarget, hasRuntimeCleanupObligation } from "../runtime/lifecycleReservation.js";
|
|
7
|
+
import { sendAgentHostRunControl, inspectAgentHostSocket, agentHostControlSocketPath, AGENT_HOST_CONTROL_PROTOCOL } from "../runtime/agentHost.js";
|
|
8
|
+
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
9
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
10
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
11
|
+
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
12
|
+
/** Hooks in the existing Controller pass and nearest-deadline timer. SQLite's
|
|
13
|
+
* current Provider input is the only pending intent; nothing is queued here. */
|
|
14
|
+
export function createProviderRetryHooks(home, store, ports = {}) {
|
|
15
|
+
const anchors = new Map();
|
|
16
|
+
const inspectedUnknown = new Set();
|
|
17
|
+
const inspect = ports.inspect ?? ((target) => inspectAgentHostSocket(agentHostControlSocketPath(target), 250));
|
|
18
|
+
const now = (retry) => {
|
|
19
|
+
const wall = (ports.now ?? (() => new Date()))().getTime();
|
|
20
|
+
const monotonic = (ports.monotonicNow ?? (() => performance.now()))();
|
|
21
|
+
let anchor = anchors.get(retry.chainId);
|
|
22
|
+
if (anchor === undefined) {
|
|
23
|
+
anchor = { wall: Math.max(wall, Date.parse(retry.updatedAt)), monotonic };
|
|
24
|
+
anchors.set(retry.chainId, anchor);
|
|
25
|
+
}
|
|
26
|
+
return Math.max(wall, anchor.wall + Math.max(0, monotonic - anchor.monotonic));
|
|
27
|
+
};
|
|
28
|
+
const load = (owner) => owner.scope === "task"
|
|
29
|
+
? store.getTaskRoleSessionSet(owner.taskId, owner.roleName) : store.getGlobalRoleSessionSet(owner.roleName);
|
|
30
|
+
const save = (set) => {
|
|
31
|
+
if (set.owner.scope === "task")
|
|
32
|
+
store.saveTaskRoleSessionSet(set);
|
|
33
|
+
else
|
|
34
|
+
store.saveGlobalRoleSessionSet(set);
|
|
35
|
+
};
|
|
36
|
+
const stop = (set, reason, at, exhausted = false) => {
|
|
37
|
+
store.transaction(() => {
|
|
38
|
+
const current = load(set.owner);
|
|
39
|
+
if (current === null || current.providerBinding?.retry?.chainId !== set.providerBinding?.retry?.chainId)
|
|
40
|
+
return;
|
|
41
|
+
const binding = current.providerBinding;
|
|
42
|
+
if (!providerRetryPending(binding))
|
|
43
|
+
return;
|
|
44
|
+
const cancelled = cancelProviderRetry(binding, reason, at);
|
|
45
|
+
save({ ...current, providerBinding: exhausted
|
|
46
|
+
? { ...cancelled, retry: { ...cancelled.retry, status: "exhausted" } } : cancelled });
|
|
47
|
+
const owner = set.owner;
|
|
48
|
+
if (owner.scope === "global")
|
|
49
|
+
settleGlobalRetryInput(store, owner.roleName, load(owner)?.providerBinding, new Date(at));
|
|
50
|
+
if (owner.scope === "task") {
|
|
51
|
+
const event = createTaskEvent(store.nextEventId(owner.taskId), owner.taskId, "provider.retry-stopped", {
|
|
52
|
+
roleName: owner.roleName, chainId: binding.retry.chainId, reason
|
|
53
|
+
}, new Date(at));
|
|
54
|
+
store.saveEvent(owner.taskId, event);
|
|
55
|
+
enqueueWork(store, owner.roleName === "leader" ? { kind: "operator" }
|
|
56
|
+
: { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const key = (set) => set.owner.scope === "task"
|
|
61
|
+
? `role:${encodeURIComponent(set.owner.taskId)}/${encodeURIComponent(set.owner.roleName)}`
|
|
62
|
+
: `global-role:${encodeURIComponent(set.owner.roleName)}`;
|
|
63
|
+
return {
|
|
64
|
+
deadlines() {
|
|
65
|
+
return store.listProviderRetrySessions().flatMap(set => {
|
|
66
|
+
const retry = set.providerBinding.retry;
|
|
67
|
+
if (retry.status !== "waiting")
|
|
68
|
+
return [];
|
|
69
|
+
// Busy or already-queued work awaits the next ordinary pass without a
|
|
70
|
+
// zero-delay timer loop. Waiting never consumes an execution attempt.
|
|
71
|
+
const wall = (ports.now ?? (() => new Date()))().getTime();
|
|
72
|
+
const effective = now(retry);
|
|
73
|
+
return [{ key: key(set), at: wall + Math.max(250, (retry.successorAutomatic === false ? Date.parse(retry.nextEligibleAt)
|
|
74
|
+
: Math.min(Date.parse(retry.nextEligibleAt), Date.parse(retry.deadline))) - effective) }];
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
async reconcile() {
|
|
78
|
+
const current = store.listProviderRetrySessions();
|
|
79
|
+
const activeChains = new Set(current.map(set => set.providerBinding.retry.chainId));
|
|
80
|
+
for (const id of anchors.keys())
|
|
81
|
+
if (!activeChains.has(id))
|
|
82
|
+
anchors.delete(id);
|
|
83
|
+
const pendingAttempts = new Set(current.map(set => set.providerBinding.retry.currentAttemptId));
|
|
84
|
+
for (const id of inspectedUnknown)
|
|
85
|
+
if (!pendingAttempts.has(id))
|
|
86
|
+
inspectedUnknown.delete(id);
|
|
87
|
+
for (const set of current) {
|
|
88
|
+
const owner = set.owner;
|
|
89
|
+
const binding = set.providerBinding;
|
|
90
|
+
const retry = binding.retry;
|
|
91
|
+
const at = now(retry);
|
|
92
|
+
const scoped = { ...(owner.scope === "task" ? { taskId: owner.taskId } : {}), roleName: owner.roleName };
|
|
93
|
+
const session = set.sessions[set.activeAgentId];
|
|
94
|
+
const blocker = retryIntentBlocker(store, scoped, retry);
|
|
95
|
+
if (blocker !== undefined && blocker !== "background-work-unsettled") {
|
|
96
|
+
stop(set, blocker, at);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (binding.retryDisabled && retry.successorAutomatic !== false || binding.authority.owner !== "controller"
|
|
100
|
+
|| binding.authority.epoch !== retry.authorityEpoch
|
|
101
|
+
|| currentProviderConversation(binding).conversationId !== retry.nativeSessionId
|
|
102
|
+
|| session?.status !== "active" || session.nativeSessionId !== retry.nativeSessionId
|
|
103
|
+
|| hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget(owner)))) {
|
|
104
|
+
stop(set, "session-or-authority-changed", at);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Never replay an admitted attempt. Existing exact runtime queries and
|
|
108
|
+
// terminal ingress resolve it, even across Controller restart.
|
|
109
|
+
if (retry.status === "in-flight") {
|
|
110
|
+
if (binding.run?.status === "delivery-unknown" && retry.currentAttemptId !== undefined
|
|
111
|
+
&& !inspectedUnknown.has(retry.currentAttemptId)) {
|
|
112
|
+
inspectedUnknown.add(retry.currentAttemptId);
|
|
113
|
+
let detail;
|
|
114
|
+
try {
|
|
115
|
+
const host = await inspect({ home, ...scoped, scope: owner.scope });
|
|
116
|
+
detail = host.nativeSessionId === retry.nativeSessionId && host.attemptId === retry.currentAttemptId
|
|
117
|
+
? `Exact Host query: ${host.state}; awaiting original canonical Provider evidence.`
|
|
118
|
+
: "Host query did not prove the original Session/input identity; no replay.";
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
detail = `Original Host query unavailable; no replay: ${redactAgentErrorText(error instanceof Error ? error.message : String(error))}`;
|
|
122
|
+
}
|
|
123
|
+
store.transaction(() => {
|
|
124
|
+
const latest = load(owner);
|
|
125
|
+
if (latest === null || latest.providerBinding == null || latest.providerBinding.run === null
|
|
126
|
+
|| latest.providerBinding.run.attemptId !== retry.currentAttemptId
|
|
127
|
+
|| latest.providerBinding.run.status !== "delivery-unknown")
|
|
128
|
+
return;
|
|
129
|
+
save({ ...latest, providerBinding: { ...latest.providerBinding,
|
|
130
|
+
retry: { ...latest.providerBinding.retry, reason: detail } } });
|
|
131
|
+
if (owner.scope === "task") {
|
|
132
|
+
const event = createTaskEvent(store.nextEventId(owner.taskId), owner.taskId, "provider.retry-unconfirmed", {
|
|
133
|
+
roleName: owner.roleName, attemptId: retry.currentAttemptId, nativeSessionId: retry.nativeSessionId, detail
|
|
134
|
+
}, new Date(at));
|
|
135
|
+
store.saveEvent(owner.taskId, event);
|
|
136
|
+
enqueueWork(store, owner.roleName === "leader" ? { kind: "operator" }
|
|
137
|
+
: { kind: "role", taskId: owner.taskId, roleName: "leader" }, "provider-retry-needs-attention", new Date(at), [{ type: "event", taskId: owner.taskId, id: event.id }]);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (retry.successorAutomatic !== false && at >= Date.parse(retry.deadline)) {
|
|
144
|
+
stop(set, "deadline-exhausted", at, true);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (at < Date.parse(retry.nextEligibleAt) || blocker !== undefined || binding.goal?.status === "active")
|
|
148
|
+
continue;
|
|
149
|
+
if (binding.run?.attemptId !== retry.failedAttemptId) {
|
|
150
|
+
stop(set, "superseded-input", at);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
if (owner.scope === "task" && retry.previousRunId !== undefined) {
|
|
155
|
+
if (retry.successorRunId !== undefined || retry.successorReviewRoundId !== undefined)
|
|
156
|
+
continue;
|
|
157
|
+
const previous = store.getRun(owner.taskId, retry.previousRunId);
|
|
158
|
+
if (previous?.status === "active")
|
|
159
|
+
continue; // ordinary rejected-delivery terminal fold
|
|
160
|
+
if (previous?.status !== "failed") {
|
|
161
|
+
stop(set, "run-no-longer-failed", at);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const actualTaskReviewCandidate = await ports.snapshotTaskCandidate?.(previous);
|
|
165
|
+
store.transaction(() => {
|
|
166
|
+
const latest = load(owner)?.providerBinding?.retry;
|
|
167
|
+
if (latest?.chainId !== retry.chainId || latest.failedAttemptId !== retry.failedAttemptId
|
|
168
|
+
|| latest.status !== "waiting")
|
|
169
|
+
return;
|
|
170
|
+
const reason = retryIntentBlocker(store, scoped, latest);
|
|
171
|
+
if (reason !== undefined)
|
|
172
|
+
throw new Error(reason);
|
|
173
|
+
runTaskCommand(["run", "retry", `${owner.taskId}/${previous.id}`], store, {
|
|
174
|
+
now: () => new Date(at), environment: {}, providerRetryChainId: retry.chainId,
|
|
175
|
+
...(actualTaskReviewCandidate === undefined ? {} : { actualTaskReviewCandidate })
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const original = retry.input.kind === "text" ? retry.input.text
|
|
181
|
+
: retry.input.kind === "wake"
|
|
182
|
+
? `Yui Task notification: task=${retry.input.taskId} wake=${retry.input.wakeId}.\nRead current Task context and that fixed wake window, including original Messages in full. This is not an execution assignment.`
|
|
183
|
+
: retry.input.kind === "message"
|
|
184
|
+
? `Yui Global Message: role=${retry.input.roleName} message=${retry.input.messageId}.\nRead your Session Context and referenced original Message in full.`
|
|
185
|
+
: undefined;
|
|
186
|
+
if (original === undefined) {
|
|
187
|
+
stop(set, "original-input-unavailable", at);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const result = await (ports.submit ?? sendAgentHostRunControl)({
|
|
191
|
+
home, ...scoped, scope: owner.scope,
|
|
192
|
+
control: {
|
|
193
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL, type: "submit-turn", nativeSessionId: retry.nativeSessionId,
|
|
194
|
+
authority: { epoch: binding.authority.epoch, owner: "controller", holderId: binding.authority.holderId },
|
|
195
|
+
run: { attemptId: providerRetryAttemptId(retry), boundedText: original }
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
// Durable settlement belongs to Host/Controller ingress. In
|
|
199
|
+
// particular a lost response is never a reason to replay.
|
|
200
|
+
if (result.outcome === "rejected" && result.failure?.registrationDisposition === "not-committed"
|
|
201
|
+
&& result.failure.errorName !== "ProviderTurnBusyError") {
|
|
202
|
+
stop(set, result.failure.detail, at);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
// A send failure may have applied. A durable submitting/unknown
|
|
208
|
+
// identity fences it; do not manufacture a successor to uncertainty.
|
|
209
|
+
const latest = load(owner);
|
|
210
|
+
if (latest?.providerBinding?.run?.attemptId === retry.failedAttemptId) {
|
|
211
|
+
stop(set, error instanceof Error ? error.message : String(error), at);
|
|
212
|
+
}
|
|
213
|
+
ports.onError?.(error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
/** Archiving may retain execution; a Task lifecycle label is not exit proof. */
|
|
3
|
+
export function runtimeRoleProtectsDomain(role) {
|
|
4
|
+
return (role.ownerKind === "task-role"
|
|
5
|
+
&& (role.taskStatus === "active" || role.taskStatus === "archived"))
|
|
6
|
+
// Global Roles have no Task lifecycle; require a current native Session.
|
|
7
|
+
|| (role.ownerKind === "global-role" && role.nativeSessionId !== undefined);
|
|
8
|
+
}
|
|
2
9
|
export function createRuntimeResourceActivityTracker() {
|
|
3
10
|
const previous = new Map();
|
|
4
11
|
return (identity, resource) => {
|
|
@@ -112,8 +119,7 @@ export function buildControllerResourceInventory(facts) {
|
|
|
112
119
|
for (const process of paneProcesses)
|
|
113
120
|
claimed.add(process.pid);
|
|
114
121
|
const role = findRole(homeFact.roles, pane);
|
|
115
|
-
const terminalIsolation =
|
|
116
|
-
|| role?.taskStatus === "archived";
|
|
122
|
+
const terminalIsolation = role?.taskStatus === "cancelled" && role.taskRetirementIsolated === true;
|
|
117
123
|
resources.push(processResource({
|
|
118
124
|
kind: "agent-session",
|
|
119
125
|
state: pane.dead ? "dead" : role === undefined ? "orphaned" : "running",
|
|
@@ -122,7 +128,8 @@ export function buildControllerResourceInventory(facts) {
|
|
|
122
128
|
: terminalIsolation ? "safe" : "protected",
|
|
123
129
|
reasonCode: role === undefined
|
|
124
130
|
? pane.dead ? "dead-orphan-pane" : "orphan-pane"
|
|
125
|
-
: terminalIsolation ?
|
|
131
|
+
: terminalIsolation ? "retired-task-pane"
|
|
132
|
+
: role.taskStatus === "archived" ? "archived-task-pane" : "owned-role-pane",
|
|
126
133
|
yuiHome,
|
|
127
134
|
owner: role === undefined ? { kind: "none" } : roleOwner(role),
|
|
128
135
|
processes: paneProcesses,
|
|
@@ -358,7 +365,10 @@ function artifactResource(artifact, yuiHome, domain, homeId) {
|
|
|
358
365
|
};
|
|
359
366
|
}
|
|
360
367
|
function domainDisposition(base, reason, domain, target) {
|
|
361
|
-
|
|
368
|
+
// Even an expired test domain cannot authorize deleting retained execution.
|
|
369
|
+
if (reason === "archived-task-pane")
|
|
370
|
+
return "protected";
|
|
371
|
+
if (reason === "retired-task-pane") {
|
|
362
372
|
return "safe";
|
|
363
373
|
}
|
|
364
374
|
if (domain === undefined)
|
|
@@ -11,7 +11,7 @@ import { readControllerDiscovery } from "../core/controllerClient.js";
|
|
|
11
11
|
import { CONTROLLER_DISCOVERY_PATH } from "../core/protocol.js";
|
|
12
12
|
import { controllerSocketPath } from "../core/controllerEndpoint.js";
|
|
13
13
|
import { readHomeFilesystemId } from "../core/homeFilesystemIdentity.js";
|
|
14
|
-
import { buildControllerResourceInventory } from "./resourceInventory.js";
|
|
14
|
+
import { buildControllerResourceInventory, runtimeRoleProtectsDomain } from "./resourceInventory.js";
|
|
15
15
|
import { CONTROLLER_DOMAIN_PATH, EPHEMERAL_DOMAIN_GRACE_MS, ephemeralDomainFingerprint, readEphemeralDomainIdentity, readLinuxProcessStartIdentity } from "./domainIdentity.js";
|
|
16
16
|
const LINUX_PROCESS_SCAN_BATCH_SIZE = 64;
|
|
17
17
|
export const INVENTORY_EVENT_LOOP_RUN_BUDGET_MS = 25;
|
|
@@ -518,11 +518,7 @@ function inspectRuntimeDomain(home, processes, roles, storageStatus, now) {
|
|
|
518
518
|
graceMs: EPHEMERAL_DOMAIN_GRACE_MS
|
|
519
519
|
};
|
|
520
520
|
}
|
|
521
|
-
const activeRole = roles.some(
|
|
522
|
-
// Global Roles have no Task status. Only a live native Session is a
|
|
523
|
-
// durable liveness fact for that scope; stopped/broken history must not
|
|
524
|
-
// protect an expired disposable domain merely because it has an ID.
|
|
525
|
-
|| (role.ownerKind === "global-role" && role.nativeSessionId !== undefined)));
|
|
521
|
+
const activeRole = roles.some(runtimeRoleProtectsDomain);
|
|
526
522
|
if (!storageSafe) {
|
|
527
523
|
return {
|
|
528
524
|
kind: "ephemeral-test",
|