@zq-silk/yui 0.6.16 → 0.7.1
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/coordination/workMailbox.js +25 -22
- 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 +48 -4
- 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 {
|
|
@@ -121,22 +121,26 @@ function appendUnique(existing, incoming, keyOf) {
|
|
|
121
121
|
}
|
|
122
122
|
return result;
|
|
123
123
|
}
|
|
124
|
-
function mergeBatches(
|
|
124
|
+
function mergeBatches(left, right) {
|
|
125
125
|
const merged = {
|
|
126
|
-
fromSequence:
|
|
127
|
-
toSequence:
|
|
128
|
-
reasons: appendUnique(
|
|
129
|
-
refs: appendUnique(
|
|
130
|
-
requestCount:
|
|
131
|
-
firstQueuedAt:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
126
|
+
fromSequence: Math.min(left.fromSequence, right.fromSequence),
|
|
127
|
+
toSequence: Math.max(left.toSequence, right.toSequence),
|
|
128
|
+
reasons: appendUnique(left.reasons, right.reasons, (reason) => reason),
|
|
129
|
+
refs: appendUnique(left.refs, right.refs, mailboxEntityRefKey),
|
|
130
|
+
requestCount: left.requestCount + right.requestCount,
|
|
131
|
+
firstQueuedAt: Date.parse(left.firstQueuedAt) <= Date.parse(right.firstQueuedAt)
|
|
132
|
+
? left.firstQueuedAt
|
|
133
|
+
: right.firstQueuedAt,
|
|
134
|
+
lastQueuedAt: Date.parse(left.lastQueuedAt) >= Date.parse(right.lastQueuedAt)
|
|
135
|
+
? left.lastQueuedAt
|
|
136
|
+
: right.lastQueuedAt,
|
|
137
|
+
sources: appendUnique(left.sources, right.sources, (source) => source),
|
|
138
|
+
dedupeKeys: appendUnique(left.dedupeKeys, right.dedupeKeys, (key) => key),
|
|
139
|
+
deliveryModes: appendUnique(left.deliveryModes, right.deliveryModes, (mode) => mode),
|
|
140
|
+
...((left.highestFactRevision ?? right.highestFactRevision) === undefined
|
|
137
141
|
? {}
|
|
138
142
|
: {
|
|
139
|
-
highestFactRevision: Math.max(
|
|
143
|
+
highestFactRevision: Math.max(left.highestFactRevision ?? 0, right.highestFactRevision ?? 0)
|
|
140
144
|
})
|
|
141
145
|
};
|
|
142
146
|
return merged;
|
|
@@ -196,17 +200,16 @@ export function validateWorkMailbox(value) {
|
|
|
196
200
|
pending.userCorrection,
|
|
197
201
|
inputDelivery?.batch
|
|
198
202
|
].filter((batch) => batch !== null && batch !== undefined);
|
|
203
|
+
const activeDedupeKeys = new Set();
|
|
199
204
|
for (const batch of batches) {
|
|
200
205
|
if (batch.toSequence >= nextSequence) {
|
|
201
206
|
throw new Error("WorkMailbox batch sequence must be lower than nextSequence");
|
|
202
207
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (ranges[index - 1][1] >= ranges[index][0]) {
|
|
209
|
-
throw new Error("WorkMailbox batch sequences overlap");
|
|
208
|
+
for (const dedupeKey of batch.dedupeKeys) {
|
|
209
|
+
if (activeDedupeKeys.has(dedupeKey)) {
|
|
210
|
+
throw new Error("WorkMailbox active batches share a dedupe key");
|
|
211
|
+
}
|
|
212
|
+
activeDedupeKeys.add(dedupeKey);
|
|
210
213
|
}
|
|
211
214
|
}
|
|
212
215
|
if (inputDelivery !== null && target.kind !== "role" && target.kind !== "operator") {
|
|
@@ -304,8 +307,8 @@ function parseBatch(value, label) {
|
|
|
304
307
|
const fromSequence = requireInteger(batch.fromSequence, 1, `${label} fromSequence`);
|
|
305
308
|
const toSequence = requireInteger(batch.toSequence, fromSequence, `${label} toSequence`);
|
|
306
309
|
const requestCount = requireInteger(batch.requestCount, 1, `${label} requestCount`);
|
|
307
|
-
if (requestCount
|
|
308
|
-
throw new Error(`${label} requestCount
|
|
310
|
+
if (requestCount > toSequence - fromSequence + 1) {
|
|
311
|
+
throw new Error(`${label} requestCount exceeds its sequence envelope`);
|
|
309
312
|
}
|
|
310
313
|
const reasons = requireStringArray(batch.reasons, `${label} reasons`);
|
|
311
314
|
if (reasons.length === 0)
|
|
@@ -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 }
|