@zq-silk/yui 0.16.1 → 1.0.0-alpha
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 +12 -0
- package/ARCHITECTURE.zh-CN.md +8 -0
- package/README.md +9 -4
- package/dist/agent/agent.js +4 -9
- package/dist/agent/executionComponents.js +4 -4
- package/dist/agent/managedRuntimeEnvironment.js +0 -4
- package/dist/agentRun/agentRun.js +13 -23
- package/dist/artifacts/managedGit.js +3 -56
- package/dist/brief/taskBrief.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +24 -21
- package/dist/cli/commandCatalog.js +18 -10
- package/dist/cli/roleWizard.js +41 -30
- package/dist/cli/updateCommand.js +35 -13
- package/dist/cli/updateOrchestrator.js +73 -44
- package/dist/cli/updatePorts.js +76 -86
- package/dist/cli/upgradeCommand.js +4 -8
- package/dist/cli.js +39 -33
- package/dist/commands/controllerCommands.js +2 -2
- package/dist/commands/executionAuditCommands.js +2 -2
- package/dist/commands/projectCommands.js +1 -1
- package/dist/commands/releaseCommands.js +9 -38
- package/dist/commands/taskCommands.js +91 -81
- package/dist/commands/taskCompletionGate.js +0 -117
- package/dist/commands/taskRoleRuntimeStatus.js +2 -2
- package/dist/commands/taskUpstreamCommands.js +93 -54
- package/dist/context/runContextPack.js +30 -54
- package/dist/context/runInputContract.js +9 -0
- package/dist/context/sessionBootstrapManifest.js +11 -19
- package/dist/controller/agentHostObservation.js +4 -2
- package/dist/controller/agentRuntimeObserver.js +4 -4
- package/dist/controller/clientRuntime.js +19 -44
- package/dist/controller/controller.js +24 -10
- package/dist/controller/fileSchedulerStoreAdapter.js +91 -24
- package/dist/controller/globalInputDelivery.js +31 -8
- package/dist/controller/globalRuntimeAttention.js +34 -0
- package/dist/controller/jobSupervisor.js +3 -3
- package/dist/controller/operatorNotification.js +31 -0
- package/dist/controller/providerRetryAdmission.js +3 -1
- package/dist/controller/providerRetryDelivery.js +136 -119
- package/dist/controller/runtime.js +2 -28
- package/dist/controller/sessionOwnerReconciliation.js +42 -24
- package/dist/controller/structuredProviderObservation.js +28 -1
- package/dist/controller/updateReconciliation.js +72 -20
- package/dist/coordination/workMailbox.js +4 -4
- package/dist/core/controllerIdentity.js +25 -0
- package/dist/core/controllerProcessIdentity.js +2 -2
- package/dist/core/controllerServer.js +4 -2
- package/dist/core/protocol.js +1 -1
- package/dist/doctor/doctor.js +2 -1
- package/dist/domain/validation.js +6 -0
- package/dist/event/taskEvent.js +3 -3
- package/dist/execution/workItemExecution.js +2 -2
- package/dist/executor/agentAdapter.js +28 -96
- package/dist/executor/agentConfigurationCatalog.js +13 -68
- package/dist/executor/agentConfigurationFields.js +120 -0
- package/dist/executor/agentConfigurationProbe.js +55 -80
- package/dist/executor/agentExecutor.js +11 -8
- package/dist/executor/effectiveLaunch.js +3 -3
- package/dist/executor/executorRegistry.js +0 -9
- package/dist/executor/fileRoleLaunchPlanner.js +12 -11
- package/dist/grant/capabilityGrant.js +2 -2
- package/dist/input/inputRequest.js +4 -4
- package/dist/integration/changeSet.js +3 -3
- package/dist/integration/integrationAttempt.js +3 -3
- package/dist/integration/integrationSourceApplication.js +1 -1
- package/dist/interaction/operatorPresentation.js +2 -1
- package/dist/job/durableJob.js +1 -1
- package/dist/job/jobRunner.js +2 -2
- package/dist/message/message.js +8 -6
- package/dist/message/messageContinuation.js +8 -9
- package/dist/milestone/milestone.js +3 -3
- package/dist/observability/executionAudit.js +1 -10
- package/dist/observability/runtimeIdentity.js +0 -23
- package/dist/output/agentConfigurationPresentation.js +4 -2
- package/dist/profile/agentProfile.js +3 -3
- package/dist/release/releaseHandover.js +2 -2
- package/dist/release/releaseIdempotencyStore.js +0 -23
- package/dist/release/releaseWorkflowPorts.js +8 -4
- package/dist/release/runtimeRelease.js +9 -7
- package/dist/repository/gitWorkspace.js +66 -19
- package/dist/repository/project.js +3 -3
- package/dist/repository/taskWorkspacePreparer.js +75 -68
- package/dist/resources/liveReferences.js +1 -1
- package/dist/resources/resourceRegistry.js +90 -44
- package/dist/resources/sqliteResourceRegistry.js +5 -7
- package/dist/review/reviewRound.js +5 -5
- package/dist/role/role.js +7 -8
- package/dist/runtime/acpProtocol.js +2 -3
- package/dist/runtime/acpSession.js +21 -5
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentEndpoint.js +4 -1
- package/dist/runtime/agentHost.js +25 -65
- package/dist/runtime/agentHostCleanup.js +85 -0
- package/dist/runtime/agentHostProtocol.js +3 -3
- package/dist/runtime/builtinAgentDrivers.js +46 -119
- package/dist/runtime/builtinTranscriptObserver.js +10 -6
- package/dist/runtime/codexAppServerRuntime.js +55 -89
- package/dist/runtime/codexInteractiveHost.js +1 -1
- package/dist/runtime/jsonLineChannel.js +35 -7
- package/dist/runtime/launchBroker.js +1 -1
- package/dist/runtime/processExitObservation.js +1 -1
- package/dist/runtime/providerContinuationReconciliationService.js +86 -38
- package/dist/runtime/providerRetry.js +15 -0
- package/dist/runtime/providerRuntimeIdentity.js +3 -3
- package/dist/runtime/providerRuntimeReconciler.js +12 -2
- package/dist/runtime/runtimeCoherence.js +7 -3
- package/dist/runtime/runtimeObservation.js +3 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/structuredProviderHost.js +106 -81
- package/dist/runtime/taskRuntimeIsolation.js +3 -3
- package/dist/runtime/tmuxAdapters.js +3 -3
- package/dist/scheduler/activeRoleRunDelivery.js +5 -1
- package/dist/scheduler/operatorInputNotificationProcessor.js +7 -17
- package/dist/scheduler/taskWake.js +1 -1
- package/dist/storage/baselineSchema.js +606 -0
- package/dist/storage/homeLayout.js +5 -16
- package/dist/storage/recordValidation.js +16 -4
- package/dist/storage/sqliteSchema.js +106 -1741
- package/dist/storage/sqliteStore.js +16 -14
- package/dist/storage/storageSchema.js +8 -7
- package/dist/storage/storageVersions.js +23 -16
- package/dist/storage/taskStore.js +3 -29
- package/dist/storage/upgrade/upgradeOrchestrator.js +14 -112
- package/dist/task/nextAction.js +33 -23
- package/dist/task/task.js +17 -8
- package/dist/task/taskActivation.js +5 -4
- package/dist/telemetry/sqliteTelemetryStore.js +2 -2
- package/dist/verification/gateArtifact.js +5 -3
- package/dist/verification/verificationPlan.js +6 -7
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app.js +72 -3
- package/dist/web/assets/client/components.js +2 -1
- package/dist/web/assets/client/i18n.js +2 -0
- package/dist/web/assets/client/taskSummary.js +345 -0
- package/dist/web/assets/client/taskSurface.js +44 -26
- package/dist/web/assets/client/view.js +33 -0
- package/dist/web/assets/shell.js +1 -0
- package/dist/web/assets/styles/cards.js +21 -0
- package/dist/web/webServer.js +39 -5
- package/dist/web/webSessions.js +165 -0
- package/dist/web/webSnapshot.js +23 -0
- package/dist/web/webTaskSurface.js +23 -0
- package/dist/workItem/workItem.js +6 -6
- package/dist/workspace/cleanupInspection.js +1 -9
- package/dist/worktree/managedWorkspace.js +3 -3
- package/docs/managed-turn-and-session-runtime.md +50 -7
- package/docs/managed-turn-and-session-runtime.zh-CN.md +36 -5
- package/docs/observability/README.md +47 -0
- package/docs/observability/README.zh-CN.md +37 -0
- package/docs/project-refresh.md +9 -0
- package/docs/project-refresh.zh-CN.md +8 -0
- package/docs/provider-retry.md +35 -0
- package/docs/release-workflow.md +70 -214
- package/docs/release-workflow.zh-CN.md +53 -157
- package/docs/roles-and-configuration.md +30 -0
- package/docs/roles-and-configuration.zh-CN.md +20 -0
- package/docs/sqlite-control-plane-design.md +48 -289
- package/docs/sqlite-control-plane-design.zh-CN.md +37 -53
- package/docs/storage-baseline.md +132 -0
- package/docs/storage-baseline.zh-CN.md +106 -0
- package/docs/task-delivery.md +110 -2
- package/docs/task-delivery.zh-CN.md +82 -2
- package/docs/task-discovery.md +9 -0
- package/docs/task-discovery.zh-CN.md +6 -0
- package/docs/testing/verification-levels.md +42 -164
- package/docs/testing/verification-levels.zh-CN.md +29 -115
- package/i18n/README.zh-CN.md +7 -4
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +11 -0
- package/skills/yui-leader/references/execution.md +22 -0
- package/skills/yui-leader/references/planning.md +6 -2
- package/skills/yui-operator/SKILL.md +45 -13
- package/skills/yui-operator/references/task-delivery.md +99 -0
- package/skills/yui-runtime/SKILL.md +5 -3
- package/skills/yui-runtime/references/publication.md +56 -5
- package/skills/yui-runtime/references/recovery.md +12 -2
- package/dist/runtime/agentHostCompatibility.js +0 -127
- package/dist/storage/migrations/agentFailureContext.js +0 -22
- package/dist/storage/migrations/agentRunContract.js +0 -159
- package/dist/storage/migrations/artifactsToGit.js +0 -338
- package/dist/storage/migrations/collapseWorktreeLayout.js +0 -963
- package/dist/storage/migrations/currentInputContract.js +0 -86
- package/dist/storage/migrations/currentRuntimeContract.js +0 -228
- package/dist/storage/migrations/historicalVerificationPlan.js +0 -35
- package/dist/storage/migrations/integrationContinuation.js +0 -105
- package/dist/storage/migrations/narrowAgentFailureContext.js +0 -65
- package/dist/storage/migrations/notificationOnlyWakes.js +0 -74
- package/dist/storage/migrations/removeRuntimeGeneration.js +0 -207
- package/dist/storage/migrations/submitIntent.js +0 -126
- package/dist/storage/migrations/unifyHomeLayout.js +0 -925
- package/dist/storage/migrations/verificationPlanV1.js +0 -162
- package/dist/storage/migrations/verificationPolicy.js +0 -74
- package/dist/storage/migrations/workItemHistory.js +0 -46
|
@@ -143,7 +143,7 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
|
|
|
143
143
|
}));
|
|
144
144
|
const laterWakeups = await processLeaderWakeups(store, delivery, now, exactTaskSelection(laterWakeupTaskIds));
|
|
145
145
|
const inputNotifications = includeOperator
|
|
146
|
-
? await processOperatorInputNotifications(store,
|
|
146
|
+
? await processOperatorInputNotifications(store, selection, now)
|
|
147
147
|
: [];
|
|
148
148
|
return {
|
|
149
149
|
activeRunDeliveries,
|
|
@@ -948,6 +948,18 @@ export class FileTaskController {
|
|
|
948
948
|
#requestBackgroundPump() {
|
|
949
949
|
void this.pump().catch(this.#onError);
|
|
950
950
|
}
|
|
951
|
+
/** Optional maintenance is not execution admission. Keep its failure visible
|
|
952
|
+
* without bypassing the scheduler's own ownership and delivery fences. */
|
|
953
|
+
async #observeMaintenance(phase, action) {
|
|
954
|
+
try {
|
|
955
|
+
return await action();
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
this.#onError(new Error(`${phase} failed: ${error instanceof Error ? error.message : String(error)}. `
|
|
959
|
+
+ "No successful cleanup or quiescence is inferred; inspect the original failure.", { cause: error }));
|
|
960
|
+
return undefined;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
951
963
|
async #runCoalesced() {
|
|
952
964
|
let result = emptyControllerSchedulerResult();
|
|
953
965
|
let pendingRuntimeDrain = false;
|
|
@@ -966,15 +978,15 @@ export class FileTaskController {
|
|
|
966
978
|
if (scope.kind === "full")
|
|
967
979
|
this.reloadReconciliationInterval();
|
|
968
980
|
if (scope.kind === "full" && this.#resourceReaper !== undefined) {
|
|
969
|
-
const reap = await this.#resourceReaper
|
|
970
|
-
for (const failure of reap
|
|
981
|
+
const reap = await this.#observeMaintenance("Ephemeral runtime reap", this.#resourceReaper);
|
|
982
|
+
for (const failure of reap?.failed ?? []) {
|
|
971
983
|
this.#onError(new Error(`Ephemeral runtime reap failed for ${failure.id}: ${failure.message}`));
|
|
972
984
|
}
|
|
973
985
|
// The reaper owns the exact resource fences. Once a whole expired
|
|
974
986
|
// domain converges, let the detached Controller close itself on a
|
|
975
987
|
// later turn; never close while this reconciliation pass is still
|
|
976
988
|
// the in-flight server request.
|
|
977
|
-
if (reap.failed.length === 0 && (reap.expiredDomains?.length ?? 0) > 0) {
|
|
989
|
+
if (reap !== undefined && reap.failed.length === 0 && (reap.expiredDomains?.length ?? 0) > 0) {
|
|
978
990
|
for (const domain of reap.expiredDomains ?? []) {
|
|
979
991
|
queueMicrotask(() => {
|
|
980
992
|
this.#onExpiredEphemeralDomain?.(domain);
|
|
@@ -992,7 +1004,7 @@ export class FileTaskController {
|
|
|
992
1004
|
// full pass. It queries only identities already present in Task
|
|
993
1005
|
// facts and cannot start a model AgentRun or scan unknown children.
|
|
994
1006
|
if (scope.kind === "full" && this.#continuationReconciler !== undefined) {
|
|
995
|
-
await this.#continuationReconciler.reconcile(this.#now());
|
|
1007
|
+
await this.#observeMaintenance("Continuation reconciliation", () => this.#continuationReconciler.reconcile(this.#now()));
|
|
996
1008
|
}
|
|
997
1009
|
// DurableJob reconciliation runs before the scheduler pass so a
|
|
998
1010
|
// terminal job's Leader wakeup is enqueued in the same pass that
|
|
@@ -1407,18 +1419,20 @@ export class FileTaskController {
|
|
|
1407
1419
|
else if (wakeup.status === "dispatched")
|
|
1408
1420
|
settled.add(key);
|
|
1409
1421
|
}
|
|
1410
|
-
const operatorRetries = result.inputNotifications.filter((notification) => notification.reason === "operator-
|
|
1411
|
-
|
|
1412
|
-
&& this.#operatorStartupRetryArmed));
|
|
1422
|
+
const operatorRetries = result.inputNotifications.filter((notification) => notification.reason === "operator-unavailable"
|
|
1423
|
+
&& this.#operatorStartupRetryArmed);
|
|
1413
1424
|
if (operatorRetries.length > 0) {
|
|
1414
1425
|
retry.set("operator", {
|
|
1415
1426
|
identity: operatorRetries.map((notification) => (`${notification.sourceKind}:${notification.taskId}:${notification.sourceId}`)).join("|")
|
|
1416
1427
|
});
|
|
1417
1428
|
}
|
|
1418
|
-
else if (result.inputNotifications.some((notification) => notification.status === "
|
|
1419
|
-
|| notification.status === "already-
|
|
1429
|
+
else if (result.inputNotifications.some((notification) => notification.status === "queued"
|
|
1430
|
+
|| notification.status === "already-queued")) {
|
|
1420
1431
|
this.#operatorStartupRetryArmed = false;
|
|
1421
1432
|
settled.add("operator");
|
|
1433
|
+
// The Global queue owns delivery now. Request its next pass without
|
|
1434
|
+
// retaining a second attention retry loop while the Provider is busy.
|
|
1435
|
+
resignal.add("global-role:operator");
|
|
1422
1436
|
}
|
|
1423
1437
|
for (const key of settled)
|
|
1424
1438
|
this.#clearDeliveryRetry(key);
|
|
@@ -5,17 +5,17 @@ import { freezeRunContextSnapshot } from "../context/runContextPack.js";
|
|
|
5
5
|
import { settleGlobalRetryInput } from "../message/globalProviderRetry.js";
|
|
6
6
|
import { interruptThenTerminalState, prepareMessageContinuations } from "../message/messageContinuation.js";
|
|
7
7
|
import { assertExecutionEnvironmentCurrent } from "../runtime/executionEnvironment.js";
|
|
8
|
-
import { deferProviderRetry, providerRetryPending, recordProviderFailure } from "../runtime/providerRetry.js";
|
|
8
|
+
import { deferProviderRetry, providerRetryPending, providerRetryProjection, recordProviderFailure } from "../runtime/providerRetry.js";
|
|
9
9
|
import { AgentHostObservationDeferred, recordAgentHostConnection, resolveAgentHostObservation } from "./agentHostObservation.js";
|
|
10
10
|
import { admitOwnedProviderInput, ownedProviderInput } from "./providerRetryAdmission.js";
|
|
11
11
|
import { RuntimeHookRunFenceError } from "./runtimeHookRunFence.js";
|
|
12
|
-
import { appendRunInput, createRun, runPurposeAdmitsTaskState
|
|
12
|
+
import { appendRunInput, createRun, runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
13
13
|
import { createRunInput } from "../context/runInputContract.js";
|
|
14
14
|
import { bindExecution, claimPending, completeProcessing, consumePendingBatch, mailboxHasWork, releaseProcessing } from "../coordination/workMailbox.js";
|
|
15
15
|
import { enqueueWork, settleRoleRunDispatch as settleRoleRunDispatchMailbox } from "../coordination/workMailboxQueue.js";
|
|
16
16
|
import { transportAgentResult } from "../domain/agentResultTransport.js";
|
|
17
17
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
18
|
-
import { activeLiveRoleAgentSession, bindGlobalRoleProviderRuntime, bindTaskRoleProviderRuntime, createRoleSessionSet, detachRoleAgentSessionHost, recordRoleAgentSession, recordTaskRoleNativeTurnBoundary, rememberRoleAgentCompletedTurn, replaceTaskRoleAgentSession, selectNewTaskRoleSession,
|
|
18
|
+
import { activeLiveRoleAgentSession, bindGlobalRoleProviderRuntime, bindTaskRoleProviderRuntime, createRoleSessionSet, detachRoleAgentSessionHost, recordRoleAgentSession, recordTaskRoleNativeTurnBoundary, rememberRoleAgentCompletedTurn, replaceTaskRoleAgentSession, selectNewTaskRoleSession, roleSessionControlTarget, updateGlobalRoleProviderRuntime, updateRoleAgentSessionStatus, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
|
|
19
19
|
import { effectiveLaunchWithTaskMainWorkspace, resolveEffectiveLaunch, roleSessionMayContinue, validateEffectiveLaunchSnapshot } from "../executor/effectiveLaunch.js";
|
|
20
20
|
import { answerInputRequest } from "../input/inputRequest.js";
|
|
21
21
|
import { createCanonicalLifecycleEvent, foldCanonicalLifecycleEvent } from "../lifecycle/canonicalLifecycleEvent.js";
|
|
@@ -26,6 +26,8 @@ import { activeRoleAgentBinding } from "../role/role.js";
|
|
|
26
26
|
import { SYSTEM_OPERATOR_ROLE } from "../role/systemRoles.js";
|
|
27
27
|
import { standardAgentError } from "../runtime/agentError.js";
|
|
28
28
|
import { recordTaskAgentError } from "./taskAgentError.js";
|
|
29
|
+
import { recordGlobalRuntimeAttention, recordGlobalRuntimeFailure } from "./globalRuntimeAttention.js";
|
|
30
|
+
import { queueOperatorNotification } from "./operatorNotification.js";
|
|
29
31
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
30
32
|
import { RUNTIME_CLEANUP_REQUIRED_REASON, RUNTIME_HOST_DETACH_REQUIRED_REASON, RUNTIME_LIFECYCLE_OWNER, hasRuntimeCleanupObligation, hasRuntimeLifecycleWork, isRuntimeCleanupReason, runtimeCleanupDisposition, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
31
33
|
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
@@ -384,6 +386,16 @@ export class FileSchedulerStoreAdapter {
|
|
|
384
386
|
const matches = receipt !== undefined && binding.run?.attemptId === receipt
|
|
385
387
|
&& (fence.nativeTurnId === undefined || binding.run.nativeTurnId === undefined
|
|
386
388
|
|| binding.run.nativeTurnId === fence.nativeTurnId);
|
|
389
|
+
if ((input.kind === "observer.health" || input.kind === "conversation.observed") && input.payload.failure !== undefined) {
|
|
390
|
+
if (receipt !== undefined && !matches)
|
|
391
|
+
return "obsolete";
|
|
392
|
+
recordGlobalRuntimeAttention(store, fence.roleName, input.eventId, {
|
|
393
|
+
fence, failure: input.payload.failure,
|
|
394
|
+
confirmed: binding.run?.status ?? "no-owned-input",
|
|
395
|
+
unknown: "Native execution outcome and resource quiescence are not proven by this diagnostic."
|
|
396
|
+
}, now);
|
|
397
|
+
return "applied";
|
|
398
|
+
}
|
|
387
399
|
if (input.kind === "turn.accepted") {
|
|
388
400
|
if (input.authority === "transport" && matches) {
|
|
389
401
|
const message = store.listGlobalRoleMessages(fence.roleName).find(entry => receipt === `global-input:${fence.roleName}/${entry.id}`);
|
|
@@ -456,12 +468,27 @@ export class FileSchedulerStoreAdapter {
|
|
|
456
468
|
binding = updateProviderConversationRecoverability(binding, input.payload.recoverability);
|
|
457
469
|
}
|
|
458
470
|
if (input.kind === "turn.failed" && matches && input.payload.failure !== undefined) {
|
|
471
|
+
const failureRef = recordGlobalRuntimeFailure(store, fence.roleName, input.eventId, {
|
|
472
|
+
fence, failure: input.payload.failure,
|
|
473
|
+
chainId: binding.retry?.chainId ?? receipt,
|
|
474
|
+
attempts: binding.retry?.attempts ?? 0,
|
|
475
|
+
confirmed: "Native Turn failed. Prior side effects are not undone.",
|
|
476
|
+
unknown: "External effects and remaining unfinished work."
|
|
477
|
+
}, now);
|
|
459
478
|
binding = recordProviderFailure(binding, {
|
|
460
|
-
error: input.payload.failure.error, failureRef
|
|
479
|
+
error: input.payload.failure.error, failureRef, at: now.getTime()
|
|
461
480
|
});
|
|
462
481
|
}
|
|
463
482
|
store.saveGlobalRoleSessionSet(updateGlobalRoleProviderRuntime(sessions, binding, now));
|
|
464
483
|
settleGlobalRetryInput(store, fence.roleName, binding, now);
|
|
484
|
+
if (input.kind === "turn.failed" && matches && input.payload.failure !== undefined
|
|
485
|
+
&& !providerRetryPending(binding)) {
|
|
486
|
+
recordGlobalRuntimeAttention(store, fence.roleName, input.eventId, {
|
|
487
|
+
fence, failure: input.payload.failure, retry: providerRetryProjection(binding),
|
|
488
|
+
confirmed: "Native Turn failed; this does not prove absence of prior side effects.",
|
|
489
|
+
unknown: "External effects must be inspected before continuing."
|
|
490
|
+
}, now);
|
|
491
|
+
}
|
|
465
492
|
return "applied";
|
|
466
493
|
});
|
|
467
494
|
}
|
|
@@ -518,11 +545,19 @@ export class FileSchedulerStoreAdapter {
|
|
|
518
545
|
&& (native.nativeTurnId === undefined || native.nativeTurnId === input.fence.nativeTurnId)
|
|
519
546
|
? "applied" : "obsolete";
|
|
520
547
|
}
|
|
521
|
-
const knownContinuation = input.kind.startsWith("continuation.")
|
|
522
|
-
&&
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
548
|
+
const knownContinuation = (input.kind.startsWith("continuation.") || input.kind === "observer.health")
|
|
549
|
+
&& projectProviderContinuations(store.listEvents(input.fence.taskId)).some((entry) => (entry.runId === input.fence.runId
|
|
550
|
+
&& entry.identity.providerNamespace === input.fence.driverId
|
|
551
|
+
&& entry.identity.accountScope === input.fence.agentId
|
|
552
|
+
&& entry.identity.conversationId === input.fence.conversationId
|
|
553
|
+
&& entry.identity.continuationId === input.fence.continuationId));
|
|
554
|
+
if (input.kind === "observer.health" && input.payload.failure !== undefined) {
|
|
555
|
+
const native = sessions?.providerBinding?.run;
|
|
556
|
+
return knownContinuation || session?.nativeSessionId === input.fence.nativeSessionId
|
|
557
|
+
&& (input.fence.receiptId === undefined || native?.attemptId === input.fence.receiptId
|
|
558
|
+
&& (input.fence.nativeTurnId === undefined || native.nativeTurnId === input.fence.nativeTurnId))
|
|
559
|
+
? "applied" : "obsolete";
|
|
560
|
+
}
|
|
526
561
|
const requiresCurrentRuntime = input.kind !== "turn.cancelled" && !knownContinuation;
|
|
527
562
|
const valid = run !== null && (!requiresCurrentRuntime || (run.status === "active" && active?.id === run.id)) && run.roleName === input.fence.roleName && run.effective.agentId === input.fence.agentId && this.drivers.requireByAdapterId(run.effective.adapterId).id === input.fence.driverId && (knownContinuation || (session !== undefined && session.nativeSessionId === input.fence.nativeSessionId)) && runtimeReceiptBelongsToRun(store, input);
|
|
528
563
|
if (!valid) {
|
|
@@ -600,7 +635,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
600
635
|
const key = providerContinuationKey(continuation.identity);
|
|
601
636
|
const identityDigest = createHash("sha256").update(key).digest("hex");
|
|
602
637
|
const detached = createRuntimeObservation({
|
|
603
|
-
schemaVersion:
|
|
638
|
+
schemaVersion: 1,
|
|
604
639
|
eventId: `derived-continuation-detached:${identityDigest}`,
|
|
605
640
|
semanticKey: `continuation-detached:${identityDigest}`,
|
|
606
641
|
kind: "continuation.started",
|
|
@@ -689,7 +724,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
689
724
|
.update(`${input.semanticKey}\u0000${identityKey}`)
|
|
690
725
|
.digest("hex");
|
|
691
726
|
const settled = createRuntimeObservation({
|
|
692
|
-
schemaVersion:
|
|
727
|
+
schemaVersion: 1,
|
|
693
728
|
eventId: `native-snapshot-settled:${digest}`,
|
|
694
729
|
semanticKey: `native-snapshot-settled:${digest}`,
|
|
695
730
|
kind: "continuation.settled",
|
|
@@ -733,7 +768,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
733
768
|
? "provider-goal-cleared"
|
|
734
769
|
: `provider-goal-${input.payload.goalStatus}`, now);
|
|
735
770
|
}
|
|
736
|
-
if (
|
|
771
|
+
if (["turn.failed", "observer.health", "conversation.observed"].includes(input.kind) && input.payload.failure !== undefined) {
|
|
737
772
|
const failure = input.payload.failure;
|
|
738
773
|
const error = failure.error;
|
|
739
774
|
const run = input.fence.runId === undefined
|
|
@@ -753,10 +788,12 @@ export class FileSchedulerStoreAdapter {
|
|
|
753
788
|
runId: input.fence.runId,
|
|
754
789
|
nativeSessionId: input.fence.nativeSessionId,
|
|
755
790
|
nativeTurnId: input.fence.nativeTurnId,
|
|
791
|
+
attemptId: input.fence.receiptId,
|
|
756
792
|
lastOutput: failure.lastOutput
|
|
757
793
|
},
|
|
758
794
|
}, now);
|
|
759
|
-
if (created &&
|
|
795
|
+
if (created && (input.kind !== "turn.failed"
|
|
796
|
+
|| !providerRetryPending(store.getTaskRoleSessionSet(taskId, input.fence.roleName)?.providerBinding)))
|
|
760
797
|
routeRoleEvent(store, errorEvent, input.fence.roleName, wakeReason("agent-error", errorEvent.id), now);
|
|
761
798
|
}
|
|
762
799
|
if ((input.kind === "operation.completed" || input.kind === "operation.failed")
|
|
@@ -857,6 +894,9 @@ export class FileSchedulerStoreAdapter {
|
|
|
857
894
|
getActiveRun(taskId, roleName) {
|
|
858
895
|
return this.store.getActiveRun(taskId, roleName);
|
|
859
896
|
}
|
|
897
|
+
getContextSnapshot(taskId, snapshotId) {
|
|
898
|
+
return this.store.getContextSnapshot(taskId, snapshotId);
|
|
899
|
+
}
|
|
860
900
|
hasOpenInputRequest(taskId) {
|
|
861
901
|
return this.store.listOpenInputRequests([taskId]).length > 0;
|
|
862
902
|
}
|
|
@@ -879,8 +919,8 @@ export class FileSchedulerStoreAdapter {
|
|
|
879
919
|
adapterId: effectiveSession.effective.adapterId
|
|
880
920
|
};
|
|
881
921
|
}
|
|
882
|
-
|
|
883
|
-
|
|
922
|
+
queueOperatorNotification(input, now) {
|
|
923
|
+
return queueOperatorNotification(this.store, input, now);
|
|
884
924
|
}
|
|
885
925
|
resolveExpiredInputRecommendations(now, taskIds) {
|
|
886
926
|
return this.store.transaction((store) => {
|
|
@@ -1224,8 +1264,26 @@ export class FileSchedulerStoreAdapter {
|
|
|
1224
1264
|
let resolved = settleProviderTurnSubmission(binding, { attemptId: input.attemptId, status: input.status,
|
|
1225
1265
|
reason: input.reason, resolvedAt: input.now.toISOString() });
|
|
1226
1266
|
resolved = this.retrySubmissionResolution(resolved, input, sessions.sessions[sessions.activeAgentId]?.adapterId);
|
|
1267
|
+
if (input.status !== "deferred" && resolved.run?.failure !== undefined) {
|
|
1268
|
+
const failureRef = recordGlobalRuntimeFailure(store, input.roleName, `${input.attemptId}:submission`, {
|
|
1269
|
+
nativeSessionId: currentProviderConversation(resolved).conversationId,
|
|
1270
|
+
attemptId: input.attemptId, status: input.status, failure: resolved.run.failure,
|
|
1271
|
+
chainId: resolved.retry?.chainId ?? input.attemptId, attempts: resolved.retry?.attempts ?? 0
|
|
1272
|
+
}, input.now);
|
|
1273
|
+
resolved = { ...resolved, run: { ...resolved.run, failure: { ...resolved.run.failure, ref: failureRef } },
|
|
1274
|
+
...(resolved.retry?.failedAttemptId === input.attemptId
|
|
1275
|
+
? { retry: { ...resolved.retry, failureRef } } : {}) };
|
|
1276
|
+
}
|
|
1227
1277
|
store.saveGlobalRoleSessionSet(updateGlobalRoleProviderRuntime(sessions, resolved, input.now));
|
|
1228
1278
|
settleGlobalRetryInput(store, input.roleName, resolved, input.now);
|
|
1279
|
+
if (input.status !== "deferred" && !providerRetryPending(resolved)) {
|
|
1280
|
+
recordGlobalRuntimeAttention(store, input.roleName, `${input.attemptId}:submission`, {
|
|
1281
|
+
nativeSessionId: currentProviderConversation(resolved).conversationId,
|
|
1282
|
+
attemptId: input.attemptId, status: input.status,
|
|
1283
|
+
failure: resolved.run?.failure, retry: providerRetryProjection(resolved),
|
|
1284
|
+
unknown: "External effects are not established by submission settlement."
|
|
1285
|
+
}, input.now);
|
|
1286
|
+
}
|
|
1229
1287
|
const message = store.listGlobalRoleMessages(input.roleName).find(entry => input.attemptId === `global-input:${input.roleName}/${entry.id}`);
|
|
1230
1288
|
if (message !== undefined && message.delivery?.via !== "provider") {
|
|
1231
1289
|
const outcome = input.status === "delivery-unknown" ? "delivery-unknown" : "rejected";
|
|
@@ -1681,15 +1739,17 @@ export class FileSchedulerStoreAdapter {
|
|
|
1681
1739
|
const mailbox = store.getWorkMailbox(target);
|
|
1682
1740
|
if (mailbox?.pending == null || mailbox.processing !== null)
|
|
1683
1741
|
return false;
|
|
1742
|
+
const snapshot = freezeRunContextSnapshot(store, {
|
|
1743
|
+
taskId, roleName: "leader", purpose: "planning"
|
|
1744
|
+
}, now);
|
|
1684
1745
|
const run = createRun(store.nextRunId(taskId), taskId, "leader", "new", createRunInput({ source: { type: "yui", channel: "task-dispatch" },
|
|
1685
1746
|
directive: `Plan Task ${taskId} with the user. Persist requirements and decisions. Request activation explicitly; planning grants no delivery authority.`,
|
|
1747
|
+
contextSnapshotRef: contextSnapshotRef(snapshot),
|
|
1686
1748
|
deltaRefIds: [] }), now, {
|
|
1687
1749
|
purpose: "planning", effective: resolveEffectiveLaunch({ role, purpose: "planning" })
|
|
1688
1750
|
});
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
store.saveRun(frozen);
|
|
1692
|
-
store.saveActiveRun(frozen);
|
|
1751
|
+
store.saveRun(run);
|
|
1752
|
+
store.saveActiveRun(run);
|
|
1693
1753
|
// Freeze the initial notification prefix against this planning Run.
|
|
1694
1754
|
// Later messages remain pending; acceptance or an exact terminal settles
|
|
1695
1755
|
// only this batch, so a failed initial launch cannot replay it forever.
|
|
@@ -2211,7 +2271,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2211
2271
|
&& event.payload.observationId === observationId)))
|
|
2212
2272
|
return;
|
|
2213
2273
|
const observation = validateRuntimeProcessExitObservation({
|
|
2214
|
-
schemaVersion:
|
|
2274
|
+
schemaVersion: 1,
|
|
2215
2275
|
observationId,
|
|
2216
2276
|
hostSequence: 1,
|
|
2217
2277
|
hostInstanceId: `tmux-${input.roleName}`,
|
|
@@ -2256,6 +2316,13 @@ export class FileSchedulerStoreAdapter {
|
|
|
2256
2316
|
recordCanonicalObservationObsolete(store, input, "provider-conversation-mismatch", now);
|
|
2257
2317
|
return "obsolete";
|
|
2258
2318
|
}
|
|
2319
|
+
if (input.payload.failure !== undefined) {
|
|
2320
|
+
// A diagnostic without an owned Turn describes this exact connection,
|
|
2321
|
+
// not a new recoverability observation or proof of resource release.
|
|
2322
|
+
return sessions.activeAgentId === input.fence.agentId
|
|
2323
|
+
&& sessions.sessions[input.fence.agentId]?.nativeSessionId === input.fence.nativeSessionId
|
|
2324
|
+
? "applied" : "obsolete";
|
|
2325
|
+
}
|
|
2259
2326
|
try {
|
|
2260
2327
|
if (input.kind === "conversation.observed") {
|
|
2261
2328
|
binding = updateProviderConversationRecoverability(binding, input.payload.recoverability);
|
|
@@ -2653,7 +2720,7 @@ export class FileSchedulerStoreAdapter {
|
|
|
2653
2720
|
// this Global Role, settle it to the observed terminal so the scope-generic
|
|
2654
2721
|
// resolver and the interrupt-then gate see the real stop on the live binding
|
|
2655
2722
|
// — the same edge Task settles via settleStructuredProviderTurn. This is a
|
|
2656
|
-
// strict guarded no-op for
|
|
2723
|
+
// strict guarded no-op for a Session without a controlled binding or a
|
|
2657
2724
|
// binding holding a different/already-terminal Turn, and the
|
|
2658
2725
|
// durable native terminal recorded just above remains the proof of record.
|
|
2659
2726
|
current = settleGlobalRoleRuntimeTurn(current, input.nativeTurnId, input.providerStatus, now);
|
|
@@ -2864,7 +2931,7 @@ function dormantRuntimeCandidateIsCurrent(store, candidate, allowActiveRun = fal
|
|
|
2864
2931
|
}
|
|
2865
2932
|
const sessions = runtimeOwnerSessionSet(store, owner);
|
|
2866
2933
|
const active = allowActiveRun && owner.scope === "task"
|
|
2867
|
-
?
|
|
2934
|
+
? roleSessionControlTarget(store.getTaskRoleSessionSet(owner.taskId, owner.roleName))
|
|
2868
2935
|
: sessions?.sessions[sessions.activeAgentId];
|
|
2869
2936
|
return active !== undefined && active.agentId === candidate.agentId && active.adapterId === candidate.adapterId
|
|
2870
2937
|
&& active.nativeSessionId === candidate.nativeSessionId && (allowActiveRun || active.updatedAt === candidate.sessionUpdatedAt);
|
|
@@ -3416,12 +3483,12 @@ function settleStructuredProviderTurn(sessions, runId, status, now, attemptId) {
|
|
|
3416
3483
|
* It is a strict guarded no-op unless a controller-owned Turn with that exact
|
|
3417
3484
|
* native id is bound and still in flight (`accepted`) — an absent binding, a
|
|
3418
3485
|
* different/only-submitting Turn, or an already-terminal one is left untouched, so
|
|
3419
|
-
*
|
|
3486
|
+
* a Session without a controlled binding settles nothing
|
|
3420
3487
|
* and the durable `recentCompletedTurnIds` terminal remains the proof of record.
|
|
3421
3488
|
*/
|
|
3422
3489
|
function settleGlobalRoleRuntimeTurn(sessions, nativeTurnId, status, now) {
|
|
3423
3490
|
const binding = sessions.providerBinding;
|
|
3424
|
-
if (binding === null || binding
|
|
3491
|
+
if (binding === null || binding.run === null
|
|
3425
3492
|
|| binding.run.nativeTurnId !== nativeTurnId
|
|
3426
3493
|
|| binding.run.status !== "accepted") {
|
|
3427
3494
|
return sessions;
|
|
@@ -13,6 +13,10 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
13
13
|
if (pending.length === 0 || hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name }))))
|
|
14
14
|
continue;
|
|
15
15
|
let sessions = store.getGlobalRoleSessionSet(role.name);
|
|
16
|
+
// A detached Host keeps an active Session and may reconnect. An explicit
|
|
17
|
+
// Session stop/switch must not be undone by background notification work.
|
|
18
|
+
if (sessions !== null && sessions.sessions[sessions.activeAgentId]?.status !== "active")
|
|
19
|
+
continue;
|
|
16
20
|
if (sessions?.providerBinding?.retry !== undefined && !providerRetryPending(sessions.providerBinding)) {
|
|
17
21
|
store.transaction(tx => settleGlobalRetryInput(tx, role.name, tx.getGlobalRoleSessionSet(role.name)?.providerBinding, new Date()));
|
|
18
22
|
pending = store.listGlobalRoleMessages(role.name).filter(message => message.delivery === undefined && message.notDelivered === undefined
|
|
@@ -37,6 +41,9 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
37
41
|
continue;
|
|
38
42
|
if (current != null && ["submitting", "accepted", "delivery-unknown"].includes(current.status))
|
|
39
43
|
continue;
|
|
44
|
+
const matchesTarget = (message) => message.deliveryTarget === undefined
|
|
45
|
+
|| sessions?.activeAgentId === message.deliveryTarget.agentId
|
|
46
|
+
&& sessions.sessions[message.deliveryTarget.agentId]?.nativeSessionId === message.deliveryTarget.nativeSessionId;
|
|
40
47
|
const claims = pending.filter(message => message.interruptThen !== undefined);
|
|
41
48
|
let next;
|
|
42
49
|
if (claims.length > 0) {
|
|
@@ -54,22 +61,36 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
54
61
|
if (next === undefined)
|
|
55
62
|
continue;
|
|
56
63
|
}
|
|
57
|
-
else
|
|
58
|
-
|
|
64
|
+
else {
|
|
65
|
+
// Retain each stale original with its exact failure, but do not spend
|
|
66
|
+
// one reconciliation interval per unsent notice before reaching user
|
|
67
|
+
// input for the successor. An existing attempt/uncertain control still
|
|
68
|
+
// fences the queue; target mismatch alone is not non-acceptance.
|
|
69
|
+
next = pending.find(message => {
|
|
70
|
+
if (matchesTarget(message)
|
|
71
|
+
|| current?.attemptId === `global-input:${role.name}/${message.id}`
|
|
72
|
+
|| message.control !== undefined && message.control.outcome !== "rejected")
|
|
73
|
+
return true;
|
|
74
|
+
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(message, "native-session-changed", new Date()));
|
|
75
|
+
return false;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (next === undefined)
|
|
79
|
+
continue;
|
|
59
80
|
const attemptId = `global-input:${role.name}/${next.id}`;
|
|
60
81
|
if (current?.attemptId === attemptId)
|
|
61
82
|
continue;
|
|
62
83
|
if (next.control !== undefined && next.control.outcome !== "rejected")
|
|
63
84
|
continue;
|
|
64
|
-
|
|
65
|
-
|| sessions?.activeAgentId === next.deliveryTarget.agentId
|
|
66
|
-
&& sessions.sessions[next.deliveryTarget.agentId]?.nativeSessionId === next.deliveryTarget.nativeSessionId;
|
|
67
|
-
if (!matchesTarget()) {
|
|
85
|
+
if (!matchesTarget(next)) {
|
|
68
86
|
store.updateGlobalRoleMessage(markGlobalRoleMessageNotDelivered(next, "native-session-changed", new Date()));
|
|
69
87
|
continue;
|
|
70
88
|
}
|
|
71
89
|
await ensure(role.name);
|
|
72
90
|
sessions = store.getGlobalRoleSessionSet(role.name);
|
|
91
|
+
if (hasRuntimeCleanupObligation(store.getWorkMailbox(runtimeLifecycleTarget({ scope: "global", roleName: role.name })))
|
|
92
|
+
|| sessions?.sessions[sessions.activeAgentId]?.status !== "active")
|
|
93
|
+
continue;
|
|
73
94
|
const binding = sessions?.providerBinding;
|
|
74
95
|
if (binding == null || binding.authority.owner !== "controller")
|
|
75
96
|
continue;
|
|
@@ -78,7 +99,7 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
78
99
|
if (persisted === undefined || JSON.stringify(persisted) !== JSON.stringify(next))
|
|
79
100
|
continue;
|
|
80
101
|
const claim = next.interruptThen;
|
|
81
|
-
if (!matchesTarget() || claim !== undefined
|
|
102
|
+
if (!matchesTarget(next) || claim !== undefined
|
|
82
103
|
&& (sessions?.activeAgentId !== claim.targetAgentId
|
|
83
104
|
|| nativeSessionId !== claim.targetNativeSessionId
|
|
84
105
|
|| binding.authority.epoch !== claim.targetAuthorityEpoch
|
|
@@ -126,7 +147,9 @@ export async function deliverGlobalInputs(home, store, ensure, onError) {
|
|
|
126
147
|
}
|
|
127
148
|
}
|
|
128
149
|
catch (error) {
|
|
129
|
-
onError(
|
|
150
|
+
onError(new Error(`Global input delivery failed for ${role.name}: `
|
|
151
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
152
|
+
+ "Inspect the persisted Message and attempt before retrying; delivery is not inferred.", { cause: error }));
|
|
130
153
|
}
|
|
131
154
|
}
|
|
132
155
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createGlobalRoleMessage } from "../message/message.js";
|
|
3
|
+
import { serializeAgentErrorRaw } from "../runtime/agentError.js";
|
|
4
|
+
/** Global observations have no Task event history. Keep original failed
|
|
5
|
+
* attempts as ordinary record-only Messages before replacing the live binding. */
|
|
6
|
+
export function recordGlobalRuntimeFailure(store, roleName, identity, facts, now) {
|
|
7
|
+
const header = `Yui Global failure evidence ${createHash("sha256").update(identity).digest("hex")}\n`;
|
|
8
|
+
const previous = store.listGlobalRoleMessages(roleName).find(message => message.kind === "system" && message.body.startsWith(header));
|
|
9
|
+
if (previous !== undefined)
|
|
10
|
+
return `${roleName}/${previous.id}`;
|
|
11
|
+
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), roleName, header + serializeAgentErrorRaw(facts), "system", { type: "system" }, now);
|
|
12
|
+
store.saveGlobalRoleMessage(message);
|
|
13
|
+
return `${roleName}/${message.id}`;
|
|
14
|
+
}
|
|
15
|
+
/** Reuse the Global Message inbox for an exact failure, not a recovery queue.
|
|
16
|
+
* A failed Operator cannot be asked to retry its own diagnostic automatically. */
|
|
17
|
+
export function recordGlobalRuntimeAttention(store, roleName, identity, facts, now) {
|
|
18
|
+
// A Home may have a custom Global Role without an Operator. Keep its own
|
|
19
|
+
// diagnostic readable; never create a Role or launch a substitute Agent.
|
|
20
|
+
const recipient = store.getGlobalRole("operator") === null ? roleName : "operator";
|
|
21
|
+
const requestId = `runtime-attention:${createHash("sha256").update(`${roleName}\0${identity}`).digest("hex")}`;
|
|
22
|
+
if (store.listGlobalRoleMessages(recipient).some(message => message.inputControl?.requestId === requestId))
|
|
23
|
+
return;
|
|
24
|
+
const message = createGlobalRoleMessage(store.nextGlobalRoleMessageId(), recipient, [
|
|
25
|
+
`Global Role ${roleName} needs attention. No recovery or new authority is implied.`,
|
|
26
|
+
"Inspect the exact Session and original input before choosing a next action; unknown effects must not be replayed.",
|
|
27
|
+
`Original failed attempts remain in Role ${roleName}'s record-only failure Messages; failureRef names the latest evidence.`,
|
|
28
|
+
serializeAgentErrorRaw(facts)
|
|
29
|
+
].join("\n"), "system", { type: "system" }, now, { inputControl: { action: "queue", requestId } });
|
|
30
|
+
store.saveGlobalRoleMessage(recipient === roleName ? {
|
|
31
|
+
...message,
|
|
32
|
+
notDelivered: { reason: "runtime-needs-attention; retained for user or successor inspection", at: now.toISOString() }
|
|
33
|
+
} : message);
|
|
34
|
+
}
|
|
@@ -50,9 +50,9 @@ export class DurableJobSupervisor {
|
|
|
50
50
|
this.#reconcileJob(job, now);
|
|
51
51
|
}
|
|
52
52
|
catch (error) {
|
|
53
|
-
this.#onError(
|
|
54
|
-
? error
|
|
55
|
-
|
|
53
|
+
this.#onError(new Error(`DurableJob supervision failed for ${job.taskId}/${job.id}: `
|
|
54
|
+
+ `${error instanceof Error ? error.message : String(error)}. `
|
|
55
|
+
+ "Inspect retained process/artifact evidence before retry or cancellation; effects remain unconfirmed.", { cause: error }));
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { completeProcessing } from "../coordination/workMailbox.js";
|
|
2
|
+
import { createGlobalRoleMessage } from "../message/message.js";
|
|
3
|
+
/**
|
|
4
|
+
* Transfer one exact attention claim to the Global Message queue atomically.
|
|
5
|
+
* Queue ownership is not Provider acceptance or Agent acknowledgement. A failed
|
|
6
|
+
* or uncertain Message retains its identity/evidence; it is never reissued here.
|
|
7
|
+
*/
|
|
8
|
+
export function queueOperatorNotification(store, input, now = new Date()) {
|
|
9
|
+
return store.transaction(tx => {
|
|
10
|
+
const requestId = `operator-notice:${encodeURIComponent(input.receiptId)}`;
|
|
11
|
+
const previous = tx.listGlobalRoleMessages("operator").find(entry => (entry.inputControl ?? entry.interruptThen?.reusedInput)?.requestId === requestId);
|
|
12
|
+
const mailbox = tx.getWorkMailbox({ kind: "operator" });
|
|
13
|
+
const claimed = mailbox?.processing?.batchId === input.batchId;
|
|
14
|
+
if (previous !== undefined) {
|
|
15
|
+
if (claimed)
|
|
16
|
+
tx.saveWorkMailbox(completeProcessing(mailbox, input.batchId));
|
|
17
|
+
return { status: "already-queued", messageId: previous.id };
|
|
18
|
+
}
|
|
19
|
+
if (!claimed)
|
|
20
|
+
throw new Error(`Operator notification claim changed: ${input.batchId}`);
|
|
21
|
+
const sessions = tx.getGlobalRoleSessionSet("operator");
|
|
22
|
+
const session = sessions?.sessions[sessions.activeAgentId];
|
|
23
|
+
if (session?.status !== "active" || sessions?.providerBinding == null)
|
|
24
|
+
return { status: "unavailable" };
|
|
25
|
+
const created = { ...createGlobalRoleMessage(tx.nextGlobalRoleMessageId(), "operator", input.text, "system", { type: "system" }, now, { inputControl: { action: "queue", requestId } }),
|
|
26
|
+
deliveryTarget: { agentId: session.agentId, nativeSessionId: session.nativeSessionId } };
|
|
27
|
+
tx.saveGlobalRoleMessage(created);
|
|
28
|
+
tx.saveWorkMailbox(completeProcessing(mailbox, input.batchId));
|
|
29
|
+
return { status: "queued", messageId: created.id };
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -24,7 +24,9 @@ export function retryIntentBlocker(store, input, retry) {
|
|
|
24
24
|
return "role-changed";
|
|
25
25
|
if (sessions.sessions[sessions.activeAgentId]?.effective.sourceDesiredRevision !== role.launchRevision)
|
|
26
26
|
return "role-configuration-changed";
|
|
27
|
-
if (store.listGlobalRoleMessages(input.roleName).some(message =>
|
|
27
|
+
if (store.listGlobalRoleMessages(input.roleName).some(message => (message.kind === "user" || message.kind === "operator"
|
|
28
|
+
|| message.inputControl !== undefined || message.interruptThen !== undefined)
|
|
29
|
+
&& Date.parse(message.createdAt) > Date.parse(retry.intentAt)
|
|
28
30
|
&& message.delivery === undefined && message.notDelivered === undefined))
|
|
29
31
|
return "new-user-input";
|
|
30
32
|
return undefined;
|