@zq-silk/yui 0.10.0 → 0.11.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/README.md +53 -0
- package/dist/cli/commandCatalog.js +28 -7
- package/dist/cli.js +50 -47
- package/dist/commands/projectCommands.js +69 -6
- package/dist/commands/taskCommands.js +639 -89
- package/dist/commands/taskContextCommand.js +78 -27
- package/dist/commands/taskNextActionCommand.js +13 -2
- package/dist/commands/taskOverviewCommand.js +21 -5
- package/dist/commands/taskUpstreamCommands.js +136 -0
- package/dist/context/runContextPack.js +184 -17
- package/dist/controller/agentRuntimeObserver.js +31 -20
- package/dist/controller/fileSchedulerStoreAdapter.js +7 -13
- package/dist/execution/candidateConvergence.js +623 -0
- package/dist/execution/executionGroup.js +255 -13
- package/dist/execution/executionHealth.js +324 -0
- package/dist/execution/resourceBroker.js +425 -0
- package/dist/executor/fileRoleLaunchPlanner.js +6 -9
- package/dist/executor/workspacePreflightClassification.js +117 -0
- package/dist/lifecycle/exactRunTerminalization.js +13 -2
- package/dist/lifecycle/taskRoleSessionReset.js +4 -2
- package/dist/repository/taskBaseFreshness.js +26 -1
- package/dist/repository/taskWorkspacePreparer.js +17 -1
- package/dist/review/reviewRound.js +27 -6
- package/dist/run/agentRun.js +2 -2
- package/dist/run/recoveryProjection.js +15 -0
- package/dist/runtime/runtimeContinuationProjection.js +7 -0
- package/dist/runtime/tmuxAdapters.js +8 -2
- package/dist/scheduler/actionability.js +169 -3
- package/dist/scheduler/activeTaskProgress.js +15 -10
- package/dist/scheduler/leaderWakeupProcessor.js +17 -1
- package/dist/scheduler/taskExecutionProjection.js +105 -8
- package/dist/scheduler/taskObservabilityProjection.js +282 -0
- package/dist/storage/migration/productionRegistry.js +14 -0
- package/dist/storage/sqliteStore.js +12 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/task/completionReadiness.js +1 -1
- package/dist/task/nextAction.js +314 -2
- package/dist/web/assets/client/components.js +116 -0
- package/dist/web/assets/client/i18n.js +66 -0
- package/dist/web/assets/client/view.js +15 -0
- package/dist/web/assets/styles/cards.js +23 -0
- package/dist/web/assets/styles/responsive.js +2 -0
- package/dist/web/webSnapshot.js +8 -2
- package/dist/workItem/workItem.js +262 -5
- package/i18n/README.zh-CN.md +42 -0
- package/package.json +1 -1
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { isRoleRunStalled, latestStallProgressAt } from "../scheduler/roleRunStall.js";
|
|
2
|
+
import { validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
3
|
+
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
4
|
+
import { projectRuntimeTaskEvents } from "../runtime/runtimeProjection.js";
|
|
5
|
+
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "../runtime/runtimeHealthPolicy.js";
|
|
6
|
+
import { runOwnsBlockingProviderContinuation } from "../runtime/runtimeContinuationProjection.js";
|
|
7
|
+
import { summarizeExecutionGroup } from "./executionGroup.js";
|
|
8
|
+
import { executionStageSpendClosed, observedExecutionResourceUsage, projectExecutionStageResources } from "./resourceBroker.js";
|
|
9
|
+
/**
|
|
10
|
+
* Fold existing Run, runtime, Session, process-exit, and stall facts into the
|
|
11
|
+
* four Lane health states. This is a read model only: it never advances a
|
|
12
|
+
* Lane, terminalizes a Run, or guesses that silence means death.
|
|
13
|
+
*/
|
|
14
|
+
export function projectExecutionGroupHealth(input) {
|
|
15
|
+
const policy = input.policy ?? DEFAULT_RUNTIME_HEALTH_POLICY;
|
|
16
|
+
const lanes = input.group.lanes.map((lane) => projectExecutionLaneHealth(lane, input, policy));
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
groupId: input.group.id,
|
|
19
|
+
lanes,
|
|
20
|
+
activeLaneCount: countHealth(lanes, "active"),
|
|
21
|
+
silentLaneCount: countHealth(lanes, "silent"),
|
|
22
|
+
suspectedStalledLaneCount: countHealth(lanes, "suspected-stalled"),
|
|
23
|
+
confirmedDeadLaneCount: countHealth(lanes, "confirmed-dead"),
|
|
24
|
+
reusableLaneIds: lanes.filter(({ resultReusable }) => resultReusable)
|
|
25
|
+
.map(({ laneId }) => laneId),
|
|
26
|
+
retryableLaneIds: lanes.filter(({ recovery }) => recovery === "retry-new-agent-run")
|
|
27
|
+
.map(({ laneId }) => laneId)
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Structural Group summary plus the health/recovery projection used by CLI/Web reads. */
|
|
31
|
+
export function summarizeExecutionGroupHealth(input) {
|
|
32
|
+
const summary = summarizeExecutionGroup(input.group);
|
|
33
|
+
const projection = projectExecutionGroupHealth(input);
|
|
34
|
+
const resources = input.group.stage === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: projectExecutionStageResources({
|
|
37
|
+
group: input.group,
|
|
38
|
+
...(input.stageGroups === undefined ? {} : { stageGroups: input.stageGroups }),
|
|
39
|
+
usage: observedExecutionResourceUsage({
|
|
40
|
+
group: input.group,
|
|
41
|
+
...(input.stageGroups === undefined ? {} : { stageGroups: input.stageGroups }),
|
|
42
|
+
runs: input.runs,
|
|
43
|
+
events: input.events
|
|
44
|
+
}),
|
|
45
|
+
now: input.now
|
|
46
|
+
});
|
|
47
|
+
const healthByLane = new Map(projection.lanes.map((lane) => [lane.laneId, lane]));
|
|
48
|
+
return Object.freeze({
|
|
49
|
+
...summary,
|
|
50
|
+
laneSummaries: summary.laneSummaries.map((lane) => Object.freeze({
|
|
51
|
+
...lane,
|
|
52
|
+
...healthByLane.get(lane.laneId)
|
|
53
|
+
})),
|
|
54
|
+
health: Object.freeze({
|
|
55
|
+
activeLaneCount: projection.activeLaneCount,
|
|
56
|
+
silentLaneCount: projection.silentLaneCount,
|
|
57
|
+
suspectedStalledLaneCount: projection.suspectedStalledLaneCount,
|
|
58
|
+
confirmedDeadLaneCount: projection.confirmedDeadLaneCount,
|
|
59
|
+
reusableLaneIds: projection.reusableLaneIds,
|
|
60
|
+
retryableLaneIds: projection.retryableLaneIds
|
|
61
|
+
}),
|
|
62
|
+
...(resources === undefined ? {} : { resources })
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** Unresolved Lane recovery in deterministic operational priority order. */
|
|
66
|
+
export function actionableExecutionLaneRecoveries(groups) {
|
|
67
|
+
const priority = {
|
|
68
|
+
"terminate-exact-run": 0,
|
|
69
|
+
"retry-new-agent-run": 1,
|
|
70
|
+
diagnose: 2
|
|
71
|
+
};
|
|
72
|
+
return groups
|
|
73
|
+
.filter(({ resolution }) => resolution === undefined)
|
|
74
|
+
.flatMap((group) => group.laneSummaries.flatMap((lane) => {
|
|
75
|
+
if (lane.recovery === "retry-new-agent-run"
|
|
76
|
+
&& group.resources !== undefined
|
|
77
|
+
&& executionStageSpendClosed(group.resources))
|
|
78
|
+
return [];
|
|
79
|
+
if (lane.recovery !== "diagnose"
|
|
80
|
+
&& lane.recovery !== "terminate-exact-run"
|
|
81
|
+
&& lane.recovery !== "retry-new-agent-run")
|
|
82
|
+
return [];
|
|
83
|
+
return [{
|
|
84
|
+
groupId: group.groupId,
|
|
85
|
+
laneId: lane.laneId,
|
|
86
|
+
...(lane.runId === undefined ? {} : { runId: lane.runId }),
|
|
87
|
+
...(lane.runtimeHealth === undefined ? {} : { runtimeHealth: lane.runtimeHealth }),
|
|
88
|
+
recovery: lane.recovery
|
|
89
|
+
}];
|
|
90
|
+
}))
|
|
91
|
+
.sort((left, right) => priority[left.recovery] - priority[right.recovery]);
|
|
92
|
+
}
|
|
93
|
+
function projectExecutionLaneHealth(lane, input, policy) {
|
|
94
|
+
const run = lane.runId === undefined
|
|
95
|
+
? undefined
|
|
96
|
+
: input.runs.find((candidate) => exactLaneRun(candidate, input.group, lane));
|
|
97
|
+
const continuationAgentId = run?.effective.agentId ?? lane.effective?.agentId;
|
|
98
|
+
if (lane.status !== "completed"
|
|
99
|
+
&& lane.status !== "yielded"
|
|
100
|
+
&& lane.runId !== undefined
|
|
101
|
+
&& continuationAgentId !== undefined
|
|
102
|
+
&& runOwnsBlockingProviderContinuation(input.events, {
|
|
103
|
+
taskId: input.group.taskId,
|
|
104
|
+
roleName: lane.roleName,
|
|
105
|
+
runId: lane.runId,
|
|
106
|
+
agentId: continuationAgentId
|
|
107
|
+
})) {
|
|
108
|
+
return projection(lane, {
|
|
109
|
+
runtimeHealth: "active",
|
|
110
|
+
recovery: "none",
|
|
111
|
+
resultReusable: false,
|
|
112
|
+
reason: "the exact Run still owns an unsettled Provider continuation writer",
|
|
113
|
+
evidence: ["runtime-continuation-writer-owned"]
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (lane.status === "completed" || lane.status === "yielded") {
|
|
117
|
+
return projection(lane, {
|
|
118
|
+
recovery: "reuse-result",
|
|
119
|
+
resultReusable: true,
|
|
120
|
+
reason: "the terminal Lane result is durable and must be reused",
|
|
121
|
+
evidence: ["execution-lane-result"]
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
if (lane.status === "failed") {
|
|
125
|
+
return projection(lane, {
|
|
126
|
+
runtimeHealth: "confirmed-dead",
|
|
127
|
+
recovery: "retry-new-agent-run",
|
|
128
|
+
resultReusable: false,
|
|
129
|
+
reason: "the exact Lane attempt is durably failed; a retry must create a new AgentRun",
|
|
130
|
+
evidence: ["execution-lane-terminal-failure"]
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (lane.status === "skipped") {
|
|
134
|
+
return projection(lane, {
|
|
135
|
+
recovery: "none",
|
|
136
|
+
resultReusable: false,
|
|
137
|
+
reason: "the Lane was never started after sufficient stage evidence made further spend unnecessary",
|
|
138
|
+
evidence: ["execution-lane-resource-skip"]
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (lane.status === "pending") {
|
|
142
|
+
return projection(lane, {
|
|
143
|
+
recovery: "none",
|
|
144
|
+
resultReusable: false,
|
|
145
|
+
reason: "the Lane has not started",
|
|
146
|
+
evidence: []
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (run === undefined) {
|
|
150
|
+
return projection(lane, {
|
|
151
|
+
runtimeHealth: "suspected-stalled",
|
|
152
|
+
recovery: "diagnose",
|
|
153
|
+
resultReusable: false,
|
|
154
|
+
reason: "the running Lane has no exact AgentRun record",
|
|
155
|
+
evidence: ["execution-lineage-missing"]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (run.status === "failed") {
|
|
159
|
+
return projection(lane, {
|
|
160
|
+
runtimeHealth: "confirmed-dead",
|
|
161
|
+
recovery: "retry-new-agent-run",
|
|
162
|
+
resultReusable: false,
|
|
163
|
+
reason: "the exact AgentRun is durably failed",
|
|
164
|
+
evidence: ["agent-run-terminal-failure"]
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (run.status !== "active") {
|
|
168
|
+
return projection(lane, {
|
|
169
|
+
runtimeHealth: "suspected-stalled",
|
|
170
|
+
recovery: "diagnose",
|
|
171
|
+
resultReusable: false,
|
|
172
|
+
reason: "the Lane is running but its exact AgentRun is terminal without a Lane result",
|
|
173
|
+
evidence: ["execution-lineage-inconsistent"]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const session = input.sessions.find((candidate) => (candidate.roleName === run.roleName
|
|
177
|
+
&& candidate.agentId === run.effective.agentId
|
|
178
|
+
&& candidate.adapterId === run.effective.adapterId));
|
|
179
|
+
const observations = exactRunObservations(input.events, run, session);
|
|
180
|
+
const runtime = runtimeProjection(observations, input.events, run);
|
|
181
|
+
const unsettledContinuation = runtime !== null
|
|
182
|
+
&& Object.values(runtime.continuations).some((continuation) => (continuation.execution === "active"
|
|
183
|
+
|| continuation.execution === "unknown"
|
|
184
|
+
|| continuation.identityConflict));
|
|
185
|
+
const unsettledChildWork = runtime !== null
|
|
186
|
+
&& (Object.values(runtime.operations).some(({ kind }) => kind === "subagent")
|
|
187
|
+
|| unsettledContinuation);
|
|
188
|
+
if (observations.some((observation) => (observation.kind === "turn.failed"
|
|
189
|
+
&& observation.payload.failure?.runTerminal === true)) && !unsettledChildWork) {
|
|
190
|
+
return projection(lane, {
|
|
191
|
+
runtimeHealth: "confirmed-dead",
|
|
192
|
+
recovery: "terminate-exact-run",
|
|
193
|
+
resultReusable: false,
|
|
194
|
+
reason: "the Provider reported an exact run-terminal failure",
|
|
195
|
+
evidence: ["provider-run-terminal"]
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
const runtimeTerminalEvidence = runtime === null
|
|
199
|
+
? []
|
|
200
|
+
: [
|
|
201
|
+
...(runtime.host === "exited" ? ["runtime-host-exited"] : []),
|
|
202
|
+
...(runtime.session === "ended" || runtime.session === "failed"
|
|
203
|
+
? [`runtime-session-${runtime.session}`]
|
|
204
|
+
: [])
|
|
205
|
+
];
|
|
206
|
+
if (runtimeTerminalEvidence.length > 0 && !unsettledChildWork) {
|
|
207
|
+
return projection(lane, {
|
|
208
|
+
runtimeHealth: "confirmed-dead",
|
|
209
|
+
recovery: "terminate-exact-run",
|
|
210
|
+
resultReusable: false,
|
|
211
|
+
reason: "the exact runtime host or Session is terminal and no unsettled child work remains",
|
|
212
|
+
evidence: runtimeTerminalEvidence
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const exit = latestExactProcessExit(input.events, run, session);
|
|
216
|
+
const sessionDead = session?.status === "stopped" || session?.status === "broken";
|
|
217
|
+
if (sessionDead
|
|
218
|
+
&& exit !== null
|
|
219
|
+
&& isAbnormalExit(exit.classification)
|
|
220
|
+
&& !unsettledChildWork) {
|
|
221
|
+
return projection(lane, {
|
|
222
|
+
runtimeHealth: "confirmed-dead",
|
|
223
|
+
recovery: "terminate-exact-run",
|
|
224
|
+
resultReusable: false,
|
|
225
|
+
reason: "the exact Session and abnormal process exit independently confirm death",
|
|
226
|
+
evidence: ["native-session-terminal", `process-exit:${exit.classification}`]
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (isRoleRunStalled(input.events, run.id)) {
|
|
230
|
+
return projection(lane, {
|
|
231
|
+
runtimeHealth: "suspected-stalled",
|
|
232
|
+
recovery: "diagnose",
|
|
233
|
+
resultReusable: false,
|
|
234
|
+
reason: `the durable progress clock has not advanced since ${latestStallProgressAt(input.events, run.id) ?? run.updatedAt}; no death proof exists`,
|
|
235
|
+
evidence: ["run-stalled"]
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
const activeOperation = runtime !== null
|
|
239
|
+
&& (Object.keys(runtime.operations).length > 0 || unsettledContinuation);
|
|
240
|
+
const lastActivityAt = runtime?.lastRuntimeActivityAt
|
|
241
|
+
?? run.deliveredAt
|
|
242
|
+
?? run.pushedAt
|
|
243
|
+
?? run.createdAt;
|
|
244
|
+
const recentActivity = input.now.getTime() - Date.parse(lastActivityAt) < policy.quietAfterMs;
|
|
245
|
+
if (activeOperation || recentActivity) {
|
|
246
|
+
return projection(lane, {
|
|
247
|
+
runtimeHealth: "active",
|
|
248
|
+
recovery: "none",
|
|
249
|
+
resultReusable: false,
|
|
250
|
+
reason: unsettledContinuation
|
|
251
|
+
? "the exact runtime reports unsettled continuation work"
|
|
252
|
+
: activeOperation
|
|
253
|
+
? "the exact runtime reports an active operation"
|
|
254
|
+
: "the exact Run has recent structured runtime activity",
|
|
255
|
+
evidence: unsettledContinuation
|
|
256
|
+
? ["runtime-continuation-unsettled"]
|
|
257
|
+
: activeOperation ? ["runtime-operation-active"] : ["runtime-activity-recent"]
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return projection(lane, {
|
|
261
|
+
runtimeHealth: "silent",
|
|
262
|
+
recovery: "none",
|
|
263
|
+
resultReusable: false,
|
|
264
|
+
reason: "the exact Run remains active without recent structured activity; silence alone is not death",
|
|
265
|
+
evidence: ["agent-run-active"]
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
function projection(lane, value) {
|
|
269
|
+
return Object.freeze({ laneId: lane.id, ...value });
|
|
270
|
+
}
|
|
271
|
+
function exactLaneRun(run, group, lane) {
|
|
272
|
+
return run.id === lane.runId
|
|
273
|
+
&& run.taskId === group.taskId
|
|
274
|
+
&& run.roleName === lane.roleName
|
|
275
|
+
&& run.executionGroupId === group.id
|
|
276
|
+
&& run.executionLaneId === lane.id;
|
|
277
|
+
}
|
|
278
|
+
function exactRunObservations(events, run, session) {
|
|
279
|
+
return events.map(runtimeObservationFromTaskEvent)
|
|
280
|
+
.filter((observation) => (observation !== null
|
|
281
|
+
&& observation.fence.taskId === run.taskId
|
|
282
|
+
&& observation.fence.runId === run.id
|
|
283
|
+
&& observation.fence.roleName === run.roleName
|
|
284
|
+
&& observation.fence.agentId === run.effective.agentId
|
|
285
|
+
&& (session?.launchId === undefined || observation.fence.launchId === session.launchId)
|
|
286
|
+
&& (session?.nativeSessionId === undefined
|
|
287
|
+
|| observation.fence.nativeSessionId === session.nativeSessionId)));
|
|
288
|
+
}
|
|
289
|
+
function runtimeProjection(observations, events, run) {
|
|
290
|
+
const first = observations[0];
|
|
291
|
+
return first === undefined
|
|
292
|
+
? null
|
|
293
|
+
: projectRuntimeTaskEvents(first.fence, run.createdAt, events);
|
|
294
|
+
}
|
|
295
|
+
function latestExactProcessExit(events, run, session) {
|
|
296
|
+
const matching = events.flatMap((event) => {
|
|
297
|
+
if (event.type !== "runtime.process-exit-observed")
|
|
298
|
+
return [];
|
|
299
|
+
try {
|
|
300
|
+
const observation = validateRuntimeProcessExitObservation(JSON.parse(event.payload.observation ?? ""));
|
|
301
|
+
if (observation.taskId !== run.taskId
|
|
302
|
+
|| observation.runId !== run.id
|
|
303
|
+
|| observation.roleName !== run.roleName
|
|
304
|
+
|| (session?.launchId !== undefined && observation.launchId !== session.launchId)
|
|
305
|
+
|| (session?.nativeSessionId !== undefined
|
|
306
|
+
&& observation.nativeSessionId !== session.nativeSessionId))
|
|
307
|
+
return [];
|
|
308
|
+
return [{
|
|
309
|
+
observation,
|
|
310
|
+
classification: event.payload.classification ?? "unknown"
|
|
311
|
+
}];
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return [];
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
return matching.sort((left, right) => (Date.parse(right.observation.observedAt) - Date.parse(left.observation.observedAt)))[0] ?? null;
|
|
318
|
+
}
|
|
319
|
+
function isAbnormalExit(classification) {
|
|
320
|
+
return classification === "host-abnormal" || classification === "provider-turn-failed";
|
|
321
|
+
}
|
|
322
|
+
function countHealth(lanes, health) {
|
|
323
|
+
return lanes.filter(({ runtimeHealth }) => runtimeHealth === health).length;
|
|
324
|
+
}
|