@zq-silk/yui 2.5.0 → 2.6.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/dist/cli/commandCatalog.js +3 -2
- package/dist/commands/taskCommands.js +8 -3
- package/dist/commands/taskContextCommand.js +3 -2
- package/dist/context/taskContext.js +7 -4
- package/dist/kernel/builtinCapabilities.js +2 -1
- package/dist/runtime/builtinAgentErrorMappers.js +9 -5
- package/dist/runtime/providerRetry.js +38 -0
- package/dist/runtime/taskSessions.js +240 -0
- package/dist/scheduler/taskExecutionProjection.js +78 -6
- package/dist/web/assets/assetManifest.js +2 -0
- package/dist/web/assets/client/app/taskView.js +1 -0
- package/dist/web/assets/client/domain/records.js +70 -2
- package/dist/web/assets/client/domain/runs.js +3 -2
- package/dist/web/assets/client/domain/vocab.js +1 -1
- package/dist/web/assets/client/i18n/en.js +17 -1
- package/dist/web/assets/client/i18n/zh-CN.js +17 -1
- package/dist/web/assets/client/lib/api.js +4 -1
- package/dist/web/assets/client/lib/theme.js +31 -10
- package/dist/web/assets/client/views/task/actions.js +38 -21
- package/dist/web/assets/client/views/task/observation.js +12 -3
- package/dist/web/assets/client/views/task/overview.js +7 -40
- package/dist/web/assets/client/views/task/runtime.js +71 -34
- package/dist/web/assets/shared/icons.js +2 -0
- package/dist/web/assets/shell/index.js +1 -0
- package/dist/web/assets/shell/settings.js +1 -0
- package/dist/web/assets/styles/views/task.js +14 -0
- package/dist/web/webServer.js +12 -0
- package/dist/web/webSessions.js +2 -165
- package/dist/web/webSnapshot.js +9 -5
- package/dist/web/webTaskActivity.js +54 -0
- package/dist/web/webTaskSurface.js +1 -0
- package/docs/observability/README.md +25 -1
- package/docs/observability/README.zh-CN.md +18 -1
- package/docs/provider-retry.md +11 -0
- package/docs/release-workflow.md +27 -0
- package/docs/release-workflow.zh-CN.md +22 -0
- package/package.json +1 -1
- package/skills/yui-runtime/references/recovery.md +9 -0
|
@@ -408,8 +408,9 @@ const taskChildren = [
|
|
|
408
408
|
{
|
|
409
409
|
name: "context", discovery: { surface: "public", audiences: ["public", "operator", "leader", "assignment", "unbound"] },
|
|
410
410
|
summary: "Read compact authorized facts, fixed-bound delta, or inspect a Context reference.",
|
|
411
|
-
usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>]",
|
|
412
|
-
options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item"]
|
|
411
|
+
usage: "yui task context [read|list|delta|inspect] <task> [--after <cursor>] [--continuation <cursor>] [--cursor <cursor>] [--limit <n>] [--store <store> --ref <id>] [--digest <digest>] [--view <current|all>]",
|
|
412
|
+
options: ["--after", "--continuation", "--cursor", "--limit", "--store", "--ref", "--digest", "--status", "--work-item", "--view"],
|
|
413
|
+
optionValues: { "--view": ["current", "all"] }
|
|
413
414
|
},
|
|
414
415
|
{
|
|
415
416
|
name: "next-action", discovery: { surface: "managed", audiences: ["operator", "leader"] },
|
|
@@ -45,7 +45,7 @@ import { copyGlobalRoleToTaskRole, activeRoleAgentBinding, createRole, createRol
|
|
|
45
45
|
import { roleLaunchEventPayload, saveTaskRoleUpdate } from "../role/taskRoleUpdate.js";
|
|
46
46
|
import { RUNTIME_SESSION_REPLACE_REQUIRED_REASON, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
47
47
|
import { currentManagedRuntime, resolveManagedTaskReader } from "../runtime/managedCaller.js";
|
|
48
|
-
import { controlProviderRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
48
|
+
import { controlProviderRetry, providerRunRetry, providerRetryProjection, renderProviderRetry } from "../runtime/providerRetry.js";
|
|
49
49
|
import { currentProviderConversation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
|
|
50
50
|
import { projectProviderContinuations } from "../runtime/runtimeContinuationProjection.js";
|
|
51
51
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
@@ -4897,7 +4897,9 @@ function retryRun(args, store, options) {
|
|
|
4897
4897
|
return output(`Retry already recorded as ${existing.payload.runId}; retry that exact successor if needed.\n`);
|
|
4898
4898
|
}
|
|
4899
4899
|
const sessions = tx.getTaskRoleSessionSet(previous.taskId, previous.roleName);
|
|
4900
|
-
const retry = sessions?.providerBinding?.retry
|
|
4900
|
+
const retry = options.providerRetryChainId !== undefined ? sessions?.providerBinding?.retry
|
|
4901
|
+
: sessions?.providerBinding == null ? undefined
|
|
4902
|
+
: providerRunRetry(sessions.providerBinding, previous.id, clock(options).getTime());
|
|
4901
4903
|
if (options.providerRetryChainId !== undefined
|
|
4902
4904
|
&& (retry?.chainId !== options.providerRetryChainId || retry.status !== "waiting"
|
|
4903
4905
|
|| retry.previousRunId !== previous.id || clock(options).getTime() < Date.parse(retry.nextEligibleAt)
|
|
@@ -4909,7 +4911,10 @@ function retryRun(args, store, options) {
|
|
|
4909
4911
|
const successor = tx.getActiveRun(previous.taskId, previous.roleName);
|
|
4910
4912
|
tx.saveTaskRoleSessionSet({
|
|
4911
4913
|
...sessions, providerBinding: {
|
|
4912
|
-
...sessions.providerBinding,
|
|
4914
|
+
...sessions.providerBinding,
|
|
4915
|
+
...(sessions.providerBinding.retry !== undefined && sessions.providerBinding.retry.chainId !== retry.chainId
|
|
4916
|
+
? { retryHistory: [...(sessions.providerBinding.retryHistory ?? []), sessions.providerBinding.retry] } : {}),
|
|
4917
|
+
retry: {
|
|
4913
4918
|
...retry, status: "waiting", successorAutomatic: options.providerRetryChainId !== undefined,
|
|
4914
4919
|
...(options.providerRetryChainId === undefined ? {
|
|
4915
4920
|
intentAt: clock(options).toISOString(),
|
|
@@ -17,12 +17,12 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
17
17
|
const action = explicit ? args[0] : "read";
|
|
18
18
|
const taskId = args[explicit ? 1 : 0];
|
|
19
19
|
if (!taskId || taskId.startsWith("--"))
|
|
20
|
-
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--status <status>] [--work-item <id>].");
|
|
20
|
+
throw usageError("Task context usage: yui task context [read|list|delta|inspect] <task> [--after <cursor|timestamp>] [--continuation <cursor>] [--cursor <cursor>] [--limit <1..100>] [--store <store> --ref <id> --digest <digest>] [--view <current|all>] [--status <status>] [--work-item <id>].");
|
|
21
21
|
const options = new Map();
|
|
22
22
|
const rest = args.slice(explicit ? 2 : 1);
|
|
23
23
|
const allowed = action === "read" ? [] : action === "delta"
|
|
24
24
|
? ["--after", "--continuation", "--limit"] : action === "list"
|
|
25
|
-
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
25
|
+
? ["--store", "--cursor", "--limit", "--status", "--after", "--work-item", "--view"] : ["--store", "--ref", "--digest", "--cursor"];
|
|
26
26
|
for (let i = 0; i < rest.length; i += 2) {
|
|
27
27
|
const flag = rest[i];
|
|
28
28
|
const value = rest[i + 1];
|
|
@@ -40,6 +40,7 @@ export function runTaskContextCommand(args, store, environment = {}) {
|
|
|
40
40
|
throw usageError("Context list requires --store.");
|
|
41
41
|
data = listTaskContext(store, taskId, family, {
|
|
42
42
|
cursor: options.get("--cursor"),
|
|
43
|
+
view: options.get("--view"),
|
|
43
44
|
status: options.get("--status"), after: options.get("--after"), workItemId: options.get("--work-item"),
|
|
44
45
|
...(options.has("--limit") ? { limit: Number(options.get("--limit")) } : {})
|
|
45
46
|
}, environment);
|
|
@@ -191,7 +191,10 @@ export function readTaskContextResource(store, taskId, selector, environment = {
|
|
|
191
191
|
export function listTaskContext(store, taskId, family, options = {}, environment = {}) {
|
|
192
192
|
return store.readTransaction(reader => {
|
|
193
193
|
const scope = authorizeContext(reader, taskId, environment);
|
|
194
|
-
|
|
194
|
+
if (options.view !== undefined && !["current", "all"].includes(options.view)) {
|
|
195
|
+
throw usageError("Context list view must be current or all.");
|
|
196
|
+
}
|
|
197
|
+
const sources = authorizedSources(reader, scope, options.view === "current", family, true);
|
|
195
198
|
if (!CONTEXT_FAMILIES.includes(family))
|
|
196
199
|
throw usageError(`Unknown Context store: ${family}.`);
|
|
197
200
|
if (options.after !== undefined && !Number.isFinite(Date.parse(options.after)))
|
|
@@ -216,7 +219,7 @@ export function listTaskContext(store, taskId, family, options = {}, environment
|
|
|
216
219
|
}
|
|
217
220
|
}
|
|
218
221
|
return recordPage(records, `task:${taskId}/${family}/${JSON.stringify({
|
|
219
|
-
status: options.status, after: options.after, workItemId: options.workItemId
|
|
222
|
+
view: options.view ?? "all", status: options.status, after: options.after, workItemId: options.workItemId
|
|
220
223
|
})}`, options);
|
|
221
224
|
});
|
|
222
225
|
}
|
|
@@ -447,7 +450,7 @@ function inspectValue(store, { task, allow, caller }, { store: family, refId })
|
|
|
447
450
|
default: return null;
|
|
448
451
|
}
|
|
449
452
|
}
|
|
450
|
-
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily) {
|
|
453
|
+
function authorizedSources(store, { task, allow, caller }, current = false, requestedFamily, discovery = false) {
|
|
451
454
|
const taskId = task.id;
|
|
452
455
|
const sources = [];
|
|
453
456
|
const add = (family, id, value) => {
|
|
@@ -462,7 +465,7 @@ function authorizedSources(store, { task, allow, caller }, current = false, requ
|
|
|
462
465
|
const { count } = store.queryContextRecords(taskId, { ...selection, limit: 0 });
|
|
463
466
|
sources.push({ family, count, *read(offset = 0) {
|
|
464
467
|
for (const { id, value } of store.queryContextRecords(taskId, {
|
|
465
|
-
...selection, offset, limit: current ? 8 : MAX_RECORDS
|
|
468
|
+
...selection, offset, limit: current && !discovery ? 8 : MAX_RECORDS
|
|
466
469
|
}).records)
|
|
467
470
|
yield materialize(family, id, value);
|
|
468
471
|
} });
|
|
@@ -47,7 +47,7 @@ const definitions = [
|
|
|
47
47
|
name: "context.list", summary: "Discover one authorized record family with bounded summaries and exact references.",
|
|
48
48
|
effect: "query", requiredPermissions: ["task:read"], source: "listTaskContext",
|
|
49
49
|
inputSchema: object({ taskId: text, store: text, cursor: text, limit: { type: "integer" },
|
|
50
|
-
status: text, after: text, workItemId: text }, ["taskId", "store"]),
|
|
50
|
+
view: { type: "string", enum: ["current", "all"] }, status: text, after: text, workItemId: text }, ["taskId", "store"]),
|
|
51
51
|
outputSchema: { type: "object", required: ["items", "total", "complete", "nextCursor"] }
|
|
52
52
|
},
|
|
53
53
|
{
|
|
@@ -304,6 +304,7 @@ export function createBuiltinCapabilities(host, store, jobs, signal = () => unde
|
|
|
304
304
|
}, callerEnvironment(caller));
|
|
305
305
|
if (name === "context.list")
|
|
306
306
|
return listTaskContext(store, taskId, params.store, {
|
|
307
|
+
view: params.view,
|
|
307
308
|
cursor: params.cursor, limit: params.limit,
|
|
308
309
|
status: params.status, after: params.after,
|
|
309
310
|
workItemId: params.workItemId
|
|
@@ -26,7 +26,7 @@ function mapClaudeDisplayError(input) {
|
|
|
26
26
|
}
|
|
27
27
|
/** Provider-specific Codex failure recognition. */
|
|
28
28
|
export function mapCodexAgentError(input) {
|
|
29
|
-
return withTransientEvidence(mapCodexDisplayError(input), input);
|
|
29
|
+
return withTransientEvidence(mapCodexDisplayError(input), input, true);
|
|
30
30
|
}
|
|
31
31
|
function mapCodexDisplayError(input) {
|
|
32
32
|
const text = `${input.message}\n${input.raw}`;
|
|
@@ -208,7 +208,7 @@ function sessionUnavailable(code) {
|
|
|
208
208
|
}
|
|
209
209
|
/** Replay qualification is stricter than the readable, best-effort category.
|
|
210
210
|
* Never promote a number occurring in provider prose to submission safety. */
|
|
211
|
-
function structuredTransientError(input) {
|
|
211
|
+
function structuredTransientError(input, codex) {
|
|
212
212
|
if (/insufficient[_ -]quota|quota.{0,25}(exhaust|exceed)|billing[_ -]hard[_ -]limit|usageLimitExceeded|credits?.{0,20}(exhaust|insufficient)/iu.test(`${input.message}\n${input.raw}`))
|
|
213
213
|
return { category: "access", code: "provider.quota-exhausted", retryable: false };
|
|
214
214
|
const nodes = [];
|
|
@@ -228,7 +228,8 @@ function structuredTransientError(input) {
|
|
|
228
228
|
catch { /* Display classification may still use prose. */ }
|
|
229
229
|
const codes = nodes.flatMap(n => [n.code, n.type, n.error, n.codexErrorInfo]).filter(v => typeof v === "string");
|
|
230
230
|
if (codes.some(c => ["authentication_error", "permission_error", "invalid_api_key", "invalid_model",
|
|
231
|
-
"model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))
|
|
231
|
+
"model_not_found", "usage_limit_exceeded", "insufficient_quota"].includes(c))
|
|
232
|
+
|| codex && nodes.some(n => ["usageLimitExceeded", "unauthorized", "badRequest"].includes(String(n.codexErrorInfo)))) {
|
|
232
233
|
return { category: "access", code: "provider.access-denied", retryable: false };
|
|
233
234
|
}
|
|
234
235
|
const statuses = nodes.flatMap(n => [n.httpStatusCode, n.statusCode, n.status]).filter(v => typeof v === "number");
|
|
@@ -240,6 +241,9 @@ function structuredTransientError(input) {
|
|
|
240
241
|
if (statuses.includes(429) || codes.includes("rate_limit_error") || nativeCode === "rate_limit_error") {
|
|
241
242
|
result = recoverable("rate-limit", "provider.rate-limit");
|
|
242
243
|
}
|
|
244
|
+
else if (codex && nodes.some(n => n.codexErrorInfo === "serverOverloaded")) {
|
|
245
|
+
result = recoverable("availability", "provider.model-capacity");
|
|
246
|
+
}
|
|
243
247
|
else if (statuses.some(s => [500, 502, 503, 504].includes(s))
|
|
244
248
|
|| codes.some(c => ["server_error", "overloaded_error"].includes(c))
|
|
245
249
|
|| ["server_error", "overloaded_error"].includes(nativeCode)) {
|
|
@@ -277,8 +281,8 @@ function structuredTransientError(input) {
|
|
|
277
281
|
}
|
|
278
282
|
/** Add positive evidence without renaming established diagnostics or letting
|
|
279
283
|
* a transient nested cause override a known fatal Session/request failure. */
|
|
280
|
-
function withTransientEvidence(base, input) {
|
|
281
|
-
const evidence = structuredTransientError(input);
|
|
284
|
+
function withTransientEvidence(base, input, codex = false) {
|
|
285
|
+
const evidence = structuredTransientError(input, codex);
|
|
282
286
|
if (evidence === undefined)
|
|
283
287
|
return base;
|
|
284
288
|
const possiblyTransient = ["rate-limit", "availability", "transport", "unknown"].includes(base.category);
|
|
@@ -3,6 +3,44 @@ export const PROVIDER_RETRY_BUDGET_MS = 600_000;
|
|
|
3
3
|
export function providerRetryPending(binding) {
|
|
4
4
|
return binding?.retry?.status === "waiting" || binding?.retry?.status === "in-flight";
|
|
5
5
|
}
|
|
6
|
+
/** An explicit Run retry can carry exact failed-input proof even when the
|
|
7
|
+
* error did not qualify for automation. This does not grant automatic replay:
|
|
8
|
+
* the shared admission and native latest-Turn/quiescence checks still apply. */
|
|
9
|
+
export function providerRunRetry(binding, runId, at) {
|
|
10
|
+
const turn = binding.run;
|
|
11
|
+
const nativeSessionId = binding.conversations.find(c => c.epoch === binding.currentConversationEpoch)?.conversationId;
|
|
12
|
+
if (turn?.runId !== runId || !["failed", "rejected", "deferred"].includes(turn.status)
|
|
13
|
+
|| binding.authority.owner !== "controller" || turn.authorityEpoch !== binding.authority.epoch
|
|
14
|
+
|| nativeSessionId === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
const previous = binding.retry;
|
|
17
|
+
if (previous?.previousRunId === runId && previous.status !== "recovered"
|
|
18
|
+
&& previous.failedAttemptId === turn.attemptId
|
|
19
|
+
&& previous.authorityEpoch === binding.authority.epoch
|
|
20
|
+
&& previous.nativeSessionId === nativeSessionId)
|
|
21
|
+
return previous;
|
|
22
|
+
const failure = turn.failure;
|
|
23
|
+
if (turn.status !== "failed" || turn.nativeTurnId === undefined || turn.input === undefined
|
|
24
|
+
|| turn.retrySupported !== true || failure?.error.inputDisposition !== "accepted"
|
|
25
|
+
|| failure.error.sessionDisposition !== "recoverable")
|
|
26
|
+
return undefined;
|
|
27
|
+
// A manual attempt can cancel automation when its error changes character.
|
|
28
|
+
// Refresh its failed-Turn proof without granting the same input a new budget.
|
|
29
|
+
const continuing = previous?.status !== "recovered" && previous?.currentAttemptId === turn.attemptId
|
|
30
|
+
&& previous.authorityEpoch === binding.authority.epoch && previous.nativeSessionId === nativeSessionId;
|
|
31
|
+
return {
|
|
32
|
+
policyVersion: 1, chainId: continuing ? previous.chainId : turn.attemptId, status: "waiting",
|
|
33
|
+
input: continuing ? previous.input : turn.input, nativeSessionId, authorityEpoch: binding.authority.epoch,
|
|
34
|
+
attempts: continuing ? previous.attempts : 0,
|
|
35
|
+
transportSequence: continuing ? previous.transportSequence : 0,
|
|
36
|
+
startedAt: continuing ? previous.startedAt : iso(at), intentAt: iso(at),
|
|
37
|
+
deadline: continuing ? previous.deadline : iso(at + PROVIDER_RETRY_BUDGET_MS),
|
|
38
|
+
nextEligibleAt: iso(at), updatedAt: iso(at),
|
|
39
|
+
failedAttemptId: turn.attemptId, failureAttemptId: turn.attemptId,
|
|
40
|
+
failedNativeTurnId: turn.nativeTurnId, previousRunId: runId,
|
|
41
|
+
failureRef: failure.ref, error: failure.error, successorAutomatic: false
|
|
42
|
+
};
|
|
43
|
+
}
|
|
6
44
|
/** Called only in the transaction recording the exact failed/rejected input. */
|
|
7
45
|
export function recordProviderFailure(binding, input) {
|
|
8
46
|
let turn = binding.run;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
2
|
+
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
3
|
+
import { projectRuntimeTaskEvents, classifyRuntimeHealth } from "../runtime/runtimeProjection.js";
|
|
4
|
+
import { DEFAULT_RUNTIME_HEALTH_POLICY } from "../runtime/runtimeHealthPolicy.js";
|
|
5
|
+
import { providerRetryProjection } from "../runtime/providerRetry.js";
|
|
6
|
+
/** Read-only presentation of the selected native connections, never an AgentRun
|
|
7
|
+
* count. Foreground facts are fenced to the exact input; retained continuations
|
|
8
|
+
* stay attached to their original input and are not counted as extra Sessions.
|
|
9
|
+
* No live probe, timers, persistence or recovery actions are introduced here. */
|
|
10
|
+
export function projectTaskSessions(input) {
|
|
11
|
+
const policy = input.policy ?? DEFAULT_RUNTIME_HEALTH_POLICY;
|
|
12
|
+
const sessions = [];
|
|
13
|
+
const seen = new Map();
|
|
14
|
+
const selectedIdentities = new Set();
|
|
15
|
+
const observationIdentity = (fence) => JSON.stringify([fence.roleName, fence.agentId, fence.driverId, fence.nativeSessionId]);
|
|
16
|
+
const observations = input.events.map(event => ({ event, observation: runtimeObservationFromTaskEvent(event) }))
|
|
17
|
+
.filter(entry => entry.event.taskId === input.taskId && entry.observation?.fence.taskId === input.taskId);
|
|
18
|
+
for (const set of input.sessionSets) {
|
|
19
|
+
if (set.owner.scope !== "task" || set.owner.taskId !== input.taskId)
|
|
20
|
+
continue;
|
|
21
|
+
const session = set.sessions[set.activeAgentId];
|
|
22
|
+
const binding = set.providerBinding;
|
|
23
|
+
const conversation = binding?.conversations.find(c => c.epoch === binding.currentConversationEpoch && c.status === "current");
|
|
24
|
+
// Replacement can leave an empty set. That is absence, not uncertain
|
|
25
|
+
// execution; retained child facts are still discovered below.
|
|
26
|
+
if (!session && !conversation && !binding?.run)
|
|
27
|
+
continue;
|
|
28
|
+
const nativeSessionId = session?.nativeSessionId ?? conversation?.conversationId ?? null;
|
|
29
|
+
const identity = JSON.stringify([binding?.providerNamespace ?? session?.adapterId,
|
|
30
|
+
binding?.accountScope ?? set.activeAgentId, nativeSessionId ?? set.owner.roleName]);
|
|
31
|
+
const prior = seen.get(identity);
|
|
32
|
+
if (prior !== undefined) {
|
|
33
|
+
if (sessions[prior].roleName !== set.owner.roleName)
|
|
34
|
+
sessions[prior] = {
|
|
35
|
+
...sessions[prior], group: "unknown", reason: "Multiple Roles name this native Session; inspect ownership."
|
|
36
|
+
};
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
seen.set(identity, sessions.length);
|
|
40
|
+
const turn = binding?.run;
|
|
41
|
+
let driverId = null;
|
|
42
|
+
if (session) {
|
|
43
|
+
try {
|
|
44
|
+
driverId = builtinDriverIdForAdapter(session.adapterId);
|
|
45
|
+
}
|
|
46
|
+
catch { /* Unsupported native observation remains explicitly unknown. */ }
|
|
47
|
+
}
|
|
48
|
+
const row = {
|
|
49
|
+
taskId: input.taskId, roleName: set.owner.roleName, agentId: set.activeAgentId,
|
|
50
|
+
driverId,
|
|
51
|
+
nativeSessionId, attemptId: turn?.attemptId ?? null, nativeTurnId: turn?.nativeTurnId ?? null,
|
|
52
|
+
runId: turn?.runId ?? null, group: "unknown", reason: "Current native input is not observable.",
|
|
53
|
+
layer: null, lastActivityAt: null, semanticProgressAt: input.semanticProgressAt ?? null,
|
|
54
|
+
sourceUpdatedAt: turn?.updatedAt ?? session?.updatedAt ?? set.updatedAt,
|
|
55
|
+
waitingReason: null, operations: [], background: []
|
|
56
|
+
};
|
|
57
|
+
if (!session || driverId === null) {
|
|
58
|
+
sessions.push(row);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const fence = { taskId: input.taskId, roleName: set.owner.roleName,
|
|
62
|
+
agentId: session.agentId, driverId, nativeSessionId: session.nativeSessionId };
|
|
63
|
+
selectedIdentities.add(observationIdentity(fence));
|
|
64
|
+
const sessionFacts = observations.filter(({ observation }) => Object.entries(fence).every(([key, value]) => observation.fence[key] === value));
|
|
65
|
+
// Each Run's continuation fold has its own authority. Foreground operations
|
|
66
|
+
// from older inputs must not make the current input look active.
|
|
67
|
+
const groups = new Map();
|
|
68
|
+
for (const { event, observation } of sessionFacts) {
|
|
69
|
+
const runId = observation.fence.runId;
|
|
70
|
+
const group = groups.get(runId) ?? [];
|
|
71
|
+
group.push(event);
|
|
72
|
+
groups.set(runId, group);
|
|
73
|
+
}
|
|
74
|
+
const background = backgroundForRuns(fence, session.createdAt, groups);
|
|
75
|
+
if (!binding || !conversation || conversation.conversationId !== session.nativeSessionId) {
|
|
76
|
+
sessions.push({ ...row, background });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (!turn) {
|
|
80
|
+
const sessionEvents = sessionFacts.filter(({ observation }) => {
|
|
81
|
+
const f = observation.fence;
|
|
82
|
+
return f.runId === undefined && f.nativeTurnId === undefined
|
|
83
|
+
&& f.receiptId === undefined && f.continuationId === undefined;
|
|
84
|
+
}).map(entry => entry.event);
|
|
85
|
+
const projection = projectRuntimeTaskEvents(fence, session.createdAt, sessionEvents);
|
|
86
|
+
const ready = session.status !== "ended" && projection.session === "ready"
|
|
87
|
+
&& projection.host !== "exited" && projection.run === "none"
|
|
88
|
+
&& projection.observer.status !== "unavailable" && projection.observer.status !== "degraded"
|
|
89
|
+
&& projection.goal !== "active" && binding.goal?.status !== "active"
|
|
90
|
+
&& Object.keys(projection.operations).length === 0;
|
|
91
|
+
sessions.push({ ...row, background, ...(background.length ? {
|
|
92
|
+
group: "background", reason: "Retained background execution is not settled."
|
|
93
|
+
} : session.status === "ended" || projection.session === "ended" ? {
|
|
94
|
+
group: "stopped", reason: "The selected Session ended with no submitted native input."
|
|
95
|
+
} : ready ? {
|
|
96
|
+
group: "idle", reason: "The selected Session is ready with no submitted native input."
|
|
97
|
+
} : {}) });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const exactEvents = sessionFacts.filter(({ observation }) => {
|
|
101
|
+
const observed = observation;
|
|
102
|
+
const f = observed.fence;
|
|
103
|
+
if (f.continuationId !== undefined || f.runId !== turn.runId)
|
|
104
|
+
return false;
|
|
105
|
+
if (f.receiptId === undefined && f.nativeTurnId === undefined
|
|
106
|
+
&& ["host.observed", "session.ended", "session.failed", "observer.health"].includes(observed.kind)) {
|
|
107
|
+
return observed.receivedAt >= turn.submittedAt;
|
|
108
|
+
}
|
|
109
|
+
if (f.receiptId !== undefined && f.receiptId !== turn.attemptId)
|
|
110
|
+
return false;
|
|
111
|
+
if (f.nativeTurnId !== undefined && f.nativeTurnId !== turn.nativeTurnId)
|
|
112
|
+
return false;
|
|
113
|
+
return f.receiptId === turn.attemptId
|
|
114
|
+
|| (turn.nativeTurnId !== undefined && f.nativeTurnId === turn.nativeTurnId);
|
|
115
|
+
}).map(entry => entry.event);
|
|
116
|
+
const projection = projectRuntimeTaskEvents({
|
|
117
|
+
...fence, ...(turn.runId ? { runId: turn.runId } : {}),
|
|
118
|
+
receiptId: turn.attemptId, ...(turn.nativeTurnId ? { nativeTurnId: turn.nativeTurnId } : {})
|
|
119
|
+
}, turn.submittedAt, exactEvents);
|
|
120
|
+
const health = classifyRuntimeHealth({ projection, now: input.now, policy,
|
|
121
|
+
semanticProgressAt: input.semanticProgressAt ?? turn.submittedAt });
|
|
122
|
+
const retry = providerRetryProjection(binding);
|
|
123
|
+
let group;
|
|
124
|
+
let reason = health.reason;
|
|
125
|
+
if (turn.status === "delivery-unknown") {
|
|
126
|
+
group = "unknown";
|
|
127
|
+
reason = "Original input delivery is unknown; do not replay it.";
|
|
128
|
+
}
|
|
129
|
+
else if (session.status === "ended") {
|
|
130
|
+
group = background.length ? "background" : "stopped";
|
|
131
|
+
reason = "The selected Session ended; Task acceptance and retained background execution are separate.";
|
|
132
|
+
}
|
|
133
|
+
else if (retry?.status === "waiting") {
|
|
134
|
+
group = "waiting";
|
|
135
|
+
reason = `Controller-owned Provider retry waiting (${retry.attempts}/${retry.limit}); next eligible ${retry.nextEligibleAt}. Existing work is preserved.`;
|
|
136
|
+
}
|
|
137
|
+
else if (["failed", "cancelled", "rejected"].includes(turn.status)) {
|
|
138
|
+
group = background.length ? "background" : "stopped";
|
|
139
|
+
reason = turn.terminalReason ?? "The selected native execution stopped; Task acceptance is separate.";
|
|
140
|
+
}
|
|
141
|
+
else if (turn.status === "completed") {
|
|
142
|
+
group = background.length ? "background" : "idle";
|
|
143
|
+
reason = background.length ? "Main Turn ended; background execution remains unsettled."
|
|
144
|
+
: "The native Turn ended; this is not Task completion.";
|
|
145
|
+
}
|
|
146
|
+
else if (turn.status !== "accepted") {
|
|
147
|
+
group = "waiting";
|
|
148
|
+
reason = "Waiting for native input acceptance.";
|
|
149
|
+
}
|
|
150
|
+
else if (projection.run === "waiting") {
|
|
151
|
+
group = "waiting";
|
|
152
|
+
}
|
|
153
|
+
else if (["broken", "stopped"].includes(health.layer)) {
|
|
154
|
+
group = "stopped";
|
|
155
|
+
}
|
|
156
|
+
else if (["completed", "failed", "cancelled"].includes(projection.run)) {
|
|
157
|
+
group = "unknown";
|
|
158
|
+
reason = "Native terminal and current input binding disagree; inspect the exact original input.";
|
|
159
|
+
}
|
|
160
|
+
else if (projection.observer.status === "unavailable") {
|
|
161
|
+
group = "unknown";
|
|
162
|
+
}
|
|
163
|
+
else if (health.layer === "diagnostic-needed") {
|
|
164
|
+
group = "diagnostic";
|
|
165
|
+
}
|
|
166
|
+
else if (health.runtimeIdleMs < policy.quietAfterMs && projection.activity.kind !== "provider") {
|
|
167
|
+
group = "active";
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
group = "quiet";
|
|
171
|
+
reason = "Accepted input has no recent structured activity; silence is not a proven deadlock.";
|
|
172
|
+
}
|
|
173
|
+
sessions.push({ ...row, group, reason, layer: health.layer, background,
|
|
174
|
+
lastActivityAt: projection.lastRuntimeActivityAt ?? null,
|
|
175
|
+
waitingReason: projection.waitingReason ?? null, operations: health.activeOperations });
|
|
176
|
+
}
|
|
177
|
+
// Retained, exactly fenced child observations can outlive a selected Session.
|
|
178
|
+
// Do not revive old foreground Turns or enumerate historical Sessions: only
|
|
179
|
+
// identities with still-unsettled child facts enter this current projection.
|
|
180
|
+
const retained = new Map();
|
|
181
|
+
for (const entry of observations) {
|
|
182
|
+
const fence = entry.observation.fence;
|
|
183
|
+
const key = observationIdentity(fence);
|
|
184
|
+
if (!fence.nativeSessionId || selectedIdentities.has(key))
|
|
185
|
+
continue;
|
|
186
|
+
const group = retained.get(key) ?? [];
|
|
187
|
+
group.push(entry);
|
|
188
|
+
retained.set(key, group);
|
|
189
|
+
}
|
|
190
|
+
for (const group of retained.values()) {
|
|
191
|
+
const first = group[0].observation;
|
|
192
|
+
const fence = first.fence;
|
|
193
|
+
const runs = new Map();
|
|
194
|
+
for (const entry of group) {
|
|
195
|
+
const runId = entry.observation.fence.runId;
|
|
196
|
+
const events = runs.get(runId) ?? [];
|
|
197
|
+
events.push(entry.event);
|
|
198
|
+
runs.set(runId, events);
|
|
199
|
+
}
|
|
200
|
+
const background = backgroundForRuns({
|
|
201
|
+
taskId: input.taskId, roleName: fence.roleName, agentId: fence.agentId,
|
|
202
|
+
driverId: fence.driverId, nativeSessionId: fence.nativeSessionId
|
|
203
|
+
}, first.receivedAt, runs);
|
|
204
|
+
if (!background.length)
|
|
205
|
+
continue;
|
|
206
|
+
sessions.push({
|
|
207
|
+
taskId: input.taskId, roleName: fence.roleName, agentId: fence.agentId,
|
|
208
|
+
driverId: fence.driverId,
|
|
209
|
+
nativeSessionId: fence.nativeSessionId, attemptId: null, nativeTurnId: null, runId: null,
|
|
210
|
+
group: "background", reason: "Retained child observations from a non-selected native Session; foreground execution is not inferred.",
|
|
211
|
+
layer: null, lastActivityAt: group.at(-1).observation.receivedAt,
|
|
212
|
+
semanticProgressAt: null, sourceUpdatedAt: group.at(-1).observation.receivedAt,
|
|
213
|
+
waitingReason: null, operations: [], background
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
const counts = {
|
|
217
|
+
active: 0, waiting: 0, quiet: 0, diagnostic: 0, unknown: 0, stopped: 0, idle: 0, background: 0
|
|
218
|
+
};
|
|
219
|
+
for (const session of sessions)
|
|
220
|
+
counts[session.group]++;
|
|
221
|
+
return { scope: "task-recorded-native-activity", taskId: input.taskId,
|
|
222
|
+
readAt: input.now.toISOString(), counts, sessions,
|
|
223
|
+
coverage: "Selected Sessions and retained, exactly fenced child observations only; not a live process or complete historical Session inventory." };
|
|
224
|
+
}
|
|
225
|
+
function backgroundForRuns(fence, since, groups) {
|
|
226
|
+
return [...groups].flatMap(([runId, events]) => {
|
|
227
|
+
const projection = projectRuntimeTaskEvents({ ...fence, ...(runId === undefined ? {} : { runId }) }, since, events);
|
|
228
|
+
return [
|
|
229
|
+
...Object.entries(projection.continuations)
|
|
230
|
+
.filter(([, child]) => child.execution !== "quiescent" || child.identityConflict)
|
|
231
|
+
.map(([id, child]) => ({ id: `${runId ?? "native"}/${id}`, runId: runId ?? null,
|
|
232
|
+
execution: child.identityConflict ? "unknown" : child.execution })),
|
|
233
|
+
// Drivers may emit both forms for one child. This fold is already
|
|
234
|
+
// fenced to Task/Role/Agent/driver/Session/Run; continuation is richer.
|
|
235
|
+
...Object.entries(projection.operations).filter(([id, operation]) => operation.kind === "subagent" && !Object.hasOwn(projection.continuations, id))
|
|
236
|
+
.map(([id]) => ({ id: `${runId ?? "native"}/operation:${id}`, runId: runId ?? null,
|
|
237
|
+
execution: "unsettled" }))
|
|
238
|
+
];
|
|
239
|
+
});
|
|
240
|
+
}
|
|
@@ -5,6 +5,7 @@ import { currentWorkItemExecutionGroup } from "../workItem/workItem.js";
|
|
|
5
5
|
import { mailboxBatches } from "../coordination/workMailbox.js";
|
|
6
6
|
import { actionableExecutionLaneRecoveries, summarizeExecutionGroupHealth } from "../execution/executionHealth.js";
|
|
7
7
|
import { buildTaskObservabilityProjection } from "./taskObservabilityProjection.js";
|
|
8
|
+
import { projectTaskSessions } from "../runtime/taskSessions.js";
|
|
8
9
|
import { isRoleRunStalled, latestStallProgressAt } from "./roleRunStall.js";
|
|
9
10
|
import { resolveRuntimeHealth } from "../config/yuiConfig.js";
|
|
10
11
|
import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runtime/sessionTokenMetrics.js";
|
|
@@ -13,7 +14,7 @@ import { projectSessionTokenMetrics, resolveSessionTokenIdentity } from "../runt
|
|
|
13
14
|
* aggregate. This function only reads; it never starts a Controller, queues a
|
|
14
15
|
* wake, writes a Message, or mutates any record.
|
|
15
16
|
*/
|
|
16
|
-
export function buildTaskExecutionProjection(store, taskId, now = new Date()) {
|
|
17
|
+
export function buildTaskExecutionProjection(store, taskId, now = new Date(), nativeSessions) {
|
|
17
18
|
const task = store.getTask(taskId);
|
|
18
19
|
if (task === null)
|
|
19
20
|
return null;
|
|
@@ -23,6 +24,7 @@ export function buildTaskExecutionProjection(store, taskId, now = new Date()) {
|
|
|
23
24
|
const workItems = store.listWorkItems(taskId);
|
|
24
25
|
const reviewRounds = store.listReviewRounds(taskId);
|
|
25
26
|
const runs = operationalTaskRecords(usageRuns, events, "run");
|
|
27
|
+
const brief = store.getTaskBrief(taskId);
|
|
26
28
|
const leaderMailbox = store.getWorkMailbox({
|
|
27
29
|
kind: "role",
|
|
28
30
|
taskId,
|
|
@@ -36,6 +38,12 @@ export function buildTaskExecutionProjection(store, taskId, now = new Date()) {
|
|
|
36
38
|
});
|
|
37
39
|
return projectTaskExecution({
|
|
38
40
|
task,
|
|
41
|
+
jobs: store.listDurableJobs(taskId),
|
|
42
|
+
nativeSessions: nativeSessions ?? projectTaskSessions({
|
|
43
|
+
taskId, sessionSets: store.listRoleSessionSets(taskId), events, now,
|
|
44
|
+
...(brief?.updatedAt === undefined ? {} : { semanticProgressAt: brief.updatedAt }),
|
|
45
|
+
policy: resolveRuntimeHealth(store.getConfig().runtimeHealth)
|
|
46
|
+
}),
|
|
39
47
|
usageFacts: store.readTaskUsageFacts(taskId),
|
|
40
48
|
roles,
|
|
41
49
|
runs,
|
|
@@ -56,7 +64,7 @@ export function buildTaskExecutionProjection(store, taskId, now = new Date()) {
|
|
|
56
64
|
changeSets: store.listChangeSets(taskId),
|
|
57
65
|
integrations: store.listIntegrationAttempts(taskId),
|
|
58
66
|
events,
|
|
59
|
-
brief
|
|
67
|
+
brief,
|
|
60
68
|
pendingWakeup: store.getPendingWakeup(taskId),
|
|
61
69
|
leaderMailbox,
|
|
62
70
|
leaderFailure: store.getLeaderFailure(taskId),
|
|
@@ -118,8 +126,27 @@ export function projectTaskExecution(facts) {
|
|
|
118
126
|
now
|
|
119
127
|
});
|
|
120
128
|
const laneRecovery = actionableExecutionLaneRecoveries(groupSummaries)[0];
|
|
129
|
+
const jobs = facts.jobs ?? [];
|
|
130
|
+
const native = facts.nativeSessions;
|
|
131
|
+
const activity = {
|
|
132
|
+
jobs: {
|
|
133
|
+
queued: jobs.filter(job => job.status === "queued").length,
|
|
134
|
+
running: jobs.filter(job => job.status === "running").length,
|
|
135
|
+
unknown: jobs.filter(job => job.status === "unknown-needs-attention").length
|
|
136
|
+
},
|
|
137
|
+
openInputs: inputRequests.filter(input => input.status === "open").length,
|
|
138
|
+
native: {
|
|
139
|
+
active: (native?.counts.active ?? 0) + (native?.counts.quiet ?? 0),
|
|
140
|
+
waiting: native?.counts.waiting ?? 0,
|
|
141
|
+
unknown: (native?.counts.unknown ?? 0) + (native?.counts.diagnostic ?? 0)
|
|
142
|
+
+ (native?.sessions.reduce((sum, session) => sum
|
|
143
|
+
+ session.background.filter(child => child.execution === "unknown").length, 0) ?? 0),
|
|
144
|
+
background: native?.sessions.reduce((sum, session) => sum + session.background.length, 0) ?? 0
|
|
145
|
+
}
|
|
146
|
+
};
|
|
121
147
|
const render = (input) => projection({
|
|
122
148
|
...input,
|
|
149
|
+
activity,
|
|
123
150
|
executionGroups: groupSummaries,
|
|
124
151
|
observability
|
|
125
152
|
});
|
|
@@ -128,10 +155,12 @@ export function projectTaskExecution(facts) {
|
|
|
128
155
|
const known = facts.runDelivery?.[run.id];
|
|
129
156
|
if (known !== undefined && known !== "unobserved")
|
|
130
157
|
return known;
|
|
131
|
-
const observed = events.map(runtimeObservationFromTaskEvent).filter((event) => event?.fence.runId === run.id && event.
|
|
158
|
+
const observed = events.map(runtimeObservationFromTaskEvent).filter((event) => event?.fence.runId === run.id && event.fence.taskId === task.id
|
|
159
|
+
&& event.fence.roleName === run.roleName && event.fence.agentId === run.effective.agentId
|
|
160
|
+
&& event.authority !== "transport"
|
|
132
161
|
&& ["turn.accepted", "turn.completed", "turn.failed", "turn.cancelled"].includes(event.kind)).at(-1);
|
|
133
162
|
return observed === undefined || observed === null ? "unobserved"
|
|
134
|
-
: observed.kind.slice("
|
|
163
|
+
: observed.kind.slice("turn.".length);
|
|
135
164
|
};
|
|
136
165
|
const runHasSession = (run) => roleSessions.some((session) => (session.roleName === run.roleName
|
|
137
166
|
&& session.agentId === run.effective.agentId
|
|
@@ -206,7 +235,7 @@ export function projectTaskExecution(facts) {
|
|
|
206
235
|
const activeLeader = activeRuns.find((run) => run.roleName === "leader");
|
|
207
236
|
const pendingDeliveryRuns = activeRuns.filter((run) => deliveryOf(run) !== "accepted");
|
|
208
237
|
const hasPendingLeaderWork = pendingWakeup !== null
|
|
209
|
-
|| leaderMailbox?.pending
|
|
238
|
+
|| leaderMailbox?.pending != null;
|
|
210
239
|
const recoveryPending = isRecoveryPending(pendingWakeup, leaderMailbox, leaderFailure);
|
|
211
240
|
const providerRecoveries = (facts.providerRetries ?? []).filter(retry => retry.status === "waiting" || retry.status === "in-flight");
|
|
212
241
|
const recoveringRunIds = new Set(providerRecoveries.flatMap(retry => [retry.previousRunId, retry.successorRunId].filter((id) => id !== undefined)));
|
|
@@ -257,13 +286,30 @@ export function projectTaskExecution(facts) {
|
|
|
257
286
|
summary: openInputs[0].question,
|
|
258
287
|
reason: "open-input-request",
|
|
259
288
|
monitoring,
|
|
260
|
-
failClosed:
|
|
289
|
+
failClosed: activity.jobs.unknown > 0 || activity.native.unknown > 0,
|
|
261
290
|
activeRuns: activeRunViews,
|
|
262
291
|
attention,
|
|
263
292
|
blockers,
|
|
264
293
|
pendingWakeup
|
|
265
294
|
});
|
|
266
295
|
}
|
|
296
|
+
if (activity.jobs.unknown > 0 || activity.native.unknown > 0) {
|
|
297
|
+
return render({
|
|
298
|
+
task, status: "attention", owner: "leader", action: "inspect-attention",
|
|
299
|
+
summary: `${activity.jobs.unknown} Job(s) have unknown outcomes; ${activity.native.unknown} native observation(s) need attention. Other parallel activity remains visible; do not infer completion or replay.`,
|
|
300
|
+
reason: "parallel-execution-unknown", monitoring, failClosed: true,
|
|
301
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
const nativeWait = native?.sessions.find(session => session.group === "waiting" && ["user", "permission"].includes(session.waitingReason ?? ""));
|
|
305
|
+
if (nativeWait) {
|
|
306
|
+
return render({
|
|
307
|
+
task, status: "waiting-user", owner: "user", action: "inspect-attention",
|
|
308
|
+
summary: `Native Session ${nativeWait.roleName} is waiting for ${nativeWait.waitingReason}; other activity remains visible.`,
|
|
309
|
+
reason: "native-input-wait", monitoring, failClosed: false,
|
|
310
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
311
|
+
});
|
|
312
|
+
}
|
|
267
313
|
if (laneRecovery !== undefined && (laneRecovery.runId === undefined || !recoveringRunIds.has(laneRecovery.runId))) {
|
|
268
314
|
return render({
|
|
269
315
|
task,
|
|
@@ -382,6 +428,15 @@ export function projectTaskExecution(facts) {
|
|
|
382
428
|
pendingWakeup
|
|
383
429
|
});
|
|
384
430
|
}
|
|
431
|
+
if (native?.sessions.some(session => session.roleName === "leader"
|
|
432
|
+
&& ["active", "quiet"].includes(session.group))) {
|
|
433
|
+
return render({
|
|
434
|
+
task, status: "working", owner: "leader", action: "advance-task",
|
|
435
|
+
summary: "Leader native input is accepted; managed AgentRuns and parallel background work remain separate facts.",
|
|
436
|
+
reason: "leader-native-turn", monitoring, failClosed: false,
|
|
437
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
438
|
+
});
|
|
439
|
+
}
|
|
385
440
|
if (hasPendingLeaderWork) {
|
|
386
441
|
return render({
|
|
387
442
|
task,
|
|
@@ -449,6 +504,22 @@ export function projectTaskExecution(facts) {
|
|
|
449
504
|
pendingWakeup
|
|
450
505
|
});
|
|
451
506
|
}
|
|
507
|
+
if (activity.jobs.queued + activity.jobs.running > 0) {
|
|
508
|
+
return render({
|
|
509
|
+
task, status: "waiting-on-jobs", owner: "none", action: "wait-for-jobs",
|
|
510
|
+
summary: `${activity.jobs.running} background Job(s) running; ${activity.jobs.queued} queued. Leader is not currently executing.`,
|
|
511
|
+
reason: "background-jobs", monitoring, failClosed: false,
|
|
512
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
if (activity.native.active + activity.native.waiting + activity.native.background > 0) {
|
|
516
|
+
return render({
|
|
517
|
+
task, status: "waiting-on-agents", owner: "none", action: "wait-for-agents",
|
|
518
|
+
summary: "Recorded native execution or retained background activity remains unsettled; this is separate from managed AgentRuns.",
|
|
519
|
+
reason: "native-execution", monitoring, failClosed: false,
|
|
520
|
+
activeRuns: activeRunViews, attention, blockers, pendingWakeup
|
|
521
|
+
});
|
|
522
|
+
}
|
|
452
523
|
if (candidateReady || unresolvedIntegration) {
|
|
453
524
|
return render({
|
|
454
525
|
task,
|
|
@@ -496,6 +567,7 @@ function projection(input) {
|
|
|
496
567
|
monitoring: input.monitoring,
|
|
497
568
|
failClosed: input.failClosed,
|
|
498
569
|
activeRuns: input.activeRuns,
|
|
570
|
+
activity: input.activity,
|
|
499
571
|
executionGroups: input.executionGroups ?? [],
|
|
500
572
|
observability: input.observability,
|
|
501
573
|
attention: input.attention,
|