@zq-silk/yui 0.6.16 → 0.7.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 -7
- package/dist/cli.js +12 -33
- package/dist/commands/executionAuditCommands.js +19 -0
- package/dist/commands/globalRoleCommands.js +70 -0
- package/dist/commands/taskActor.js +3 -2
- package/dist/commands/taskCommands.js +160 -41
- package/dist/commands/taskContextCommand.js +1 -1
- package/dist/commands/taskInputCommands.js +3 -2
- package/dist/commands/taskRoleRuntimeStatus.js +3 -3
- package/dist/context/contextSnapshot.js +228 -0
- package/dist/context/roleSessionContext.js +3 -1
- package/dist/context/runContextContract.js +162 -0
- package/dist/context/runContextPack.js +322 -0
- package/dist/context/sessionBootstrapManifest.js +81 -0
- package/dist/context/sessionProtocolIdentity.js +23 -0
- package/dist/controller/agentRuntimeObserver.js +6 -1
- package/dist/controller/controller.js +4 -3
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -145
- package/dist/controller/jobControl.js +2 -1
- package/dist/controller/runtime.js +83 -0
- package/dist/controller/runtimeHookRunFence.js +6 -2
- package/dist/controller/sessionOwnerReconciliation.js +5 -0
- package/dist/executor/agentAdapter.js +7 -2
- package/dist/executor/agentExecutor.js +23 -0
- package/dist/executor/effectiveLaunch.js +24 -0
- package/dist/executor/executorRegistry.js +7 -1
- package/dist/executor/fileRoleLaunchPlanner.js +73 -27
- package/dist/lifecycle/exactRunTerminalization.js +2 -3
- package/dist/lifecycle/providerErrorClass.js +8 -3
- package/dist/observability/executionAudit.js +87 -2
- package/dist/repository/taskWorkspacePreparer.js +2 -2
- package/dist/run/agentRun.js +101 -16
- package/dist/run/providerRetry.js +167 -56
- package/dist/run/providerRetryConfig.js +5 -1
- package/dist/run/runControlRequest.js +50 -0
- package/dist/runtime/agentDriver.js +47 -0
- package/dist/runtime/agentHost.js +327 -0
- package/dist/runtime/builtinAgentDrivers.js +23 -1
- package/dist/runtime/builtinTranscriptObserver.js +4 -0
- package/dist/runtime/builtinTranscriptUsage.js +2 -0
- package/dist/runtime/exactControlPlane.js +2 -2
- package/dist/runtime/globalProcessExitStore.js +38 -0
- package/dist/runtime/launchBroker.js +95 -0
- package/dist/runtime/processExitObservation.js +60 -0
- package/dist/runtime/runtimeBinding.js +6 -0
- package/dist/runtime/runtimeObservation.js +27 -6
- package/dist/runtime/runtimeProjection.js +6 -3
- package/dist/runtime/runtimeStopReceipt.js +42 -0
- package/dist/runtime/sessionTerminationGuard.js +13 -0
- package/dist/runtime/tmuxAdapters.js +203 -220
- package/dist/scheduler/activeRoleRunDelivery.js +24 -3
- package/dist/scheduler/leaderWakeupProcessor.js +18 -60
- package/dist/scheduler/roleRunLiveness.js +61 -27
- package/dist/storage/migration/productionRegistry.js +264 -0
- package/dist/storage/sqliteSchema.js +23 -2
- package/dist/storage/sqliteStore.js +39 -2
- package/dist/storage/taskStore.js +54 -5
- package/dist/storage/upgrade/recordVersions.js +3 -1
- package/dist/storage/upgrade/sqliteStateMigration.js +10 -0
- package/dist/task/taskRecordReference.js +1 -0
- package/dist/tmux/tmuxManager.js +15 -4
- package/dist/web/assets/client/components.js +1 -1
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +10 -5
- package/skills/yui-operator/SKILL.md +4 -0
- package/skills/yui-reviewer/SKILL.md +4 -0
- package/skills/yui-runtime/SKILL.md +61 -0
- package/skills/yui-worker/SKILL.md +82 -218
- package/dist/executor/managedClaudeRunner.js +0 -121
|
@@ -12,6 +12,7 @@ import { createHash } from "node:crypto";
|
|
|
12
12
|
import { resolve, sep } from "node:path";
|
|
13
13
|
import { acknowledgeUnknownDurableJob, createDurableJob, durableJobIdempotencyKey, isDurableJobTerminal, requestDurableJobCancel, retryDurableJobIdempotencyKey } from "../job/durableJob.js";
|
|
14
14
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
15
|
+
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
15
16
|
export function createDurableJobControl(store) {
|
|
16
17
|
return {
|
|
17
18
|
startJob(params, now) {
|
|
@@ -350,7 +351,7 @@ function assertLeaderActionRun(store, taskId, assertion) {
|
|
|
350
351
|
if (sessions === null
|
|
351
352
|
|| sessions.inFlight === null
|
|
352
353
|
|| sessions.inFlight.runId !== run.id
|
|
353
|
-
|| sessions.inFlight.receiptId !==
|
|
354
|
+
|| sessions.inFlight.receiptId !== agentRunDeliveryReceiptId(run)) {
|
|
354
355
|
throw jobControlError("UNAUTHORIZED", "job.acknowledge Leader Run is not in flight.");
|
|
355
356
|
}
|
|
356
357
|
}
|
|
@@ -34,6 +34,12 @@ import { ResourceInventoryClient } from "./resourceInventoryRpc.js";
|
|
|
34
34
|
import { createResourceAutoGc } from "../resources/autoResourceGc.js";
|
|
35
35
|
import { createRuntimeResourceActivityTracker } from "./resourceInventory.js";
|
|
36
36
|
import { SessionOwnerReconciliation } from "./sessionOwnerReconciliation.js";
|
|
37
|
+
import { launchBrokerForHome } from "../runtime/launchBroker.js";
|
|
38
|
+
import { classifyRuntimeProcessExit, validateRuntimeProcessExitObservation } from "../runtime/processExitObservation.js";
|
|
39
|
+
import { appendGlobalProcessExitObservation } from "../runtime/globalProcessExitStore.js";
|
|
40
|
+
import { builtinAgentDriverRegistry } from "../runtime/builtinAgentDrivers.js";
|
|
41
|
+
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
42
|
+
import { createTaskEvent } from "../event/taskEvent.js";
|
|
37
43
|
/** Refreshes only the exact Task runtime generation folded by the event transaction. */
|
|
38
44
|
export function refreshAppliedTaskRuntimeDescriptor(store, planner, input) {
|
|
39
45
|
if (input.launchId === undefined)
|
|
@@ -521,6 +527,83 @@ export function createRuntimeLifecycleDispatcher(store, schedulerStore, sessionH
|
|
|
521
527
|
});
|
|
522
528
|
const lifecycleTails = new Map();
|
|
523
529
|
return async (method, params) => {
|
|
530
|
+
if (method === "runtime.process-exit-observe") {
|
|
531
|
+
const observation = validateRuntimeProcessExitObservation(params);
|
|
532
|
+
const run = observation.taskId === undefined || observation.runId === undefined
|
|
533
|
+
? null
|
|
534
|
+
: store.getAgentRun(observation.taskId, observation.runId);
|
|
535
|
+
const globalRole = observation.taskId === undefined
|
|
536
|
+
? store.getGlobalRole(observation.roleName)
|
|
537
|
+
: null;
|
|
538
|
+
const adapterId = run?.effective.adapterId
|
|
539
|
+
?? globalRole?.agentBindings[globalRole.activeAgentId]?.adapterId;
|
|
540
|
+
const driver = adapterId === undefined
|
|
541
|
+
? null
|
|
542
|
+
: builtinAgentDriverRegistry().findByAdapterId(adapterId);
|
|
543
|
+
const turnTerminalObserved = observation.taskId !== undefined
|
|
544
|
+
&& observation.runId !== undefined
|
|
545
|
+
&& store.listEvents(observation.taskId).some((event) => {
|
|
546
|
+
const runtime = runtimeObservationFromTaskEvent(event);
|
|
547
|
+
return runtime !== null
|
|
548
|
+
&& runtime.fence.runId === observation.runId
|
|
549
|
+
&& ["turn.completed", "turn.failed", "turn.cancelled"].includes(runtime.kind)
|
|
550
|
+
&& Date.parse(runtime.receivedAt) <= Date.parse(observation.observedAt);
|
|
551
|
+
});
|
|
552
|
+
const turnFailureObserved = observation.taskId !== undefined
|
|
553
|
+
&& observation.runId !== undefined
|
|
554
|
+
&& store.listEvents(observation.taskId).some((event) => {
|
|
555
|
+
const runtime = runtimeObservationFromTaskEvent(event);
|
|
556
|
+
return runtime !== null
|
|
557
|
+
&& runtime.fence.runId === observation.runId
|
|
558
|
+
&& runtime.fence.launchId === observation.launchId
|
|
559
|
+
&& runtime.kind === "turn.failed"
|
|
560
|
+
&& Date.parse(runtime.receivedAt) <= Date.parse(observation.observedAt);
|
|
561
|
+
});
|
|
562
|
+
const classification = classifyRuntimeProcessExit(observation, {
|
|
563
|
+
...(driver === null
|
|
564
|
+
? {}
|
|
565
|
+
: { childLifecycle: driver.capabilities.lifecycle.providerProcess }),
|
|
566
|
+
turnTerminalObserved,
|
|
567
|
+
turnFailureObserved
|
|
568
|
+
});
|
|
569
|
+
if (observation.taskId === undefined) {
|
|
570
|
+
const recorded = appendGlobalProcessExitObservation(store.rootDirectory(), observation, classification);
|
|
571
|
+
return { recorded, scope: "global", classification };
|
|
572
|
+
}
|
|
573
|
+
const recorded = store.transaction((tx) => {
|
|
574
|
+
if (tx.getTask(observation.taskId) === null) {
|
|
575
|
+
throw applicationError("INVALID_PARAMS", `Task not found: ${observation.taskId}.`);
|
|
576
|
+
}
|
|
577
|
+
const duplicate = tx.listEvents(observation.taskId).some((event) => (event.type === "runtime.process-exit-observed"
|
|
578
|
+
&& event.payload.observationId === observation.observationId));
|
|
579
|
+
if (duplicate)
|
|
580
|
+
return false;
|
|
581
|
+
tx.saveEvent(observation.taskId, createTaskEvent(tx.nextEventId(observation.taskId), observation.taskId, "runtime.process-exit-observed", {
|
|
582
|
+
observationId: observation.observationId,
|
|
583
|
+
processKind: observation.processKind,
|
|
584
|
+
roleName: observation.roleName,
|
|
585
|
+
launchId: observation.launchId,
|
|
586
|
+
observedAt: observation.observedAt,
|
|
587
|
+
classification,
|
|
588
|
+
observation: JSON.stringify(observation)
|
|
589
|
+
}, new Date(observation.observedAt)));
|
|
590
|
+
return true;
|
|
591
|
+
});
|
|
592
|
+
return { recorded, classification };
|
|
593
|
+
}
|
|
594
|
+
if (method === "runtime.launch-redeem") {
|
|
595
|
+
if (params === null || typeof params !== "object" || Array.isArray(params)) {
|
|
596
|
+
throw applicationError("INVALID_PARAMS", "Launch redemption params are invalid.");
|
|
597
|
+
}
|
|
598
|
+
const launchId = params.launchId;
|
|
599
|
+
const ticket = params.ticket;
|
|
600
|
+
const hostPid = params.hostPid;
|
|
601
|
+
if (typeof launchId !== "string" || typeof ticket !== "string"
|
|
602
|
+
|| !Number.isSafeInteger(hostPid) || hostPid <= 0) {
|
|
603
|
+
throw applicationError("INVALID_PARAMS", "Launch redemption identity is invalid.");
|
|
604
|
+
}
|
|
605
|
+
return launchBrokerForHome(store.rootDirectory()).redeem(launchId, ticket);
|
|
606
|
+
}
|
|
524
607
|
if (method === "runtime.replace-agent-environment") {
|
|
525
608
|
if (environmentRefresher === undefined) {
|
|
526
609
|
throw applicationError("METHOD_NOT_FOUND", "Controller method was not found.");
|
|
@@ -3,7 +3,7 @@ import { hasRuntimeCleanupObligation, isRuntimeLaunchReservation, runtimeLifecyc
|
|
|
3
3
|
import { nativeSessionIdForLaunch } from "../runtime/preallocatedNativeSession.js";
|
|
4
4
|
import { runtimeObservationFromTaskEvent } from "../runtime/runtimeObservation.js";
|
|
5
5
|
import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR, assertExactTaskRuntimeEnvironment, exactControlPlaneDigest, parseExactControlPlaneDescriptor, refreshReusedTaskRuntimeDescriptorSource } from "../runtime/exactControlPlane.js";
|
|
6
|
-
import {
|
|
6
|
+
import { agentRunDeliveryReceiptId } from "../run/agentRun.js";
|
|
7
7
|
/**
|
|
8
8
|
* Resolves turn identity from the current durable in-flight fence. The one
|
|
9
9
|
* exception is a Driver-declared startup Session Hook, which can arrive before
|
|
@@ -98,6 +98,9 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
98
98
|
&& executionRef?.type === "run"
|
|
99
99
|
&& executionRef.taskId === taskId
|
|
100
100
|
&& executionRef.id === startupRunId;
|
|
101
|
+
const startupRun = startupRunId === undefined
|
|
102
|
+
? null
|
|
103
|
+
: store.getAgentRun(taskId, startupRunId);
|
|
101
104
|
const preallocatedStartup = options.startupSession === "preallocated"
|
|
102
105
|
&& expectedNativeSessionId !== undefined
|
|
103
106
|
&& session === undefined
|
|
@@ -137,7 +140,8 @@ export function resolveRuntimeHookRunFence(environment, adapterId, payloadNative
|
|
|
137
140
|
&& inFlight !== null
|
|
138
141
|
&& inFlight !== undefined
|
|
139
142
|
&& (inFlight.runId !== startupRunId
|
|
140
|
-
||
|
|
143
|
+
|| startupRun === null
|
|
144
|
+
|| inFlight.receiptId !== agentRunDeliveryReceiptId(startupRun))) {
|
|
141
145
|
throw new Error("Runtime observation Hook has no matching durable in-flight Run.");
|
|
142
146
|
}
|
|
143
147
|
const runId = acceptedBinding?.fence.runId
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
4
4
|
import { createSessionOwnerIdentity, discoverProviderRootByLaunchEnv, isLinuxProcessLive, listLaunchFencedProcesses, listOwnedProcessTree, readLinuxProcessIdentity, reconcileSessionOwners, terminateSessionOwners } from "../runtime/index.js";
|
|
5
|
+
import { removeRuntimeStopReceipt, writeRuntimeStopReceipt } from "../runtime/runtimeStopReceipt.js";
|
|
5
6
|
import { tmuxSocketDirectory } from "../tmux/tmuxSocketEndpoint.js";
|
|
6
7
|
import { yuiTmuxServerName, yuiTmuxSessionName, yuiTmuxTarget } from "../tmux/tmuxManager.js";
|
|
7
8
|
/**
|
|
@@ -148,12 +149,16 @@ export class SessionOwnerReconciliation {
|
|
|
148
149
|
if (result.outcome === "stop-confirmed") {
|
|
149
150
|
for (const record of result.confirmed) {
|
|
150
151
|
this.#store.removeSessionOwner(record.launchId);
|
|
152
|
+
removeRuntimeStopReceipt(this.#home, record.launchId);
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
return result;
|
|
154
156
|
}
|
|
155
157
|
#recordTerminationEvent(event) {
|
|
156
158
|
const owner = event.owner;
|
|
159
|
+
if (event.stage === "stop-requested" && event.launchId !== undefined) {
|
|
160
|
+
writeRuntimeStopReceipt(this.#home, event.launchId, event.at);
|
|
161
|
+
}
|
|
157
162
|
if (owner.scope !== "task")
|
|
158
163
|
return;
|
|
159
164
|
try {
|
|
@@ -126,7 +126,7 @@ class CodexAdapter extends BaseAdapter {
|
|
|
126
126
|
"--config",
|
|
127
127
|
`projects={${JSON.stringify(resolve(input.workspace))}={trust_level="trusted"}}`
|
|
128
128
|
];
|
|
129
|
-
const instructions = [
|
|
129
|
+
const instructions = input.sessionManifestPath === undefined ? [
|
|
130
130
|
input.developerInstructions,
|
|
131
131
|
...(input.skills === undefined || input.skills.length === 0
|
|
132
132
|
? []
|
|
@@ -134,7 +134,9 @@ class CodexAdapter extends BaseAdapter {
|
|
|
134
134
|
"Yui Role Skills are available at the paths below. Before performing work governed by one, read and follow its SKILL.md on demand; do not treat this list as a user message.",
|
|
135
135
|
...input.skills.map((skill) => `- ${skill.id}: ${skill.path}/SKILL.md`)
|
|
136
136
|
])
|
|
137
|
-
].filter((value) => value !== undefined && value.trim().length > 0)
|
|
137
|
+
].filter((value) => value !== undefined && value.trim().length > 0) : [
|
|
138
|
+
`Yui managed Session. Read and follow the Session Manifest at ${input.sessionManifestPath} (digest ${input.sessionManifestDigest ?? "unknown"}). Load each Skill and Role Profile by its manifest path before acting; Task content is available only through the manifest's exact Context API.`
|
|
139
|
+
];
|
|
138
140
|
if (instructions.length === 0)
|
|
139
141
|
return workspaceTrust;
|
|
140
142
|
const nativeInstructions = input.codexDeveloperInstructions
|
|
@@ -236,6 +238,9 @@ class ClaudeAdapter extends BaseAdapter {
|
|
|
236
238
|
};
|
|
237
239
|
}
|
|
238
240
|
launchContextArgs(input) {
|
|
241
|
+
if (input.sessionManifestPath !== undefined) {
|
|
242
|
+
return ["--append-system-prompt-file", input.sessionManifestPath];
|
|
243
|
+
}
|
|
239
244
|
const sections = [
|
|
240
245
|
input.developerInstructions,
|
|
241
246
|
...(input.skills ?? []).map((skill) => [
|
|
@@ -412,6 +412,29 @@ export function recordObservedTaskRoleCompletion(set, completion) {
|
|
|
412
412
|
turnId: observed.turnId
|
|
413
413
|
}, new Date(observed.observedAt));
|
|
414
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Rebinds the same active Run to a bounded control-input receipt while keeping
|
|
417
|
+
* its observed native Turn completion unsettled. No new Run fence is created.
|
|
418
|
+
*/
|
|
419
|
+
export function rebindTaskRoleRunControlReceipt(set, input, now) {
|
|
420
|
+
validateRoleSessionSet(set);
|
|
421
|
+
assertTaskRoleSessionSet(set);
|
|
422
|
+
const inFlight = set.inFlight;
|
|
423
|
+
if (inFlight === null
|
|
424
|
+
|| inFlight.agentId !== input.agentId
|
|
425
|
+
|| inFlight.runId !== input.runId) {
|
|
426
|
+
throw new Error("Workflow outcome control request does not match the unsettled Run.");
|
|
427
|
+
}
|
|
428
|
+
const timestamp = requireDate(now, "Workflow outcome control receipt timestamp");
|
|
429
|
+
return validateRoleSessionSet({
|
|
430
|
+
...set,
|
|
431
|
+
inFlight: {
|
|
432
|
+
...inFlight,
|
|
433
|
+
receiptId: requireSafeIdentity(input.receiptId, "Workflow outcome control receipt id")
|
|
434
|
+
},
|
|
435
|
+
updatedAt: timestamp
|
|
436
|
+
});
|
|
437
|
+
}
|
|
415
438
|
export function clearTaskRoleRun(set, fence, clearedAt) {
|
|
416
439
|
validateRoleSessionSet(set);
|
|
417
440
|
assertTaskRoleSessionSet(set);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { isDeepStrictEqual } from "node:util";
|
|
2
2
|
import { validateManagedWorkspace } from "../worktree/managedWorkspace.js";
|
|
3
3
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
4
|
+
import { roleSessionKind } from "../context/roleSessionContext.js";
|
|
5
|
+
import { SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION, sessionManifestCompatibilityDigest } from "../context/sessionProtocolIdentity.js";
|
|
4
6
|
export function resolveEffectiveLaunch(input) {
|
|
5
7
|
validateDesiredRole(input.role);
|
|
6
8
|
const binding = input.role.agentBindings[input.role.activeAgentId];
|
|
@@ -15,6 +17,10 @@ export function resolveEffectiveLaunch(input) {
|
|
|
15
17
|
writeProjectIds,
|
|
16
18
|
workspace,
|
|
17
19
|
context: snapshotContext(input.role),
|
|
20
|
+
contextProtocolVersion: SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION,
|
|
21
|
+
sessionManifestCompatibilityDigest: sessionManifestCompatibilityDigest(input.role.name, roleSessionKind(input.role, "taskId" in input.role
|
|
22
|
+
? { scope: "task", taskId: input.role.taskId }
|
|
23
|
+
: { scope: "global" }, input.purpose), input.role),
|
|
18
24
|
...(input.purpose === "review"
|
|
19
25
|
? {
|
|
20
26
|
reviewRoundId: identity(input.reviewRoundId ?? "", "ReviewRound id"),
|
|
@@ -152,6 +158,18 @@ export function validateEffectiveLaunchSnapshot(snapshot) {
|
|
|
152
158
|
identity(snapshot.reviewRoundId, "Effective ReviewRound id");
|
|
153
159
|
commit(snapshot.reviewBaseCommit, "Effective review base commit");
|
|
154
160
|
}
|
|
161
|
+
if (snapshot.contextProtocolVersion !== undefined
|
|
162
|
+
&& snapshot.contextProtocolVersion !== SESSION_BOOTSTRAP_MANIFEST_SCHEMA_VERSION) {
|
|
163
|
+
throw new Error("Effective launch context protocol version is unsupported.");
|
|
164
|
+
}
|
|
165
|
+
if (snapshot.sessionManifestCompatibilityDigest !== undefined
|
|
166
|
+
&& !/^[a-f0-9]{64}$/u.test(snapshot.sessionManifestCompatibilityDigest)) {
|
|
167
|
+
throw new Error("Effective launch Session Manifest compatibility digest is invalid.");
|
|
168
|
+
}
|
|
169
|
+
if ((snapshot.contextProtocolVersion === undefined)
|
|
170
|
+
!== (snapshot.sessionManifestCompatibilityDigest === undefined)) {
|
|
171
|
+
throw new Error("Effective launch Context protocol compatibility identity is incomplete.");
|
|
172
|
+
}
|
|
155
173
|
validateWorkspace(snapshot.workspace);
|
|
156
174
|
cloneContext(snapshot.context);
|
|
157
175
|
const config = effectiveLaunchConfigUnchecked(snapshot);
|
|
@@ -204,6 +222,12 @@ function snapshotFromConfig(input) {
|
|
|
204
222
|
writeProjectIds: [...input.writeProjectIds],
|
|
205
223
|
workspace: cloneWorkspace(input.workspace),
|
|
206
224
|
context: cloneContext(input.context),
|
|
225
|
+
...(input.contextProtocolVersion === undefined
|
|
226
|
+
? {}
|
|
227
|
+
: { contextProtocolVersion: input.contextProtocolVersion }),
|
|
228
|
+
...(input.sessionManifestCompatibilityDigest === undefined
|
|
229
|
+
? {}
|
|
230
|
+
: { sessionManifestCompatibilityDigest: input.sessionManifestCompatibilityDigest }),
|
|
207
231
|
...review
|
|
208
232
|
};
|
|
209
233
|
const snapshot = config.adapterId === "codex"
|
|
@@ -337,11 +337,17 @@ export class ExecutorRegistry {
|
|
|
337
337
|
return inputs.map((input) => {
|
|
338
338
|
const key = `${input.taskId}\0${input.roleName}`;
|
|
339
339
|
const resource = resources.get(key);
|
|
340
|
+
const deadPane = inventory.find((pane) => (pane.taskId === input.taskId && pane.roleName === input.roleName && pane.dead));
|
|
340
341
|
return {
|
|
341
342
|
taskId: input.taskId,
|
|
342
343
|
roleName: input.roleName,
|
|
343
344
|
status: present.has(key) ? "present" : "absent",
|
|
344
|
-
...(resource === undefined ? {} : { resource })
|
|
345
|
+
...(resource === undefined ? {} : { resource }),
|
|
346
|
+
...(deadPane === undefined
|
|
347
|
+
? {}
|
|
348
|
+
: { hostExit: {
|
|
349
|
+
...(deadPane.deadStatus === undefined ? {} : { deadStatus: deadPane.deadStatus })
|
|
350
|
+
} })
|
|
345
351
|
};
|
|
346
352
|
});
|
|
347
353
|
}
|
|
@@ -7,7 +7,10 @@ import { configuredAgentToDefinition, resolveAgentEnvironment } from "../agent/a
|
|
|
7
7
|
import { NATIVE_AGENT_ENVIRONMENT_NAMES, nativeAgentEnvironmentNames, operationalAgentEnvironment, selectEnvironment } from "../agent/launchEnvironment.js";
|
|
8
8
|
import { activeRoleAgentBinding } from "../role/role.js";
|
|
9
9
|
import { writeTextFileAtomically } from "../storage/durableFile.js";
|
|
10
|
-
import { compileRoleSessionContext } from "../context/roleSessionContext.js";
|
|
10
|
+
import { compileRoleSessionContext, roleSessionKind } from "../context/roleSessionContext.js";
|
|
11
|
+
import { materializeSessionBootstrap } from "../context/sessionBootstrapManifest.js";
|
|
12
|
+
import { serializeRunBootstrapEnvelope, serializeRunHostRecoveryEnvelope } from "../context/runContextContract.js";
|
|
13
|
+
import { serializeProviderRetryEnvelope } from "../run/providerRetry.js";
|
|
11
14
|
import { resolveAgentAdapter } from "./agentAdapter.js";
|
|
12
15
|
import { inspectCodexLaunchConfig } from "./codexConfigConflict.js";
|
|
13
16
|
import { taskRoleSessionTitle } from "../runtime/sessionTitle.js";
|
|
@@ -285,15 +288,27 @@ export class FileRoleLaunchPlanner {
|
|
|
285
288
|
? {}
|
|
286
289
|
: taskRuntimeIsolationEnvironment(runtimeIsolation));
|
|
287
290
|
const baseSessionContext = compileRoleSessionContext(this.home, launchRole, owner, sessionPolicy);
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
291
|
+
const bootstrap = materializeSessionBootstrap({
|
|
292
|
+
yuiHome: this.home,
|
|
293
|
+
role: launchRole,
|
|
294
|
+
owner,
|
|
295
|
+
roleKind: roleSessionKind(launchRole, owner, sessionPolicy.purpose),
|
|
296
|
+
skills: baseSessionContext.skills,
|
|
297
|
+
controlPlane: this.#controlPlane
|
|
298
|
+
});
|
|
299
|
+
if (effective.contextProtocolVersion !== bootstrap.manifest.schemaVersion
|
|
300
|
+
|| effective.sessionManifestCompatibilityDigest
|
|
301
|
+
!== bootstrap.manifest.compatibilityDigest) {
|
|
302
|
+
throw new Error("Effective launch Context protocol identity does not match the materialized Session Manifest.");
|
|
303
|
+
}
|
|
304
|
+
const sessionContext = {
|
|
305
|
+
...baseSessionContext,
|
|
306
|
+
developerInstructions: `Read and follow the exact Yui Session Manifest at ${bootstrap.manifestPath}.`,
|
|
307
|
+
managedContextFile: bootstrap.manifestPath,
|
|
308
|
+
sessionManifestPath: bootstrap.manifestPath,
|
|
309
|
+
sessionManifestDigest: bootstrap.manifest.digest,
|
|
310
|
+
sessionCliPath: bootstrap.sessionCliPath
|
|
311
|
+
};
|
|
297
312
|
const codexConfig = binding.config.adapterId === "codex"
|
|
298
313
|
? inspectCodexLaunchConfig({
|
|
299
314
|
environment: {
|
|
@@ -327,7 +342,7 @@ export class FileRoleLaunchPlanner {
|
|
|
327
342
|
const roleConfig = binding.config.adapterId === "claude"
|
|
328
343
|
&& owner.scope === "task"
|
|
329
344
|
&& input.runId !== undefined
|
|
330
|
-
? managedClaudeControlPlaneConfig(binding.config, owner.taskId, managedRun?.workItemId, input.runId, this.#controlPlane)
|
|
345
|
+
? managedClaudeControlPlaneConfig(binding.config, owner.taskId, managedRun?.workItemId, input.runId, this.#controlPlane, sessionContext.sessionCliPath)
|
|
331
346
|
: binding.config;
|
|
332
347
|
const effectiveConfig = withNativeProjectDirectories(roleConfig, nativeAdditionalDirectories(effective.workspace, agentWorkspace));
|
|
333
348
|
const compileInput = {
|
|
@@ -360,8 +375,17 @@ export class FileRoleLaunchPlanner {
|
|
|
360
375
|
nativeSessionId: resumeNativeSessionId
|
|
361
376
|
})
|
|
362
377
|
: adapter.compileNew(compileInput);
|
|
363
|
-
|
|
364
|
-
|
|
378
|
+
for (const path of [
|
|
379
|
+
bootstrap.manifestPath,
|
|
380
|
+
bootstrap.sessionCliPath,
|
|
381
|
+
bootstrap.roleProfilePath,
|
|
382
|
+
bootstrap.descriptorPath
|
|
383
|
+
]) {
|
|
384
|
+
this.#resourceRegistrar().registerSessionContext(path, {
|
|
385
|
+
home: resolve(this.home),
|
|
386
|
+
...(owner.scope === "task" ? { taskId: owner.taskId } : {}),
|
|
387
|
+
basis: "descriptor"
|
|
388
|
+
});
|
|
365
389
|
}
|
|
366
390
|
let args = [...compiled.argv];
|
|
367
391
|
let command = configured.command;
|
|
@@ -384,8 +408,9 @@ export class FileRoleLaunchPlanner {
|
|
|
384
408
|
args = addCodexLifecycleHooks(args, launchMode, this.#cliPath);
|
|
385
409
|
// End option parsing before the opaque prompt so a wakeup beginning
|
|
386
410
|
// with '-' can never be reinterpreted as a Codex CLI flag.
|
|
387
|
-
if (managedRun.pushedAt === undefined)
|
|
388
|
-
args.push("--", managedRun.
|
|
411
|
+
if (managedRun.pushedAt === undefined) {
|
|
412
|
+
args.push("--", managedRunLaunchEnvelope(managedRun, input.mode));
|
|
413
|
+
}
|
|
389
414
|
}
|
|
390
415
|
session = launchMode === "resume"
|
|
391
416
|
? readySession(input.agentId, binding.adapterId, resumeNativeSessionId, effective)
|
|
@@ -412,16 +437,8 @@ export class FileRoleLaunchPlanner {
|
|
|
412
437
|
const managedClaudeRun = binding.adapterId === "claude"
|
|
413
438
|
&& owner.scope === "task"
|
|
414
439
|
&& input.runId !== undefined;
|
|
415
|
-
if (managedClaudeRun) {
|
|
416
|
-
|
|
417
|
-
args = [
|
|
418
|
-
this.#cliPath,
|
|
419
|
-
"internal",
|
|
420
|
-
"managed-claude-run",
|
|
421
|
-
"--",
|
|
422
|
-
configured.command,
|
|
423
|
-
...args
|
|
424
|
-
];
|
|
440
|
+
if (managedClaudeRun && (managedRun === null || managedRun.status !== "active")) {
|
|
441
|
+
throw new Error(`Managed Claude Run is no longer active: ${input.runId}.`);
|
|
425
442
|
}
|
|
426
443
|
const runtimeDescriptor = owner.scope === "task"
|
|
427
444
|
? createExactTaskRuntimeDescriptor({
|
|
@@ -455,6 +472,18 @@ export class FileRoleLaunchPlanner {
|
|
|
455
472
|
const launch = {
|
|
456
473
|
command,
|
|
457
474
|
args,
|
|
475
|
+
...(managedRun?.providerRetry !== undefined
|
|
476
|
+
&& managedRun.providerRetry.state !== "dispatching"
|
|
477
|
+
? { deferProviderStart: true }
|
|
478
|
+
: {}),
|
|
479
|
+
...(managedClaudeRun
|
|
480
|
+
? {
|
|
481
|
+
providerInput: {
|
|
482
|
+
kind: "stdin-json-user-message",
|
|
483
|
+
boundedText: managedRunLaunchEnvelope(managedRun, input.mode)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
: {}),
|
|
458
487
|
env: {
|
|
459
488
|
...launchEnvironment,
|
|
460
489
|
YUI_HOME: resolve(this.home),
|
|
@@ -465,6 +494,8 @@ export class FileRoleLaunchPlanner {
|
|
|
465
494
|
YUI_ADAPTER_ID: configured.adapterId,
|
|
466
495
|
YUI_DRIVER_ID: driver.id,
|
|
467
496
|
YUI_WORKSPACE: effectiveWorkspace,
|
|
497
|
+
YUI_SESSION_MANIFEST: sessionContext.sessionManifestPath,
|
|
498
|
+
YUI_SESSION_CLI: sessionContext.sessionCliPath,
|
|
468
499
|
...(jobCallerKey === undefined ? {} : { YUI_JOB_CALLER_KEY: jobCallerKey }),
|
|
469
500
|
...(runtimeDescriptor === undefined
|
|
470
501
|
? {}
|
|
@@ -490,7 +521,8 @@ export class FileRoleLaunchPlanner {
|
|
|
490
521
|
...(session === null
|
|
491
522
|
? {}
|
|
492
523
|
: { YUI_NATIVE_SESSION_ID: session.nativeSessionId })
|
|
493
|
-
}
|
|
524
|
+
},
|
|
525
|
+
childLifecycle: driver.capabilities.lifecycle.providerProcess
|
|
494
526
|
};
|
|
495
527
|
const scopedLaunch = owner.scope === "task"
|
|
496
528
|
? this.#applyWorkspaceScope(owner.taskId, role, launch, workspaceOverride)
|
|
@@ -547,6 +579,19 @@ export class FileRoleLaunchPlanner {
|
|
|
547
579
|
return selectEnvironment(source, names);
|
|
548
580
|
}
|
|
549
581
|
}
|
|
582
|
+
function managedRunLaunchEnvelope(run, mode) {
|
|
583
|
+
if (run.providerRetry?.state === "dispatching") {
|
|
584
|
+
return serializeProviderRetryEnvelope({
|
|
585
|
+
taskId: run.taskId,
|
|
586
|
+
runId: run.id,
|
|
587
|
+
roleName: run.roleName,
|
|
588
|
+
retry: run.providerRetry
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
return mode === "resume" && run.pushedAt !== undefined
|
|
592
|
+
? serializeRunHostRecoveryEnvelope(run.bootstrapEnvelope)
|
|
593
|
+
: serializeRunBootstrapEnvelope(run.bootstrapEnvelope);
|
|
594
|
+
}
|
|
550
595
|
export function nativeAgentWorkspace(workspace) {
|
|
551
596
|
return workspace.entries.length === 1
|
|
552
597
|
? workspace.entries[0].path
|
|
@@ -629,11 +674,12 @@ function ensureManagedClaudeLifecyclePlugin(home, cliPath) {
|
|
|
629
674
|
}, null, 2)}\n`);
|
|
630
675
|
return root;
|
|
631
676
|
}
|
|
632
|
-
function managedClaudeControlPlaneConfig(config, taskId, workItemId, runId, controlPlane) {
|
|
677
|
+
function managedClaudeControlPlaneConfig(config, taskId, workItemId, runId, controlPlane, sessionCliPath) {
|
|
633
678
|
if (config.permission.strategy !== "configured")
|
|
634
679
|
return config;
|
|
635
680
|
const exact = exactControlPlaneCommandPrefix(controlPlane);
|
|
636
681
|
const managed = [
|
|
682
|
+
`Bash(${sessionCliPath} task run context ${taskId}/${runId}:*)`,
|
|
637
683
|
`Bash(${exact} --json task context ${taskId})`,
|
|
638
684
|
`Bash(${exact} --json task work list ${taskId})`,
|
|
639
685
|
...(workItemId === undefined
|
|
@@ -7,10 +7,9 @@ import { updateRoleStatus } from "../role/role.js";
|
|
|
7
7
|
import { createTaskEvent } from "../event/taskEvent.js";
|
|
8
8
|
import { finishReviewRound, updateReviewExecutionGroup } from "../review/reviewRound.js";
|
|
9
9
|
import { reconcileReviewFindingsAfterReview } from "../review/reviewFindingLedger.js";
|
|
10
|
-
import { failAgentRun, withYieldReceipt, yieldAgentRun } from "../run/agentRun.js";
|
|
10
|
+
import { agentRunDeliveryReceiptId, failAgentRun, withYieldReceipt, yieldAgentRun } from "../run/agentRun.js";
|
|
11
11
|
import { createYieldReceipt } from "../run/yieldReceipt.js";
|
|
12
12
|
import { recordExecutionLaneResult } from "../execution/executionGroup.js";
|
|
13
|
-
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
14
13
|
import { isRuntimeLaunchReservation, runtimeLifecycleTarget } from "../runtime/lifecycleReservation.js";
|
|
15
14
|
import { clearMatchingLeaderStallAttention, latestRunDurableProgressAt, RUN_RECOVERY_APPLIED_EVENT, RUN_RECOVERY_REQUESTED_EVENT } from "../scheduler/roleRunStall.js";
|
|
16
15
|
import { markTaskWakeConsumed } from "../scheduler/taskWake.js";
|
|
@@ -466,7 +465,7 @@ function recoverExactAgentRunInTransaction(store, input) {
|
|
|
466
465
|
roleName: input.roleName,
|
|
467
466
|
agentId: input.agentId,
|
|
468
467
|
runId: input.runId,
|
|
469
|
-
receiptId:
|
|
468
|
+
receiptId: agentRunDeliveryReceiptId(current),
|
|
470
469
|
...(input.nativeSessionId === undefined ? {} : { nativeSessionId: input.nativeSessionId }),
|
|
471
470
|
...(input.launchId === undefined ? {} : { launchId: input.launchId }),
|
|
472
471
|
outcome: { status: "failed", summary: input.reason }
|
|
@@ -51,9 +51,13 @@ const SESSION_DEAD_PATTERNS = [
|
|
|
51
51
|
{ pattern: /session (has )?ended/iu, label: "session-ended" },
|
|
52
52
|
{ pattern: /session (is )?dead/iu, label: "session-dead" },
|
|
53
53
|
{ pattern: /session terminated/iu, label: "session-terminated" },
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{ pattern: /
|
|
54
|
+
];
|
|
55
|
+
const CONTEXT_CAPACITY_PATTERNS = [
|
|
56
|
+
{ pattern: /maximum context length/iu, label: "maximum-context-length" },
|
|
57
|
+
{ pattern: /context length exceeded/iu, label: "context-length-exceeded" },
|
|
58
|
+
{ pattern: /context window (is )?(full|exceeded)/iu, label: "context-window-exceeded" },
|
|
59
|
+
{ pattern: /prompt (is )?too long/iu, label: "prompt-too-long" },
|
|
60
|
+
{ pattern: /too many tokens/iu, label: "too-many-tokens" }
|
|
57
61
|
];
|
|
58
62
|
const POLICY_DENIED_PATTERNS = [
|
|
59
63
|
{ pattern: /cyber[_-]?policy/iu, label: "cyber-policy" },
|
|
@@ -112,6 +116,7 @@ const TRANSPORT_UNCERTAIN_PATTERNS = [
|
|
|
112
116
|
const CLASS_TABLE = [
|
|
113
117
|
{ errorClass: "session-dead", patterns: SESSION_DEAD_PATTERNS },
|
|
114
118
|
{ errorClass: "policy-denied", patterns: POLICY_DENIED_PATTERNS },
|
|
119
|
+
{ errorClass: "context-capacity", patterns: CONTEXT_CAPACITY_PATTERNS },
|
|
115
120
|
{ errorClass: "invalid-request", patterns: INVALID_REQUEST_PATTERNS },
|
|
116
121
|
{ errorClass: "transient-provider", patterns: TRANSIENT_PROVIDER_PATTERNS },
|
|
117
122
|
{ errorClass: "transport-uncertain", patterns: TRANSPORT_UNCERTAIN_PATTERNS }
|
|
@@ -63,7 +63,7 @@ function directorySizeBytes(path) {
|
|
|
63
63
|
}
|
|
64
64
|
return total;
|
|
65
65
|
}
|
|
66
|
-
const WAKE_REASON_PATTERN = /
|
|
66
|
+
const WAKE_REASON_PATTERN = /Wake reasons: ([^\n.]+)/u;
|
|
67
67
|
function inWindow(createdAt, options) {
|
|
68
68
|
const time = Date.parse(createdAt);
|
|
69
69
|
if (!Number.isFinite(time))
|
|
@@ -207,6 +207,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
207
207
|
providerRetries: section,
|
|
208
208
|
workItems: section,
|
|
209
209
|
storage: section,
|
|
210
|
+
runtimeProtocol: section,
|
|
210
211
|
topLongRunning: section
|
|
211
212
|
};
|
|
212
213
|
}
|
|
@@ -314,7 +315,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
314
315
|
if (!inWindow(run.createdAt, options))
|
|
315
316
|
continue;
|
|
316
317
|
leaderRuns += 1;
|
|
317
|
-
const match = WAKE_REASON_PATTERN.exec(run.
|
|
318
|
+
const match = WAKE_REASON_PATTERN.exec(run.assignment.directive ?? "");
|
|
318
319
|
if (match === null)
|
|
319
320
|
continue;
|
|
320
321
|
withWakeReasons += 1;
|
|
@@ -705,6 +706,89 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
705
706
|
return failed(error);
|
|
706
707
|
}
|
|
707
708
|
})();
|
|
709
|
+
const runtimeProtocol = (() => {
|
|
710
|
+
try {
|
|
711
|
+
const protocolVersions = new Map();
|
|
712
|
+
const manifestDigests = new Set();
|
|
713
|
+
const retryStates = new Map();
|
|
714
|
+
const exitClassifications = new Map();
|
|
715
|
+
const usageSemantics = new Map();
|
|
716
|
+
let activeRetryEpisodes = 0;
|
|
717
|
+
let activeConsecutiveFailures = 0;
|
|
718
|
+
let activeDispatchedRetries = 0;
|
|
719
|
+
let retryClassifiedEvents = 0;
|
|
720
|
+
let retryDispatchedEvents = 0;
|
|
721
|
+
let retryRecoveredEvents = 0;
|
|
722
|
+
let retryExhaustedEvents = 0;
|
|
723
|
+
let contextCapacityFailures = 0;
|
|
724
|
+
let processExitObservations = 0;
|
|
725
|
+
let compactionEvents = 0;
|
|
726
|
+
for (const taskId of taskIds) {
|
|
727
|
+
for (const run of store.listAgentRuns(taskId)) {
|
|
728
|
+
if (!inWindow(run.createdAt, options))
|
|
729
|
+
continue;
|
|
730
|
+
const version = String(run.effective.contextProtocolVersion ?? "legacy");
|
|
731
|
+
protocolVersions.set(version, (protocolVersions.get(version) ?? 0) + 1);
|
|
732
|
+
if (run.effective.sessionManifestCompatibilityDigest !== undefined) {
|
|
733
|
+
manifestDigests.add(run.effective.sessionManifestCompatibilityDigest);
|
|
734
|
+
}
|
|
735
|
+
if (run.providerRetry !== undefined) {
|
|
736
|
+
activeRetryEpisodes += 1;
|
|
737
|
+
retryStates.set(run.providerRetry.state, (retryStates.get(run.providerRetry.state) ?? 0) + 1);
|
|
738
|
+
activeConsecutiveFailures += run.providerRetry.consecutiveFailures;
|
|
739
|
+
activeDispatchedRetries += run.providerRetry.dispatchedRetries;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
for (const event of store.listEvents(taskId)) {
|
|
743
|
+
if (!inWindow(event.createdAt, options))
|
|
744
|
+
continue;
|
|
745
|
+
if (event.type === "runtime.provider-retry-classified")
|
|
746
|
+
retryClassifiedEvents += 1;
|
|
747
|
+
else if (event.type === "runtime.provider-retry-dispatched")
|
|
748
|
+
retryDispatchedEvents += 1;
|
|
749
|
+
else if (event.type === "runtime.provider-retry-recovered")
|
|
750
|
+
retryRecoveredEvents += 1;
|
|
751
|
+
else if (event.type === "runtime.provider-retry-exhausted")
|
|
752
|
+
retryExhaustedEvents += 1;
|
|
753
|
+
else if (event.type === "runtime.context-capacity-failure")
|
|
754
|
+
contextCapacityFailures += 1;
|
|
755
|
+
else if (event.type === "runtime.process-exit-observed") {
|
|
756
|
+
processExitObservations += 1;
|
|
757
|
+
const classification = event.payload.classification ?? "unknown";
|
|
758
|
+
exitClassifications.set(classification, (exitClassifications.get(classification) ?? 0) + 1);
|
|
759
|
+
}
|
|
760
|
+
const observation = runtimeObservationFromTaskEvent(event);
|
|
761
|
+
const semantics = observation?.payload.usage?.semantics;
|
|
762
|
+
if (semantics !== undefined) {
|
|
763
|
+
usageSemantics.set(semantics, (usageSemantics.get(semantics) ?? 0) + 1);
|
|
764
|
+
}
|
|
765
|
+
if (event.type === "runtime.compaction-started"
|
|
766
|
+
|| event.type === "runtime.compaction-completed")
|
|
767
|
+
compactionEvents += 1;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return ok({
|
|
771
|
+
contextProtocolVersions: Object.fromEntries(protocolVersions),
|
|
772
|
+
manifestCompatibilityDigests: manifestDigests.size,
|
|
773
|
+
activeRetryEpisodes,
|
|
774
|
+
activeRetryStates: Object.fromEntries(retryStates),
|
|
775
|
+
activeConsecutiveFailures,
|
|
776
|
+
activeDispatchedRetries,
|
|
777
|
+
retryClassifiedEvents,
|
|
778
|
+
retryDispatchedEvents,
|
|
779
|
+
retryRecoveredEvents,
|
|
780
|
+
retryExhaustedEvents,
|
|
781
|
+
contextCapacityFailures,
|
|
782
|
+
processExitObservations,
|
|
783
|
+
processExitClassifications: Object.fromEntries(exitClassifications),
|
|
784
|
+
usageSemantics: Object.fromEntries(usageSemantics),
|
|
785
|
+
compactionEvents
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
catch (error) {
|
|
789
|
+
return failed(error);
|
|
790
|
+
}
|
|
791
|
+
})();
|
|
708
792
|
const topLongRunning = (() => {
|
|
709
793
|
try {
|
|
710
794
|
const entries = [];
|
|
@@ -751,6 +835,7 @@ export function runExecutionAudit(home, options = {}, ports = createProductionEx
|
|
|
751
835
|
providerRetries,
|
|
752
836
|
workItems,
|
|
753
837
|
storage,
|
|
838
|
+
runtimeProtocol,
|
|
754
839
|
topLongRunning
|
|
755
840
|
};
|
|
756
841
|
}
|