@zq-silk/yui 0.15.11 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +11 -6
- package/ARCHITECTURE.zh-CN.md +8 -4
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/artifacts/artifactCommitLock.js +16 -55
- package/dist/artifacts/taskArtifactRepository.js +3 -3
- package/dist/cli/agentConfigurationPicker.js +1 -1
- package/dist/cli/commandCatalog.js +92 -48
- package/dist/cli/completion.js +18 -18
- package/dist/cli/completionWizard.js +1 -1
- package/dist/cli/dynamicCompletion.js +1 -1
- package/dist/cli/interactionPolicy.js +11 -3
- package/dist/cli/invocationAuthority.js +64 -0
- package/dist/cli/managedDiagnostics.js +2 -2
- package/dist/cli/parseRepeatable.js +35 -0
- package/dist/cli/updatePorts.js +26 -5
- package/dist/cli.js +1303 -1235
- package/dist/commands/agentCommands.js +4 -4
- package/dist/commands/capabilityCommands.js +1 -1
- package/dist/commands/configCommands.js +6 -57
- package/dist/commands/configOverview.js +2 -2
- package/dist/commands/durableJobCommands.js +12 -6
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +78 -29
- package/dist/commands/grantCommands.js +0 -3
- package/dist/commands/jobCommands.js +1 -4
- package/dist/commands/operatorCommands.js +3 -3
- package/dist/commands/projectCommands.js +45 -25
- package/dist/commands/resourcesCommands.js +10 -40
- package/dist/commands/roleConfiguration.js +2 -6
- package/dist/commands/taskActivationCommands.js +4 -8
- package/dist/commands/taskAgentCapabilities.js +9 -0
- package/dist/commands/taskCommandSupport.js +155 -0
- package/dist/commands/taskCommandTypes.js +1 -0
- package/dist/commands/taskCommands.js +355 -732
- package/dist/commands/taskCompletionGate.js +1 -1
- package/dist/commands/taskExecutionCommands.js +1 -1
- package/dist/commands/taskFactCommands.js +325 -0
- package/dist/commands/taskInputCommands.js +12 -42
- package/dist/commands/taskIntegrationCommands.js +20 -58
- package/dist/commands/taskNextActionCommand.js +7 -6
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +12 -3
- package/dist/commands/taskPublicationVerifyCommand.js +24 -40
- package/dist/commands/taskRemoteDeliveryCommand.js +5 -86
- package/dist/commands/taskUpstreamCommands.js +19 -8
- package/dist/commands/workflowCommands.js +6 -1
- package/dist/completion/completionInstaller.js +26 -26
- package/dist/completion/completionState.js +26 -26
- package/dist/completion/fileCompletionManager.js +6 -11
- package/dist/config/configCatalog.js +0 -2
- package/dist/config/timeZone.js +14 -0
- package/dist/config/yuiConfig.js +0 -29
- package/dist/context/runContextPack.js +5 -3
- package/dist/context/taskCatalog.js +185 -0
- package/dist/context/taskContext.js +152 -151
- package/dist/context/wakeRunReferences.js +11 -0
- package/dist/controller/agentCapabilities.js +58 -0
- package/dist/controller/agentRuntimeObserver.js +6 -11
- package/dist/controller/clientRuntime.js +22 -14
- package/dist/controller/controller.js +20 -24
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -225
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/jobClient.js +4 -4
- package/dist/controller/jobControl.js +60 -25
- package/dist/controller/jobSupervisor.js +4 -4
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +85 -43
- package/dist/controller/runtimeEventProcessor.js +0 -5
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/controller/taskAgentError.js +35 -0
- package/dist/coordination/workMailboxQueue.js +2 -24
- package/dist/core/controllerClient.js +2 -2
- package/dist/core/fileLockOwner.js +74 -0
- package/dist/decision/decision.js +17 -0
- package/dist/doctor/doctor.js +18 -9
- package/dist/event/taskEvent.js +12 -0
- package/dist/execution/workItemExecutionProjection.js +2 -2
- package/dist/executor/agentCapabilityConfig.js +43 -0
- package/dist/executor/agentConfigurationCatalog.js +49 -11
- package/dist/executor/agentExecutor.js +5 -8
- package/dist/executor/fileRoleLaunchPlanner.js +11 -53
- package/dist/executor/taskAgentCapabilities.js +64 -0
- package/dist/integration/deliveryObligation.js +1 -69
- package/dist/integration/gitIntegrationService.js +79 -90
- package/dist/integration/integrationAttempt.js +3 -14
- package/dist/integration/integrationSourceApplication.js +4 -12
- package/dist/integration/manifestTags.js +2 -2
- package/dist/job/durableJob.js +4 -25
- package/dist/job/jobAssignmentScope.js +22 -0
- package/dist/job/jobOperation.js +16 -0
- package/dist/job/jobRunner.js +2 -1
- package/dist/job/stepDirectory.js +15 -0
- package/dist/kernel/builtinCapabilities.js +15 -11
- package/dist/kernel/kernelPorts.js +1 -17
- package/dist/lifecycle/exactRunTerminalization.js +5 -10
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/message/inputControlResolution.js +3 -4
- package/dist/message/message.js +17 -20
- package/dist/message/messageContinuation.js +1 -1
- package/dist/milestone/milestone.js +11 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/observability/orchestrationMetrics.js +24 -1
- package/dist/observability/runtimeIdentity.js +1 -17
- package/dist/output/agentConfigurationPresentation.js +2 -0
- package/dist/output/timePresentation.js +1 -14
- package/dist/plugins/pluginService.js +12 -4
- package/dist/release/releaseWorkflowPorts.js +45 -96
- package/dist/release/runtimeRelease.js +9 -15
- package/dist/repository/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +98 -82
- package/dist/repository/taskBaseFreshness.js +1 -1
- package/dist/repository/taskWorkspaceCoordinator.js +82 -144
- package/dist/repository/taskWorkspacePreparer.js +113 -47
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/resources/autoResourceGc.js +4 -77
- package/dist/resources/liveReferences.js +7 -54
- package/dist/resources/resourceDiscovery.js +5 -118
- package/dist/resources/resourceGc.js +233 -181
- package/dist/resources/resourceRegistrar.js +5 -4
- package/dist/resources/resourceRegistry.js +3 -37
- package/dist/resources/resourceRegistryStore.js +13 -36
- package/dist/resources/sqliteResourceRegistry.js +43 -26
- package/dist/review/deltaRecheck.js +1 -1
- package/dist/review/reviewAcceptance.js +1 -1
- package/dist/review/reviewDecision.js +1 -1
- package/dist/review/reviewRound.js +8 -7
- package/dist/review/taskFinalReviewContractResolution.js +1 -1
- package/dist/runtime/acpProtocol.js +4 -51
- package/dist/runtime/acpSession.js +10 -75
- package/dist/runtime/acpSessionConfiguration.js +1 -8
- package/dist/runtime/agentEndpoint.js +3 -2
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentFailureContext.js +43 -0
- package/dist/runtime/agentHost.js +42 -25
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/codexInteractiveHost.js +2 -2
- package/dist/runtime/index.js +0 -1
- package/dist/runtime/managedCaller.js +21 -1
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerErrors.js +38 -0
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/runtimeObservation.js +0 -5
- package/dist/runtime/runtimeSessionCandidate.js +2 -3
- package/dist/runtime/sessionOwnerIdentity.js +1 -1
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +10 -43
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/runtime/tmuxAdapters.js +6 -8
- package/dist/scheduler/activeRoleRunDelivery.js +19 -7
- package/dist/scheduler/leaderWakeupProcessor.js +24 -6
- package/dist/scheduler/operatorEvent.js +12 -20
- package/dist/scheduler/operatorInputNotificationProcessor.js +1 -1
- package/dist/scheduler/ports.js +5 -8
- package/dist/scheduler/roleRunLiveness.js +4 -4
- package/dist/scheduler/roleRunStall.js +16 -23
- package/dist/scheduler/taskExecutionProjection.js +49 -36
- package/dist/scheduler/taskObservabilityProjection.js +6 -45
- package/dist/scheduler/taskWake.js +5 -10
- package/dist/scheduler/wakeReason.js +2 -0
- package/dist/scheduler/wakeupQueue.js +2 -4
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/contextRecords.js +106 -0
- package/dist/storage/currentTaskStore.js +1 -1
- package/dist/storage/homeLayout.js +13 -17
- package/dist/storage/migrations/agentFailureContext.js +22 -0
- package/dist/storage/migrations/currentInputContract.js +86 -0
- package/dist/storage/migrations/currentRuntimeContract.js +228 -0
- package/dist/storage/migrations/historicalVerificationPlan.js +35 -0
- package/dist/storage/migrations/integrationContinuation.js +5 -4
- package/dist/storage/migrations/narrowAgentFailureContext.js +65 -0
- package/dist/storage/migrations/notificationOnlyWakes.js +74 -0
- package/dist/storage/migrations/verificationPlanV1.js +162 -0
- package/dist/storage/migrations/verificationPolicy.js +74 -0
- package/dist/storage/migrations/workItemHistory.js +46 -0
- package/dist/storage/persistenceWorker.js +12 -4
- package/dist/storage/recordValidation.js +87 -0
- package/dist/storage/sqliteSchema.js +173 -1
- package/dist/storage/sqliteStore.js +172 -161
- package/dist/storage/storageSchema.js +2 -14
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +16 -7
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +5 -7
- package/dist/storage/upgrade/upgradeOrchestrator.js +25 -37
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/completionReadiness.js +5 -24
- package/dist/task/draftPlan.js +0 -53
- package/dist/task/nextAction.js +7 -13
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/task/remoteDeliveryService.js +89 -0
- package/dist/task/taskActivation.js +2 -25
- package/dist/task/taskActivationService.js +0 -2
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/task/taskSubmission.js +3 -14
- package/dist/telemetry/sqliteTelemetryBatch.js +29 -0
- package/dist/telemetry/sqliteTelemetryStore.js +49 -49
- package/dist/telemetry/telemetryWiring.js +4 -3
- package/dist/tmux/tmuxManager.js +29 -20
- package/dist/verification/gateArtifact.js +15 -24
- package/dist/verification/gateArtifactStore.js +14 -7
- package/dist/verification/verificationGateService.js +29 -88
- package/dist/verification/verificationPlan.js +27 -72
- package/dist/web/assets/client/app.js +92 -17
- package/dist/web/assets/client/components.js +55 -13
- package/dist/web/assets/client/i18n.js +72 -4
- package/dist/web/assets/client/taskSurface.js +6 -5
- package/dist/web/assets/client/view.js +35 -7
- package/dist/web/assets/shell.js +6 -0
- package/dist/web/assets/styles/layout.js +7 -0
- package/dist/web/webServer.js +12 -3
- package/dist/web/webSnapshot.js +14 -85
- package/dist/web/webTaskSurface.js +17 -46
- package/dist/workItem/workItem.js +1 -12
- package/dist/workspace/cleanupInspection.js +63 -0
- package/dist/workspace/workItemChangeSetManager.js +110 -50
- 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 +42 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +33 -3
- package/docs/managed-turn-and-session-runtime.md +121 -1
- package/docs/managed-turn-and-session-runtime.zh-CN.md +96 -2
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/plugin-sdk.md +4 -2
- 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/provider-runtime.md +3 -1
- package/docs/provider-runtime.zh-CN.md +4 -2
- package/docs/release-workflow.md +190 -7
- package/docs/release-workflow.zh-CN.md +139 -5
- package/docs/roles-and-configuration.md +29 -0
- package/docs/roles-and-configuration.zh-CN.md +21 -0
- package/docs/sqlite-control-plane-design.md +16 -3
- package/docs/sqlite-control-plane-design.zh-CN.md +13 -2
- package/docs/task-dag-semantics.md +3 -3
- package/docs/task-dag-semantics.zh-CN.md +2 -2
- package/docs/task-delivery.md +223 -17
- package/docs/task-delivery.zh-CN.md +171 -14
- package/docs/task-discovery.md +101 -0
- package/docs/task-discovery.zh-CN.md +85 -0
- package/docs/testing/verification-levels.md +161 -9
- package/docs/testing/verification-levels.zh-CN.md +116 -9
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +5 -0
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-leader/references/integration.md +9 -5
- package/skills/yui-leader/references/planning.md +9 -3
- package/skills/yui-operator/SKILL.md +24 -8
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +17 -2
- package/skills/yui-runtime/references/publication.md +26 -4
- package/skills/yui-runtime/references/recovery.md +71 -0
- package/dist/agent/agentRegistry.js +0 -10
- package/dist/agentRun/runIdentity.js +0 -22
- package/dist/commands/deliveryGuardPreflight.js +0 -30
- package/dist/commands/taskIntegrationQueueCommands.js +0 -226
- package/dist/commands/taskOverviewCommand.js +0 -376
- package/dist/completion/completionWizard.js +0 -125
- package/dist/context/dispatchContext.js +0 -110
- package/dist/context/wakeNotification.js +0 -144
- package/dist/integration/integrationCheckEvidenceReuse.js +0 -53
- package/dist/integration/integrationQueueEntry.js +0 -191
- package/dist/integration/integrationQueueService.js +0 -810
- package/dist/release/fakeReleasePorts.js +0 -55
- package/dist/runtime/sessionOwnerRegistry.js +0 -137
- package/dist/task/deliveryGuard.js +0 -226
- /package/dist/{commands/taskActor.js → task/taskAuthority.js} +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { builtinDriverIdForAdapter } from "./builtinAgentDrivers.js";
|
|
2
|
+
import { isRuntimeTokenEvidence, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
|
|
3
|
+
import { projectSessionTokenObservations, unobservedSessionTokenMetrics } from "./sessionTokenMetrics.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pure lifetime projection. No transcript reads, collection, persistence,
|
|
6
|
+
* lifecycle decisions, guessed Run identities, or cumulative counter allocation.
|
|
7
|
+
*/
|
|
8
|
+
export function projectTaskUsageMetrics(input) {
|
|
9
|
+
const now = input.now ?? new Date();
|
|
10
|
+
const events = input.events.filter((event) => event.taskId === input.task.id);
|
|
11
|
+
const observations = events.flatMap((event) => {
|
|
12
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
13
|
+
return observation !== null
|
|
14
|
+
&& observation.fence.taskId === input.task.id
|
|
15
|
+
&& Date.parse(observation.receivedAt) <= now.getTime()
|
|
16
|
+
? [observation] : [];
|
|
17
|
+
}).sort(compare);
|
|
18
|
+
const runs = new Map(input.runs.filter((run) => run.taskId === input.task.id).map((run) => [run.id, run]));
|
|
19
|
+
const inScope = (observation) => {
|
|
20
|
+
if (input.workItemId === undefined)
|
|
21
|
+
return true;
|
|
22
|
+
const run = exactRun(observation, runs);
|
|
23
|
+
return run?.workItemId === input.workItemId;
|
|
24
|
+
};
|
|
25
|
+
const scoped = observations.filter(inScope);
|
|
26
|
+
const buckets = new Map();
|
|
27
|
+
for (const observation of observations) {
|
|
28
|
+
const identity = sessionIdentity(observation);
|
|
29
|
+
if (identity === null)
|
|
30
|
+
continue;
|
|
31
|
+
const key = identityKey(identity);
|
|
32
|
+
const bucket = buckets.get(key) ?? { identity, observations: [] };
|
|
33
|
+
bucket.observations.push(observation);
|
|
34
|
+
buckets.set(key, bucket);
|
|
35
|
+
}
|
|
36
|
+
const resourceOwners = new Map();
|
|
37
|
+
for (const { identity, observations: all } of buckets.values()) {
|
|
38
|
+
if (!all.some((observation) => !child(observation) && usageAuthority(observation)))
|
|
39
|
+
continue;
|
|
40
|
+
const resource = resourceKey(identity);
|
|
41
|
+
const owners = resourceOwners.get(resource) ?? new Set();
|
|
42
|
+
owners.add(identity.roleName);
|
|
43
|
+
resourceOwners.set(resource, owners);
|
|
44
|
+
}
|
|
45
|
+
const sessions = [...buckets.values()]
|
|
46
|
+
.sort((a, b) => identityKey(a.identity).localeCompare(identityKey(b.identity)))
|
|
47
|
+
.flatMap(({ identity, observations: all }) => {
|
|
48
|
+
if (!all.some(inScope))
|
|
49
|
+
return [];
|
|
50
|
+
const main = all.filter((observation) => !child(observation) && usageAuthority(observation));
|
|
51
|
+
const evidence = main.filter(isRuntimeTokenEvidence);
|
|
52
|
+
const metrics = projectSessionTokenObservations(main, identity);
|
|
53
|
+
let displayedMetrics = input.workItemId === undefined ? metrics : unobservedSessionTokenMetrics(identity);
|
|
54
|
+
const reasons = [];
|
|
55
|
+
if (all.some(child))
|
|
56
|
+
reasons.push("child-usage-overlap-unproven");
|
|
57
|
+
if (evidence.some(({ payload }) => payload.observationQuality === "partial"
|
|
58
|
+
|| payload.observationQuality === "unavailable"))
|
|
59
|
+
reasons.push("source-coverage-partial");
|
|
60
|
+
let tokens = attributableTokens(evidence, metrics);
|
|
61
|
+
if (input.workItemId !== undefined) {
|
|
62
|
+
// Establish semantics and request ownership before filtering. Filtering
|
|
63
|
+
// first could hide a mixed stream or allocate a revised request twice.
|
|
64
|
+
if (metrics.cumulativeTotal.status !== "observed") {
|
|
65
|
+
tokens = metric(null, ["session-usage-unavailable"]);
|
|
66
|
+
}
|
|
67
|
+
else if (evidence.some(({ payload }) => payload.usage?.semantics === "cumulative-session")) {
|
|
68
|
+
tokens = metric(null, ["cumulative-not-allocatable-to-work-item"]);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const requests = new Map();
|
|
72
|
+
for (const observation of evidence) {
|
|
73
|
+
if (observation.payload.usage?.semantics !== "request-context")
|
|
74
|
+
continue;
|
|
75
|
+
const key = observation.payload.activityId ?? observation.semanticKey;
|
|
76
|
+
requests.set(key, [...(requests.get(key) ?? []), observation]);
|
|
77
|
+
}
|
|
78
|
+
const selected = [];
|
|
79
|
+
for (const versions of requests.values()) {
|
|
80
|
+
if (!versions.some(inScope))
|
|
81
|
+
continue;
|
|
82
|
+
const owners = new Set(versions.map((observation) => exactRun(observation, runs)?.id));
|
|
83
|
+
if (owners.size !== 1 || owners.has(undefined) || !versions.every(inScope)) {
|
|
84
|
+
reasons.push("request-run-attribution-conflict");
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
selected.push(...versions);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
displayedMetrics = projectSessionTokenObservations(selected, identity);
|
|
91
|
+
const subtotal = displayedMetrics.cumulativeTotal;
|
|
92
|
+
tokens = metric(subtotal.status === "observed" ? subtotal.totalTokens : null, subtotal.status === "observed" ? [] : ["no-attributable-request-usage"]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if ((resourceOwners.get(resourceKey(identity))?.size ?? 0) > 1) {
|
|
96
|
+
tokens = metric(null, ["native-session-role-overlap"]);
|
|
97
|
+
displayedMetrics = unobservedSessionTokenMetrics(identity);
|
|
98
|
+
}
|
|
99
|
+
tokens = metric(tokens.value, [...tokens.reasons, ...reasons]);
|
|
100
|
+
return [{
|
|
101
|
+
identity,
|
|
102
|
+
semantics: [...new Set(evidence.flatMap(({ payload }) => payload.usage === undefined ? [] : [payload.usage.semantics]))].sort(),
|
|
103
|
+
sources: [...new Set(evidence.map(({ authority, payload }) => payload.observerSource?.sourceId ?? authority))].sort(),
|
|
104
|
+
observedThrough: all.filter(inScope).at(-1).receivedAt,
|
|
105
|
+
tokens,
|
|
106
|
+
metrics: displayedMetrics
|
|
107
|
+
}];
|
|
108
|
+
});
|
|
109
|
+
const reasons = [];
|
|
110
|
+
if (scoped.some((observation) => sessionIdentity(observation) === null))
|
|
111
|
+
reasons.push("session-identity-missing");
|
|
112
|
+
for (const run of runs.values()) {
|
|
113
|
+
if (input.workItemId !== undefined && run.workItemId !== input.workItemId)
|
|
114
|
+
continue;
|
|
115
|
+
if (!observations.some((observation) => exactRun(observation, runs)?.id === run.id
|
|
116
|
+
&& usageAuthority(observation) && isRuntimeTokenEvidence(observation)))
|
|
117
|
+
reasons.push("run-usage-unobserved");
|
|
118
|
+
}
|
|
119
|
+
const tokenValues = sessions.flatMap(({ tokens }) => tokens.value === null ? [] : [tokens.value]);
|
|
120
|
+
const sum = safeSum(tokenValues);
|
|
121
|
+
const tokens = metric(tokenValues.length === 0 ? null : sum, [...reasons, ...sessions.flatMap(({ tokens }) => tokens.reasons),
|
|
122
|
+
...(sum === null ? ["counter-overflow"] : []),
|
|
123
|
+
...(sessions.length === 0 ? ["no-token-evidence"] : [])]);
|
|
124
|
+
const operations = new Map();
|
|
125
|
+
for (const observation of observations) {
|
|
126
|
+
const identity = sessionIdentity(observation);
|
|
127
|
+
const { nativeTurnId } = observation.fence;
|
|
128
|
+
if (!usageAuthority(observation) || child(observation) || identity === null || nativeTurnId === undefined
|
|
129
|
+
|| !observation.kind.startsWith("operation.")
|
|
130
|
+
|| observation.payload.operation !== "tool" || observation.payload.operationId === undefined)
|
|
131
|
+
continue;
|
|
132
|
+
const key = JSON.stringify([resourceKey(identity), nativeTurnId, observation.payload.operationId]);
|
|
133
|
+
const versions = operations.get(key) ?? [];
|
|
134
|
+
versions.push(observation);
|
|
135
|
+
operations.set(key, versions);
|
|
136
|
+
}
|
|
137
|
+
const toolCount = [...operations.values()].filter((versions) => {
|
|
138
|
+
if (input.workItemId === undefined)
|
|
139
|
+
return true;
|
|
140
|
+
const owners = new Set(versions.map((observation) => exactRun(observation, runs)?.id));
|
|
141
|
+
return owners.size === 1 && !owners.has(undefined) && versions.every(inScope);
|
|
142
|
+
}).length;
|
|
143
|
+
return {
|
|
144
|
+
scope: "task-lifetime", taskId: input.task.id,
|
|
145
|
+
...(input.workItemId === undefined ? {} : { workItemId: input.workItemId }),
|
|
146
|
+
observedThrough: scoped.at(-1)?.receivedAt ?? null,
|
|
147
|
+
tokens,
|
|
148
|
+
toolCalls: metric(toolCount === 0 ? null : toolCount, ["tool-history-compacted"]),
|
|
149
|
+
elapsedSeconds: input.workItemId === undefined
|
|
150
|
+
? taskElapsed(input.task, events, now) : metric(null, ["work-item-lifecycle-not-task-elapsed"]),
|
|
151
|
+
executionSeconds: executionDuration(scoped, now),
|
|
152
|
+
coverage: {
|
|
153
|
+
basis: "task-fenced-observations",
|
|
154
|
+
observedSessions: sessions.length,
|
|
155
|
+
tokenSessions: tokenValues.length,
|
|
156
|
+
completeness: "observed-sources-only"
|
|
157
|
+
},
|
|
158
|
+
sessions
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function attributableTokens(evidence, metrics) {
|
|
162
|
+
const total = metrics.cumulativeTotal;
|
|
163
|
+
if (total.status !== "observed")
|
|
164
|
+
return metric(null, ["session-usage-unavailable"]);
|
|
165
|
+
const cumulative = evidence.filter(({ payload }) => payload.usage?.semantics === "cumulative-session");
|
|
166
|
+
if (cumulative.length === 0)
|
|
167
|
+
return metric(total.totalTokens);
|
|
168
|
+
const first = cumulative[0].payload.usage;
|
|
169
|
+
const baseline = first.inputTokens + first.outputTokens;
|
|
170
|
+
if (baseline === 0)
|
|
171
|
+
return metric(total.totalTokens);
|
|
172
|
+
// session.started also occurs on resume: it cannot prove a fresh counter.
|
|
173
|
+
// A nonzero first counter may predate this Task, so exclude it. A single
|
|
174
|
+
// snapshot does not prove even a zero delta.
|
|
175
|
+
return cumulative.length < 2
|
|
176
|
+
? metric(null, ["nonzero-session-baseline"])
|
|
177
|
+
: metric(total.totalTokens - baseline, ["nonzero-session-baseline"]);
|
|
178
|
+
}
|
|
179
|
+
function exactRun(observation, runs) {
|
|
180
|
+
const { fence } = observation;
|
|
181
|
+
const run = fence.runId === undefined ? undefined : runs.get(fence.runId);
|
|
182
|
+
if (run === undefined || fence.nativeSessionId === undefined || run.roleName !== fence.roleName
|
|
183
|
+
|| run.effective.agentId !== fence.agentId)
|
|
184
|
+
return undefined;
|
|
185
|
+
try {
|
|
186
|
+
return builtinDriverIdForAdapter(run.effective.adapterId) === fence.driverId ? run : undefined;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function sessionIdentity({ fence }) {
|
|
193
|
+
return fence.taskId === undefined || fence.nativeSessionId === undefined ? null : {
|
|
194
|
+
taskId: fence.taskId, roleName: fence.roleName, agentId: fence.agentId,
|
|
195
|
+
driverId: fence.driverId, nativeSessionId: fence.nativeSessionId
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function identityKey(identity) {
|
|
199
|
+
return JSON.stringify([identity.taskId, identity.roleName, identity.agentId, identity.driverId, identity.nativeSessionId]);
|
|
200
|
+
}
|
|
201
|
+
function resourceKey(identity) {
|
|
202
|
+
return JSON.stringify([identity.agentId, identity.driverId, identity.nativeSessionId]);
|
|
203
|
+
}
|
|
204
|
+
function child({ fence }) {
|
|
205
|
+
return fence.continuationId !== undefined || fence.parentContinuationId !== undefined;
|
|
206
|
+
}
|
|
207
|
+
function usageAuthority(observation) {
|
|
208
|
+
return observation.authority === "provider-structured" || observation.authority === "driver-inferred";
|
|
209
|
+
}
|
|
210
|
+
function compare(left, right) {
|
|
211
|
+
return left.receivedAt.localeCompare(right.receivedAt)
|
|
212
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
213
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
214
|
+
|| left.eventId.localeCompare(right.eventId);
|
|
215
|
+
}
|
|
216
|
+
function metric(value, reasons = []) {
|
|
217
|
+
return {
|
|
218
|
+
value, status: value === null ? "unknown" : reasons.length === 0 ? "known" : "partial",
|
|
219
|
+
reasons: [...new Set(reasons)].sort()
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function safeSum(values) {
|
|
223
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
224
|
+
return Number.isSafeInteger(sum) && sum >= 0 ? sum : null;
|
|
225
|
+
}
|
|
226
|
+
function taskElapsed(task, events, now) {
|
|
227
|
+
const terminal = task.status === "completed"
|
|
228
|
+
|| task.status === "cancelled" || task.status === "archived";
|
|
229
|
+
const cancelledAt = events.filter((event) => event.type === "task.cancelled")
|
|
230
|
+
.map(({ createdAt }) => createdAt).sort().at(-1);
|
|
231
|
+
const end = terminal ? task.completedAt ?? task.retiredAt ?? cancelledAt : now.toISOString();
|
|
232
|
+
const elapsed = end === undefined ? NaN : (Date.parse(end) - Date.parse(task.createdAt)) / 1000;
|
|
233
|
+
return Number.isFinite(elapsed) && elapsed >= 0
|
|
234
|
+
? metric(elapsed) : metric(null, ["task-lifecycle-time-missing"]);
|
|
235
|
+
}
|
|
236
|
+
function executionDuration(observations, now) {
|
|
237
|
+
const turns = new Map();
|
|
238
|
+
for (const observation of observations) {
|
|
239
|
+
const identity = sessionIdentity(observation);
|
|
240
|
+
const { nativeTurnId } = observation.fence;
|
|
241
|
+
if (identity === null || nativeTurnId === undefined || child(observation) || !usageAuthority(observation))
|
|
242
|
+
continue;
|
|
243
|
+
const resource = resourceKey(identity);
|
|
244
|
+
const key = JSON.stringify([resource, nativeTurnId]);
|
|
245
|
+
const turn = turns.get(key) ?? { resource };
|
|
246
|
+
const time = Date.parse(observation.observedAt ?? observation.receivedAt);
|
|
247
|
+
if (observation.kind === "turn.accepted")
|
|
248
|
+
turn.start = Math.min(turn.start ?? time, time);
|
|
249
|
+
if (["turn.completed", "turn.failed", "turn.cancelled"].includes(observation.kind)) {
|
|
250
|
+
turn.end = Math.max(turn.end ?? time, time);
|
|
251
|
+
}
|
|
252
|
+
turns.set(key, turn);
|
|
253
|
+
}
|
|
254
|
+
const intervals = new Map();
|
|
255
|
+
for (const turn of turns.values()) {
|
|
256
|
+
if (turn.start === undefined || turn.end === undefined || turn.end < turn.start || turn.end > now.getTime())
|
|
257
|
+
continue;
|
|
258
|
+
intervals.set(turn.resource, [...(intervals.get(turn.resource) ?? []), [turn.start, turn.end]]);
|
|
259
|
+
}
|
|
260
|
+
let milliseconds = 0;
|
|
261
|
+
for (const ranges of intervals.values()) {
|
|
262
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
263
|
+
let end = -Infinity;
|
|
264
|
+
for (const range of ranges) {
|
|
265
|
+
milliseconds += Math.max(0, range[1] - Math.max(end, range[0]));
|
|
266
|
+
end = Math.max(end, range[1]);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return metric(intervals.size === 0 ? null : milliseconds / 1000, ["native-turn-history-compacted"]);
|
|
270
|
+
}
|
|
271
|
+
/** CLI/audit formatting shares the same metric contract; Web localizes labels. */
|
|
272
|
+
export function formatUsageMetric(metric, suffix = "") {
|
|
273
|
+
return metric.value === null ? `unknown (${metric.reasons.join(", ")})`
|
|
274
|
+
: `${metric.value}${suffix}${metric.status === "partial" ? ` (partial: ${metric.reasons.join(", ")})` : ""}`;
|
|
275
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
2
|
import { resolve } from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
import { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
6
|
-
import { RuntimeHostContentionError, RuntimeHostUnavailableError, promptPushOutcome } from "./ports.js";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
7
4
|
import { providerDeliveryFailureFrom } from "./agentError.js";
|
|
5
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostRunControl, sendAgentHostSteerControl, waitForAgentHostLaunchAck } from "./agentHost.js";
|
|
6
|
+
import { launchBrokerForHome } from "./launchBroker.js";
|
|
8
7
|
import { toRuntimeLaunchFailure } from "./launchDiagnostics.js";
|
|
8
|
+
import { RuntimeHostContentionError, RuntimeHostUnavailableError, promptPushOutcome } from "./ports.js";
|
|
9
|
+
import { createRuntimeBinding } from "./runtimeBinding.js";
|
|
10
|
+
import { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
9
11
|
import { requireSafeIdentity } from "./validation.js";
|
|
10
|
-
import { launchBrokerForHome } from "./launchBroker.js";
|
|
11
|
-
import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostRunControl, sendAgentHostSteerControl, waitForAgentHostLaunchAck } from "./agentHost.js";
|
|
12
12
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 300_000;
|
|
13
13
|
/**
|
|
14
14
|
* Runtime lifecycle adapter for the current tmux host. The returned hostRef is
|
|
@@ -656,8 +656,6 @@ async function killRole(tmux, hostId, roleName) {
|
|
|
656
656
|
await tmux.killRoleAsync(hostId, roleName);
|
|
657
657
|
}
|
|
658
658
|
async function stopExactRole(tmux, hostId, roleName) {
|
|
659
|
-
if (await probeRoleStatus(tmux, hostId, roleName) !== "running")
|
|
660
|
-
return;
|
|
661
659
|
try {
|
|
662
660
|
await killRole(tmux, hostId, roleName);
|
|
663
661
|
}
|
|
@@ -25,7 +25,7 @@ export async function processActiveRoleRunDeliveries(store, delivery, now, selec
|
|
|
25
25
|
for (const task of selectedActiveSchedulerTasks(store, selection, {
|
|
26
26
|
includePlanningDrafts: true
|
|
27
27
|
})) {
|
|
28
|
-
store.prepareMessageContinuations
|
|
28
|
+
store.prepareMessageContinuations(task.id, now);
|
|
29
29
|
for (const role of selectedSchedulerRoles(store, task.id, selection)) {
|
|
30
30
|
const run = store.getActiveRun(task.id, role.name);
|
|
31
31
|
if (run === null)
|
|
@@ -49,8 +49,20 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
49
49
|
roleName: role.name,
|
|
50
50
|
runId: run.id
|
|
51
51
|
});
|
|
52
|
-
const sessionSet = store.getTaskRoleSessionSet
|
|
52
|
+
const sessionSet = store.getTaskRoleSessionSet(task.id, role.name);
|
|
53
53
|
const binding = sessionSet?.providerBinding ?? null;
|
|
54
|
+
if (binding?.retry !== undefined && (binding.retry.successorRunId === run.id
|
|
55
|
+
|| binding.retry.successorReviewRoundId !== undefined && binding.retry.successorReviewRoundId === run.reviewRoundId)
|
|
56
|
+
&& ["cancelled", "exhausted", "needs-attention"].includes(binding.retry.status)
|
|
57
|
+
&& binding.run?.runId !== run.id) {
|
|
58
|
+
return failRunDelivery(store, run, now, "runtime-failed", `Scheduled Provider retry ${binding.retry.status}: ${binding.retry.reason ?? "not admitted"}`);
|
|
59
|
+
}
|
|
60
|
+
if (binding?.retry?.status === "waiting"
|
|
61
|
+
&& (binding.retry.successorRunId === run.id || binding.retry.successorReviewRoundId !== undefined
|
|
62
|
+
&& binding.retry.successorReviewRoundId === run.reviewRoundId)
|
|
63
|
+
&& now.getTime() < Date.parse(binding.retry.nextEligibleAt)) {
|
|
64
|
+
return { ...base, status: "skipped", reason: "not-ready" };
|
|
65
|
+
}
|
|
54
66
|
const observedRun = binding?.run ?? null;
|
|
55
67
|
const initialAttemptId = formatRunReceiptId(task.id, run.id);
|
|
56
68
|
const currentProviderTurn = managedProviderTurnId(observedRun) === run.id ? observedRun : null;
|
|
@@ -85,7 +97,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
85
97
|
// report. Keep the exact input fenced; never submit it again.
|
|
86
98
|
return { ...base, status: "skipped", reason: "delivery-uncertain", error: reason };
|
|
87
99
|
}
|
|
88
|
-
const lastSubmitError =
|
|
100
|
+
const lastSubmitError = store.listEvents(task.id).filter((event) => event.type === "runtime.agent-error" && event.payload.runId === run.id
|
|
89
101
|
&& event.payload.roleName === role.name && event.payload.phase === "turn-submit").at(-1);
|
|
90
102
|
if (currentProviderTurn === null && lastSubmitError?.payload.inputDisposition === "unknown") {
|
|
91
103
|
return { ...base, status: "skipped", reason: "delivery-uncertain", error: lastSubmitError.payload.message };
|
|
@@ -102,7 +114,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
102
114
|
// The first attempted admission may already have opened this fixed native
|
|
103
115
|
// Session. A busy retry resumes it; it does not repeat the original launch.
|
|
104
116
|
const preparedHere = existingSession?.nativeSessionId !== undefined
|
|
105
|
-
&&
|
|
117
|
+
&& store.listEvents(task.id).some((event) => event.type === "run.session-prepared"
|
|
106
118
|
&& event.payload.runId === run.id && event.payload.nativeSessionId === existingSession.nativeSessionId);
|
|
107
119
|
const mode = (currentProviderTurn?.status === "deferred" || preparedHere) && existingSession?.nativeSessionId !== undefined
|
|
108
120
|
? "resume" : run.mode;
|
|
@@ -171,7 +183,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
171
183
|
}
|
|
172
184
|
if (outcome.status === "busy" || outcome.status === "unavailable") {
|
|
173
185
|
if (outcome.status === "busy" && outcome.failure?.inputDisposition === "not-accepted") {
|
|
174
|
-
store.recordAgentError
|
|
186
|
+
store.recordAgentError({ taskId: task.id, roleName: role.name, runId: run.id,
|
|
175
187
|
source: "host", phase: "turn-submit", message: outcome.failure.detail,
|
|
176
188
|
raw: outcome.failure.raw ?? serializeAgentErrorRaw(outcome.failure),
|
|
177
189
|
inputDisposition: "not-accepted", ...providerDeliveryFailureFacts(outcome.failure),
|
|
@@ -196,7 +208,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
196
208
|
: formatProviderDeliveryFailure(failure);
|
|
197
209
|
// This path is the common Provider write failure and previously left no
|
|
198
210
|
// durable fact at all, so the cause was unrecoverable after the fact.
|
|
199
|
-
store.recordAgentError
|
|
211
|
+
store.recordAgentError({
|
|
200
212
|
taskId: task.id,
|
|
201
213
|
roleName: role.name,
|
|
202
214
|
runId: run.id,
|
|
@@ -226,7 +238,7 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
226
238
|
const message = error instanceof Error ? error.message : String(error);
|
|
227
239
|
// Preserve the structured cause alongside the readable diagnosis.
|
|
228
240
|
const causeName = innermostCauseName(error);
|
|
229
|
-
store.recordAgentError
|
|
241
|
+
store.recordAgentError({
|
|
230
242
|
taskId: task.id,
|
|
231
243
|
roleName: role.name,
|
|
232
244
|
runId: run.id,
|
|
@@ -3,6 +3,10 @@ import { roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
|
|
|
3
3
|
import { isSchedulerTaskWorkspaceReady } from "./ports.js";
|
|
4
4
|
import { taskOwnsManagedWorkspace } from "../task/task.js";
|
|
5
5
|
import { hasImmediateWakeReason } from "./wakeReason.js";
|
|
6
|
+
import { providerRetryPending } from "../runtime/providerRetry.js";
|
|
7
|
+
import { RuntimeLaunchError } from "../runtime/ports.js";
|
|
8
|
+
import { RuntimeLifecycleBusyError } from "../runtime/lifecycleReservation.js";
|
|
9
|
+
import { redactAgentErrorText } from "../runtime/agentError.js";
|
|
6
10
|
export const LEADER_WAKE_AGGREGATION_MS = 60_000;
|
|
7
11
|
/** Deliver a durable mailbox notification, not an implicit execution assignment.
|
|
8
12
|
* Acceptance settles only its claimed batch; no Agent report is required.
|
|
@@ -28,12 +32,16 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
28
32
|
results.push({ ...base, status: "skipped", reason: "aggregating" });
|
|
29
33
|
continue;
|
|
30
34
|
}
|
|
31
|
-
if (task.status === "draft" && store.prepareDraftPlanning
|
|
35
|
+
if (task.status === "draft" && store.prepareDraftPlanning(task.id, now)) {
|
|
32
36
|
results.push({ ...base, status: "skipped", reason: "not-ready" });
|
|
33
37
|
continue;
|
|
34
38
|
}
|
|
35
|
-
const sessions = store.getTaskRoleSessionSet
|
|
39
|
+
const sessions = store.getTaskRoleSessionSet(task.id, role.name);
|
|
36
40
|
const provider = sessions?.providerBinding;
|
|
41
|
+
if (providerRetryPending(provider)) {
|
|
42
|
+
results.push({ ...base, status: "skipped", reason: "not-ready" });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
37
45
|
if (provider?.authority.owner === "human" || provider?.authority.owner === "unknown") {
|
|
38
46
|
results.push({ ...base, status: "skipped", reason: "busy" });
|
|
39
47
|
continue;
|
|
@@ -50,13 +58,17 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
50
58
|
...(notification.disposition === "unknown" ? {
|
|
51
59
|
error: `Notification acceptance is unknown; the fixed wake is retained and will not be replayed. `
|
|
52
60
|
+ `After establishing native quiescence, release only this claim with yui task wake resolve ${task.id} ${notification.wakeId} --reason <evidence>.`
|
|
61
|
+
} : notification.disposition === "rejected" ? {
|
|
62
|
+
error: `Notification was rejected; inspect yui task wake show ${task.id} ${notification.wakeId}. `
|
|
63
|
+
+ `After correcting the cause, retry with yui task wake retry ${task.id} ${notification.wakeId} --reason <correction>.`
|
|
53
64
|
} : {}) });
|
|
54
65
|
continue;
|
|
55
66
|
}
|
|
56
67
|
let attempted = false;
|
|
68
|
+
let effective = role.effective;
|
|
57
69
|
try {
|
|
58
70
|
const session = store.getRoleSession(task.id, role.name, role.effective.agentId);
|
|
59
|
-
|
|
71
|
+
effective = session?.status === "active" ? session.effective : role.effective;
|
|
60
72
|
if (session?.status === "active" && !roleSessionMayContinue(session.effective, role.effective)) {
|
|
61
73
|
throw new Error("Leader Session configuration changed; explicitly apply it before notification delivery.");
|
|
62
74
|
}
|
|
@@ -91,12 +103,18 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
91
103
|
: outcome.status === "rejected" ? "rejected"
|
|
92
104
|
: outcome.status === "pending" ? null : "deferred";
|
|
93
105
|
if (disposition !== null)
|
|
94
|
-
store.settleLeaderNotification(task.id, notification.attemptId, disposition, now, outcome.failure?.detail
|
|
106
|
+
store.settleLeaderNotification(task.id, notification.attemptId, disposition, now, outcome.failure?.detail, { effective, raw: outcome.failure?.raw ?? outcome.failure?.detail ?? `Notification ${disposition}.`,
|
|
107
|
+
phase: "turn-submit" });
|
|
95
108
|
results.push({ ...base, status: accepted ? "dispatched" : "skipped", reason: accepted ? undefined : "not-ready" });
|
|
96
109
|
}
|
|
97
110
|
catch (error) {
|
|
98
|
-
const detail = error instanceof Error ? error.message : String(error);
|
|
99
|
-
|
|
111
|
+
const detail = redactAgentErrorText(error instanceof Error ? error.message : String(error));
|
|
112
|
+
// Match explicit AgentRun admission: only positive, typed backpressure
|
|
113
|
+
// permits another automatic launch. A config/auth/executable failure
|
|
114
|
+
// needs correction; an exception after submit cannot authorize replay.
|
|
115
|
+
const retryable = error instanceof RuntimeLifecycleBusyError
|
|
116
|
+
|| (error instanceof RuntimeLaunchError && error.retryable);
|
|
117
|
+
store.settleLeaderNotification(task.id, notification.attemptId, attempted ? "unknown" : retryable ? "deferred" : "rejected", now, detail, { effective, raw: detail, phase: attempted ? "turn-submit" : "session-start" });
|
|
100
118
|
results.push({ ...base, status: "failed", reason: "not-ready", error: detail });
|
|
101
119
|
}
|
|
102
120
|
finally {
|
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
2
2
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
3
|
-
import { queueLeaderWakeup } from "./wakeupQueue.js";
|
|
4
3
|
export const LEADER_ATTENTION_REQUIRED_EVENT = "leader.attention-required";
|
|
5
|
-
/**
|
|
6
|
-
export function
|
|
7
|
-
if (store.getTask
|
|
4
|
+
/** One event, one supervisor signal; the Leader wake is this mailbox's projection. */
|
|
5
|
+
export function enqueueSupervisorEvent(store, event, recipient, reason, now) {
|
|
6
|
+
if (store.getTask(event.taskId)?.status === "archived")
|
|
8
7
|
return;
|
|
9
|
-
|
|
8
|
+
const target = recipient === "operator" ? { kind: "operator" }
|
|
9
|
+
: { kind: "role", taskId: event.taskId, roleName: "leader" };
|
|
10
|
+
enqueueWork(store, target, reason, now, [
|
|
10
11
|
{ type: "event", taskId: event.taskId, id: event.id }
|
|
11
|
-
], {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
], { source: "task-event", dedupeKey: `${recipient}-event:${event.taskId}:${event.id}` });
|
|
13
|
+
}
|
|
14
|
+
/** The sole Task-event boundary into the global Operator mailbox. */
|
|
15
|
+
export function enqueueOperatorEvent(store, event, reason, now) {
|
|
16
|
+
enqueueSupervisorEvent(store, event, "operator", reason, now);
|
|
15
17
|
}
|
|
16
18
|
/** Routes one Role fact to its supervisor without exposing Provider details to Operator code. */
|
|
17
19
|
export function routeRoleEvent(store, event, roleName, reason, now) {
|
|
18
|
-
|
|
19
|
-
return;
|
|
20
|
-
if (roleName === "leader") {
|
|
21
|
-
enqueueOperatorEvent(store, event, reason, now);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
enqueueWork(store, { kind: "role", taskId: event.taskId, roleName: "leader" }, reason, now, [{ type: "event", taskId: event.taskId, id: event.id }], {
|
|
25
|
-
source: "task-event",
|
|
26
|
-
dedupeKey: `leader-event:${event.taskId}:${event.id}`
|
|
27
|
-
});
|
|
28
|
-
queueLeaderWakeup(store, event.taskId, reason, now);
|
|
20
|
+
enqueueSupervisorEvent(store, event, roleName === "leader" ? "operator" : "leader", reason, now);
|
|
29
21
|
}
|
|
30
22
|
/** Records the semantic boundary used when a Leader can no longer continue. */
|
|
31
23
|
export function recordLeaderAttentionRequired(store, input) {
|
|
@@ -26,7 +26,7 @@ export async function processOperatorInputNotifications(store, delivery, selecti
|
|
|
26
26
|
? [{ kind: "input", request }]
|
|
27
27
|
: [];
|
|
28
28
|
}
|
|
29
|
-
if (ref.type !== "event"
|
|
29
|
+
if (ref.type !== "event")
|
|
30
30
|
return [];
|
|
31
31
|
const event = store.listEvents(ref.taskId).find(({ id }) => id === ref.id);
|
|
32
32
|
return event === undefined ? [] : [{ kind: "event", event }];
|
package/dist/scheduler/ports.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
|
|
2
1
|
import { taskOwnsManagedWorkspace } from "../task/task.js";
|
|
2
|
+
import { isTaskOwnedWorkspace } from "../worktree/managedWorkspace.js";
|
|
3
3
|
/**
|
|
4
4
|
* Whether the Task's workspace state admits a launch.
|
|
5
5
|
*
|
|
@@ -72,14 +72,11 @@ export function selectedActiveSchedulerTasks(store, selection, options) {
|
|
|
72
72
|
return planningDrafts && hasActivePlanningRun(store, task);
|
|
73
73
|
};
|
|
74
74
|
if (selection === undefined || selection.full) {
|
|
75
|
-
const indexedTaskIds = store.listActiveTaskIds
|
|
76
|
-
if (indexedTaskIds === undefined) {
|
|
77
|
-
return store.listTasks().filter((task) => (admits(task) && !selection?.blockedTaskIds?.has(task.id)));
|
|
78
|
-
}
|
|
75
|
+
const indexedTaskIds = store.listActiveTaskIds();
|
|
79
76
|
// The active index is bounded and authoritative for active Tasks; planning
|
|
80
77
|
// Drafts come from their own bounded index rather than a full-history scan.
|
|
81
78
|
const taskIds = planningDrafts
|
|
82
|
-
? [...indexedTaskIds, ...
|
|
79
|
+
? [...indexedTaskIds, ...store.listPlanningDraftTaskIds()]
|
|
83
80
|
: [...indexedTaskIds];
|
|
84
81
|
const seen = new Set();
|
|
85
82
|
return taskIds.flatMap((taskId) => {
|
|
@@ -106,9 +103,9 @@ export function selectedActiveSchedulerTasks(store, selection, options) {
|
|
|
106
103
|
function hasActivePlanningRun(store, task) {
|
|
107
104
|
if (task.status !== "draft")
|
|
108
105
|
return false;
|
|
109
|
-
const roles = store.listRoles
|
|
106
|
+
const roles = store.listRoles(task.id);
|
|
110
107
|
return roles.some((role) => {
|
|
111
|
-
const run = store.getActiveRun
|
|
108
|
+
const run = store.getActiveRun(task.id, role.name);
|
|
112
109
|
return run !== null
|
|
113
110
|
&& run.status === "active"
|
|
114
111
|
&& isSchedulerPlanningDraft(task, run.purpose);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { selectedSchedulerRoles, selectedActiveSchedulerTasks } from "./ports.js";
|
|
2
|
-
import { formatTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
1
|
import { runPurposeAdmitsTaskState } from "../agentRun/agentRun.js";
|
|
2
|
+
import { formatTaskRecordReference } from "../task/taskRecordReference.js";
|
|
3
|
+
import { selectedActiveSchedulerTasks, selectedSchedulerRoles } from "./ports.js";
|
|
4
4
|
import { currentRoleRunProgressAt, DEFAULT_WORKFLOW_STALL_CANDIDATE_AGE_MS } from "./roleRunStall.js";
|
|
5
5
|
/**
|
|
6
6
|
* Lightweight liveness only. Pane/process loss is runtime-health evidence,
|
|
@@ -85,7 +85,7 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
for (const { task, role, run, session
|
|
88
|
+
for (const { task, role, run, session } of eligible) {
|
|
89
89
|
const status = batchSnapshot.statuses.get(`${task.id}\0${role.name}`);
|
|
90
90
|
if (status === undefined)
|
|
91
91
|
throw new Error("Role liveness snapshot is incomplete.");
|
|
@@ -93,7 +93,7 @@ export async function reconcileExitedRoleRuns(store, delivery, now, selection, e
|
|
|
93
93
|
continue;
|
|
94
94
|
const hostExit = batchSnapshot.hostExits.get(`${task.id}\0${role.name}`);
|
|
95
95
|
if (hostExit !== undefined) {
|
|
96
|
-
store.saveRoleHostExitObservation
|
|
96
|
+
store.saveRoleHostExitObservation({
|
|
97
97
|
taskId: task.id,
|
|
98
98
|
roleName: role.name,
|
|
99
99
|
runId: run.id,
|