@zq-silk/yui 0.6.13 → 0.6.15
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 +34 -5
- package/dist/cli/commandCatalog.js +172 -62
- package/dist/cli/helpRenderer.js +3 -1
- package/dist/cli.js +102 -11
- package/dist/commands/configCommands.js +521 -171
- package/dist/commands/deliveryGuardPreflight.js +2 -2
- package/dist/commands/executionAuditCommands.js +56 -3
- package/dist/commands/projectCommands.js +518 -55
- package/dist/commands/releaseCommands.js +0 -1
- package/dist/commands/taskActor.js +17 -0
- package/dist/commands/taskBaseCommands.js +29 -0
- package/dist/commands/taskCommands.js +577 -126
- package/dist/commands/taskContextCommand.js +36 -2
- package/dist/commands/taskNextActionCommand.js +48 -3
- package/dist/commands/taskPublicationCommands.js +319 -0
- package/dist/commands/taskRoleRuntimeStatus.js +83 -59
- package/dist/commands/telemetryCommands.js +14 -13
- package/dist/config/yuiConfig.js +161 -8
- package/dist/context/sessionContextBudget.js +71 -0
- package/dist/context/wakeNotification.js +65 -0
- package/dist/controller/clientRuntime.js +2 -1
- package/dist/controller/ephemeralResourceReaper.js +2 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +191 -16
- package/dist/controller/jobSupervisor.js +5 -4
- package/dist/controller/resourceCleanupLinux.js +6 -6
- package/dist/controller/resourceInventoryLinux.js +3 -3
- package/dist/controller/runtime.js +88 -10
- package/dist/controller/updateReconciliation.js +4 -3
- package/dist/doctor/doctor.js +26 -7
- package/dist/executor/agentConfigurationCatalog.js +18 -0
- package/dist/executor/fileRoleLaunchPlanner.js +3 -3
- package/dist/lifecycle/contextBudgetRollover.js +81 -0
- package/dist/lifecycle/exactRunTerminalization.js +16 -2
- package/dist/lifecycle/providerErrorClass.js +33 -12
- package/dist/observability/executionAudit.js +214 -6
- package/dist/output/table.js +18 -0
- package/dist/repository/gitWorkspace.js +92 -0
- package/dist/repository/project.js +218 -4
- package/dist/repository/taskBaseFreshness.js +318 -0
- package/dist/repository/taskWorkspacePreparer.js +16 -2
- package/dist/review/deltaRecheck.js +253 -0
- package/dist/review/reviewConfig.js +31 -0
- package/dist/review/reviewFindingLedger.js +5 -1
- package/dist/review/reviewRound.js +156 -1
- package/dist/run/providerRetry.js +21 -3
- package/dist/run/providerRetryConfig.js +13 -60
- package/dist/run/recoveryProjection.js +198 -0
- package/dist/runtime/builtinAgentDrivers.js +3 -0
- package/dist/runtime/builtinTranscriptUsage.js +76 -32
- package/dist/runtime/continuationManager.js +17 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/launchDiagnostics.js +154 -0
- package/dist/runtime/lifecycleReservation.js +13 -0
- package/dist/runtime/providerContinuation.js +38 -0
- package/dist/runtime/providerContinuationReconciliationService.js +1 -0
- package/dist/runtime/providerErrorCodes.js +278 -0
- package/dist/runtime/runtimeHealthPolicy.js +20 -0
- package/dist/runtime/runtimeObservation.js +7 -1
- package/dist/runtime/runtimeProjection.js +115 -23
- package/dist/runtime/tmuxAdapters.js +242 -48
- package/dist/scheduler/activeRoleRunDelivery.js +139 -3
- package/dist/scheduler/activeTaskProgress.js +4 -3
- package/dist/scheduler/leaderWakeupProcessor.js +105 -15
- package/dist/scheduler/roleRunLiveness.js +2 -1
- package/dist/scheduler/roleRunStall.js +3 -2
- package/dist/scheduler/taskWake.js +72 -0
- package/dist/scheduler/wakeReason.js +64 -0
- package/dist/scheduler/wakeupQueue.js +2 -1
- package/dist/setup/setupCommand.js +1 -1
- package/dist/storage/migration/productionRegistry.js +325 -1
- package/dist/storage/sqliteSchema.js +61 -2
- package/dist/storage/sqliteStore.js +129 -2
- package/dist/storage/storeRpc.js +1 -0
- package/dist/storage/taskStore.js +262 -5
- package/dist/storage/upgrade/recordVersions.js +6 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +22 -2
- package/dist/task/completionReadiness.js +289 -0
- package/dist/task/publicationReference.js +123 -0
- package/dist/task/taskRecordReference.js +3 -1
- package/dist/telemetry/telemetryConfig.js +23 -18
- package/dist/telemetry/telemetryWiring.js +8 -8
- package/dist/tmux/tmuxManager.js +50 -9
- package/dist/web/assets/client/i18n.js +4 -0
- package/dist/web/assets/client/view.js +18 -0
- package/dist/web/webSnapshot.js +100 -10
- package/i18n/README.zh-CN.md +5 -5
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +49 -10
- package/skills/yui-operator/SKILL.md +17 -3
- package/skills/yui-worker/SKILL.md +8 -0
|
@@ -116,6 +116,7 @@ function sameContinuationState(left, right) {
|
|
|
116
116
|
&& left.attachment === right.attachment
|
|
117
117
|
&& left.observation === right.observation
|
|
118
118
|
&& left.mayWriteWorkspace === right.mayWriteWorkspace
|
|
119
|
+
&& left.durability === right.durability
|
|
119
120
|
&& left.resultRef === right.resultRef
|
|
120
121
|
&& left.lastProviderSequence === right.lastProviderSequence
|
|
121
122
|
&& left.identityConflict === right.identityConflict;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-neutral structured error taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* Each Agent Driver parses its own Provider's raw failure text into one of
|
|
5
|
+
* these codes at the driver boundary. The retry classifier then maps codes to
|
|
6
|
+
* Yui error classes by lookup — no Provider-specific regex lives in the
|
|
7
|
+
* classifier. Text matching remains only as a fallback for drivers that
|
|
8
|
+
* cannot yet produce a structured code.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Maps each structured code to its Yui error class. This is the single
|
|
12
|
+
* authoritative lookup that replaces regex matching in the classifier.
|
|
13
|
+
*/
|
|
14
|
+
export const PROVIDER_ERROR_CODE_CLASS = Object.freeze({
|
|
15
|
+
// Stream/transport → transport-uncertain (delivery may have happened)
|
|
16
|
+
"stream-internal-error": "transport-uncertain",
|
|
17
|
+
"stream-protocol-error": "transport-uncertain",
|
|
18
|
+
"stream-error": "transport-uncertain",
|
|
19
|
+
"connection-reset": "transport-uncertain",
|
|
20
|
+
"connection-lost": "transport-uncertain",
|
|
21
|
+
timeout: "transport-uncertain",
|
|
22
|
+
// HTTP 5xx / capacity → transient-provider
|
|
23
|
+
"http-5xx": "transient-provider",
|
|
24
|
+
overloaded: "transient-provider",
|
|
25
|
+
"server-error": "transient-provider",
|
|
26
|
+
// HTTP 429 → transient-provider (retryable with backoff)
|
|
27
|
+
"http-429": "transient-provider",
|
|
28
|
+
// HTTP 4xx → invalid-request (non-retryable)
|
|
29
|
+
"http-4xx": "invalid-request",
|
|
30
|
+
// Policy
|
|
31
|
+
"policy-denied": "policy-denied",
|
|
32
|
+
// Session
|
|
33
|
+
"session-not-found": "session-dead",
|
|
34
|
+
"session-expired": "session-dead",
|
|
35
|
+
"session-ended": "session-dead",
|
|
36
|
+
"process-exited": "session-dead",
|
|
37
|
+
// Request
|
|
38
|
+
"invalid-request": "invalid-request",
|
|
39
|
+
unknown: "unclassified"
|
|
40
|
+
});
|
|
41
|
+
/** Whether a structured code is retryable in place. */
|
|
42
|
+
export function isRetryableErrorCode(code) {
|
|
43
|
+
const cls = PROVIDER_ERROR_CODE_CLASS[code];
|
|
44
|
+
return cls === "transient-provider" || cls === "transport-uncertain";
|
|
45
|
+
}
|
|
46
|
+
// ── Claude Code driver ──────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Parses a Claude Code StopFailure `error` string into a structured code.
|
|
49
|
+
*
|
|
50
|
+
* Claude sends structured API error codes (server_error, overloaded_error,
|
|
51
|
+
* rate_limit_error, etc.) as the `error` field, and raw transport text
|
|
52
|
+
* ("stream error: stream ID …; INTERNAL_ERROR") in error_details or the
|
|
53
|
+
* CLI's own output. This function handles both.
|
|
54
|
+
*/
|
|
55
|
+
export function parseClaudeError(error, details) {
|
|
56
|
+
const text = [error, details]
|
|
57
|
+
.filter((v) => typeof v === "string" && v.length > 0)
|
|
58
|
+
.join("\n");
|
|
59
|
+
// ── Structured Claude API error codes ──────────────────────────────
|
|
60
|
+
// Claude's StopFailure hook sends these as the `error` field.
|
|
61
|
+
if (/^server_error$/iu.test(text)) {
|
|
62
|
+
return { code: "server-error", raw: error };
|
|
63
|
+
}
|
|
64
|
+
if (/^overloaded_error$/iu.test(text)) {
|
|
65
|
+
return { code: "overloaded", raw: error };
|
|
66
|
+
}
|
|
67
|
+
if (/^rate_limit_error$/iu.test(text)) {
|
|
68
|
+
return { code: "http-429", raw: error };
|
|
69
|
+
}
|
|
70
|
+
if (/^invalid_request_error$/iu.test(text)) {
|
|
71
|
+
return { code: "invalid-request", raw: error };
|
|
72
|
+
}
|
|
73
|
+
if (/^(authentication_error|permission_error)$/iu.test(text)) {
|
|
74
|
+
return { code: "policy-denied", raw: error };
|
|
75
|
+
}
|
|
76
|
+
if (/^not_found_error$/iu.test(text)) {
|
|
77
|
+
return { code: "session-not-found", raw: error };
|
|
78
|
+
}
|
|
79
|
+
if (/^api_error$/iu.test(text)) {
|
|
80
|
+
return { code: "server-error", raw: error };
|
|
81
|
+
}
|
|
82
|
+
// Claude CLI structured error codes (e.g. "[claude-code:unrecognized_model]")
|
|
83
|
+
if (/\[claude-code:(unrecognized_model|invalid_model|model_not_found)\]/iu.test(text)) {
|
|
84
|
+
return { code: "invalid-request", raw: error };
|
|
85
|
+
}
|
|
86
|
+
// HTTP/2 RST_STREAM (the Task-27 failure mode)
|
|
87
|
+
if (/stream error:.*INTERNAL_ERROR/iu.test(text)) {
|
|
88
|
+
return { code: "stream-internal-error", raw: error };
|
|
89
|
+
}
|
|
90
|
+
if (/stream error:.*PROTOCOL_ERROR/iu.test(text)) {
|
|
91
|
+
return { code: "stream-protocol-error", raw: error };
|
|
92
|
+
}
|
|
93
|
+
if (/stream error/iu.test(text)) {
|
|
94
|
+
return { code: "stream-error", raw: error };
|
|
95
|
+
}
|
|
96
|
+
// "Server error mid-response" and similar
|
|
97
|
+
if (/server[\s_-]?error/iu.test(text)) {
|
|
98
|
+
return { code: "server-error", raw: error };
|
|
99
|
+
}
|
|
100
|
+
// HTTP status codes
|
|
101
|
+
if (/\b429\b/u.test(text))
|
|
102
|
+
return { code: "http-429", raw: error };
|
|
103
|
+
if (/\b40[0-9]\b/u.test(text))
|
|
104
|
+
return { code: "http-4xx", raw: error };
|
|
105
|
+
if (/\b50[024]\b/u.test(text))
|
|
106
|
+
return { code: "http-5xx", raw: error };
|
|
107
|
+
// Connection
|
|
108
|
+
if (/connection[\s_-]?reset/iu.test(text))
|
|
109
|
+
return { code: "connection-reset", raw: error };
|
|
110
|
+
if (/connection[\s_-]?lost/iu.test(text))
|
|
111
|
+
return { code: "connection-lost", raw: error };
|
|
112
|
+
if (/econnreset/iu.test(text))
|
|
113
|
+
return { code: "connection-reset", raw: error };
|
|
114
|
+
if (/socket hang up/iu.test(text))
|
|
115
|
+
return { code: "connection-reset", raw: error };
|
|
116
|
+
// Timeout
|
|
117
|
+
if (/timed?[ -]?out/iu.test(text))
|
|
118
|
+
return { code: "timeout", raw: error };
|
|
119
|
+
if (/etimedout/iu.test(text))
|
|
120
|
+
return { code: "timeout", raw: error };
|
|
121
|
+
// Capacity
|
|
122
|
+
if (/overloaded/iu.test(text))
|
|
123
|
+
return { code: "overloaded", raw: error };
|
|
124
|
+
if (/rate[\s_-]?limit/iu.test(text))
|
|
125
|
+
return { code: "http-429", raw: error };
|
|
126
|
+
// Policy
|
|
127
|
+
if (/cyber[_-]?policy/iu.test(text))
|
|
128
|
+
return { code: "policy-denied", raw: error };
|
|
129
|
+
if (/policy[\s_-]?violation/iu.test(text))
|
|
130
|
+
return { code: "policy-denied", raw: error };
|
|
131
|
+
if (/usage[\s_-]?policy/iu.test(text))
|
|
132
|
+
return { code: "policy-denied", raw: error };
|
|
133
|
+
if (/content[\s_-]?policy/iu.test(text))
|
|
134
|
+
return { code: "policy-denied", raw: error };
|
|
135
|
+
if (/safety[\s_-]?policy/iu.test(text))
|
|
136
|
+
return { code: "policy-denied", raw: error };
|
|
137
|
+
// Session lifecycle
|
|
138
|
+
if (/session[\s_-]?not[\s_-]?found/iu.test(text))
|
|
139
|
+
return { code: "session-not-found", raw: error };
|
|
140
|
+
if (/no[\s_-]?such[\s_-]?(session|thread)/iu.test(text))
|
|
141
|
+
return { code: "session-not-found", raw: error };
|
|
142
|
+
if (/thread[\s_-]?not[\s_-]?found/iu.test(text))
|
|
143
|
+
return { code: "session-not-found", raw: error };
|
|
144
|
+
if (/session[\s_-]?(has[\s_-]?)?expired/iu.test(text))
|
|
145
|
+
return { code: "session-expired", raw: error };
|
|
146
|
+
if (/session[\s_-]?(has[\s_-]?)?ended/iu.test(text))
|
|
147
|
+
return { code: "session-ended", raw: error };
|
|
148
|
+
if (/process[\s_-]?exited/iu.test(text))
|
|
149
|
+
return { code: "process-exited", raw: error };
|
|
150
|
+
// Request validity
|
|
151
|
+
if (/invalid[\s_-]?request/iu.test(text))
|
|
152
|
+
return { code: "invalid-request", raw: error };
|
|
153
|
+
if (/validation[\s_-]?error/iu.test(text))
|
|
154
|
+
return { code: "invalid-request", raw: error };
|
|
155
|
+
if (/bad[\s_-]?request/iu.test(text))
|
|
156
|
+
return { code: "http-4xx", raw: error };
|
|
157
|
+
if (/unknown[\s_-]?(flag|tool|argument)/iu.test(text))
|
|
158
|
+
return { code: "invalid-request", raw: error };
|
|
159
|
+
return { code: "unknown", raw: error };
|
|
160
|
+
}
|
|
161
|
+
// ── Codex driver ────────────────────────────────────────────────────────
|
|
162
|
+
/**
|
|
163
|
+
* Parses a Codex CLI failure into a structured code.
|
|
164
|
+
*
|
|
165
|
+
* Codex surfaces errors through process exit, transcript messages, and
|
|
166
|
+
* stream-level failures. Its error formats overlap with Claude's (HTTP/2
|
|
167
|
+
* stream errors, API status codes) but also include Codex-specific patterns.
|
|
168
|
+
*/
|
|
169
|
+
export function parseCodexError(error, details) {
|
|
170
|
+
const text = [error, details]
|
|
171
|
+
.filter((v) => typeof v === "string" && v.length > 0)
|
|
172
|
+
.join("\n");
|
|
173
|
+
// ── Structured API error codes ────────────────────────────────────
|
|
174
|
+
if (/^server_error$/iu.test(text)) {
|
|
175
|
+
return { code: "server-error", raw: error };
|
|
176
|
+
}
|
|
177
|
+
if (/^overloaded_error$/iu.test(text)) {
|
|
178
|
+
return { code: "overloaded", raw: error };
|
|
179
|
+
}
|
|
180
|
+
if (/^rate_limit_error$/iu.test(text)) {
|
|
181
|
+
return { code: "http-429", raw: error };
|
|
182
|
+
}
|
|
183
|
+
if (/^invalid_request_error$/iu.test(text)) {
|
|
184
|
+
return { code: "invalid-request", raw: error };
|
|
185
|
+
}
|
|
186
|
+
if (/^(authentication_error|permission_error)$/iu.test(text)) {
|
|
187
|
+
return { code: "policy-denied", raw: error };
|
|
188
|
+
}
|
|
189
|
+
// Codex CLI structured error codes
|
|
190
|
+
if (/\[codex:(unrecognized_model|invalid_model|model_not_found)\]/iu.test(text)) {
|
|
191
|
+
return { code: "invalid-request", raw: error };
|
|
192
|
+
}
|
|
193
|
+
// Model not supported / invalid model (non-retryable)
|
|
194
|
+
if (/model.*not supported|invalid.*model|model.*not found/iu.test(text)) {
|
|
195
|
+
return { code: "invalid-request", raw: error };
|
|
196
|
+
}
|
|
197
|
+
// Stream disconnected (retryable transport)
|
|
198
|
+
if (/stream disconnected/iu.test(text)) {
|
|
199
|
+
return { code: "stream-error", raw: error };
|
|
200
|
+
}
|
|
201
|
+
// ── Stream / transport ────────────────────────────────────────────
|
|
202
|
+
if (/stream error:.*INTERNAL_ERROR/iu.test(text)) {
|
|
203
|
+
return { code: "stream-internal-error", raw: error };
|
|
204
|
+
}
|
|
205
|
+
if (/stream error:.*PROTOCOL_ERROR/iu.test(text)) {
|
|
206
|
+
return { code: "stream-protocol-error", raw: error };
|
|
207
|
+
}
|
|
208
|
+
if (/stream error/iu.test(text)) {
|
|
209
|
+
return { code: "stream-error", raw: error };
|
|
210
|
+
}
|
|
211
|
+
// ── HTTP status codes ─────────────────────────────────────────────
|
|
212
|
+
if (/\b429\b/u.test(text))
|
|
213
|
+
return { code: "http-429", raw: error };
|
|
214
|
+
if (/\b40[0-9]\b/u.test(text))
|
|
215
|
+
return { code: "http-4xx", raw: error };
|
|
216
|
+
if (/\b50[024]\b/u.test(text))
|
|
217
|
+
return { code: "http-5xx", raw: error };
|
|
218
|
+
// ── Server / capacity ─────────────────────────────────────────────
|
|
219
|
+
if (/server[\s_-]?error/iu.test(text)) {
|
|
220
|
+
return { code: "server-error", raw: error };
|
|
221
|
+
}
|
|
222
|
+
if (/overloaded/iu.test(text))
|
|
223
|
+
return { code: "overloaded", raw: error };
|
|
224
|
+
if (/rate[\s_-]?limit/iu.test(text))
|
|
225
|
+
return { code: "http-429", raw: error };
|
|
226
|
+
if (/bad gateway/iu.test(text))
|
|
227
|
+
return { code: "http-5xx", raw: error };
|
|
228
|
+
if (/gateway timeout/iu.test(text))
|
|
229
|
+
return { code: "timeout", raw: error };
|
|
230
|
+
if (/service unavailable/iu.test(text))
|
|
231
|
+
return { code: "http-5xx", raw: error };
|
|
232
|
+
if (/temporarily unavailable/iu.test(text))
|
|
233
|
+
return { code: "http-5xx", raw: error };
|
|
234
|
+
// ── Connection ────────────────────────────────────────────────────
|
|
235
|
+
if (/connection[\s_-]?reset/iu.test(text))
|
|
236
|
+
return { code: "connection-reset", raw: error };
|
|
237
|
+
if (/connection[\s_-]?lost/iu.test(text))
|
|
238
|
+
return { code: "connection-lost", raw: error };
|
|
239
|
+
if (/econnreset/iu.test(text))
|
|
240
|
+
return { code: "connection-reset", raw: error };
|
|
241
|
+
if (/socket hang up/iu.test(text))
|
|
242
|
+
return { code: "connection-reset", raw: error };
|
|
243
|
+
// ── Timeout ───────────────────────────────────────────────────────
|
|
244
|
+
if (/timed?[ -]?out/iu.test(text))
|
|
245
|
+
return { code: "timeout", raw: error };
|
|
246
|
+
if (/etimedout/iu.test(text))
|
|
247
|
+
return { code: "timeout", raw: error };
|
|
248
|
+
// ── Policy ────────────────────────────────────────────────────────
|
|
249
|
+
if (/cyber[_-]?policy/iu.test(text))
|
|
250
|
+
return { code: "policy-denied", raw: error };
|
|
251
|
+
if (/policy[\s_-]?violation/iu.test(text))
|
|
252
|
+
return { code: "policy-denied", raw: error };
|
|
253
|
+
if (/usage[\s_-]?policy/iu.test(text))
|
|
254
|
+
return { code: "policy-denied", raw: error };
|
|
255
|
+
if (/content[\s_-]?policy/iu.test(text))
|
|
256
|
+
return { code: "policy-denied", raw: error };
|
|
257
|
+
// ── Session ───────────────────────────────────────────────────────
|
|
258
|
+
if (/session[\s_-]?not[\s_-]?found/iu.test(text))
|
|
259
|
+
return { code: "session-not-found", raw: error };
|
|
260
|
+
if (/no[\s_-]?such[\s_-]?(session|thread)/iu.test(text))
|
|
261
|
+
return { code: "session-not-found", raw: error };
|
|
262
|
+
if (/session[\s_-]?(has[\s_-]?)?expired/iu.test(text))
|
|
263
|
+
return { code: "session-expired", raw: error };
|
|
264
|
+
if (/session[\s_-]?(has[\s_-]?)?ended/iu.test(text))
|
|
265
|
+
return { code: "session-ended", raw: error };
|
|
266
|
+
if (/process[\s_-]?exited/iu.test(text))
|
|
267
|
+
return { code: "process-exited", raw: error };
|
|
268
|
+
// ── Request validity ──────────────────────────────────────────────
|
|
269
|
+
if (/invalid[\s_-]?request/iu.test(text))
|
|
270
|
+
return { code: "invalid-request", raw: error };
|
|
271
|
+
if (/validation[\s_-]?error/iu.test(text))
|
|
272
|
+
return { code: "invalid-request", raw: error };
|
|
273
|
+
if (/bad[\s_-]?request/iu.test(text))
|
|
274
|
+
return { code: "http-4xx", raw: error };
|
|
275
|
+
if (/unknown[\s_-]?(flag|tool|argument)/iu.test(text))
|
|
276
|
+
return { code: "invalid-request", raw: error };
|
|
277
|
+
return { code: "unknown", raw: error };
|
|
278
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for runtime health thresholds shared by the CLI
|
|
3
|
+
* status projection, the Web snapshot, and the scheduler stall pass.
|
|
4
|
+
*
|
|
5
|
+
* The layers are deliberately time-based and conservative: short silence is
|
|
6
|
+
* normal for high-reasoning-effort turns, large reviews, and tool waits.
|
|
7
|
+
* Only deterministic dead/broken evidence or the durable semantic stall
|
|
8
|
+
* window authorizes recovery; quiet time alone never resets a Run.
|
|
9
|
+
*/
|
|
10
|
+
/** Runtime silence after which a live turn is surfaced as "quiet" (hint only). */
|
|
11
|
+
export const RUNTIME_QUIET_AFTER_MS = 5 * 60_000;
|
|
12
|
+
/** No durable semantic progress after which a read-only diagnostic is warranted. */
|
|
13
|
+
export const RUNTIME_DIAGNOSTIC_AFTER_MS = 10 * 60_000;
|
|
14
|
+
/** No durable semantic progress after which the scheduler raises a stall candidate. */
|
|
15
|
+
export const SEMANTIC_STALL_WINDOW_MS = 30 * 60_000;
|
|
16
|
+
export const DEFAULT_RUNTIME_HEALTH_POLICY = Object.freeze({
|
|
17
|
+
quietAfterMs: RUNTIME_QUIET_AFTER_MS,
|
|
18
|
+
diagnosticAfterMs: RUNTIME_DIAGNOSTIC_AFTER_MS,
|
|
19
|
+
stallWindowMs: SEMANTIC_STALL_WINDOW_MS
|
|
20
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import { requireDriverId } from "./agentDriver.js";
|
|
2
3
|
export const RUNTIME_OBSERVATION_TASK_EVENT = "runtime.observation";
|
|
3
4
|
const KINDS = [
|
|
@@ -405,7 +406,11 @@ export function runtimeObservationSemanticKey(input) {
|
|
|
405
406
|
].join(":");
|
|
406
407
|
}
|
|
407
408
|
if (input.kind === "continuation.reported") {
|
|
408
|
-
|
|
409
|
+
const summary = input.payload?.summary?.trim();
|
|
410
|
+
const resultIdentity = summary === undefined || summary.length === 0
|
|
411
|
+
? input.payload?.reportId ?? "missing"
|
|
412
|
+
: `sha256:${createHash("sha256").update(summary).digest("hex")}`;
|
|
413
|
+
return ["continuation-report", ...continuationIdentity, resultIdentity]
|
|
409
414
|
.join(":");
|
|
410
415
|
}
|
|
411
416
|
if (input.kind === "continuation.started") {
|
|
@@ -453,6 +458,7 @@ function normalizeFailure(input) {
|
|
|
453
458
|
throw new Error("Runtime failure evidence must be an object.");
|
|
454
459
|
}
|
|
455
460
|
return Object.freeze({
|
|
461
|
+
...(input.errorCode === undefined ? {} : { errorCode: input.errorCode }),
|
|
456
462
|
code: requireText(input.code, "Runtime failure code"),
|
|
457
463
|
...(input.details === undefined
|
|
458
464
|
? {}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRuntimeObservation, runtimeObservationFromTaskEvent, runtimeObservationRunFenceMatches } from "./runtimeObservation.js";
|
|
2
|
+
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "./runtimeHealthPolicy.js";
|
|
2
3
|
export function createRuntimeProjection(fence, createdAt) {
|
|
3
4
|
const timestamp = requireTimestamp(createdAt);
|
|
4
5
|
return Object.freeze({
|
|
@@ -313,34 +314,125 @@ export function runtimeDisplayStatus(current) {
|
|
|
313
314
|
return "runtime-unobservable";
|
|
314
315
|
return "starting";
|
|
315
316
|
}
|
|
316
|
-
|
|
317
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Classify one RuntimeProjection into the layered health state shared by CLI,
|
|
319
|
+
* Web, and scheduler. The semantic progress timestamp is the same durable
|
|
320
|
+
* fence the scheduler stall pass consumes (deliveredAt plus Work/Review/
|
|
321
|
+
* Integration checkpoints), so token/tool/CPU activity can never masquerade
|
|
322
|
+
* as business progress.
|
|
323
|
+
*
|
|
324
|
+
* Layers below the durable stall window are advisory: `quiet` and
|
|
325
|
+
* `diagnostic-needed` never authorize a reset. The 30-minute semantic stall is
|
|
326
|
+
* persisted by the scheduler and surfaced as `stalled-candidate` by the
|
|
327
|
+
* caller; this function deliberately stops at `diagnostic-needed` so the
|
|
328
|
+
* Leader's waiting-user/waiting-on-workers classification stays authoritative.
|
|
329
|
+
*/
|
|
330
|
+
export function classifyRuntimeHealth(input) {
|
|
331
|
+
const policy = input.policy ?? DEFAULT_RUNTIME_HEALTH_POLICY;
|
|
332
|
+
const current = input.projection;
|
|
333
|
+
const semanticMs = Date.parse(input.semanticProgressAt);
|
|
334
|
+
if (!Number.isFinite(semanticMs)) {
|
|
335
|
+
throw new Error("Runtime health semantic progress timestamp is invalid.");
|
|
336
|
+
}
|
|
318
337
|
const runtimeIdleMs = current.lastRuntimeActivityAt === undefined
|
|
319
338
|
? Number.POSITIVE_INFINITY
|
|
320
|
-
: now.getTime() - Date.parse(current.lastRuntimeActivityAt);
|
|
321
|
-
const semanticIdleMs = now.getTime() -
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
339
|
+
: input.now.getTime() - Date.parse(current.lastRuntimeActivityAt);
|
|
340
|
+
const semanticIdleMs = input.now.getTime() - semanticMs;
|
|
341
|
+
const activeOperations = Object.entries(current.operations)
|
|
342
|
+
.map(([id, operation]) => `${operation.kind}:${id}`);
|
|
343
|
+
const base = {
|
|
344
|
+
lastSemanticProgressAt: input.semanticProgressAt,
|
|
345
|
+
activeOperations,
|
|
346
|
+
host: current.host,
|
|
347
|
+
observerStatus: current.observer.status,
|
|
348
|
+
runtimeIdleMs,
|
|
349
|
+
semanticIdleMs,
|
|
350
|
+
...(current.lastRuntimeActivityAt === undefined
|
|
351
|
+
? {}
|
|
352
|
+
: { lastRuntimeActivityAt: current.lastRuntimeActivityAt })
|
|
353
|
+
};
|
|
354
|
+
const operation = dominantOperation(current.operations);
|
|
355
|
+
const layer = classifyLayer(current, operation, runtimeIdleMs, semanticIdleMs, policy);
|
|
335
356
|
return Object.freeze({
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
: semanticIdleMs > policy.semanticSilenceMs
|
|
340
|
-
? "not-progressing"
|
|
341
|
-
: "progressing"
|
|
357
|
+
layer,
|
|
358
|
+
reason: runtimeHealthReason(layer, current),
|
|
359
|
+
...base
|
|
342
360
|
});
|
|
343
361
|
}
|
|
362
|
+
function classifyLayer(current, operation, runtimeIdleMs, semanticIdleMs, policy) {
|
|
363
|
+
// Deterministic terminal evidence is immediate: no waiting for any window.
|
|
364
|
+
if (current.session === "failed")
|
|
365
|
+
return "broken";
|
|
366
|
+
if (current.host === "exited" || current.session === "ended")
|
|
367
|
+
return "stopped";
|
|
368
|
+
// Recent structured runtime activity is the strongest liveness signal.
|
|
369
|
+
if (operation === "subagent")
|
|
370
|
+
return "subagent-active";
|
|
371
|
+
if (operation === "tool")
|
|
372
|
+
return "tool-active";
|
|
373
|
+
if (operation === "model")
|
|
374
|
+
return "model-active";
|
|
375
|
+
// An incomplete observer signal warrants a read-only diagnostic, but a
|
|
376
|
+
// dominant operation above is still trusted as the most recent fact.
|
|
377
|
+
if (current.observer.status === "degraded" || current.observer.status === "unavailable") {
|
|
378
|
+
return "diagnostic-needed";
|
|
379
|
+
}
|
|
380
|
+
if (current.turn === "waiting") {
|
|
381
|
+
return `waiting-${current.waitingReason ?? "external"}`;
|
|
382
|
+
}
|
|
383
|
+
// No durable semantic progress past the diagnostic window: read-only look.
|
|
384
|
+
if (semanticIdleMs >= policy.diagnosticAfterMs)
|
|
385
|
+
return "diagnostic-needed";
|
|
386
|
+
// A live turn with no recent structured activity is quiet, not dead.
|
|
387
|
+
if (runtimeIdleMs >= policy.quietAfterMs)
|
|
388
|
+
return "quiet";
|
|
389
|
+
if (current.turn === "accepted" || current.session === "active")
|
|
390
|
+
return "active-quiet";
|
|
391
|
+
if (current.session === "ready"
|
|
392
|
+
|| current.turn === "completed"
|
|
393
|
+
|| current.turn === "failed"
|
|
394
|
+
|| current.turn === "cancelled")
|
|
395
|
+
return "ready";
|
|
396
|
+
if (current.session === "started")
|
|
397
|
+
return "awaiting-provider-acceptance";
|
|
398
|
+
if (current.host === "alive")
|
|
399
|
+
return "runtime-unobservable";
|
|
400
|
+
return "starting";
|
|
401
|
+
}
|
|
402
|
+
function runtimeHealthReason(layer, current) {
|
|
403
|
+
switch (layer) {
|
|
404
|
+
case "broken":
|
|
405
|
+
return "the Agent Driver runtime is broken";
|
|
406
|
+
case "stopped":
|
|
407
|
+
return "the Provider Activation ended while the Yui Run remains active";
|
|
408
|
+
case "subagent-active":
|
|
409
|
+
case "tool-active":
|
|
410
|
+
case "model-active":
|
|
411
|
+
return `the Agent Driver reports ${layer.replaceAll("-", " ")}`;
|
|
412
|
+
case "diagnostic-needed":
|
|
413
|
+
if (current.observer.status === "degraded" || current.observer.status === "unavailable") {
|
|
414
|
+
return `the runtime observer is ${current.observer.status}; read-only diagnostic recommended`;
|
|
415
|
+
}
|
|
416
|
+
return "no durable semantic progress in the diagnostic window; read-only diagnostic recommended";
|
|
417
|
+
case "quiet":
|
|
418
|
+
return "the Agent turn is active but has reported no structured runtime activity recently";
|
|
419
|
+
case "active-quiet":
|
|
420
|
+
return "the Agent Driver reports active quiet";
|
|
421
|
+
case "waiting-user":
|
|
422
|
+
case "waiting-permission":
|
|
423
|
+
case "waiting-external":
|
|
424
|
+
return `the Agent Driver is ${layer.replaceAll("-", " ")}`;
|
|
425
|
+
case "ready":
|
|
426
|
+
return "the Agent turn ended while the workflow Run is still active";
|
|
427
|
+
case "awaiting-provider-acceptance":
|
|
428
|
+
return "the pushed active Run is awaiting provider acceptance";
|
|
429
|
+
case "runtime-unobservable":
|
|
430
|
+
return "the host is present but the Agent Driver exposes no current runtime state";
|
|
431
|
+
case "starting":
|
|
432
|
+
default:
|
|
433
|
+
return "the Agent Driver runtime is starting";
|
|
434
|
+
}
|
|
435
|
+
}
|
|
344
436
|
function withActivity(current, kind, at) {
|
|
345
437
|
return next(current, {
|
|
346
438
|
activity: Object.freeze({ kind, observedAt: at }),
|