@zq-silk/yui 0.11.3 → 0.12.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 +23 -4
- package/README.md +36 -13
- package/dist/cli/commandCatalog.js +2 -4
- package/dist/cli.js +105 -43
- package/dist/commands/configCommands.js +12 -45
- package/dist/commands/executionAuditCommands.js +1 -1
- package/dist/commands/jobCommands.js +4 -10
- package/dist/commands/taskCommands.js +154 -171
- package/dist/commands/taskCompletionGate.js +29 -54
- package/dist/commands/taskContextCommand.js +55 -11
- package/dist/commands/taskInputCommands.js +8 -5
- package/dist/commands/taskNextActionCommand.js +56 -6
- package/dist/commands/taskOverviewCommand.js +17 -31
- package/dist/commands/taskRoleRuntimeStatus.js +24 -0
- package/dist/commands/taskWorkspaceCommands.js +5 -2
- package/dist/config/configCatalog.js +2 -2
- package/dist/config/yuiConfig.js +3 -7
- package/dist/context/wakeNotification.js +20 -5
- package/dist/controller/agentRuntimeObserver.js +247 -51
- package/dist/controller/clientRuntime.js +1 -1
- package/dist/controller/controller.js +8 -12
- package/dist/controller/fileSchedulerStoreAdapter.js +146 -198
- package/dist/controller/runtime.js +3 -3
- package/dist/controller/runtimeEventInbox.js +17 -7
- package/dist/controller/runtimeEventProcessor.js +12 -19
- package/dist/controller/runtimeObservationHook.js +45 -0
- package/dist/execution/resourceBroker.js +5 -4
- package/dist/executor/agentExecutor.js +4 -4
- package/dist/executor/effectiveLaunch.js +8 -48
- package/dist/executor/fileRoleLaunchPlanner.js +36 -16
- package/dist/executor/workspacePreflightClassification.js +23 -2
- package/dist/interaction/operatorPresentation.js +33 -89
- package/dist/lifecycle/exactRunTerminalization.js +1 -2
- package/dist/lifecycle/taskRoleSessionReset.js +5 -13
- package/dist/observability/orchestrationMetrics.js +8 -3
- package/dist/profile/agentProfile.js +1 -1
- package/dist/release/cliHomeReleaseFence.js +123 -0
- package/dist/repository/taskWorkspacePreparer.js +176 -60
- package/dist/resources/sqliteResourceRegistry.js +1 -1
- package/dist/review/deltaRecheck.js +12 -51
- package/dist/review/reviewAcceptance.js +26 -0
- package/dist/review/reviewConfig.js +0 -31
- package/dist/review/reviewDecision.js +113 -0
- package/dist/review/reviewOutcomeClassifier.js +1 -1
- package/dist/review/reviewRound.js +1 -1
- package/dist/review/reviewerAvailability.js +69 -0
- package/dist/runtime/agentDriverObservation.js +24 -10
- package/dist/runtime/builtinAgentDrivers.js +4 -3
- package/dist/runtime/builtinTranscriptObserver.js +301 -64
- package/dist/runtime/builtinTranscriptUsage.js +9 -7
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtimeObservation.js +13 -0
- package/dist/runtime/runtimeProjection.js +16 -25
- package/dist/runtime/sessionTokenMetrics.js +181 -0
- package/dist/runtime/structuredProviderHost.js +7 -1
- package/dist/scheduler/activeRoleRunDelivery.js +4 -4
- package/dist/scheduler/activeTaskProgress.js +7 -6
- package/dist/scheduler/leaderWakeupProcessor.js +3 -5
- package/dist/scheduler/operatorEvent.js +34 -0
- package/dist/scheduler/operatorInputNotificationProcessor.js +54 -94
- package/dist/scheduler/roleRunStall.js +0 -14
- package/dist/scheduler/taskExecutionProjection.js +97 -76
- package/dist/scheduler/taskObservabilityProjection.js +10 -11
- package/dist/storage/migration/productionRegistry.js +379 -0
- package/dist/storage/sqliteSchema.js +18 -8
- package/dist/storage/sqliteStore.js +24 -20
- package/dist/storage/storeRpc.js +0 -1
- package/dist/storage/taskStore.js +15 -30
- package/dist/storage/upgrade/recordVersions.js +3 -2
- package/dist/storage/upgrade/sqliteMigrationTarget.js +30 -8
- package/dist/storage/upgrade/sqliteRecordMigrationTarget.js +23 -11
- package/dist/storage/upgrade/sqliteStateMigration.js +66 -8
- package/dist/task/completionReadiness.js +10 -6
- package/dist/task/nextAction.js +82 -24
- package/dist/telemetry/sqliteTelemetryStore.js +1 -1
- package/dist/web/assets/client/components.js +13 -3
- package/dist/web/assets/client/i18n.js +6 -2
- package/dist/web/webSnapshot.js +4 -0
- package/i18n/README.zh-CN.md +2 -2
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +53 -16
- package/skills/yui-operator/SKILL.md +9 -0
- package/skills/yui-reviewer/SKILL.md +28 -7
- package/dist/context/sessionContextBudget.js +0 -71
- package/dist/lifecycle/contextBudgetRollover.js +0 -81
- package/dist/scheduler/operatorNotification.js +0 -59
|
@@ -12,13 +12,13 @@ export function codexTranscriptUsage(transcript) {
|
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
14
|
export function codexTranscriptUsageReport(transcript) {
|
|
15
|
-
// Codex token_count events are cumulative
|
|
16
|
-
// input is the delta between consecutive snapshots; the first
|
|
17
|
-
//
|
|
15
|
+
// Codex token_count events are cumulative Session snapshots. Per-request
|
|
16
|
+
// input is the non-negative delta between consecutive snapshots; the first
|
|
17
|
+
// snapshot only establishes the generation baseline.
|
|
18
18
|
let latestInput = 0;
|
|
19
19
|
let latestCached = 0;
|
|
20
20
|
let latestOutput = 0;
|
|
21
|
-
let previous
|
|
21
|
+
let previous;
|
|
22
22
|
let peak = 0;
|
|
23
23
|
let requests = 0;
|
|
24
24
|
let seen = false;
|
|
@@ -39,9 +39,11 @@ export function codexTranscriptUsageReport(transcript) {
|
|
|
39
39
|
requests += 1;
|
|
40
40
|
seen = true;
|
|
41
41
|
const total = inputTokens + cachedInputTokens;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if (previous !== undefined) {
|
|
43
|
+
const delta = Math.max(0, total - previous);
|
|
44
|
+
if (delta > peak)
|
|
45
|
+
peak = delta;
|
|
46
|
+
}
|
|
45
47
|
previous = total;
|
|
46
48
|
latestInput = inputTokens;
|
|
47
49
|
latestCached = cachedInputTokens;
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { createPromptEnvelope } from "./promptEnvelope.js";
|
|
2
2
|
export { codexTranscriptUsage, codexTranscriptUsageReport, claudeTranscriptUsage, claudeTranscriptUsageReport } from "./builtinTranscriptUsage.js";
|
|
3
|
+
export { projectSessionTokenMetrics, resolveSessionTokenIdentity, unobservedSessionTokenMetrics } from "./sessionTokenMetrics.js";
|
|
3
4
|
export { createRuntimeBinding } from "./runtimeBinding.js";
|
|
4
5
|
export { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
5
6
|
export { createSessionLaunchRequest } from "./sessionLaunchRequest.js";
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { requireDriverId } from "./agentDriver.js";
|
|
3
3
|
export const RUNTIME_OBSERVATION_TASK_EVENT = "runtime.observation";
|
|
4
|
+
/**
|
|
5
|
+
* Numeric usage and value-less partial request boundaries are both durable
|
|
6
|
+
* token-projection evidence. The latter deliberately carries no activityId so
|
|
7
|
+
* it cannot be mistaken for lifecycle progress.
|
|
8
|
+
*/
|
|
9
|
+
export function isRuntimeTokenEvidence(observation) {
|
|
10
|
+
return observation.kind === "activity.observed"
|
|
11
|
+
&& (observation.payload.usage !== undefined
|
|
12
|
+
|| (observation.payload.sourceId !== undefined
|
|
13
|
+
&& observation.payload.activityId === undefined
|
|
14
|
+
&& (observation.payload.observationQuality === "partial"
|
|
15
|
+
|| observation.payload.observationQuality === "unavailable")));
|
|
16
|
+
}
|
|
4
17
|
const KINDS = [
|
|
5
18
|
"host.observed",
|
|
6
19
|
"session.started",
|
|
@@ -158,16 +158,23 @@ export function projectRuntimeObservation(current, raw) {
|
|
|
158
158
|
}
|
|
159
159
|
case "activity.observed": {
|
|
160
160
|
const usage = event.payload.usage;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return withActivity(next(current, {
|
|
161
|
+
const lifecycleActivity = event.payload.activityId !== undefined
|
|
162
|
+
&& usage === undefined;
|
|
163
|
+
const projected = next(current, {
|
|
165
164
|
...(usage === undefined ? {} : { usage: Object.freeze({ ...usage }) }),
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
165
|
+
...(lifecycleActivity ? {
|
|
166
|
+
session: current.turn === "waiting" ? "active" : current.session,
|
|
167
|
+
turn: current.turn === "waiting" ? "accepted" : current.turn,
|
|
168
|
+
waitingReason: undefined,
|
|
169
|
+
waitId: undefined
|
|
170
|
+
} : {})
|
|
171
|
+
});
|
|
172
|
+
// Token counters are read-only measurements. Only a separate explicit
|
|
173
|
+
// activity observation may advance runtime health or waiting state;
|
|
174
|
+
// usage activityId values identify requests only for token projection.
|
|
175
|
+
if (!lifecycleActivity)
|
|
176
|
+
return projected;
|
|
177
|
+
return withActivity(projected, event.payload.activity, at);
|
|
171
178
|
}
|
|
172
179
|
case "observer.health":
|
|
173
180
|
return next(current, {
|
|
@@ -488,22 +495,6 @@ function next(current, patch) {
|
|
|
488
495
|
runActivity
|
|
489
496
|
});
|
|
490
497
|
}
|
|
491
|
-
function usageAdvanced(previous, current) {
|
|
492
|
-
if (current.semantics === "remaining-context")
|
|
493
|
-
return false;
|
|
494
|
-
if (current.semantics === "request-context")
|
|
495
|
-
return usageTotal(current) > 0;
|
|
496
|
-
// A first cumulative snapshot may contain history from a resumed native
|
|
497
|
-
// Session. It establishes a baseline but cannot prove current progress.
|
|
498
|
-
if (previous === undefined || previous.semantics !== "cumulative-session")
|
|
499
|
-
return false;
|
|
500
|
-
return usageTotal(current) > usageTotal(previous);
|
|
501
|
-
}
|
|
502
|
-
function usageTotal(usage) {
|
|
503
|
-
// cachedInputTokens and reasoningTokens are breakdowns of the normalized
|
|
504
|
-
// input/output totals, not additional usage.
|
|
505
|
-
return usage.inputTokens + usage.outputTokens;
|
|
506
|
-
}
|
|
507
498
|
function dominantOperation(operations) {
|
|
508
499
|
const kinds = Object.values(operations).map(({ kind }) => kind);
|
|
509
500
|
if (kinds.includes("subagent"))
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { builtinDriverIdForAdapter } from "./builtinAgentDrivers.js";
|
|
2
|
+
import { isRuntimeTokenEvidence, runtimeObservationFromTaskEvent } from "./runtimeObservation.js";
|
|
3
|
+
const UNOBSERVED = Object.freeze({ status: "unobserved" });
|
|
4
|
+
export function resolveSessionTokenIdentity(input) {
|
|
5
|
+
if (input?.taskId === undefined
|
|
6
|
+
|| input.roleName === undefined
|
|
7
|
+
|| input.agentId === undefined
|
|
8
|
+
|| input.adapterId === undefined
|
|
9
|
+
|| input.launchId === undefined
|
|
10
|
+
|| input.nativeSessionId === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
let driverId;
|
|
13
|
+
try {
|
|
14
|
+
driverId = builtinDriverIdForAdapter(input.adapterId);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return Object.freeze({
|
|
20
|
+
taskId: input.taskId,
|
|
21
|
+
roleName: input.roleName,
|
|
22
|
+
agentId: input.agentId,
|
|
23
|
+
driverId,
|
|
24
|
+
launchId: input.launchId,
|
|
25
|
+
nativeSessionId: input.nativeSessionId,
|
|
26
|
+
sessionGenerationId: input.launchId
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read-only projection over canonical runtime observations. Token values never
|
|
31
|
+
* decide lifecycle, health, scheduling, resource admission, or workflow state.
|
|
32
|
+
*/
|
|
33
|
+
export function projectSessionTokenMetrics(events, identity) {
|
|
34
|
+
if (identity === null)
|
|
35
|
+
return unobservedSessionTokenMetrics();
|
|
36
|
+
const observations = events
|
|
37
|
+
.map(runtimeObservationFromTaskEvent)
|
|
38
|
+
.filter((observation) => (observation !== null
|
|
39
|
+
&& isRuntimeTokenEvidence(observation)
|
|
40
|
+
&& matchesSessionGeneration(observation, identity)))
|
|
41
|
+
.sort(compareObservations);
|
|
42
|
+
const incompleteBoundaries = observations.filter(({ payload }) => (payload.usage === undefined));
|
|
43
|
+
const request = observations.filter(({ payload }) => (payload.usage?.semantics === "request-context"));
|
|
44
|
+
const cumulative = observations.filter(({ payload }) => (payload.usage?.semantics === "cumulative-session"));
|
|
45
|
+
// A generation cannot switch counter semantics without an explicit fact
|
|
46
|
+
// explaining how the two streams overlap. Remaining-context is capacity,
|
|
47
|
+
// not consumption, and is intentionally absent from both metrics.
|
|
48
|
+
if (request.length > 0 && cumulative.length > 0) {
|
|
49
|
+
return unobservedSessionTokenMetrics(identity);
|
|
50
|
+
}
|
|
51
|
+
if (request.length > 0) {
|
|
52
|
+
return projectRequestSnapshots(identity, request, incompleteBoundaries.length > 0);
|
|
53
|
+
}
|
|
54
|
+
if (cumulative.length > 0) {
|
|
55
|
+
return projectCumulativeSnapshots(identity, cumulative, incompleteBoundaries.length > 0);
|
|
56
|
+
}
|
|
57
|
+
return unobservedSessionTokenMetrics(identity);
|
|
58
|
+
}
|
|
59
|
+
export function unobservedSessionTokenMetrics(identity = null) {
|
|
60
|
+
return Object.freeze({
|
|
61
|
+
identity: identity === null ? null : Object.freeze({ ...identity }),
|
|
62
|
+
cumulativeTotal: UNOBSERVED,
|
|
63
|
+
maximumRequestInput: UNOBSERVED
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function projectRequestSnapshots(identity, observations, hasIncompleteBoundary) {
|
|
67
|
+
if (hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
|
|
68
|
+
|| payload.observationQuality === "unavailable")))
|
|
69
|
+
return unobservedSessionTokenMetrics(identity);
|
|
70
|
+
const requests = new Map();
|
|
71
|
+
for (const observation of observations) {
|
|
72
|
+
const usage = observation.payload.usage;
|
|
73
|
+
const requestId = observation.payload.activityId ?? observation.semanticKey;
|
|
74
|
+
// Structured providers may publish multiple snapshots for one stable
|
|
75
|
+
// request identity. The latest snapshot supersedes the earlier delivery;
|
|
76
|
+
// replays with the same identity never become a second request.
|
|
77
|
+
requests.set(requestId, usage);
|
|
78
|
+
}
|
|
79
|
+
const values = [...requests.values()];
|
|
80
|
+
const inputTokens = safeSum(values.map((usage) => usage.inputTokens));
|
|
81
|
+
const outputTokens = safeSum(values.map((usage) => usage.outputTokens));
|
|
82
|
+
if (inputTokens === null || outputTokens === null) {
|
|
83
|
+
return unobservedSessionTokenMetrics(identity);
|
|
84
|
+
}
|
|
85
|
+
const totalTokens = safeAdd(inputTokens, outputTokens);
|
|
86
|
+
if (totalTokens === null)
|
|
87
|
+
return unobservedSessionTokenMetrics(identity);
|
|
88
|
+
const cachedInputTokens = values.every((usage) => usage.cachedInputTokens !== undefined)
|
|
89
|
+
? safeSum(values.map((usage) => usage.cachedInputTokens))
|
|
90
|
+
: null;
|
|
91
|
+
const reasoningTokens = values.every((usage) => usage.reasoningTokens !== undefined)
|
|
92
|
+
? safeSum(values.map((usage) => usage.reasoningTokens))
|
|
93
|
+
: null;
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
identity: Object.freeze({ ...identity }),
|
|
96
|
+
cumulativeTotal: Object.freeze({
|
|
97
|
+
status: "observed",
|
|
98
|
+
totalTokens,
|
|
99
|
+
inputTokens,
|
|
100
|
+
outputTokens,
|
|
101
|
+
...(cachedInputTokens === null ? {} : { cachedInputTokens }),
|
|
102
|
+
...(reasoningTokens === null ? {} : { reasoningTokens })
|
|
103
|
+
}),
|
|
104
|
+
maximumRequestInput: Object.freeze({
|
|
105
|
+
status: "observed",
|
|
106
|
+
inputTokens: Math.max(...values.map((usage) => usage.inputTokens))
|
|
107
|
+
})
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function projectCumulativeSnapshots(identity, observations, hasIncompleteBoundary) {
|
|
111
|
+
const values = observations.map(({ payload }) => payload.usage);
|
|
112
|
+
const requestBoundaryIncomplete = hasIncompleteBoundary || observations.some(({ payload }) => (payload.observationQuality === "partial"
|
|
113
|
+
|| payload.observationQuality === "unavailable"));
|
|
114
|
+
let counterRollback = false;
|
|
115
|
+
let maximumRequestInput = null;
|
|
116
|
+
for (let index = 1; index < values.length; index += 1) {
|
|
117
|
+
const previous = values[index - 1];
|
|
118
|
+
const current = values[index];
|
|
119
|
+
if (current.inputTokens < previous.inputTokens
|
|
120
|
+
|| current.outputTokens < previous.outputTokens)
|
|
121
|
+
counterRollback = true;
|
|
122
|
+
const requestInput = Math.max(0, current.inputTokens - previous.inputTokens);
|
|
123
|
+
maximumRequestInput = maximumRequestInput === null
|
|
124
|
+
? requestInput
|
|
125
|
+
: Math.max(maximumRequestInput, requestInput);
|
|
126
|
+
}
|
|
127
|
+
const latest = values.at(-1);
|
|
128
|
+
const totalTokens = safeAdd(latest.inputTokens, latest.outputTokens);
|
|
129
|
+
const cumulativeTotal = counterRollback || totalTokens === null
|
|
130
|
+
? UNOBSERVED
|
|
131
|
+
: Object.freeze({
|
|
132
|
+
status: "observed",
|
|
133
|
+
totalTokens,
|
|
134
|
+
inputTokens: latest.inputTokens,
|
|
135
|
+
outputTokens: latest.outputTokens,
|
|
136
|
+
...(latest.cachedInputTokens === undefined
|
|
137
|
+
? {}
|
|
138
|
+
: { cachedInputTokens: latest.cachedInputTokens }),
|
|
139
|
+
...(latest.reasoningTokens === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: { reasoningTokens: latest.reasoningTokens })
|
|
142
|
+
});
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
identity: Object.freeze({ ...identity }),
|
|
145
|
+
cumulativeTotal,
|
|
146
|
+
maximumRequestInput: counterRollback
|
|
147
|
+
|| requestBoundaryIncomplete
|
|
148
|
+
|| maximumRequestInput === null
|
|
149
|
+
? UNOBSERVED
|
|
150
|
+
: Object.freeze({ status: "observed", inputTokens: maximumRequestInput })
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
function matchesSessionGeneration(observation, identity) {
|
|
154
|
+
const fence = observation.fence;
|
|
155
|
+
return fence.taskId === identity.taskId
|
|
156
|
+
&& fence.roleName === identity.roleName
|
|
157
|
+
&& fence.agentId === identity.agentId
|
|
158
|
+
&& fence.driverId === identity.driverId
|
|
159
|
+
&& fence.launchId === identity.launchId
|
|
160
|
+
&& fence.nativeSessionId === identity.nativeSessionId
|
|
161
|
+
&& fence.sessionGenerationId === identity.sessionGenerationId;
|
|
162
|
+
}
|
|
163
|
+
function compareObservations(left, right) {
|
|
164
|
+
return left.receivedAt.localeCompare(right.receivedAt)
|
|
165
|
+
|| (left.sequence ?? -1) - (right.sequence ?? -1)
|
|
166
|
+
|| (left.ordinal ?? -1) - (right.ordinal ?? -1)
|
|
167
|
+
|| left.eventId.localeCompare(right.eventId);
|
|
168
|
+
}
|
|
169
|
+
function safeSum(values) {
|
|
170
|
+
let total = 0;
|
|
171
|
+
for (const value of values) {
|
|
172
|
+
total = safeAdd(total, value) ?? Number.NaN;
|
|
173
|
+
if (!Number.isSafeInteger(total))
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return total;
|
|
177
|
+
}
|
|
178
|
+
function safeAdd(left, right) {
|
|
179
|
+
const total = left + right;
|
|
180
|
+
return Number.isSafeInteger(total) && total >= 0 ? total : null;
|
|
181
|
+
}
|
|
@@ -391,7 +391,13 @@ class ClaudeStructuredProviderSession {
|
|
|
391
391
|
waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input for a different native Session.", waiter.attemptId));
|
|
392
392
|
return;
|
|
393
393
|
}
|
|
394
|
-
const nativeTurnId =
|
|
394
|
+
const nativeTurnId = optionalId(message.uuid);
|
|
395
|
+
if (nativeTurnId === undefined) {
|
|
396
|
+
clearTimeout(waiter.timer);
|
|
397
|
+
this.#waiter = undefined;
|
|
398
|
+
waiter.reject(new ProviderDeliveryUnknownError("Claude replayed input without a native message identity.", waiter.attemptId));
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
395
401
|
this.#activeTurnId = nativeTurnId;
|
|
396
402
|
clearTimeout(waiter.timer);
|
|
397
403
|
this.#waiter = undefined;
|
|
@@ -4,7 +4,7 @@ import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
|
4
4
|
import { prefixYuiTitleInput } from "../run/runIdentity.js";
|
|
5
5
|
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
6
6
|
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
7
|
-
import { effectiveLaunchSnapshotsCompatible,
|
|
7
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
|
|
8
8
|
import { RuntimeLaunchError } from "../runtime/ports.js";
|
|
9
9
|
import { RuntimeLaunchFailure } from "../runtime/launchDiagnostics.js";
|
|
10
10
|
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
@@ -881,7 +881,7 @@ function requireResumeSession(role, effective, session) {
|
|
|
881
881
|
if (session === null || !hasText(session.nativeSessionId)) {
|
|
882
882
|
throw new Error(`Role resume has no fixed native session: ${role.taskId}/${role.name}.`);
|
|
883
883
|
}
|
|
884
|
-
if (!
|
|
884
|
+
if (!effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)) {
|
|
885
885
|
throw new Error(`Role resume effective snapshot drifted: ${role.taskId}/${role.name}.`);
|
|
886
886
|
}
|
|
887
887
|
return session.nativeSessionId;
|
|
@@ -948,13 +948,13 @@ function validateRoleSession(role, effective, existing, mode, session) {
|
|
|
948
948
|
throw new Error(`Ready Role session identity changed: ${role.taskId}/${role.name}.`);
|
|
949
949
|
}
|
|
950
950
|
const compatible = mode === "resume"
|
|
951
|
-
?
|
|
951
|
+
? effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)
|
|
952
952
|
: effectiveLaunchSnapshotsCompatible(session.effective, effective);
|
|
953
953
|
if (!compatible) {
|
|
954
954
|
throw new Error(`Ready Role session effective snapshot changed: ${role.taskId}/${role.name}.`);
|
|
955
955
|
}
|
|
956
956
|
if (existing?.nativeSessionId !== undefined
|
|
957
|
-
&&
|
|
957
|
+
&& effectiveLaunchSnapshotsCompatibleForTaskSession(existing.effective, effective)
|
|
958
958
|
&& session.nativeSessionId !== existing.nativeSessionId) {
|
|
959
959
|
throw new Error(`Ready Role session changed the fixed native session id: ${role.taskId}/${role.name}.`);
|
|
960
960
|
}
|
|
@@ -35,8 +35,7 @@ export function repairOrphanedActiveTasks(store, now, selection) {
|
|
|
35
35
|
runs: activeRuns,
|
|
36
36
|
pendingWakeup: store.getPendingWakeup(task.id),
|
|
37
37
|
leaderMailbox,
|
|
38
|
-
leaderFailure: store.getLeaderFailure(task.id)
|
|
39
|
-
operatorNotification: store.getOperatorNotification(task.id)
|
|
38
|
+
leaderFailure: store.getLeaderFailure(task.id)
|
|
40
39
|
});
|
|
41
40
|
const queuedAlongsideActiveSibling = hasDispatchableQueuedResourceLane(store, task.id)
|
|
42
41
|
&& !activeRuns.some(({ roleName }) => roleName === "leader")
|
|
@@ -45,7 +44,6 @@ export function repairOrphanedActiveTasks(store, now, selection) {
|
|
|
45
44
|
if ((queuedAlongsideActiveSibling ? hasLeaderInFlightTurn : hasInFlightTurn)
|
|
46
45
|
|| store.hasOpenInputRequest(task.id)
|
|
47
46
|
|| store.getLeaderFailure(task.id) !== null
|
|
48
|
-
|| store.getOperatorNotification(task.id) !== null
|
|
49
47
|
|| (projection.status !== "needs-leader-action" && !queuedAlongsideActiveSibling)
|
|
50
48
|
|| hasUnclaimedLeaderWork(store, task.id, leaderMailbox)) {
|
|
51
49
|
continue;
|
|
@@ -121,8 +119,11 @@ function findLastLeaderRun(store, taskId) {
|
|
|
121
119
|
};
|
|
122
120
|
}
|
|
123
121
|
function hasUnclaimedLeaderWork(store, taskId, leaderMailbox) {
|
|
122
|
+
if (store.getPendingWakeup(taskId) !== null)
|
|
123
|
+
return true;
|
|
124
124
|
if (leaderMailbox === null)
|
|
125
|
-
return
|
|
126
|
-
return leaderMailbox
|
|
127
|
-
&& leaderMailbox.pending !== null
|
|
125
|
+
return false;
|
|
126
|
+
return leaderMailbox.processing === null
|
|
127
|
+
&& (leaderMailbox.pending.normal !== null
|
|
128
|
+
|| leaderMailbox.pending.userCorrection !== null);
|
|
128
129
|
}
|
|
@@ -3,10 +3,9 @@ import { createRunAssignment, serializeRunBootstrapEnvelope } from "../context/r
|
|
|
3
3
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
4
4
|
import { prefixYuiTitleInput } from "../run/runIdentity.js";
|
|
5
5
|
import { resolveTaskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
6
|
-
import { effectiveLaunchSnapshotsCompatible,
|
|
6
|
+
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskSession } from "../executor/effectiveLaunch.js";
|
|
7
7
|
import { hasRuntimeLifecycleWork, RuntimeLifecycleBusyError, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
8
8
|
import { recordLeaderFailure } from "./leaderFailure.js";
|
|
9
|
-
import { createLeaderRecoveryNotification } from "./operatorNotification.js";
|
|
10
9
|
import { isSchedulerTaskWorkspaceReady } from "./ports.js";
|
|
11
10
|
import { RuntimeLaunchError } from "../runtime/ports.js";
|
|
12
11
|
export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
@@ -114,7 +113,7 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
114
113
|
const compatibleSession = existingSession !== null
|
|
115
114
|
&& (reopening
|
|
116
115
|
? effectiveLaunchSnapshotsCompatible(existingSession.effective, role.effective)
|
|
117
|
-
:
|
|
116
|
+
: effectiveLaunchSnapshotsCompatibleForTaskSession(existingSession.effective, role.effective));
|
|
118
117
|
const reopenIdentityDrift = reopening
|
|
119
118
|
&& hasNativeSession(existingSession)
|
|
120
119
|
&& !compatibleSession;
|
|
@@ -504,7 +503,6 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
504
503
|
session: effectiveSession,
|
|
505
504
|
claimed: { run, wakeup },
|
|
506
505
|
failure: recordLeaderFailure(task.id, effectiveSession?.nativeSessionId ?? "(unregistered)", message, now, store.getLeaderFailure(task.id)),
|
|
507
|
-
notification: createLeaderRecoveryNotification(task.id, message, now, store.getOperatorNotification(task.id)),
|
|
508
506
|
now
|
|
509
507
|
});
|
|
510
508
|
if (failureResult === "state-changed") {
|
|
@@ -535,7 +533,7 @@ function validateReadySession(role, effective, existing, mode, session) {
|
|
|
535
533
|
throw new Error(`Ready session belongs to another Agent: ${session.agentId}.`);
|
|
536
534
|
}
|
|
537
535
|
const compatible = mode === "resume"
|
|
538
|
-
?
|
|
536
|
+
? effectiveLaunchSnapshotsCompatibleForTaskSession(session.effective, effective)
|
|
539
537
|
: effectiveLaunchSnapshotsCompatible(session.effective, effective);
|
|
540
538
|
if (!compatible) {
|
|
541
539
|
throw new Error("Ready Leader session effective snapshot changed.");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { enqueueWork } from "../coordination/workMailboxQueue.js";
|
|
2
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
3
|
+
export const LEADER_ATTENTION_REQUIRED_EVENT = "leader.attention-required";
|
|
4
|
+
/** The sole Task-event boundary into the global Operator mailbox. */
|
|
5
|
+
export function enqueueOperatorEvent(store, event, reason, now) {
|
|
6
|
+
enqueueWork(store, { kind: "operator" }, reason, now, [
|
|
7
|
+
{ type: "event", taskId: event.taskId, id: event.id }
|
|
8
|
+
], {
|
|
9
|
+
source: "task-event",
|
|
10
|
+
dedupeKey: `operator-event:${event.taskId}:${event.id}`,
|
|
11
|
+
deliveryMode: "followup",
|
|
12
|
+
lane: "normal"
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
/** Routes one Role fact to its supervisor without exposing Provider details to Operator code. */
|
|
16
|
+
export function routeRoleEvent(store, event, roleName, reason, now) {
|
|
17
|
+
if (roleName === "leader") {
|
|
18
|
+
enqueueOperatorEvent(store, event, reason, now);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
enqueueWork(store, { kind: "role", taskId: event.taskId, roleName: "leader" }, reason, now, [{ type: "event", taskId: event.taskId, id: event.id }], {
|
|
22
|
+
source: "task-event",
|
|
23
|
+
dedupeKey: `leader-event:${event.taskId}:${event.id}`,
|
|
24
|
+
deliveryMode: "followup",
|
|
25
|
+
lane: "normal"
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/** Records the semantic boundary used when a Leader can no longer continue. */
|
|
29
|
+
export function recordLeaderAttentionRequired(store, input) {
|
|
30
|
+
const event = createTaskEvent(store.nextEventId(input.taskId), input.taskId, LEADER_ATTENTION_REQUIRED_EVENT, { ...(input.payload ?? {}), reason: input.reason }, input.now);
|
|
31
|
+
store.saveEvent(input.taskId, event);
|
|
32
|
+
enqueueOperatorEvent(store, event, "leader-attention-required", input.now);
|
|
33
|
+
return event;
|
|
34
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createOperatorBatchPresentation } from "../interaction/operatorPresentation.js";
|
|
2
2
|
import { mailboxHasWork, nextPendingBatch } from "../coordination/workMailbox.js";
|
|
3
3
|
export async function processOperatorInputNotifications(store, delivery, selection, now = new Date()) {
|
|
4
4
|
if (selection !== undefined && !selection.full && !selection.operator)
|
|
@@ -19,18 +19,18 @@ export async function processOperatorInputNotifications(store, delivery, selecti
|
|
|
19
19
|
if (claim.status === "empty")
|
|
20
20
|
return [];
|
|
21
21
|
const processing = claim.processing;
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
const attentions = deduplicateAttention(processing.batch.refs.flatMap((ref) => {
|
|
23
|
+
if (ref.type === "input") {
|
|
24
|
+
const request = store.getInputRequest(ref.taskId, ref.id);
|
|
25
|
+
return request !== null && request.status === "open"
|
|
26
|
+
? [{ kind: "input", request }]
|
|
27
|
+
: [];
|
|
28
|
+
}
|
|
29
|
+
if (ref.type !== "event" || store.listEvents === undefined)
|
|
30
|
+
return [];
|
|
31
|
+
const event = store.listEvents(ref.taskId).find(({ id }) => id === ref.id);
|
|
32
|
+
return event === undefined ? [] : [{ kind: "event", event }];
|
|
33
|
+
}));
|
|
34
34
|
if (attentions.length === 0) {
|
|
35
35
|
store.completeWorkMailbox(targetMailbox, processing.batchId);
|
|
36
36
|
return [];
|
|
@@ -41,58 +41,35 @@ export async function processOperatorInputNotifications(store, delivery, selecti
|
|
|
41
41
|
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
42
42
|
return attentions.map((attention) => skipped(attention, reason));
|
|
43
43
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
if (outcome === "unavailable") {
|
|
54
|
-
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
55
|
-
results.push(skipped(attention, "operator-unavailable"));
|
|
56
|
-
results.push(...attentions.slice(index + 1).map((pending) => (skipped(pending, "operator-unavailable"))));
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
else if (outcome === "not-ready") {
|
|
60
|
-
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
61
|
-
results.push(skipped(attention, "operator-not-ready"));
|
|
62
|
-
results.push(...attentions.slice(index + 1).map((pending) => (skipped(pending, "operator-not-ready"))));
|
|
63
|
-
break;
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
results.push({
|
|
67
|
-
...attentionIdentity(attention),
|
|
68
|
-
status: outcome
|
|
69
|
-
});
|
|
70
|
-
if (outcome === "sent") {
|
|
71
|
-
if (index === attentions.length - 1) {
|
|
72
|
-
store.completeWorkMailbox(targetMailbox, processing.batchId);
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
76
|
-
}
|
|
77
|
-
results.push(...attentions.slice(index + 1).map((pending) => (skipped(pending, "operator-not-ready"))));
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
44
|
+
try {
|
|
45
|
+
const presentation = createOperatorBatchPresentation(processing.batchId, attentions.map(toPresentationItem));
|
|
46
|
+
const outcome = await delivery.notifyOperatorInputOnce({
|
|
47
|
+
...target,
|
|
48
|
+
receiptId: presentation.receiptId,
|
|
49
|
+
text: presentation.text
|
|
50
|
+
});
|
|
51
|
+
if (outcome === "unavailable" || outcome === "not-ready") {
|
|
83
52
|
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
status: "failed",
|
|
87
|
-
error: error instanceof Error ? error.message : String(error)
|
|
88
|
-
});
|
|
89
|
-
break;
|
|
53
|
+
const reason = outcome === "unavailable" ? "operator-unavailable" : "operator-not-ready";
|
|
54
|
+
return attentions.map((attention) => skipped(attention, reason));
|
|
90
55
|
}
|
|
91
|
-
|
|
92
|
-
|
|
56
|
+
if (outcome === "sent")
|
|
57
|
+
store.markOperatorTurnStarted(now);
|
|
93
58
|
store.completeWorkMailbox(targetMailbox, processing.batchId);
|
|
59
|
+
return attentions.map((attention) => ({
|
|
60
|
+
...attentionIdentity(attention),
|
|
61
|
+
status: outcome
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
store.releaseWorkMailbox(targetMailbox, processing.batchId);
|
|
66
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
67
|
+
return attentions.map((attention) => ({
|
|
68
|
+
...attentionIdentity(attention),
|
|
69
|
+
status: "failed",
|
|
70
|
+
error: detail
|
|
71
|
+
}));
|
|
94
72
|
}
|
|
95
|
-
return results;
|
|
96
73
|
}
|
|
97
74
|
function skipped(attention, reason) {
|
|
98
75
|
return {
|
|
@@ -102,45 +79,28 @@ function skipped(attention, reason) {
|
|
|
102
79
|
};
|
|
103
80
|
}
|
|
104
81
|
function attentionIdentity(attention) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
stallTaskId: attention.notification.taskId,
|
|
117
|
-
taskId: attention.notification.taskId
|
|
82
|
+
return attention.kind === "input"
|
|
83
|
+
? {
|
|
84
|
+
sourceKind: "input",
|
|
85
|
+
sourceId: attention.request.id,
|
|
86
|
+
taskId: attention.request.taskId
|
|
87
|
+
}
|
|
88
|
+
: {
|
|
89
|
+
sourceKind: "event",
|
|
90
|
+
sourceId: attention.event.id,
|
|
91
|
+
taskId: attention.event.taskId
|
|
118
92
|
};
|
|
119
|
-
}
|
|
120
|
-
return {
|
|
121
|
-
terminalTaskId: attention.notification.taskId,
|
|
122
|
-
taskId: attention.notification.taskId
|
|
123
|
-
};
|
|
124
93
|
}
|
|
125
|
-
function
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (attention.notification.type === "leader-recovery-failed") {
|
|
130
|
-
return createLeaderRecoveryOperatorPresentation(attention.notification);
|
|
131
|
-
}
|
|
132
|
-
return attention.notification.type === "leader-stalled"
|
|
133
|
-
? createLeaderStallOperatorPresentation(attention.notification)
|
|
134
|
-
: createTaskTerminalOperatorPresentation(attention.notification);
|
|
94
|
+
function toPresentationItem(attention) {
|
|
95
|
+
return attention.kind === "input"
|
|
96
|
+
? { kind: "input-request", request: attention.request }
|
|
97
|
+
: { kind: "task-event", event: attention.event };
|
|
135
98
|
}
|
|
136
99
|
function deduplicateAttention(attentions) {
|
|
137
100
|
const seen = new Set();
|
|
138
101
|
return attentions.filter((attention) => {
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
: attention.notification.type === "leader-stalled"
|
|
142
|
-
? `stall:${attention.notification.taskId}:${attention.notification.runId}:${attention.notification.progressAt}`
|
|
143
|
-
: `recovery:${attention.notification.taskId}:${attention.notification.createdAt}`;
|
|
102
|
+
const identity = attentionIdentity(attention);
|
|
103
|
+
const key = `${identity.sourceKind}:${identity.taskId}:${identity.sourceId}`;
|
|
144
104
|
if (seen.has(key))
|
|
145
105
|
return false;
|
|
146
106
|
seen.add(key);
|