@zq-silk/yui 0.8.1 → 0.8.3
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/ARCHITECTURE.md +27 -28
- package/README.md +57 -46
- package/dist/cli/commandCatalog.js +57 -17
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +106 -27
- package/dist/commands/taskCommands.js +458 -77
- package/dist/commands/taskCompletionGate.js +152 -0
- package/dist/context/runContextPack.js +19 -4
- package/dist/context/sessionBootstrapManifest.js +1 -1
- package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
- package/dist/controller/resourceInventory.js +9 -5
- package/dist/controller/runtime.js +80 -7
- package/dist/controller/runtimeLaunchCoordinator.js +18 -78
- package/dist/controller/structuredProviderObservation.js +273 -0
- package/dist/executor/agentAdapter.js +40 -0
- package/dist/executor/agentExecutor.js +31 -7
- package/dist/executor/executorRegistry.js +11 -49
- package/dist/executor/fileRoleLaunchPlanner.js +115 -37
- package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
- package/dist/repository/gitWorkspace.js +7 -4
- package/dist/repository/taskBaseFreshness.js +4 -2
- package/dist/run/agentRun.js +4 -4
- package/dist/runtime/agentHost.js +767 -158
- package/dist/runtime/builtinAgentDrivers.js +1 -5
- package/dist/runtime/codexAppServerRuntime.js +67 -60
- package/dist/runtime/exactControlPlane.js +7 -2
- package/dist/runtime/index.js +6 -2
- package/dist/runtime/launchBroker.js +30 -8
- package/dist/runtime/providerAuthorityFence.js +24 -0
- package/dist/runtime/providerControl.js +63 -0
- package/dist/runtime/providerRecoveryDecision.js +55 -0
- package/dist/runtime/providerRuntimeIdentity.js +269 -19
- package/dist/runtime/runtimeBinding.js +20 -11
- package/dist/runtime/structuredProviderHost.js +476 -0
- package/dist/runtime/tmuxAdapters.js +143 -42
- package/dist/scheduler/activeRoleRunDelivery.js +206 -120
- package/dist/scheduler/leaderWakeupProcessor.js +141 -16
- package/dist/scheduler/wakeReason.js +1 -0
- package/dist/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/sqliteStore.js +2 -0
- package/dist/storage/taskStore.js +3 -1
- package/dist/task/completionReadiness.js +43 -0
- package/dist/task/nextAction.js +6 -4
- package/dist/task/publicationReference.js +1 -0
- package/dist/tmux/tmuxManager.js +1 -1
- package/dist/workItem/workItem.js +12 -0
- package/dist/workspace/workItemChangeSetManager.js +2 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
- package/skills/yui-leader/SKILL.md +8 -3
- package/skills/yui-runtime/SKILL.md +7 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { hasRecentTurnId, rememberRecentTurnId, validatePendingTurnCompletion, validateRecentTurnIds } from "./turnCompletion.js";
|
|
3
3
|
import { effectiveLaunchSnapshotsCompatible, effectiveLaunchSnapshotsCompatibleForTaskMain, validateEffectiveLaunchSnapshot } from "./effectiveLaunch.js";
|
|
4
|
-
import { validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
|
|
4
|
+
import { rebindProviderRuntimeRun, validateProviderRuntimeBinding } from "../runtime/providerRuntimeIdentity.js";
|
|
5
5
|
import { builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
6
6
|
export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
7
7
|
const base = {
|
|
@@ -14,7 +14,7 @@ export function createRoleSessionSet(owner, activeAgentId, now) {
|
|
|
14
14
|
? { ...base, schemaVersion: 3 }
|
|
15
15
|
: {
|
|
16
16
|
...base,
|
|
17
|
-
schemaVersion:
|
|
17
|
+
schemaVersion: 6,
|
|
18
18
|
inFlight: null,
|
|
19
19
|
providerBinding: null
|
|
20
20
|
};
|
|
@@ -285,13 +285,39 @@ export function bindTaskRoleRun(set, fence, preparedAt) {
|
|
|
285
285
|
throw new Error("Task Role session set already has an in-flight Run.");
|
|
286
286
|
}
|
|
287
287
|
const timestamp = requireDate(preparedAt, "Task Role Run preparedAt");
|
|
288
|
+
const providerBinding = set.providerBinding === null
|
|
289
|
+
? null
|
|
290
|
+
: rebindProviderRuntimeRun(set.providerBinding, normalized.runId);
|
|
288
291
|
const updated = {
|
|
289
292
|
...set,
|
|
290
293
|
inFlight: { ...normalized, preparedAt: timestamp },
|
|
294
|
+
providerBinding,
|
|
291
295
|
updatedAt: timestamp
|
|
292
296
|
};
|
|
293
297
|
return validateRoleSessionSet(updated);
|
|
294
298
|
}
|
|
299
|
+
/** Re-fences the same active Run for an exact Provider retry without losing its Conversation. */
|
|
300
|
+
export function prepareTaskRoleRunRedispatch(set, fence, preparedAt) {
|
|
301
|
+
validateRoleSessionSet(set);
|
|
302
|
+
const normalized = normalizeTaskRoleRunFence(fence);
|
|
303
|
+
if (set.inFlight === null
|
|
304
|
+
|| set.inFlight.agentId !== normalized.agentId
|
|
305
|
+
|| set.inFlight.runId !== normalized.runId) {
|
|
306
|
+
throw new Error("Provider retry does not match the in-flight Task Run.");
|
|
307
|
+
}
|
|
308
|
+
if (set.providerBinding !== null
|
|
309
|
+
&& set.providerBinding.turn !== null
|
|
310
|
+
&& ["submitting", "accepted", "running", "delivery-unknown"]
|
|
311
|
+
.includes(set.providerBinding.turn.status)) {
|
|
312
|
+
throw new Error("Provider retry cannot redispatch an unsettled Turn.");
|
|
313
|
+
}
|
|
314
|
+
const timestamp = requireDate(preparedAt, "Task Role retry preparedAt");
|
|
315
|
+
return validateRoleSessionSet({
|
|
316
|
+
...set,
|
|
317
|
+
inFlight: { ...normalized, preparedAt: timestamp },
|
|
318
|
+
updatedAt: timestamp
|
|
319
|
+
});
|
|
320
|
+
}
|
|
295
321
|
export function bindTaskRoleProviderRuntime(set, binding, updatedAt) {
|
|
296
322
|
validateRoleSessionSet(set);
|
|
297
323
|
const normalized = validateProviderRuntimeBinding(binding);
|
|
@@ -446,7 +472,6 @@ export function clearTaskRoleRun(set, fence, clearedAt) {
|
|
|
446
472
|
const updated = {
|
|
447
473
|
...set,
|
|
448
474
|
inFlight: null,
|
|
449
|
-
providerBinding: null,
|
|
450
475
|
updatedAt: timestamp
|
|
451
476
|
};
|
|
452
477
|
return validateRoleSessionSet(updated);
|
|
@@ -519,7 +544,6 @@ export function settleTaskRoleCompletion(set, expected, settledAt) {
|
|
|
519
544
|
}
|
|
520
545
|
},
|
|
521
546
|
inFlight: null,
|
|
522
|
-
providerBinding: null,
|
|
523
547
|
updatedAt: timestamp
|
|
524
548
|
};
|
|
525
549
|
return validateRoleSessionSet(updated);
|
|
@@ -550,7 +574,7 @@ export function validateRoleSessionSet(set) {
|
|
|
550
574
|
}
|
|
551
575
|
}
|
|
552
576
|
else {
|
|
553
|
-
if (set.schemaVersion !==
|
|
577
|
+
if (set.schemaVersion !== 6) {
|
|
554
578
|
throw new Error("Task Role session set schema version is invalid.");
|
|
555
579
|
}
|
|
556
580
|
if (!Object.hasOwn(set, "inFlight")
|
|
@@ -590,10 +614,10 @@ export function validateRoleSessionSet(set) {
|
|
|
590
614
|
throw new Error("Task Role in-flight Run Agent must be active.");
|
|
591
615
|
}
|
|
592
616
|
if (providerBinding !== null) {
|
|
593
|
-
if (inFlight
|
|
617
|
+
if (inFlight !== null && providerBinding.runId !== inFlight.runId) {
|
|
594
618
|
throw new Error("Provider Runtime Binding must match the in-flight Run.");
|
|
595
619
|
}
|
|
596
|
-
const session = taskSet.sessions[inFlight.
|
|
620
|
+
const session = taskSet.sessions[inFlight?.agentId ?? set.activeAgentId];
|
|
597
621
|
if (session === undefined) {
|
|
598
622
|
throw new Error("Provider Runtime Binding has no active Role Agent session.");
|
|
599
623
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createPromptEnvelope,
|
|
2
|
+
import { createPromptEnvelope, createSessionLaunchRequest } from "../runtime/index.js";
|
|
3
3
|
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
4
4
|
/**
|
|
5
5
|
* rr13/test: Test-only liveness seam. Integration tests that spawn a real
|
|
@@ -26,9 +26,6 @@ export class ExecutorRegistry {
|
|
|
26
26
|
this.readiness = readiness;
|
|
27
27
|
this.runtimePorts = runtimePorts;
|
|
28
28
|
}
|
|
29
|
-
canRouteProviderInput(adapterId) {
|
|
30
|
-
return adapterId === "codex" && this.runtimePorts?.providerInputRouting !== undefined;
|
|
31
|
-
}
|
|
32
29
|
async prepareRoleSession(input) {
|
|
33
30
|
if (input.mode === "resume" && !hasText(input.nativeSessionId)) {
|
|
34
31
|
throw new Error("Role session resume requires a native session id.");
|
|
@@ -122,10 +119,16 @@ export class ExecutorRegistry {
|
|
|
122
119
|
sessionStarted,
|
|
123
120
|
session,
|
|
124
121
|
...(input.runId !== undefined
|
|
125
|
-
&& ((
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
&& ((binding?.initialTurnRunId === input.runId))
|
|
123
|
+
? { turnAcceptedDuringLaunch: true }
|
|
124
|
+
: {}),
|
|
125
|
+
...(input.runId !== undefined
|
|
126
|
+
&& binding?.initialTurnDeliveryUnknownRunId === input.runId
|
|
127
|
+
? { turnDeliveryUnknownDuringLaunch: true }
|
|
128
|
+
: {}),
|
|
129
|
+
...(input.runId !== undefined
|
|
130
|
+
&& binding?.initialTurnRejectedRunId === input.runId
|
|
131
|
+
? { turnRejectedDuringLaunch: true }
|
|
129
132
|
: {})
|
|
130
133
|
};
|
|
131
134
|
this.#prepared.set(delivery.deliveryId, {
|
|
@@ -196,47 +199,6 @@ export class ExecutorRegistry {
|
|
|
196
199
|
}
|
|
197
200
|
return outcome;
|
|
198
201
|
}
|
|
199
|
-
async routeProviderInput(input) {
|
|
200
|
-
const prepared = this.requirePrepared(input.delivery.prepared);
|
|
201
|
-
if (prepared.binding === undefined || this.runtimePorts?.providerInputRouting === undefined) {
|
|
202
|
-
return "unavailable";
|
|
203
|
-
}
|
|
204
|
-
try {
|
|
205
|
-
return await this.runtimePorts.providerInputRouting.route({
|
|
206
|
-
binding: prepared.binding,
|
|
207
|
-
attemptId: input.attemptId,
|
|
208
|
-
mode: input.mode,
|
|
209
|
-
text: input.text,
|
|
210
|
-
fence: input.fence
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
finally {
|
|
214
|
-
// A routed mutation is fenced by its durable inputDelivery, not by this
|
|
215
|
-
// process-local preparation. Never let a later Turn reuse a cached
|
|
216
|
-
// Activation binding after this attempt (including an unknown result).
|
|
217
|
-
this.#prepared.delete(input.delivery.prepared.deliveryId);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
async reconcileProviderInput(input) {
|
|
221
|
-
if (this.runtimePorts?.providerInputRouting === undefined) {
|
|
222
|
-
return "unavailable";
|
|
223
|
-
}
|
|
224
|
-
return this.runtimePorts.providerInputRouting.reconcile({
|
|
225
|
-
binding: createRuntimeBinding({
|
|
226
|
-
id: `metadata:${input.taskId}:${input.roleName}:${input.launchId}`,
|
|
227
|
-
launchId: input.launchId,
|
|
228
|
-
owner: { scope: "task", taskId: input.taskId, roleName: input.roleName },
|
|
229
|
-
agentId: input.agentId,
|
|
230
|
-
adapterId: input.adapterId,
|
|
231
|
-
hostRef: "metadata-only",
|
|
232
|
-
hostCreated: false,
|
|
233
|
-
nativeSessionId: input.nativeSessionId
|
|
234
|
-
}),
|
|
235
|
-
attemptId: input.attemptId,
|
|
236
|
-
mode: input.mode,
|
|
237
|
-
fence: input.fence
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
202
|
async notifyOperatorInputOnce(input) {
|
|
241
203
|
const probe = this.readiness(input.adapterId, "operator");
|
|
242
204
|
return this.tmux.sendRoleInputOnceIfReadyAsync === undefined
|
|
@@ -24,6 +24,7 @@ import { parseTaskRuntimeIsolationDescriptor, taskRuntimeIsolationEnvironment }
|
|
|
24
24
|
import { ResourceRegistrar } from "../resources/resourceRegistrar.js";
|
|
25
25
|
import { builtinAgentDriverRegistry, builtinDriverIdForAdapter } from "../runtime/builtinAgentDrivers.js";
|
|
26
26
|
import { managedRuntimeAdmission } from "../runtime/agentDriver.js";
|
|
27
|
+
import { formatAgentRunReceiptId } from "../task/taskRecordReference.js";
|
|
27
28
|
/** Builds managed native Agent launches from the authoritative Task records. */
|
|
28
29
|
export class FileRoleLaunchPlanner {
|
|
29
30
|
home;
|
|
@@ -223,7 +224,7 @@ export class FileRoleLaunchPlanner {
|
|
|
223
224
|
if (input.mode === "resume" && !compatibleExisting) {
|
|
224
225
|
throw new Error(`Task Role resume effective snapshot drifted: ${task.id}/${role.name}.`);
|
|
225
226
|
}
|
|
226
|
-
return this.#compile(role, input, { scope: "task", taskId: task.id }, taskRoleSessionTitle(task, role.name), compatibleExisting ? existing.nativeSessionId : undefined, runWorkspace, effective, {
|
|
227
|
+
return this.#compile(role, input, { scope: "task", taskId: task.id }, taskRoleSessionTitle(task, role.name), input.mode === "resume" && compatibleExisting ? existing.nativeSessionId : undefined, runWorkspace, effective, {
|
|
227
228
|
purpose: activeRun?.purpose ?? "execution"
|
|
228
229
|
});
|
|
229
230
|
}
|
|
@@ -331,6 +332,9 @@ export class FileRoleLaunchPlanner {
|
|
|
331
332
|
const managedRun = owner.scope === "task" && input.runId !== undefined
|
|
332
333
|
? this.store.getAgentRun(owner.taskId, input.runId)
|
|
333
334
|
: null;
|
|
335
|
+
const managedSessionSet = owner.scope === "task" && input.runId !== undefined
|
|
336
|
+
? this.store.getTaskRoleSessionSet(owner.taskId, role.name)
|
|
337
|
+
: null;
|
|
334
338
|
const driver = builtinAgentDriverRegistry().require(builtinDriverIdForAdapter(configured.adapterId));
|
|
335
339
|
if (owner.scope === "task" && input.runId !== undefined) {
|
|
336
340
|
const admission = managedRuntimeAdmission(driver.capabilities);
|
|
@@ -369,12 +373,33 @@ export class FileRoleLaunchPlanner {
|
|
|
369
373
|
const launchMode = resumeNativeSessionId === undefined
|
|
370
374
|
? "new"
|
|
371
375
|
: "resume";
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
376
|
+
const managedControl = owner.scope === "task" && input.runId !== undefined;
|
|
377
|
+
const managedProviderEnvironment = managedControl
|
|
378
|
+
&& configured.adapterId === "codex"
|
|
379
|
+
? {
|
|
380
|
+
// Managed Codex Runs are non-interactive. Use the Codex execution
|
|
381
|
+
// identity for provider requests while clientInfo still identifies Yui.
|
|
382
|
+
CODEX_INTERNAL_ORIGINATOR_OVERRIDE: "codex_exec"
|
|
383
|
+
}
|
|
384
|
+
: {};
|
|
385
|
+
const preallocatedManagedNativeSessionId = managedControl
|
|
386
|
+
&& binding.adapterId === "claude"
|
|
387
|
+
&& resumeNativeSessionId === undefined
|
|
388
|
+
? requireText(input.launchId === undefined
|
|
389
|
+
? this.#createNativeSessionId()
|
|
390
|
+
: nativeSessionIdForLaunch(this.home, input.launchId, input.agentId, input.adapterId), "Native session id")
|
|
391
|
+
: resumeNativeSessionId;
|
|
392
|
+
const managedCompiled = managedControl
|
|
393
|
+
? adapter.compileManagedControl(compileInput, launchMode, preallocatedManagedNativeSessionId)
|
|
394
|
+
: undefined;
|
|
395
|
+
const compiled = managedCompiled !== undefined
|
|
396
|
+
? managedCompiled
|
|
397
|
+
: launchMode === "resume"
|
|
398
|
+
? adapter.compileResume({
|
|
399
|
+
...compileInput,
|
|
400
|
+
nativeSessionId: resumeNativeSessionId
|
|
401
|
+
})
|
|
402
|
+
: adapter.compileNew(compileInput);
|
|
378
403
|
for (const path of [
|
|
379
404
|
bootstrap.manifestPath,
|
|
380
405
|
bootstrap.sessionCliPath,
|
|
@@ -397,39 +422,32 @@ export class FileRoleLaunchPlanner {
|
|
|
397
422
|
if (owner.scope !== "task" || input.runId === undefined) {
|
|
398
423
|
args = addCodexSessionNotify(args, launchMode, this.#cliPath);
|
|
399
424
|
}
|
|
400
|
-
//
|
|
401
|
-
//
|
|
402
|
-
// submitted only after Codex completes startup; never race terminal bytes
|
|
403
|
-
// and Enter against TUI initialization.
|
|
425
|
+
// Managed Codex runs use App Server lifecycle hooks and structured Turn
|
|
426
|
+
// submission. No Run prompt is placed in argv or written as terminal input.
|
|
404
427
|
if (owner.scope === "task" && input.runId !== undefined) {
|
|
405
428
|
if (managedRun === null || managedRun.status !== "active") {
|
|
406
429
|
throw new Error(`Managed Codex Run is no longer active: ${input.runId}.`);
|
|
407
430
|
}
|
|
408
|
-
args =
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (managedRun.pushedAt === undefined) {
|
|
412
|
-
args.push("--", managedRunLaunchEnvelope(managedRun, input.mode));
|
|
413
|
-
}
|
|
431
|
+
args = managedControl
|
|
432
|
+
? addCodexManagedLifecycleHooks(args, this.#cliPath)
|
|
433
|
+
: addCodexLifecycleHooks(args, launchMode, this.#cliPath);
|
|
414
434
|
}
|
|
415
435
|
session = launchMode === "resume"
|
|
416
436
|
? readySession(input.agentId, binding.adapterId, resumeNativeSessionId, effective)
|
|
417
437
|
: null;
|
|
418
438
|
}
|
|
419
439
|
else if (launchMode === "new") {
|
|
420
|
-
if (
|
|
421
|
-
args.push("-p", "--output-format", "stream-json", "--input-format", "stream-json", "--verbose");
|
|
440
|
+
if (managedControl)
|
|
422
441
|
args.push("--plugin-dir", ensureManagedClaudeLifecyclePlugin(this.home, this.#cliPath));
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
442
|
+
const nativeSessionId = requireText(preallocatedManagedNativeSessionId, "Native session id");
|
|
443
|
+
if (!managedControl)
|
|
444
|
+
args.push("--session-id", nativeSessionId);
|
|
445
|
+
else if (!args.includes("--session-id"))
|
|
446
|
+
args.push("--session-id", nativeSessionId);
|
|
428
447
|
session = readySession(input.agentId, binding.adapterId, nativeSessionId, effective);
|
|
429
448
|
}
|
|
430
449
|
else {
|
|
431
|
-
if (
|
|
432
|
-
args.push("-p", "--output-format", "stream-json", "--input-format", "stream-json", "--verbose");
|
|
450
|
+
if (managedControl) {
|
|
433
451
|
args.push("--plugin-dir", ensureManagedClaudeLifecyclePlugin(this.home, this.#cliPath));
|
|
434
452
|
}
|
|
435
453
|
session = readySession(input.agentId, binding.adapterId, resumeNativeSessionId, effective);
|
|
@@ -469,6 +487,36 @@ export class FileRoleLaunchPlanner {
|
|
|
469
487
|
if (owner.scope === "task" && (input.mode === "new" || input.mode === "resume")) {
|
|
470
488
|
jobCallerKey = randomBytes(32).toString("hex");
|
|
471
489
|
}
|
|
490
|
+
const carriesInitialTurn = managedControl
|
|
491
|
+
&& managedRun?.pushedAt === undefined
|
|
492
|
+
&& (managedSessionSet?.providerBinding === null
|
|
493
|
+
|| managedSessionSet?.providerBinding === undefined);
|
|
494
|
+
const providerAuthority = managedControl
|
|
495
|
+
? this.#providerAuthorityForLaunch(owner.taskId, role.name, input.launchId)
|
|
496
|
+
: undefined;
|
|
497
|
+
const providerNativeSessionId = binding.adapterId === "claude"
|
|
498
|
+
? preallocatedManagedNativeSessionId
|
|
499
|
+
: resumeNativeSessionId;
|
|
500
|
+
const providerControl = managedControl
|
|
501
|
+
? {
|
|
502
|
+
schemaVersion: 1,
|
|
503
|
+
adapterId: binding.adapterId,
|
|
504
|
+
transport: managedCompiled.transport,
|
|
505
|
+
mode: resumeNativeSessionId === undefined ? "new" : "resume",
|
|
506
|
+
...(providerNativeSessionId === undefined
|
|
507
|
+
? {}
|
|
508
|
+
: { nativeSessionId: providerNativeSessionId }),
|
|
509
|
+
authority: providerAuthority,
|
|
510
|
+
...(carriesInitialTurn
|
|
511
|
+
? {
|
|
512
|
+
initialTurn: {
|
|
513
|
+
attemptId: formatAgentRunReceiptId(owner.taskId, input.runId),
|
|
514
|
+
boundedText: managedRunLaunchEnvelope(managedRun, input.mode)
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
: {})
|
|
518
|
+
}
|
|
519
|
+
: undefined;
|
|
472
520
|
const launch = {
|
|
473
521
|
command,
|
|
474
522
|
args,
|
|
@@ -476,16 +524,10 @@ export class FileRoleLaunchPlanner {
|
|
|
476
524
|
&& managedRun.providerRetry.state !== "dispatching"
|
|
477
525
|
? { deferProviderStart: true }
|
|
478
526
|
: {}),
|
|
479
|
-
...(
|
|
480
|
-
? {
|
|
481
|
-
providerInput: {
|
|
482
|
-
kind: "stdin-json-user-message",
|
|
483
|
-
boundedText: managedRunLaunchEnvelope(managedRun, input.mode)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
: {}),
|
|
527
|
+
...(providerControl === undefined ? {} : { providerControl }),
|
|
487
528
|
env: {
|
|
488
529
|
...launchEnvironment,
|
|
530
|
+
...managedProviderEnvironment,
|
|
489
531
|
YUI_HOME: resolve(this.home),
|
|
490
532
|
YUI_SESSION_SCOPE: owner.scope,
|
|
491
533
|
...(owner.scope === "task" ? { YUI_TASK_ID: owner.taskId } : {}),
|
|
@@ -536,12 +578,36 @@ export class FileRoleLaunchPlanner {
|
|
|
536
578
|
},
|
|
537
579
|
launch: scopedLaunch,
|
|
538
580
|
session,
|
|
539
|
-
...(
|
|
540
|
-
|
|
541
|
-
? { initialPromptRunId: input.runId }
|
|
581
|
+
...(carriesInitialTurn && input.runId !== undefined
|
|
582
|
+
? { initialTurnRunId: input.runId }
|
|
542
583
|
: {})
|
|
543
584
|
};
|
|
544
585
|
}
|
|
586
|
+
#providerAuthorityForLaunch(taskId, roleName, launchId) {
|
|
587
|
+
const activationId = requireText(launchId, "Managed Provider Activation id");
|
|
588
|
+
const binding = this.store.getTaskRoleSessionSet(taskId, roleName)?.providerBinding;
|
|
589
|
+
if (binding === null || binding === undefined) {
|
|
590
|
+
return { epoch: 1, owner: "controller", holderId: activationId };
|
|
591
|
+
}
|
|
592
|
+
if (binding.authority.owner === "controller") {
|
|
593
|
+
return {
|
|
594
|
+
epoch: binding.authority.epoch,
|
|
595
|
+
owner: "controller",
|
|
596
|
+
holderId: binding.authority.holderId
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
if (binding.authority.owner === "human") {
|
|
600
|
+
throw new Error(`Provider writer is held by a human: ${taskId}/${roleName}.`);
|
|
601
|
+
}
|
|
602
|
+
if (binding.authority.owner === "none") {
|
|
603
|
+
return {
|
|
604
|
+
epoch: binding.authority.epoch + 1,
|
|
605
|
+
owner: "controller",
|
|
606
|
+
holderId: activationId
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
throw new Error(`Provider writer authority is unknown: ${taskId}/${roleName}.`);
|
|
610
|
+
}
|
|
545
611
|
#applyWorkspaceScope(taskId, role, launch, workspaceOverride) {
|
|
546
612
|
const workspace = workspaceOverride
|
|
547
613
|
?? (role.name === "leader"
|
|
@@ -786,6 +852,18 @@ function addCodexLifecycleHooks(args, mode, cliPath) {
|
|
|
786
852
|
}
|
|
787
853
|
return [...args.slice(0, -2), ...managed, ...args.slice(-2)];
|
|
788
854
|
}
|
|
855
|
+
function addCodexManagedLifecycleHooks(args, cliPath) {
|
|
856
|
+
if (args.length < 2 || args.at(-2) !== "app-server" || args.at(-1) !== "--stdio") {
|
|
857
|
+
throw new Error("Managed Codex App Server launch shape is invalid.");
|
|
858
|
+
}
|
|
859
|
+
return [
|
|
860
|
+
...args.slice(0, -2),
|
|
861
|
+
"--enable", "hooks",
|
|
862
|
+
"--config", codexLifecycleHooksConfig(cliPath),
|
|
863
|
+
"--dangerously-bypass-hook-trust",
|
|
864
|
+
...args.slice(-2)
|
|
865
|
+
];
|
|
866
|
+
}
|
|
789
867
|
function readySession(agentId, adapterId, nativeSessionId, effective) {
|
|
790
868
|
return {
|
|
791
869
|
agentId,
|
|
@@ -185,9 +185,12 @@ export function foldCanonicalLifecycleEvent(event, expectation) {
|
|
|
185
185
|
// Only an identity-matched durable native event can move accepted/delivered,
|
|
186
186
|
// and only after the independently committed transport receipt. Provider
|
|
187
187
|
// acceptance and transport acknowledgement are deliberately separate
|
|
188
|
-
// evidence layers: neither may repair or infer the other.
|
|
188
|
+
// evidence layers: neither may repair or infer the other. A fresh managed
|
|
189
|
+
// Host can publish native acceptance immediately before its launch call
|
|
190
|
+
// returns and lets the scheduler persist the transport receipt. Retain
|
|
191
|
+
// that exact fenced fact for replay instead of misclassifying it as stale.
|
|
189
192
|
if (!expectation.pushed)
|
|
190
|
-
return { outcome: "
|
|
193
|
+
return { outcome: "deferred", reason: "accept-before-push" };
|
|
191
194
|
if (expectation.terminal)
|
|
192
195
|
return { outcome: "obsolete", reason: "accept-after-terminal" };
|
|
193
196
|
if (expectation.accepted)
|
|
@@ -15,15 +15,18 @@ export class RemoteBaselineConflictError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
/** The small Git boundary used by project registration and Task workspaces. */
|
|
17
17
|
export class NodeGitWorkspace {
|
|
18
|
+
async resolveTree(repositoryPath, commit) {
|
|
19
|
+
return gitLine([
|
|
20
|
+
"-C", repositoryPath,
|
|
21
|
+
"rev-parse", "--verify", "--end-of-options", `${commit}^{tree}`
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
18
24
|
async findCommitWithSameTreeInHistory(input) {
|
|
19
25
|
const source = (await this.inspect(input.repositoryPath, input.sourceCommit)).baseCommit;
|
|
20
26
|
const history = (await this.inspect(input.repositoryPath, input.historyHead)).baseCommit;
|
|
21
27
|
if (await this.isAncestor(input.repositoryPath, source, history))
|
|
22
28
|
return source;
|
|
23
|
-
const sourceTree = await
|
|
24
|
-
"-C", input.repositoryPath,
|
|
25
|
-
"rev-parse", "--verify", "--end-of-options", `${source}^{tree}`
|
|
26
|
-
]);
|
|
29
|
+
const sourceTree = await this.resolveTree(input.repositoryPath, source);
|
|
27
30
|
const pageSize = 1000;
|
|
28
31
|
for (let skip = 0;; skip += pageSize) {
|
|
29
32
|
const output = await git([
|
|
@@ -107,10 +107,12 @@ export async function inspectTaskBaseFreshness(taskId, store, options = {}) {
|
|
|
107
107
|
}));
|
|
108
108
|
return { taskId, refreshed: options.refresh === true, entries };
|
|
109
109
|
}
|
|
110
|
-
export function assertTaskBaseFreshnessForCompletion(report) {
|
|
110
|
+
export function assertTaskBaseFreshnessForCompletion(report, options = {}) {
|
|
111
111
|
const warnings = [];
|
|
112
112
|
for (const entry of report.entries) {
|
|
113
|
-
|
|
113
|
+
const acceptedPublishedTree = options.acceptedPublishedTreeProjectId === entry.projectId;
|
|
114
|
+
if ((entry.status === "behind" || entry.status === "diverged")
|
|
115
|
+
&& !acceptedPublishedTree) {
|
|
114
116
|
throw usageError(`Task ${report.taskId} Project ${entry.projectId} base is ${entry.status}; `
|
|
115
117
|
+ `run 'yui task base status ${report.taskId} --refresh' and choose an explicit delivery base. `
|
|
116
118
|
+ "Safe resolutions are to rebase or merge the Task workspace onto the refreshed remote base, "
|
package/dist/run/agentRun.js
CHANGED
|
@@ -67,11 +67,11 @@ export function withAgentRunContextSnapshot(run, snapshot, deltaRefIds = []) {
|
|
|
67
67
|
if (run.status !== "active" || run.pushedAt !== undefined || run.deliveredAt !== undefined) {
|
|
68
68
|
throw new Error(`Cannot bind Context Snapshot after Run delivery: ${run.id}.`);
|
|
69
69
|
}
|
|
70
|
-
const assignment =
|
|
70
|
+
const assignment = createRunAssignment({
|
|
71
71
|
...run.assignment,
|
|
72
72
|
contextSnapshotRef: snapshot,
|
|
73
73
|
deltaRefIds
|
|
74
|
-
})
|
|
74
|
+
});
|
|
75
75
|
return validateAgentRun(Object.freeze({
|
|
76
76
|
...run,
|
|
77
77
|
assignment,
|
|
@@ -329,7 +329,7 @@ export function withProviderRetry(run, retry) {
|
|
|
329
329
|
* remain historical facts; the new delivery receipt and `dispatching` retry
|
|
330
330
|
* state make the delivery path send only the short continuation envelope.
|
|
331
331
|
*/
|
|
332
|
-
export function reopenRunForProviderRetry(run, receiptId, now) {
|
|
332
|
+
export function reopenRunForProviderRetry(run, receiptId, now, mode = "resume") {
|
|
333
333
|
if (run.status !== "active" || run.providerRetry === undefined) {
|
|
334
334
|
throw new Error(`Agent run is not waiting for a provider retry: ${run.id}.`);
|
|
335
335
|
}
|
|
@@ -337,7 +337,7 @@ export function reopenRunForProviderRetry(run, receiptId, now) {
|
|
|
337
337
|
const { providerRetry, ...rest } = run;
|
|
338
338
|
return validateAgentRun({
|
|
339
339
|
...rest,
|
|
340
|
-
mode
|
|
340
|
+
mode,
|
|
341
341
|
deliveryReceiptId: receiptId,
|
|
342
342
|
updatedAt: timestamp,
|
|
343
343
|
providerRetry: prepareProviderRetryDispatch(providerRetry, receiptId, now)
|