@zq-silk/yui 0.14.0 → 0.14.2
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 +64 -35
- package/README.md +258 -125
- package/dist/agent/argumentPolicy.js +2 -1
- package/dist/agent/managedRuntimeEnvironment.js +5 -8
- package/dist/cli/commandCatalog.js +74 -109
- package/dist/cli/helpRenderer.js +1 -2
- package/dist/cli/interactionCandidates.js +10 -0
- package/dist/cli/interactionPolicy.js +76 -37
- package/dist/cli/roleWizard.js +5 -6
- package/dist/cli/updateOrchestrator.js +10 -8
- package/dist/cli/updatePorts.js +17 -13
- package/dist/cli/upgradeCommand.js +2 -5
- package/dist/cli.js +268 -231
- package/dist/commands/agentCommands.js +13 -0
- package/dist/commands/configCommands.js +7 -19
- package/dist/commands/configOverview.js +3 -2
- package/dist/commands/durableJobCommands.js +1 -1
- package/dist/commands/executionAuditCommands.js +4 -9
- package/dist/commands/globalRoleCommands.js +17 -0
- package/dist/commands/profileCommands.js +200 -30
- package/dist/commands/sessionCommands.js +1 -1
- package/dist/commands/taskActor.js +29 -127
- package/dist/commands/taskChangeSetCommands.js +1 -3
- package/dist/commands/taskCommands.js +1771 -2107
- package/dist/commands/taskCompletionGate.js +27 -103
- package/dist/commands/taskContextCommand.js +47 -80
- package/dist/commands/taskExecutionCommands.js +9 -27
- package/dist/commands/taskInputCommands.js +10 -10
- package/dist/commands/taskIntegrationCommands.js +69 -61
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskNextActionCommand.js +8 -29
- package/dist/commands/taskOverviewCommand.js +25 -3
- package/dist/commands/taskPublicationCommands.js +151 -85
- package/dist/commands/taskPublicationVerifyCommand.js +181 -0
- package/dist/commands/taskRemoteDeliveryCommand.js +134 -0
- package/dist/commands/taskRoleRuntimeStatus.js +5 -6
- package/dist/commands/taskUpstreamCommands.js +79 -95
- package/dist/config/configCatalog.js +1 -1
- package/dist/context/contextSnapshot.js +17 -2
- package/dist/context/sessionBootstrapManifest.js +18 -1
- package/dist/context/sourceTurnContext.js +30 -0
- package/dist/context/turnContextPack.js +107 -48
- package/dist/context/turnInputContract.js +11 -4
- package/dist/context/wakeNotification.js +84 -22
- package/dist/controller/agentRuntimeObserver.js +9 -12
- package/dist/controller/clientRuntime.js +13 -13
- package/dist/controller/controller.js +11 -11
- package/dist/controller/fileSchedulerStoreAdapter.js +262 -219
- package/dist/controller/jobControl.js +11 -10
- package/dist/controller/resourceInventory.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +2 -2
- package/dist/controller/runtime.js +18 -61
- package/dist/controller/runtimeEventInbox.js +57 -34
- package/dist/controller/runtimeEventProcessor.js +36 -96
- package/dist/controller/runtimeHookTurnFence.js +33 -57
- package/dist/controller/runtimeLaunchCoordinator.js +44 -40
- package/dist/controller/runtimeObservationHook.js +18 -12
- package/dist/controller/sessionNotify.js +43 -28
- package/dist/controller/sessionOwnerReconciliation.js +21 -21
- package/dist/controller/structuredProviderObservation.js +13 -16
- package/dist/coordination/workMailboxQueue.js +132 -0
- package/dist/core/controllerServer.js +1 -0
- package/dist/domain/agentResultTransport.js +60 -0
- package/dist/execution/executionHealth.js +111 -118
- package/dist/execution/reviewMainTurn.js +161 -0
- package/dist/execution/workItemExecution.js +454 -0
- package/dist/execution/workItemExecutionProjection.js +309 -0
- package/dist/execution/workItemMainTurn.js +164 -0
- package/dist/executor/agentExecutor.js +19 -19
- package/dist/executor/executorRegistry.js +7 -28
- package/dist/executor/fileRoleLaunchPlanner.js +51 -83
- package/dist/external/githubPublicationVerifier.js +114 -0
- package/dist/external/gitlabPublicationVerifier.js +158 -0
- package/dist/external/pinnedCommandRunner.js +73 -0
- package/dist/integration/changeSetManifest.js +0 -3
- package/dist/integration/deliveryObligation.js +43 -6
- package/dist/integration/gitIntegrationService.js +284 -101
- package/dist/integration/integrationAttempt.js +64 -14
- package/dist/integration/integrationQueueEntry.js +5 -35
- package/dist/integration/integrationQueueService.js +65 -194
- package/dist/lifecycle/canonicalLifecycleEvent.js +16 -15
- package/dist/lifecycle/exactTurnTerminalization.js +100 -97
- package/dist/message/message.js +16 -0
- package/dist/observability/executionAudit.js +4 -25
- package/dist/observability/faultClassification.js +58 -70
- package/dist/observability/orchestrationMetrics.js +8 -50
- package/dist/profile/agentProfile.js +60 -15
- package/dist/profile/agentProfileRuntime.js +100 -0
- package/dist/release/releaseWorkflowPorts.js +5 -68
- package/dist/repository/executionLaneGitSnapshot.js +80 -0
- package/dist/repository/gitWorkspace.js +9 -67
- package/dist/repository/taskBaseFreshness.js +3 -1
- package/dist/repository/taskWorkspaceCoordinator.js +32 -4
- package/dist/repository/taskWorkspacePreparer.js +252 -411
- package/dist/resources/liveReferences.js +6 -55
- package/dist/resources/resourceDiscovery.js +1 -30
- package/dist/resources/resourceRegistrar.js +0 -23
- package/dist/review/deltaRecheck.js +8 -65
- package/dist/review/reviewAcceptance.js +22 -19
- package/dist/review/reviewConfig.js +0 -10
- package/dist/review/reviewDecision.js +2 -2
- package/dist/review/reviewRound.js +147 -273
- package/dist/review/reviewerAvailability.js +13 -23
- package/dist/role/role.js +3 -0
- package/dist/runtime/agentDriverObservation.js +1 -1
- package/dist/runtime/agentHost.js +51 -51
- package/dist/runtime/builtinAgentDrivers.js +9 -2
- package/dist/runtime/codexAppServerRuntime.js +23 -5
- package/dist/runtime/exactControlPlane.js +2 -267
- package/dist/runtime/firstProgressAdvisory.js +1 -1
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/launchBroker.js +11 -11
- package/dist/runtime/lifecycleReservation.js +2 -2
- package/dist/runtime/managedCaller.js +111 -0
- package/dist/runtime/ports.js +16 -3
- package/dist/runtime/preallocatedNativeSession.js +2 -2
- package/dist/runtime/processExitObservation.js +3 -2
- package/dist/runtime/providerContinuationReconciliationService.js +1 -1
- package/dist/runtime/runtimeBinding.js +1 -1
- package/dist/runtime/runtimeObservation.js +33 -16
- package/dist/runtime/runtimeSessionCandidate.js +2 -2
- package/dist/runtime/runtimeStopReceipt.js +11 -11
- package/dist/runtime/sessionLaunchRequest.js +2 -2
- package/dist/runtime/sessionOwnerIdentity.js +9 -9
- package/dist/runtime/sessionOwnerRegistry.js +13 -13
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/sessionTerminationGuard.js +9 -9
- package/dist/runtime/sessionTokenMetrics.js +5 -7
- package/dist/runtime/structuredProviderHost.js +5 -5
- package/dist/runtime/taskRuntimeIsolation.js +24 -24
- package/dist/runtime/tmuxAdapters.js +32 -30
- package/dist/scheduler/activeRoleTurnDelivery.js +37 -15
- package/dist/scheduler/leaderWakeupProcessor.js +78 -55
- package/dist/scheduler/roleTurnLiveness.js +6 -6
- package/dist/scheduler/roleTurnStall.js +8 -9
- package/dist/scheduler/taskExecutionProjection.js +19 -41
- package/dist/scheduler/taskObservabilityProjection.js +75 -122
- package/dist/storage/sqliteSchema.js +1 -21
- package/dist/storage/sqliteStore.js +62 -47
- package/dist/storage/storageSchema.js +1 -8
- package/dist/storage/storageVersions.js +7 -4
- package/dist/storage/taskStore.js +8 -8
- package/dist/storage/upgrade/upgradeOrchestrator.js +112 -83
- package/dist/task/completionReadiness.js +17 -98
- package/dist/task/deliveryGuard.js +9 -11
- package/dist/task/draftPlan.js +142 -0
- package/dist/task/nextAction.js +179 -477
- package/dist/task/publicationVerification.js +1 -0
- package/dist/task/remoteDelivery.js +233 -0
- package/dist/task/task.js +108 -4
- package/dist/task/taskRecordReference.js +0 -1
- package/dist/turn/turn.js +109 -16
- package/dist/verification/verificationGateService.js +0 -26
- package/dist/web/assets/client/components.js +99 -121
- package/dist/web/assets/client/i18n.js +136 -58
- package/dist/web/assets/client/view.js +44 -0
- package/dist/web/assets/styles/cards.js +3 -15
- package/dist/web/assets/styles/widgets.js +4 -0
- package/dist/web/webSnapshot.js +29 -14
- package/dist/workItem/dependencyGate.js +34 -0
- package/dist/workItem/workItem.js +99 -266
- package/dist/workspace/workItemChangeSetManager.js +15 -59
- package/docs/task-local-identity.md +1 -1
- package/i18n/README.zh-CN.md +165 -59
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +272 -782
- package/skills/yui-operator/SKILL.md +190 -365
- package/skills/yui-reviewer/SKILL.md +40 -33
- package/skills/yui-runtime/SKILL.md +27 -6
- package/skills/yui-worker/SKILL.md +19 -6
- package/dist/execution/candidateConvergence.js +0 -623
- package/dist/execution/executionGroup.js +0 -854
- package/dist/execution/resourceBroker.js +0 -426
- package/dist/executor/launchPlan.js +0 -16
- package/dist/review/reviewFinding.js +0 -240
- package/dist/review/reviewFindingLedger.js +0 -550
- package/dist/review/reviewOutcomeClassifier.js +0 -255
- package/dist/scheduler/resourceQueueProjection.js +0 -71
- package/dist/task/repairWave.js +0 -150
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizedUniqueText, requireIdentity, requireText } from "../domain/validation.js";
|
|
2
2
|
import { validateContextSnapshotRef } from "./contextSnapshot.js";
|
|
3
3
|
export const TURN_INPUT_PROTOCOL_VERSION = 1;
|
|
4
4
|
export const TURN_INPUT_PROTOCOL = "yui-turn/v1";
|
|
@@ -106,7 +106,7 @@ function normalizeInput(input) {
|
|
|
106
106
|
const snapshot = input.contextSnapshotRef === undefined
|
|
107
107
|
? undefined
|
|
108
108
|
: validateContextSnapshotRef(input.contextSnapshotRef);
|
|
109
|
-
const deltaRefIds =
|
|
109
|
+
const deltaRefIds = normalizedUniqueText(input.deltaRefIds, "Turn input delta ref");
|
|
110
110
|
if (deltaRefIds.length > TURN_INPUT_MAX_DELTAS) {
|
|
111
111
|
throw new Error(`Turn input supports at most ${TURN_INPUT_MAX_DELTAS} delta refs.`);
|
|
112
112
|
}
|
|
@@ -175,7 +175,8 @@ function normalizeSubject(subject) {
|
|
|
175
175
|
"workItemId",
|
|
176
176
|
"reviewRoundId",
|
|
177
177
|
"executionGroupId",
|
|
178
|
-
"executionLaneId"
|
|
178
|
+
"executionLaneId",
|
|
179
|
+
"sourceExecutionGroupId"
|
|
179
180
|
];
|
|
180
181
|
if (Object.keys(normalized).some((key) => !allowed.includes(key))) {
|
|
181
182
|
throw new Error("Turn input subject contains an unknown field.");
|
|
@@ -183,9 +184,15 @@ function normalizeSubject(subject) {
|
|
|
183
184
|
if ((normalized.executionGroupId === undefined) !== (normalized.executionLaneId === undefined)) {
|
|
184
185
|
throw new Error("Turn input execution lineage is incomplete.");
|
|
185
186
|
}
|
|
187
|
+
if (normalized.sourceExecutionGroupId !== undefined
|
|
188
|
+
&& ((normalized.workItemId === undefined && normalized.reviewRoundId === undefined)
|
|
189
|
+
|| normalized.executionGroupId !== undefined)) {
|
|
190
|
+
throw new Error("Turn input source ExecutionGroup requires a WorkItem or Review main subject.");
|
|
191
|
+
}
|
|
186
192
|
if (normalized.taskId === undefined
|
|
187
193
|
&& (normalized.workItemId !== undefined || normalized.reviewRoundId !== undefined
|
|
188
|
-
|| normalized.executionGroupId !== undefined
|
|
194
|
+
|| normalized.executionGroupId !== undefined
|
|
195
|
+
|| normalized.sourceExecutionGroupId !== undefined)) {
|
|
189
196
|
throw new Error("Turn input child subject requires a Task id.");
|
|
190
197
|
}
|
|
191
198
|
return Object.freeze(normalized);
|
|
@@ -28,17 +28,24 @@ export function buildTaskWakeEnvelope(reader, request) {
|
|
|
28
28
|
}
|
|
29
29
|
const fromTime = Date.parse(request.fromCursor);
|
|
30
30
|
const events = reader.listEvents(request.taskId);
|
|
31
|
+
const deltaEvents = events.filter((record) => Date.parse(record.createdAt) > fromTime);
|
|
32
|
+
const turns = operationalTaskRecords(reader.listTurns(request.taskId), events, "turn");
|
|
33
|
+
const referencedTurnIds = referencedWakeTurnIds(turns, events, deltaEvents);
|
|
34
|
+
const changedTurnIds = new Set([
|
|
35
|
+
...turns
|
|
36
|
+
.filter((record) => Date.parse(record.createdAt) > fromTime)
|
|
37
|
+
.map(({ id }) => id),
|
|
38
|
+
...referencedTurnIds
|
|
39
|
+
]);
|
|
31
40
|
const counts = {
|
|
32
|
-
events:
|
|
33
|
-
.filter((record) => Date.parse(record.createdAt) > fromTime).length,
|
|
41
|
+
events: deltaEvents.length,
|
|
34
42
|
messages: operationalTaskRecords(reader.listMessages(request.taskId), events, "message")
|
|
35
43
|
.filter((record) => Date.parse(record.createdAt) > fromTime).length,
|
|
36
|
-
turns:
|
|
37
|
-
.filter((record) => Date.parse(record.createdAt) > fromTime).length
|
|
44
|
+
turns: changedTurnIds.size
|
|
38
45
|
};
|
|
39
46
|
const activeReviews = reader.listReviewRounds(request.taskId).filter((round) => ((round.scope ?? "work-item") === "task"
|
|
40
47
|
&& (round.status === "pending" || round.status === "running")));
|
|
41
|
-
const
|
|
48
|
+
const renderReviewOrientation = (limit) => activeReviews.slice(0, limit).map((round) => {
|
|
42
49
|
const projects = round.taskCandidate?.projects ?? [];
|
|
43
50
|
const heads = projects.slice(0, 2).map(({ projectId, commit }) => (`${projectId}@${commit.slice(0, 12)}`)).join("+");
|
|
44
51
|
return `${round.id}/${round.reviewerRoleName}`
|
|
@@ -47,36 +54,91 @@ export function buildTaskWakeEnvelope(reader, request) {
|
|
|
47
54
|
+ `@${heads || round.reviewBaseCommit.slice(0, 12)}`
|
|
48
55
|
+ `${projects.length > 2 ? `+${projects.length - 2}` : ""}`;
|
|
49
56
|
}).join(", ");
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
const render = (reasonLimit, resultTurnLimit, reviewLimit) => {
|
|
58
|
+
const reviewOrientation = renderReviewOrientation(reviewLimit);
|
|
59
|
+
return [
|
|
60
|
+
`Wake: ${request.wakeId} — delta since ${request.fromCursor}`,
|
|
61
|
+
` Reasons: ${renderReasons(request.reasons, reasonLimit)}`,
|
|
62
|
+
` Changed: ${counts.events} events, ${counts.messages} messages, ${counts.turns} Turns`
|
|
63
|
+
+ ` → yui task wake show ${request.taskId} ${request.wakeId}`,
|
|
64
|
+
` Result Turns: ${renderResultTurns(request.taskId, referencedTurnIds, resultTurnLimit)}`,
|
|
65
|
+
` Active Task Reviews: ${activeReviews.length === 0
|
|
66
|
+
? "none"
|
|
67
|
+
: reviewLimit === 0
|
|
68
|
+
? `${activeReviews.length} → yui task context ${request.taskId}`
|
|
69
|
+
: `${reviewOrientation}${activeReviews.length > reviewLimit
|
|
70
|
+
? `, … (+${activeReviews.length - reviewLimit})`
|
|
71
|
+
: ""}`}`,
|
|
72
|
+
`Full context: yui task context ${request.taskId}`
|
|
73
|
+
].join("\n");
|
|
74
|
+
};
|
|
75
|
+
let reasonLimit = Math.min(REASON_DISPLAY_LIMIT, request.reasons.length);
|
|
76
|
+
let resultTurnLimit = Math.min(4, referencedTurnIds.length);
|
|
77
|
+
let reviewLimit = Math.min(3, activeReviews.length);
|
|
78
|
+
let body = render(reasonLimit, resultTurnLimit, reviewLimit);
|
|
79
|
+
while (byteLength(body) + 1 > WAKE_ENVELOPE_HARD_BYTES) {
|
|
80
|
+
if (resultTurnLimit > 0)
|
|
81
|
+
resultTurnLimit -= 1;
|
|
82
|
+
else if (reasonLimit > 0)
|
|
83
|
+
reasonLimit -= 1;
|
|
84
|
+
else if (reviewLimit > 0)
|
|
85
|
+
reviewLimit -= 1;
|
|
86
|
+
else
|
|
87
|
+
break;
|
|
88
|
+
body = render(reasonLimit, resultTurnLimit, reviewLimit);
|
|
65
89
|
}
|
|
90
|
+
if (byteLength(body) + 1 > WAKE_ENVELOPE_HARD_BYTES) {
|
|
91
|
+
body = fitUtf8([
|
|
92
|
+
`Wake: ${request.wakeId}`,
|
|
93
|
+
`Inspect: yui task wake show ${request.taskId} ${request.wakeId}`,
|
|
94
|
+
`Full context: yui task context ${request.taskId}`
|
|
95
|
+
].join("\n"), WAKE_ENVELOPE_HARD_BYTES - 1);
|
|
96
|
+
}
|
|
97
|
+
const totalBytes = byteLength(body) + 1;
|
|
66
98
|
return Object.freeze({
|
|
67
99
|
taskId: request.taskId,
|
|
68
100
|
wakeId: request.wakeId,
|
|
69
101
|
fromCursor: request.fromCursor,
|
|
102
|
+
referencedTurnIds: Object.freeze(referencedTurnIds),
|
|
70
103
|
totalBytes,
|
|
71
104
|
text: `${body}\n`
|
|
72
105
|
});
|
|
73
106
|
}
|
|
74
|
-
function
|
|
75
|
-
const
|
|
107
|
+
export function referencedWakeTurnIds(turns, allEvents, terminalEvents) {
|
|
108
|
+
const turnsById = new Map(operationalTaskRecords(turns, allEvents, "turn").map((turn) => [turn.id, turn]));
|
|
109
|
+
return [...new Set(terminalEvents.flatMap((event) => {
|
|
110
|
+
if (!["turn.completed", "turn.failed", "turn.cancelled"].includes(event.type))
|
|
111
|
+
return [];
|
|
112
|
+
const turnId = event.payload.turnId;
|
|
113
|
+
return turnId !== undefined && turnsById.has(turnId) ? [turnId] : [];
|
|
114
|
+
}))];
|
|
115
|
+
}
|
|
116
|
+
function renderReasons(reasons, limit) {
|
|
117
|
+
if (limit === 0)
|
|
118
|
+
return `${reasons.length} reason tags`;
|
|
119
|
+
const selected = reasons.slice(0, limit);
|
|
76
120
|
const elided = reasons.length - selected.length;
|
|
77
121
|
const rendered = selected.map(renderWakeReason).join(", ");
|
|
78
122
|
return elided === 0 ? rendered : `${rendered}, … (+${elided} more)`;
|
|
79
123
|
}
|
|
124
|
+
function renderResultTurns(taskId, turnIds, limit) {
|
|
125
|
+
if (turnIds.length === 0)
|
|
126
|
+
return "none";
|
|
127
|
+
if (limit === 0)
|
|
128
|
+
return `${turnIds.length} → inspect the wake delta`;
|
|
129
|
+
return `${turnIds.slice(0, limit)
|
|
130
|
+
.map((turnId) => `${turnId} → yui task turn show ${taskId}/${turnId}`)
|
|
131
|
+
.join(", ")}${turnIds.length > limit ? `, … (+${turnIds.length - limit})` : ""}`;
|
|
132
|
+
}
|
|
133
|
+
function fitUtf8(value, maxBytes) {
|
|
134
|
+
let fitted = "";
|
|
135
|
+
for (const character of value) {
|
|
136
|
+
if (byteLength(fitted) + byteLength(character) > maxBytes)
|
|
137
|
+
break;
|
|
138
|
+
fitted += character;
|
|
139
|
+
}
|
|
140
|
+
return fitted;
|
|
141
|
+
}
|
|
80
142
|
function byteLength(value) {
|
|
81
143
|
return Buffer.byteLength(value, "utf8");
|
|
82
144
|
}
|
|
@@ -102,7 +102,7 @@ export class AgentRuntimeObserver {
|
|
|
102
102
|
? tokenObservationIdentity("baseline", fence, source.sourceId, baselineKey)
|
|
103
103
|
: usageObservationIdentity(fence, source.sourceId, latestOccurrence);
|
|
104
104
|
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
105
|
-
schemaVersion:
|
|
105
|
+
schemaVersion: 4,
|
|
106
106
|
eventId: identity.eventId,
|
|
107
107
|
semanticKey: identity.semanticKey,
|
|
108
108
|
kind: "activity.observed",
|
|
@@ -137,7 +137,7 @@ export class AgentRuntimeObserver {
|
|
|
137
137
|
const health = JSON.stringify([sample.status, sample.detail ?? null]);
|
|
138
138
|
if (state.health !== health) {
|
|
139
139
|
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
140
|
-
schemaVersion:
|
|
140
|
+
schemaVersion: 4,
|
|
141
141
|
eventId: observationId("health", fence, source.sourceId, health),
|
|
142
142
|
semanticKey: observationId("health", fence, source.sourceId, health),
|
|
143
143
|
kind: "observer.health",
|
|
@@ -183,7 +183,7 @@ export class AgentRuntimeObserver {
|
|
|
183
183
|
usages.forEach((occurrence, usageIndex) => {
|
|
184
184
|
const identity = usageObservationIdentity(fence, source.sourceId, occurrence);
|
|
185
185
|
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
186
|
-
schemaVersion:
|
|
186
|
+
schemaVersion: 4,
|
|
187
187
|
eventId: identity.eventId,
|
|
188
188
|
semanticKey: identity.semanticKey,
|
|
189
189
|
kind: "activity.observed",
|
|
@@ -213,7 +213,7 @@ export class AgentRuntimeObserver {
|
|
|
213
213
|
});
|
|
214
214
|
if (activityChanged && state.cursor !== undefined) {
|
|
215
215
|
this.inbox.enqueueObservation(createRuntimeObservation({
|
|
216
|
-
schemaVersion:
|
|
216
|
+
schemaVersion: 4,
|
|
217
217
|
eventId: observationId("activity", fence, source.sourceId, sample.activityId),
|
|
218
218
|
semanticKey: observationId("activity", fence, source.sourceId, sample.activityId),
|
|
219
219
|
kind: "activity.observed",
|
|
@@ -297,7 +297,7 @@ export class AgentRuntimeObserver {
|
|
|
297
297
|
continue;
|
|
298
298
|
}
|
|
299
299
|
const fence = accepted.fence;
|
|
300
|
-
const generation = taskObservations.filter((observation) => (
|
|
300
|
+
const generation = taskObservations.filter((observation) => (sessionRuntimeGenerationFenceMatches(fence, observation.fence)
|
|
301
301
|
&& observation.payload.sourceId === source.sourceId));
|
|
302
302
|
const persistedUsage = generation.filter((observation) => (observation.kind === "activity.observed"
|
|
303
303
|
&& observation.payload.usage !== undefined)).at(-1);
|
|
@@ -316,8 +316,7 @@ export class AgentRuntimeObserver {
|
|
|
316
316
|
fence.turnId,
|
|
317
317
|
fence.agentId,
|
|
318
318
|
fence.driverId,
|
|
319
|
-
fence.
|
|
320
|
-
fence.sessionGenerationId,
|
|
319
|
+
fence.runtimeGenerationId,
|
|
321
320
|
fence.nativeSessionId,
|
|
322
321
|
source.sourceId
|
|
323
322
|
]),
|
|
@@ -463,21 +462,19 @@ function tokenObservationId(kind, fence, sourceId, value) {
|
|
|
463
462
|
fence.roleName,
|
|
464
463
|
fence.agentId,
|
|
465
464
|
fence.driverId,
|
|
466
|
-
fence.
|
|
467
|
-
fence.sessionGenerationId,
|
|
465
|
+
fence.runtimeGenerationId,
|
|
468
466
|
fence.nativeSessionId ?? null,
|
|
469
467
|
sourceId,
|
|
470
468
|
value
|
|
471
469
|
])).digest("hex")}`;
|
|
472
470
|
}
|
|
473
|
-
function
|
|
471
|
+
function sessionRuntimeGenerationFenceMatches(expected, actual) {
|
|
474
472
|
for (const field of [
|
|
475
473
|
"taskId",
|
|
476
474
|
"roleName",
|
|
477
475
|
"agentId",
|
|
478
476
|
"driverId",
|
|
479
|
-
"
|
|
480
|
-
"sessionGenerationId",
|
|
477
|
+
"runtimeGenerationId",
|
|
481
478
|
"nativeSessionId"
|
|
482
479
|
]) {
|
|
483
480
|
if (expected[field] !== actual[field])
|
|
@@ -88,7 +88,7 @@ export async function ensureFileTaskController(home, options = {}) {
|
|
|
88
88
|
}
|
|
89
89
|
/** Start and await readiness for a captured Controller identity. */
|
|
90
90
|
export async function ensureFileTaskControllerIdentity(home, identity, options = {}) {
|
|
91
|
-
|
|
91
|
+
assertExpectedControllerRuntimeGenerationIdentity(identity);
|
|
92
92
|
const status = await ensureFileTaskController(home, {
|
|
93
93
|
...options,
|
|
94
94
|
expectedVersion: identity.version
|
|
@@ -102,36 +102,36 @@ export async function ensureFileTaskControllerIdentity(home, identity, options =
|
|
|
102
102
|
const actual = await call(home, "controller.identity", {}, {
|
|
103
103
|
timeoutMs: options.requestTimeoutMs
|
|
104
104
|
});
|
|
105
|
-
|
|
105
|
+
assertControllerRuntimeGenerationIdentity(actual, identity);
|
|
106
106
|
return status;
|
|
107
107
|
}
|
|
108
|
-
function
|
|
108
|
+
function assertExpectedControllerRuntimeGenerationIdentity(identity) {
|
|
109
109
|
if (typeof identity.executablePath !== "string"
|
|
110
110
|
|| identity.executablePath.length === 0
|
|
111
111
|
|| !Array.isArray(identity.args)
|
|
112
112
|
|| identity.args.some((arg) => typeof arg !== "string")
|
|
113
113
|
|| typeof identity.version !== "string"
|
|
114
114
|
|| identity.version.length === 0) {
|
|
115
|
-
throw new Error("Expected Controller
|
|
115
|
+
throw new Error("Expected Controller runtime generation identity is malformed; refusing to start or accept a Controller.");
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
function
|
|
118
|
+
function assertControllerRuntimeGenerationIdentity(actual, expected) {
|
|
119
119
|
if (!isJsonRecord(actual)) {
|
|
120
|
-
throw new Error("Authenticated Controller
|
|
120
|
+
throw new Error("Authenticated Controller runtime generation identity is malformed; refusing to accept readiness.");
|
|
121
121
|
}
|
|
122
122
|
const actualArgs = actual.args;
|
|
123
123
|
if (typeof actual.executablePath !== "string"
|
|
124
124
|
|| !Array.isArray(actualArgs)
|
|
125
125
|
|| actualArgs.some((arg) => typeof arg !== "string")
|
|
126
126
|
|| typeof actual.version !== "string") {
|
|
127
|
-
throw new Error("Authenticated Controller
|
|
127
|
+
throw new Error("Authenticated Controller runtime generation identity is malformed; refusing to accept readiness.");
|
|
128
128
|
}
|
|
129
129
|
const argsMatch = actualArgs.length === expected.args.length
|
|
130
130
|
&& actualArgs.every((arg, index) => arg === expected.args[index]);
|
|
131
131
|
if (actual.executablePath !== expected.executablePath
|
|
132
132
|
|| !argsMatch
|
|
133
133
|
|| actual.version !== expected.version) {
|
|
134
|
-
throw new Error("Authenticated Controller
|
|
134
|
+
throw new Error("Authenticated Controller runtime generation identity does not match the captured executable, argv, and version; refusing readiness.");
|
|
135
135
|
}
|
|
136
136
|
}
|
|
137
137
|
function assertCompatibleControllerStatus(status, expectedVersion) {
|
|
@@ -479,7 +479,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
479
479
|
agentId: input.agentId,
|
|
480
480
|
adapterId: input.adapterId,
|
|
481
481
|
nativeSessionId: input.nativeSessionId,
|
|
482
|
-
...(input.
|
|
482
|
+
...(input.runtimeGenerationId === undefined ? {} : { runtimeGenerationId: input.runtimeGenerationId }),
|
|
483
483
|
sessionUpdatedAt: input.sessionUpdatedAt
|
|
484
484
|
});
|
|
485
485
|
if (target === null) {
|
|
@@ -563,7 +563,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
563
563
|
if (blockers.length === 0)
|
|
564
564
|
return;
|
|
565
565
|
throw new WorkspaceCleanupBlockedError("physical-resource-live", `task:${taskId}`, true, `Task archive blocked: ${blockers.length} owned physical resource(s) still live: `
|
|
566
|
-
+ blockers.map((entry) => (`${entry.owner.roleName}/${entry.
|
|
566
|
+
+ blockers.map((entry) => (`${entry.owner.roleName}/${entry.runtimeGenerationId}`
|
|
567
567
|
+ ` (pid ${entry.physical?.alive === true ? entry.physical.pid : "?"})`)).join("; "));
|
|
568
568
|
}
|
|
569
569
|
async stopGlobalRoleSession(roleName) {
|
|
@@ -622,10 +622,10 @@ export class FileTaskWorkflowRuntime {
|
|
|
622
622
|
if (termination.outcome !== "stop-confirmed") {
|
|
623
623
|
throw new Error(`Role runtime cleanup could not prove physical exit: ${runtimeOwnerLabel(candidate.owner)}; `
|
|
624
624
|
+ termination.remaining
|
|
625
|
-
.map(({ record, detail }) => `${record.
|
|
625
|
+
.map(({ record, detail }) => `${record.runtimeGenerationId}: ${detail}`)
|
|
626
626
|
.join("; "));
|
|
627
627
|
}
|
|
628
|
-
if (candidate.owner.scope === "task" && candidate.
|
|
628
|
+
if (candidate.owner.scope === "task" && candidate.runtimeGenerationId !== undefined) {
|
|
629
629
|
const isolation = new FileTaskRuntimeIsolation({
|
|
630
630
|
runtimeRoot: `${this.home}.task-runtimes`,
|
|
631
631
|
controlPlane: {
|
|
@@ -637,7 +637,7 @@ export class FileTaskWorkflowRuntime {
|
|
|
637
637
|
});
|
|
638
638
|
isolation.cleanupTaskLaunch({
|
|
639
639
|
taskId: candidate.owner.taskId,
|
|
640
|
-
|
|
640
|
+
runtimeGenerationId: candidate.runtimeGenerationId,
|
|
641
641
|
reason: this.store.getTask(candidate.owner.taskId)?.status === "completed"
|
|
642
642
|
? "completion"
|
|
643
643
|
: "interruption"
|
|
@@ -97,7 +97,7 @@ export async function runControllerSchedulerPass(store, delivery, now, workspace
|
|
|
97
97
|
: [])));
|
|
98
98
|
// Phase-one Leader Turns did not exist at the pass's liveness boundary.
|
|
99
99
|
// Keep every newly claimed Turn outside destructive absence decisions until
|
|
100
|
-
// a later pass can observe its stable provider/
|
|
100
|
+
// a later pass can observe its stable provider/runtime generation.
|
|
101
101
|
for (const result of initialWakeupResults) {
|
|
102
102
|
if (result.turnId !== undefined
|
|
103
103
|
&& store.getActiveTurn(result.taskId, "leader")?.id === result.turnId) {
|
|
@@ -220,7 +220,7 @@ async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost
|
|
|
220
220
|
}
|
|
221
221
|
if (target.kind === "role-runtime") {
|
|
222
222
|
const session = store.getRoleSession(target.taskId, target.roleName);
|
|
223
|
-
const
|
|
223
|
+
const reservedRuntimeGenerationId = isRuntimeLaunchReservation(mailbox.processing)
|
|
224
224
|
? mailbox.processing.batchId
|
|
225
225
|
: undefined;
|
|
226
226
|
// A failed fresh-Conversation launch can leave two exact resource
|
|
@@ -228,19 +228,19 @@ async function processSelectedRoleRuntimeCleanups(store, delivery, lifecycleHost
|
|
|
228
228
|
// replacement reservation. They are not competing authorities. The
|
|
229
229
|
// owner-wide stop has already proven physical zero, so clean both
|
|
230
230
|
// exact launch roots idempotently before settling the mailbox.
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
session?.
|
|
234
|
-
].filter((
|
|
231
|
+
const runtimeGenerationIds = new Set([
|
|
232
|
+
reservedRuntimeGenerationId,
|
|
233
|
+
session?.runtimeGenerationId
|
|
234
|
+
].filter((runtimeGenerationId) => runtimeGenerationId !== undefined));
|
|
235
235
|
if (lifecycleHost.cleanupTaskLaunch !== undefined) {
|
|
236
236
|
const task = store.getTask(target.taskId);
|
|
237
237
|
const reason = task?.status === "completed"
|
|
238
238
|
? "completion"
|
|
239
239
|
: "interruption";
|
|
240
|
-
for (const
|
|
240
|
+
for (const runtimeGenerationId of runtimeGenerationIds) {
|
|
241
241
|
lifecycleHost.cleanupTaskLaunch({
|
|
242
242
|
taskId: target.taskId,
|
|
243
|
-
|
|
243
|
+
runtimeGenerationId,
|
|
244
244
|
reason
|
|
245
245
|
});
|
|
246
246
|
}
|
|
@@ -338,7 +338,7 @@ async function reconcileDormantRuntimeOwners(store, delivery, lifecycleHost, sco
|
|
|
338
338
|
for (const candidate of candidates) {
|
|
339
339
|
if (byOwner.get(runtimeOwnerIdentity(candidate.owner))?.state
|
|
340
340
|
=== "stopped") {
|
|
341
|
-
if (candidate.
|
|
341
|
+
if (candidate.runtimeGenerationId !== undefined) {
|
|
342
342
|
// The exact launch-owned resources must settle through the durable
|
|
343
343
|
// cleanup lane before its Host fact is detached. The resumable Session
|
|
344
344
|
// remains active; the candidate is the CAS fence against a concurrent
|
|
@@ -1600,9 +1600,9 @@ export class FileTaskController {
|
|
|
1600
1600
|
taskId: failure.taskId,
|
|
1601
1601
|
roleName: failure.roleName,
|
|
1602
1602
|
turnId: failure.turnId,
|
|
1603
|
-
...(failure.
|
|
1603
|
+
...(failure.runtimeGenerationId === undefined
|
|
1604
1604
|
? {}
|
|
1605
|
-
: {
|
|
1605
|
+
: { runtimeGenerationId: failure.runtimeGenerationId })
|
|
1606
1606
|
});
|
|
1607
1607
|
if (!this.#stopped)
|
|
1608
1608
|
this.signal(key);
|