@zq-silk/yui 0.15.11 → 0.15.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +8 -4
- package/ARCHITECTURE.zh-CN.md +5 -2
- package/README.md +13 -5
- package/dist/agentRun/agentRun.js +3 -0
- package/dist/cli/commandCatalog.js +38 -10
- package/dist/cli/interactionPolicy.js +4 -0
- package/dist/cli/managedDiagnostics.js +1 -1
- package/dist/cli.js +46 -15
- package/dist/commands/executionAuditCommands.js +10 -0
- package/dist/commands/globalRoleCommands.js +27 -2
- package/dist/commands/projectCommands.js +44 -15
- package/dist/commands/taskCommands.js +95 -33
- package/dist/commands/taskIntegrationCommands.js +3 -1
- package/dist/commands/taskOverviewCommand.js +4 -3
- package/dist/commands/taskPublicationAdoptCommand.js +127 -0
- package/dist/commands/taskPublicationCommands.js +11 -2
- package/dist/commands/taskPublicationVerifyCommand.js +23 -39
- package/dist/commands/taskRemoteDeliveryCommand.js +18 -7
- package/dist/context/runContextPack.js +3 -0
- package/dist/context/taskCatalog.js +187 -0
- package/dist/context/taskContext.js +17 -4
- package/dist/controller/controller.js +11 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +80 -19
- package/dist/controller/globalInputDelivery.js +13 -0
- package/dist/controller/providerRetryAdmission.js +100 -0
- package/dist/controller/providerRetryDelivery.js +218 -0
- package/dist/controller/runtime.js +36 -1
- package/dist/integration/gitIntegrationService.js +19 -6
- package/dist/lifecycle/exactRunTerminalization.js +4 -1
- package/dist/message/globalProviderRetry.js +15 -0
- package/dist/observability/executionAudit.js +19 -0
- package/dist/repository/gitWorkspace.js +371 -105
- package/dist/repository/projectMaintenanceLock.js +75 -18
- package/dist/repository/taskWorkspaceCoordinator.js +71 -124
- package/dist/repository/taskWorkspacePreparer.js +86 -24
- package/dist/repository/workspaceCleanupInspection.js +187 -0
- package/dist/runtime/agentError.js +5 -3
- package/dist/runtime/agentHost.js +28 -11
- package/dist/runtime/builtinAgentErrorMappers.js +91 -0
- package/dist/runtime/codexAppServerRuntime.js +34 -3
- package/dist/runtime/providerControl.js +5 -1
- package/dist/runtime/providerRetry.js +198 -0
- package/dist/runtime/providerRuntimeIdentity.js +28 -2
- package/dist/runtime/sessionTokenMetrics.js +15 -5
- package/dist/runtime/structuredProviderHost.js +6 -2
- package/dist/runtime/taskUsageMetrics.js +275 -0
- package/dist/scheduler/activeRoleRunDelivery.js +12 -0
- package/dist/scheduler/leaderWakeupProcessor.js +5 -0
- package/dist/scheduler/taskExecutionProjection.js +26 -5
- package/dist/scheduler/taskObservabilityProjection.js +6 -44
- package/dist/storage/sqliteSchema.js +31 -0
- package/dist/storage/sqliteStore.js +17 -0
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskCatalog.js +123 -0
- package/dist/storage/taskStore.js +2 -0
- package/dist/task/archiveDiagnostics.js +1 -0
- package/dist/task/archivePreflight.js +124 -0
- package/dist/task/publicationAdoption.js +56 -0
- package/dist/task/publicationReference.js +10 -0
- package/dist/task/remoteDelivery.js +31 -16
- package/dist/web/assets/client/app.js +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 +2 -1
- 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 +14 -3
- package/dist/web/webSnapshot.js +12 -3
- 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 +30 -5
- package/docs/architecture/capabilities-and-resources.zh-CN.md +23 -3
- package/docs/managed-turn-and-session-runtime.md +47 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +40 -0
- package/docs/observability/README.md +62 -0
- package/docs/observability/README.zh-CN.md +47 -0
- package/docs/project-refresh.md +77 -0
- package/docs/project-refresh.zh-CN.md +59 -0
- package/docs/provider-retry.md +70 -0
- package/docs/task-delivery.md +133 -13
- package/docs/task-delivery.zh-CN.md +99 -10
- package/docs/task-discovery.md +102 -0
- package/docs/task-discovery.zh-CN.md +86 -0
- package/docs/testing/verification-levels.md +16 -0
- package/docs/testing/verification-levels.zh-CN.md +13 -1
- package/i18n/README.zh-CN.md +13 -7
- package/package.json +1 -1
- package/skills/yui-leader/references/execution.md +3 -2
- package/skills/yui-operator/SKILL.md +13 -2
- 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 +24 -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
|
+
}
|
|
@@ -51,6 +51,18 @@ async function deliverActiveRun(store, delivery, task, role, run, now) {
|
|
|
51
51
|
});
|
|
52
52
|
const sessionSet = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
|
|
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;
|
|
@@ -3,6 +3,7 @@ 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";
|
|
6
7
|
export const LEADER_WAKE_AGGREGATION_MS = 60_000;
|
|
7
8
|
/** Deliver a durable mailbox notification, not an implicit execution assignment.
|
|
8
9
|
* Acceptance settles only its claimed batch; no Agent report is required.
|
|
@@ -34,6 +35,10 @@ export async function processLeaderWakeups(store, delivery, now, selection) {
|
|
|
34
35
|
}
|
|
35
36
|
const sessions = store.getTaskRoleSessionSet?.(task.id, role.name) ?? null;
|
|
36
37
|
const provider = sessions?.providerBinding;
|
|
38
|
+
if (providerRetryPending(provider)) {
|
|
39
|
+
results.push({ ...base, status: "skipped", reason: "not-ready" });
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
37
42
|
if (provider?.authority.owner === "human" || provider?.authority.owner === "unknown") {
|
|
38
43
|
results.push({ ...base, status: "skipped", reason: "busy" });
|
|
39
44
|
continue;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { operationalTaskRecords } from "../task/taskRecordRetirement.js";
|
|
2
2
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
3
|
+
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
3
4
|
import { taskOwnsManagedWorkspace } from "../task/task.js";
|
|
4
5
|
import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
5
6
|
import { mailboxBatches } from "../coordination/workMailbox.js";
|
|
@@ -19,7 +20,8 @@ export function buildTaskExecutionProjection(store, taskId, taskOverride, now =
|
|
|
19
20
|
return null;
|
|
20
21
|
const roles = store.listRoles?.(taskId) ?? [];
|
|
21
22
|
const events = store.listEvents?.(taskId) ?? [];
|
|
22
|
-
const
|
|
23
|
+
const usageRuns = store.listRuns?.(taskId) ?? [];
|
|
24
|
+
const runs = operationalTaskRecords(usageRuns, events, "run");
|
|
23
25
|
const leaderMailbox = store.getWorkMailbox?.({
|
|
24
26
|
kind: "role",
|
|
25
27
|
taskId,
|
|
@@ -35,6 +37,11 @@ export function buildTaskExecutionProjection(store, taskId, taskOverride, now =
|
|
|
35
37
|
task,
|
|
36
38
|
roles,
|
|
37
39
|
runs,
|
|
40
|
+
usageRuns,
|
|
41
|
+
providerRetries: roles.flatMap(role => {
|
|
42
|
+
const retry = providerRetryProjection(store.getTaskRoleSessionSet?.(taskId, role.name)?.providerBinding);
|
|
43
|
+
return retry === null ? [] : [retry];
|
|
44
|
+
}),
|
|
38
45
|
runDelivery: Object.fromEntries(runs.map((run) => {
|
|
39
46
|
const observed = store.getTaskRoleSessionSet?.(taskId, run.roleName)?.providerBinding?.run;
|
|
40
47
|
return [run.id, observed?.runId === run.id ? observed.status : "unobserved"];
|
|
@@ -79,6 +86,7 @@ export function projectTaskExecutionFromFacts(facts) {
|
|
|
79
86
|
return projectTaskExecution({
|
|
80
87
|
...facts,
|
|
81
88
|
runs,
|
|
89
|
+
usageRuns: facts.usageRuns ?? facts.runs,
|
|
82
90
|
executionGroups
|
|
83
91
|
});
|
|
84
92
|
}
|
|
@@ -107,9 +115,10 @@ export function projectTaskExecution(facts) {
|
|
|
107
115
|
});
|
|
108
116
|
});
|
|
109
117
|
const observability = buildTaskObservabilityProjection({
|
|
118
|
+
task,
|
|
110
119
|
workItems,
|
|
111
120
|
executionGroups: observabilityGroups,
|
|
112
|
-
runs,
|
|
121
|
+
runs: facts.usageRuns ?? runs,
|
|
113
122
|
events,
|
|
114
123
|
contextSnapshots: facts.contextSnapshots,
|
|
115
124
|
sessionTokens,
|
|
@@ -206,8 +215,12 @@ export function projectTaskExecution(facts) {
|
|
|
206
215
|
const hasPendingLeaderWork = pendingWakeup !== null
|
|
207
216
|
|| leaderMailbox?.pending !== null;
|
|
208
217
|
const recoveryPending = isRecoveryPending(pendingWakeup, leaderMailbox, leaderFailure);
|
|
209
|
-
const
|
|
210
|
-
|
|
218
|
+
const providerRecoveries = (facts.providerRetries ?? []).filter(retry => retry.status === "waiting" || retry.status === "in-flight");
|
|
219
|
+
const recoveringRunIds = new Set(providerRecoveries.flatMap(retry => [retry.previousRunId, retry.successorRunId].filter((id) => id !== undefined)));
|
|
220
|
+
const failedWork = workItems.some((item) => {
|
|
221
|
+
const last = runs.filter((run) => run.workItemId === item.id).at(-1);
|
|
222
|
+
return item.status === "open" && last?.status === "failed" && !recoveringRunIds.has(last.id);
|
|
223
|
+
});
|
|
211
224
|
const candidateReady = workItems.some((item) => (item.status === "open" && item.currentCandidateId !== undefined));
|
|
212
225
|
const blockedIntegration = integrations.some((attempt) => attempt.status === "blocked");
|
|
213
226
|
const conflictedIntegration = integrations.find((attempt) => attempt.status === "conflicted");
|
|
@@ -258,7 +271,7 @@ export function projectTaskExecution(facts) {
|
|
|
258
271
|
pendingWakeup
|
|
259
272
|
});
|
|
260
273
|
}
|
|
261
|
-
if (laneRecovery !== undefined) {
|
|
274
|
+
if (laneRecovery !== undefined && (laneRecovery.runId === undefined || !recoveringRunIds.has(laneRecovery.runId))) {
|
|
262
275
|
return render({
|
|
263
276
|
task,
|
|
264
277
|
status: laneRecovery.runtimeHealth === "confirmed-dead"
|
|
@@ -306,6 +319,14 @@ export function projectTaskExecution(facts) {
|
|
|
306
319
|
pendingWakeup
|
|
307
320
|
});
|
|
308
321
|
}
|
|
322
|
+
if (providerRecoveries.length > 0 && healthyActiveRuns.length === 0) {
|
|
323
|
+
return render({
|
|
324
|
+
task, status: "recovering", owner: "none", action: "none",
|
|
325
|
+
summary: `${providerRecoveries.length} Provider recovery chain(s) are cooling down or awaiting their exact result; existing work is preserved. Do not dispatch a duplicate.`,
|
|
326
|
+
reason: "provider-recovery", monitoring, failClosed: false,
|
|
327
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
328
|
+
});
|
|
329
|
+
}
|
|
309
330
|
if (recoveryPending) {
|
|
310
331
|
return render({
|
|
311
332
|
task,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectTaskUsageMetrics } from "../runtime/taskUsageMetrics.js";
|
|
1
2
|
/**
|
|
2
3
|
* Build the read-only DAG, execution, cost, and context view consumed by CLI
|
|
3
4
|
* and Web. It deliberately derives every value from existing Task records and
|
|
@@ -8,7 +9,7 @@ export function buildTaskObservabilityProjection(input) {
|
|
|
8
9
|
const dag = projectDag(input.workItems);
|
|
9
10
|
const workItems = input.workItems.map((item) => {
|
|
10
11
|
const groups = item.executionGroups;
|
|
11
|
-
const itemCost =
|
|
12
|
+
const itemCost = projectCost({ ...input, now }, groups, item.id);
|
|
12
13
|
const itemContext = projectContext(groups, input.runs, input.contextSnapshots);
|
|
13
14
|
const producerObservability = projectWorkItemProducerObservability(item, input.runs);
|
|
14
15
|
return Object.freeze({
|
|
@@ -21,7 +22,7 @@ export function buildTaskObservabilityProjection(input) {
|
|
|
21
22
|
resultCount: producerObservability?.resultCount ?? null
|
|
22
23
|
});
|
|
23
24
|
});
|
|
24
|
-
const cost = projectCost(input
|
|
25
|
+
const cost = projectCost({ ...input, now }, input.executionGroups);
|
|
25
26
|
const context = projectContext(input.executionGroups, input.runs, input.contextSnapshots);
|
|
26
27
|
return Object.freeze({
|
|
27
28
|
dag,
|
|
@@ -140,17 +141,13 @@ function dependencyEdgeStatus(id, byId) {
|
|
|
140
141
|
return "active";
|
|
141
142
|
return "active";
|
|
142
143
|
}
|
|
143
|
-
function projectCost(
|
|
144
|
+
function projectCost(input, groups, workItemId) {
|
|
144
145
|
const uniqueGroups = [...new Map(groups.map((group) => [group.id, group])).values()];
|
|
145
146
|
const groupIds = new Set(uniqueGroups.map(({ id }) => id));
|
|
146
|
-
const attempts = runs.filter(({ executionGroupId }) => (executionGroupId !== undefined && groupIds.has(executionGroupId)));
|
|
147
|
+
const attempts = input.runs.filter(({ executionGroupId }) => (executionGroupId !== undefined && groupIds.has(executionGroupId)));
|
|
147
148
|
const laneCount = uniqueGroups.reduce((total, group) => total + group.lanes.length, 0);
|
|
148
149
|
return Object.freeze({
|
|
149
|
-
|
|
150
|
-
toolCalls: 0,
|
|
151
|
-
wallClockSeconds: uniqueGroups.reduce((total, group) => total + groupDurationSeconds(group, now), 0),
|
|
152
|
-
tokensObservable: false,
|
|
153
|
-
toolCallsObservable: false,
|
|
150
|
+
...projectTaskUsageMetrics({ ...input, workItemId }),
|
|
154
151
|
laneCount,
|
|
155
152
|
groupCount: uniqueGroups.length,
|
|
156
153
|
retryCount: Math.max(0, attempts.length - laneCount),
|
|
@@ -158,32 +155,6 @@ function projectCost(groups, runs, _events, now) {
|
|
|
158
155
|
marginalValueStatus: "unavailable"
|
|
159
156
|
});
|
|
160
157
|
}
|
|
161
|
-
function projectWorkItemCost(groups, runs, now) {
|
|
162
|
-
const groupIds = new Set(groups.map(({ id }) => id));
|
|
163
|
-
const attempts = runs.filter(({ executionGroupId }) => (executionGroupId !== undefined && groupIds.has(executionGroupId)));
|
|
164
|
-
const wallClockSeconds = groups.reduce((total, group) => {
|
|
165
|
-
const started = Date.parse(group.createdAt);
|
|
166
|
-
const ended = group.lanes.some(({ disposition }) => disposition === "open")
|
|
167
|
-
? now.getTime()
|
|
168
|
-
: Date.parse(group.updatedAt);
|
|
169
|
-
return total + (Number.isFinite(started) && Number.isFinite(ended)
|
|
170
|
-
? Math.max(0, Math.floor((ended - started) / 1_000))
|
|
171
|
-
: 0);
|
|
172
|
-
}, 0);
|
|
173
|
-
const laneCount = groups.reduce((total, group) => total + group.lanes.length, 0);
|
|
174
|
-
return Object.freeze({
|
|
175
|
-
tokens: 0,
|
|
176
|
-
toolCalls: 0,
|
|
177
|
-
wallClockSeconds,
|
|
178
|
-
tokensObservable: false,
|
|
179
|
-
toolCallsObservable: false,
|
|
180
|
-
laneCount,
|
|
181
|
-
groupCount: groups.length,
|
|
182
|
-
retryCount: Math.max(0, attempts.length - laneCount),
|
|
183
|
-
marginalValuePercent: null,
|
|
184
|
-
marginalValueStatus: "unavailable"
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
158
|
function projectContext(groups, runs, snapshots) {
|
|
188
159
|
const refs = new Map();
|
|
189
160
|
for (const group of groups) {
|
|
@@ -223,12 +194,3 @@ function projectContext(groups, runs, snapshots) {
|
|
|
223
194
|
compressionStatus: "unavailable"
|
|
224
195
|
});
|
|
225
196
|
}
|
|
226
|
-
function groupDurationSeconds(group, now) {
|
|
227
|
-
const started = Date.parse(group.createdAt);
|
|
228
|
-
const ended = group.lanes.some(({ disposition }) => disposition === "open")
|
|
229
|
-
? now.getTime()
|
|
230
|
-
: Date.parse(group.updatedAt);
|
|
231
|
-
return Number.isFinite(started) && Number.isFinite(ended)
|
|
232
|
-
? Math.max(0, Math.floor((ended - started) / 1_000))
|
|
233
|
-
: 0;
|
|
234
|
-
}
|
|
@@ -1264,6 +1264,37 @@ UPDATE review_rounds SET payload = json_set(payload, '$.executionGroup.lanes', j
|
|
|
1264
1264
|
// The data/filesystem transform is the substantive step; the SQL is inert.
|
|
1265
1265
|
sql: COLLAPSE_WORKTREE_LAYOUT_SQL,
|
|
1266
1266
|
migrateData: migrateCollapseWorktreeLayout
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
version: 26,
|
|
1270
|
+
name: "publication-candidate-adoption",
|
|
1271
|
+
introducedIn: "0.15.12",
|
|
1272
|
+
// Add optional Publication.headCommit (the observed PR/MR source head) and
|
|
1273
|
+
// publication.candidate-adopted events binding publicationId, completionEventId,
|
|
1274
|
+
// projectId, acceptedCommit/localCommit, acceptedTree/candidateTree, diffDigest,
|
|
1275
|
+
// acceptance and by, optionally integrationId/integrationDigest. Derived
|
|
1276
|
+
// task.archived mergeCoverage may also be uncovered or unverified.
|
|
1277
|
+
// No stored delivery index and no inferred adoption or verification:
|
|
1278
|
+
// valid old Publication, completion and archive payloads remain byte-identical.
|
|
1279
|
+
sql: "SELECT 1; -- fixed acceptance-to-publication evidence; preserve all historical facts"
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
version: 27,
|
|
1283
|
+
name: "bounded-provider-input-retry",
|
|
1284
|
+
introducedIn: "0.16.0",
|
|
1285
|
+
// Optional owned input/recovery records on the existing Provider binding;
|
|
1286
|
+
// positive Driver transient evidence on StandardAgentError. No historical
|
|
1287
|
+
// failures are scanned, scheduled, reclassified or replayed by upgrading.
|
|
1288
|
+
sql: `
|
|
1289
|
+
-- ProviderTurn.input/retrySupported/failure; ProviderRuntimeBinding.retry/retryHistory/retryDisabled;
|
|
1290
|
+
-- optional error retryable and retryAfterMs facts. Index existing records only.
|
|
1291
|
+
CREATE INDEX idx_task_provider_retry ON role_session_sets(
|
|
1292
|
+
json_extract(payload, '$.providerBinding.retry.status')
|
|
1293
|
+
) WHERE json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight');
|
|
1294
|
+
CREATE INDEX idx_global_provider_retry ON global_role_session_sets(
|
|
1295
|
+
json_extract(payload, '$.providerBinding.retry.status')
|
|
1296
|
+
) WHERE json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight');
|
|
1297
|
+
`
|
|
1267
1298
|
}
|
|
1268
1299
|
]);
|
|
1269
1300
|
for (let index = 0; index < MIGRATIONS.length; index += 1) {
|
|
@@ -38,6 +38,7 @@ import { existsSync, mkdirSync } from "node:fs";
|
|
|
38
38
|
import { join } from "node:path";
|
|
39
39
|
import { isDeepStrictEqual } from "node:util";
|
|
40
40
|
import Database from "better-sqlite3";
|
|
41
|
+
import { queryTaskCatalog } from "./taskCatalog.js";
|
|
41
42
|
import { validatePluginValidation } from "../plugins/pluginPackage.js";
|
|
42
43
|
import { validatePluginIntent, validatePluginIntentFailure } from "../plugins/pluginIntent.js";
|
|
43
44
|
import { validateLocalResource, validateEnvironmentPreparation } from "../resources/projectResource.js";
|
|
@@ -921,6 +922,15 @@ export class SqliteTaskStore {
|
|
|
921
922
|
const tasks = this.#listPayload("task_records", "1=1", []);
|
|
922
923
|
return this.#sortById(tasks, (task) => task.id);
|
|
923
924
|
}
|
|
925
|
+
queryTaskCatalog(query) {
|
|
926
|
+
return queryTaskCatalog(this.#db, query);
|
|
927
|
+
}
|
|
928
|
+
listTaskChoices() {
|
|
929
|
+
const rows = this.#db.prepare(`SELECT c.task_id AS id, c.status,
|
|
930
|
+
json_extract(r.payload, '$.title') AS title
|
|
931
|
+
FROM tasks_catalog c JOIN task_records r ON r.task_id = c.task_id`).all();
|
|
932
|
+
return this.#sortById(rows, task => task.id);
|
|
933
|
+
}
|
|
924
934
|
getTask(id) {
|
|
925
935
|
return this.#getPayload("task_records", "task_id = ?", [id]);
|
|
926
936
|
}
|
|
@@ -1267,6 +1277,13 @@ export class SqliteTaskStore {
|
|
|
1267
1277
|
listRoleSessionSets(taskId) {
|
|
1268
1278
|
return this.#sortById(this.#listPayload("role_session_sets", "task_id = ?", [taskId]), (set) => set.owner.roleName);
|
|
1269
1279
|
}
|
|
1280
|
+
listProviderRetrySessions() {
|
|
1281
|
+
const pending = "json_extract(payload, '$.providerBinding.retry.status') IN ('waiting', 'in-flight')";
|
|
1282
|
+
return [
|
|
1283
|
+
...this.#listPayload("role_session_sets", pending, []),
|
|
1284
|
+
...this.#listPayload("global_role_session_sets", pending, [])
|
|
1285
|
+
];
|
|
1286
|
+
}
|
|
1270
1287
|
listRuntimeSessionCandidates(query = {}) {
|
|
1271
1288
|
const taskIds = query.taskIds === undefined
|
|
1272
1289
|
? undefined
|
package/dist/storage/storeRpc.js
CHANGED