@zq-silk/yui 0.8.1 → 0.8.2
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 +35 -46
- package/dist/cli/commandCatalog.js +49 -14
- package/dist/cli/interactionPolicy.js +4 -10
- package/dist/cli/invocationRouter.js +2 -1
- package/dist/cli.js +73 -21
- package/dist/commands/taskCommands.js +108 -53
- 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/run/agentRun.js +2 -2
- 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/storage/migration/productionRegistry.js +111 -0
- package/dist/storage/taskStore.js +1 -1
- package/dist/tmux/tmuxManager.js +1 -1
- package/i18n/README.zh-CN.md +11 -8
- package/package.json +1 -1
|
@@ -6,8 +6,9 @@ import { normalizeRuntimeOwner } from "./runtimeOwner.js";
|
|
|
6
6
|
import { RuntimeHostContentionError } from "./ports.js";
|
|
7
7
|
import { toRuntimeLaunchFailure } from "./launchDiagnostics.js";
|
|
8
8
|
import { requireSafeIdentity } from "./validation.js";
|
|
9
|
+
import { YUI_CONTROL_PLANE_DESCRIPTOR, YUI_TASK_RUNTIME_DESCRIPTOR } from "./exactControlPlane.js";
|
|
9
10
|
import { launchBrokerForHome } from "./launchBroker.js";
|
|
10
|
-
import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl } from "./agentHost.js";
|
|
11
|
+
import { AGENT_HOST_CONTROL_PROTOCOL, sendAgentHostLaunchControl, sendAgentHostTurnControl, waitForAgentHostLaunchAck } from "./agentHost.js";
|
|
11
12
|
const DEFAULT_INACTIVITY_TIMEOUT_MS = 300_000;
|
|
12
13
|
/**
|
|
13
14
|
* Runtime lifecycle adapter for the current tmux host. The returned hostRef is
|
|
@@ -216,16 +217,18 @@ export class TmuxSessionHost {
|
|
|
216
217
|
adapterId: request.adapterId,
|
|
217
218
|
effective: request.effective,
|
|
218
219
|
...(nativeSessionId === undefined ? {} : { nativeSessionId }),
|
|
219
|
-
...(planned.
|
|
220
|
+
...(planned.initialTurnRunId === undefined
|
|
220
221
|
? {}
|
|
221
|
-
: {
|
|
222
|
+
: { initialTurnRunId: planned.initialTurnRunId })
|
|
222
223
|
});
|
|
223
224
|
const yuiHome = planned.launch.env.YUI_HOME;
|
|
224
225
|
const childLifecycle = planned.launch.childLifecycle;
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
// managed Roles use the persistent Host below.
|
|
226
|
+
// Interactive/global Roles may still be native TUIs. A managed Task Run
|
|
227
|
+
// has no terminal-write fallback and must expose the Agent Host contract.
|
|
228
228
|
if (yuiHome === undefined || childLifecycle === undefined) {
|
|
229
|
+
if (request.owner.scope === "task" && request.runId !== undefined) {
|
|
230
|
+
throw new Error("Managed Task Run is missing its structured Agent Host contract.");
|
|
231
|
+
}
|
|
229
232
|
let hostCreated;
|
|
230
233
|
try {
|
|
231
234
|
hostCreated = await ensureRoleWindow(this.tmux, hostId, planned.role, planned.launch);
|
|
@@ -245,8 +248,8 @@ export class TmuxSessionHost {
|
|
|
245
248
|
roleName: request.owner.roleName
|
|
246
249
|
}),
|
|
247
250
|
hostCreated,
|
|
248
|
-
...(hostCreated && planned.
|
|
249
|
-
? {
|
|
251
|
+
...(hostCreated && planned.initialTurnRunId !== undefined
|
|
252
|
+
? { initialTurnRunId: planned.initialTurnRunId }
|
|
250
253
|
: {}),
|
|
251
254
|
...(nativeSessionId === undefined ? {} : { nativeSessionId })
|
|
252
255
|
});
|
|
@@ -258,7 +261,7 @@ export class TmuxSessionHost {
|
|
|
258
261
|
if (request.mode === "new"
|
|
259
262
|
&& request.owner.scope === "task"
|
|
260
263
|
&& request.runId !== undefined
|
|
261
|
-
&& planned.
|
|
264
|
+
&& planned.initialTurnRunId === request.runId
|
|
262
265
|
&& this.#waitForNativeSession !== undefined) {
|
|
263
266
|
binding = createRuntimeBinding({
|
|
264
267
|
...binding,
|
|
@@ -271,6 +274,12 @@ export class TmuxSessionHost {
|
|
|
271
274
|
return binding;
|
|
272
275
|
}
|
|
273
276
|
const broker = launchBrokerForHome(yuiHome);
|
|
277
|
+
const frozenControlPlane = planned.launch.env[YUI_CONTROL_PLANE_DESCRIPTOR];
|
|
278
|
+
const frozenTaskRuntime = planned.launch.env[YUI_TASK_RUNTIME_DESCRIPTOR];
|
|
279
|
+
if (request.owner.scope === "task" && request.runId !== undefined
|
|
280
|
+
&& (frozenControlPlane === undefined || frozenTaskRuntime === undefined)) {
|
|
281
|
+
throw new Error("Managed Task Agent Host launch is missing its frozen control descriptors.");
|
|
282
|
+
}
|
|
274
283
|
const reservation = broker.reserve(Object.freeze({
|
|
275
284
|
schemaVersion: 1,
|
|
276
285
|
launchId: request.launchId,
|
|
@@ -280,9 +289,9 @@ export class TmuxSessionHost {
|
|
|
280
289
|
cwd: planned.role.cwd ?? planned.role.workspace,
|
|
281
290
|
childLifecycle,
|
|
282
291
|
startMode: planned.launch.deferProviderStart === true ? "idle" : "provider",
|
|
283
|
-
...(planned.launch.
|
|
292
|
+
...(planned.launch.providerControl === undefined
|
|
284
293
|
? {}
|
|
285
|
-
: {
|
|
294
|
+
: { providerControl: planned.launch.providerControl })
|
|
286
295
|
}));
|
|
287
296
|
const hostLaunch = {
|
|
288
297
|
command: process.execPath,
|
|
@@ -298,15 +307,50 @@ export class TmuxSessionHost {
|
|
|
298
307
|
YUI_SESSION_SCOPE: request.owner.scope,
|
|
299
308
|
...(request.owner.scope === "task" ? { YUI_TASK_ID: request.owner.taskId } : {}),
|
|
300
309
|
YUI_ROLE: request.owner.roleName,
|
|
301
|
-
YUI_LAUNCH_ID: request.launchId
|
|
310
|
+
YUI_LAUNCH_ID: request.launchId,
|
|
311
|
+
...(planned.launch.env.YUI_AGENT_ID === undefined
|
|
312
|
+
? {}
|
|
313
|
+
: { YUI_AGENT_ID: planned.launch.env.YUI_AGENT_ID }),
|
|
314
|
+
...(planned.launch.env.YUI_ADAPTER_ID === undefined
|
|
315
|
+
? {}
|
|
316
|
+
: { YUI_ADAPTER_ID: planned.launch.env.YUI_ADAPTER_ID }),
|
|
317
|
+
...(planned.launch.env.YUI_WORKSPACE === undefined
|
|
318
|
+
? {}
|
|
319
|
+
: { YUI_WORKSPACE: planned.launch.env.YUI_WORKSPACE }),
|
|
320
|
+
...(frozenControlPlane === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: { [YUI_CONTROL_PLANE_DESCRIPTOR]: frozenControlPlane }),
|
|
323
|
+
...(frozenTaskRuntime === undefined
|
|
324
|
+
? {}
|
|
325
|
+
: { [YUI_TASK_RUNTIME_DESCRIPTOR]: frozenTaskRuntime })
|
|
302
326
|
}
|
|
303
327
|
};
|
|
304
328
|
let hostCreated;
|
|
305
|
-
let
|
|
306
|
-
let
|
|
329
|
+
let providerAcknowledged = false;
|
|
330
|
+
let providerDeliveryUnknown = false;
|
|
331
|
+
let providerRejected = false;
|
|
332
|
+
let providerDispatchObserved = false;
|
|
333
|
+
let providerSnapshot;
|
|
307
334
|
try {
|
|
308
335
|
hostCreated = await ensureRoleWindow(this.tmux, hostId, planned.role, hostLaunch);
|
|
309
|
-
|
|
336
|
+
if (hostCreated && planned.launch.deferProviderStart !== true) {
|
|
337
|
+
providerSnapshot = await waitForAgentHostLaunchAck({
|
|
338
|
+
home: yuiHome,
|
|
339
|
+
scope: request.owner.scope,
|
|
340
|
+
...(request.owner.scope === "task" ? { taskId: request.owner.taskId } : {}),
|
|
341
|
+
roleName: request.owner.roleName,
|
|
342
|
+
launchId: reservation.launchId,
|
|
343
|
+
requireTurnAck: planned.initialTurnRunId !== undefined
|
|
344
|
+
});
|
|
345
|
+
providerDeliveryUnknown = providerSnapshot.state === "delivery-unknown";
|
|
346
|
+
providerRejected = planned.initialTurnRunId !== undefined
|
|
347
|
+
&& providerSnapshot.state === "rejected";
|
|
348
|
+
providerAcknowledged = !providerDeliveryUnknown && !providerRejected;
|
|
349
|
+
if (providerSnapshot.state === "rejected" && !providerRejected) {
|
|
350
|
+
throw new Error("Agent Host rejected a launch without an initial Provider Turn.");
|
|
351
|
+
}
|
|
352
|
+
providerDispatchObserved = true;
|
|
353
|
+
}
|
|
310
354
|
if (!hostCreated) {
|
|
311
355
|
const controlResult = await sendAgentHostLaunchControl({
|
|
312
356
|
home: yuiHome,
|
|
@@ -320,23 +364,38 @@ export class TmuxSessionHost {
|
|
|
320
364
|
ticket: reservation.ticket
|
|
321
365
|
}
|
|
322
366
|
});
|
|
323
|
-
if (controlResult
|
|
367
|
+
if (controlResult.outcome === "active-other-launch") {
|
|
368
|
+
broker.revoke(request.launchId);
|
|
369
|
+
throw new RuntimeHostContentionError("provider-child-active", `The persistent Agent Host for ${request.owner.roleName} still owns another Provider Turn.`);
|
|
370
|
+
}
|
|
371
|
+
if (controlResult.outcome === "active-same-launch") {
|
|
324
372
|
broker.revoke(request.launchId);
|
|
325
|
-
launchPromptAlreadySubmitted = controlResult === "active-same-launch";
|
|
326
|
-
if (childLifecycle === "per-turn" && controlResult === "active-other-launch") {
|
|
327
|
-
throw new RuntimeHostContentionError("provider-child-active", `The persistent Agent Host for ${request.owner.roleName} still owns another Provider turn.`);
|
|
328
|
-
}
|
|
329
373
|
}
|
|
330
|
-
|
|
331
|
-
|
|
374
|
+
const acceptableState = controlResult.snapshot.state === "ready"
|
|
375
|
+
|| (planned.initialTurnRunId === undefined
|
|
376
|
+
&& controlResult.snapshot.state === "idle");
|
|
377
|
+
const deliveryUnknownState = planned.initialTurnRunId !== undefined
|
|
378
|
+
&& controlResult.snapshot.state === "delivery-unknown";
|
|
379
|
+
const rejectedState = planned.initialTurnRunId !== undefined
|
|
380
|
+
&& controlResult.snapshot.state === "rejected";
|
|
381
|
+
if ((!acceptableState && !deliveryUnknownState && !rejectedState)
|
|
382
|
+
|| controlResult.snapshot.launchId !== reservation.launchId) {
|
|
383
|
+
broker.revoke(request.launchId);
|
|
384
|
+
throw new Error(`Agent Host did not return an exact Provider acknowledgement for ${reservation.launchId}.`);
|
|
332
385
|
}
|
|
386
|
+
providerSnapshot = controlResult.snapshot;
|
|
387
|
+
providerAcknowledged = acceptableState;
|
|
388
|
+
providerDeliveryUnknown = deliveryUnknownState;
|
|
389
|
+
providerRejected = rejectedState;
|
|
390
|
+
providerDispatchObserved = true;
|
|
333
391
|
}
|
|
334
392
|
}
|
|
335
393
|
catch (error) {
|
|
336
394
|
broker.revoke(request.launchId);
|
|
337
395
|
throw error;
|
|
338
396
|
}
|
|
339
|
-
if (
|
|
397
|
+
if (providerDispatchObserved
|
|
398
|
+
&& hostCreated
|
|
340
399
|
&& request.owner.scope === "task"
|
|
341
400
|
&& planned.launch.env.YUI_JOB_CALLER_KEY !== undefined
|
|
342
401
|
&& this.planner.commitTaskCallerKey !== undefined) {
|
|
@@ -359,14 +418,21 @@ export class TmuxSessionHost {
|
|
|
359
418
|
roleName: request.owner.roleName
|
|
360
419
|
}),
|
|
361
420
|
hostCreated,
|
|
362
|
-
...(
|
|
363
|
-
? {
|
|
421
|
+
...(providerAcknowledged && planned.initialTurnRunId !== undefined
|
|
422
|
+
? { initialTurnRunId: planned.initialTurnRunId }
|
|
364
423
|
: {}),
|
|
365
|
-
...(
|
|
366
|
-
|
|
367
|
-
? { launchPromptUncertainRunId: planned.initialPromptRunId }
|
|
424
|
+
...(providerDeliveryUnknown && planned.initialTurnRunId !== undefined
|
|
425
|
+
? { initialTurnDeliveryUnknownRunId: planned.initialTurnRunId }
|
|
368
426
|
: {}),
|
|
369
|
-
...(
|
|
427
|
+
...(providerRejected && planned.initialTurnRunId !== undefined
|
|
428
|
+
? { initialTurnRejectedRunId: planned.initialTurnRunId }
|
|
429
|
+
: {}),
|
|
430
|
+
...((providerSnapshot?.nativeSessionId ?? nativeSessionId) === undefined
|
|
431
|
+
? {}
|
|
432
|
+
: { nativeSessionId: providerSnapshot?.nativeSessionId ?? nativeSessionId }),
|
|
433
|
+
...(planned.launch.providerControl === undefined
|
|
434
|
+
? {}
|
|
435
|
+
: { providerAuthority: planned.launch.providerControl.authority })
|
|
370
436
|
});
|
|
371
437
|
if (hostCreated && this.#onHostCreated !== undefined) {
|
|
372
438
|
const pane = await inspectRolePane(this.tmux, hostId, request.owner.roleName);
|
|
@@ -443,23 +509,58 @@ async function deadHostLaunchFailure(tmux, hostId, roleName, pane, context) {
|
|
|
443
509
|
...(stderrTail === undefined || stderrTail.length === 0 ? {} : { stderrTail })
|
|
444
510
|
});
|
|
445
511
|
}
|
|
446
|
-
/**
|
|
447
|
-
export class
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
this.tmux = tmux;
|
|
452
|
-
this.readiness = readiness;
|
|
512
|
+
/** Structured managed-Run input; tmux remains presentation/liveness only. */
|
|
513
|
+
export class AgentHostPromptPushAdapter {
|
|
514
|
+
home;
|
|
515
|
+
constructor(home) {
|
|
516
|
+
this.home = home;
|
|
453
517
|
}
|
|
454
518
|
async tryPush(request) {
|
|
455
519
|
const ref = requireMatchingHostRef(request.binding);
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
: await this.tmux.sendRoleInputOnceIfReadyAsync(ref.hostId, ref.roleName, request.envelope.id, request.envelope.text, readinessProbe);
|
|
460
|
-
if (outcome === "unavailable")
|
|
520
|
+
if (ref.scope !== "task" || request.binding.nativeSessionId === undefined
|
|
521
|
+
|| request.binding.providerAuthority === undefined
|
|
522
|
+
|| request.binding.providerAuthority.owner !== "controller") {
|
|
461
523
|
return "unavailable";
|
|
462
|
-
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const result = await sendAgentHostTurnControl({
|
|
527
|
+
home: this.home,
|
|
528
|
+
scope: "task",
|
|
529
|
+
taskId: ref.hostId,
|
|
530
|
+
roleName: ref.roleName,
|
|
531
|
+
control: {
|
|
532
|
+
protocol: AGENT_HOST_CONTROL_PROTOCOL,
|
|
533
|
+
type: "submit-turn",
|
|
534
|
+
launchId: request.binding.launchId,
|
|
535
|
+
nativeSessionId: request.binding.nativeSessionId,
|
|
536
|
+
authority: request.binding.providerAuthority,
|
|
537
|
+
turn: {
|
|
538
|
+
attemptId: request.envelope.id,
|
|
539
|
+
boundedText: request.envelope.text
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
if (result.snapshot.state === "delivery-unknown")
|
|
544
|
+
return "delivery-unknown";
|
|
545
|
+
if (result.outcome === "rejected")
|
|
546
|
+
return "rejected";
|
|
547
|
+
if (result.snapshot.attemptId !== request.envelope.id) {
|
|
548
|
+
return result.snapshot.state === "starting" || result.snapshot.state === "settling"
|
|
549
|
+
? "busy"
|
|
550
|
+
: "unavailable";
|
|
551
|
+
}
|
|
552
|
+
if (result.snapshot.state === "ready")
|
|
553
|
+
return "delivered";
|
|
554
|
+
return result.snapshot.state === "starting" || result.snapshot.state === "settling"
|
|
555
|
+
? "busy"
|
|
556
|
+
: "unavailable";
|
|
557
|
+
}
|
|
558
|
+
catch (error) {
|
|
559
|
+
const code = error.code;
|
|
560
|
+
return code === "ENOENT" || code === "ECONNREFUSED"
|
|
561
|
+
? "unavailable"
|
|
562
|
+
: "delivery-unknown";
|
|
563
|
+
}
|
|
463
564
|
}
|
|
464
565
|
}
|
|
465
566
|
async function ensureRoleWindow(tmux, hostId, role, launch) {
|