@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.
Files changed (52) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +57 -46
  3. package/dist/cli/commandCatalog.js +57 -17
  4. package/dist/cli/interactionPolicy.js +4 -10
  5. package/dist/cli/invocationRouter.js +2 -1
  6. package/dist/cli.js +106 -27
  7. package/dist/commands/taskCommands.js +458 -77
  8. package/dist/commands/taskCompletionGate.js +152 -0
  9. package/dist/context/runContextPack.js +19 -4
  10. package/dist/context/sessionBootstrapManifest.js +1 -1
  11. package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
  12. package/dist/controller/resourceInventory.js +9 -5
  13. package/dist/controller/runtime.js +80 -7
  14. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  15. package/dist/controller/structuredProviderObservation.js +273 -0
  16. package/dist/executor/agentAdapter.js +40 -0
  17. package/dist/executor/agentExecutor.js +31 -7
  18. package/dist/executor/executorRegistry.js +11 -49
  19. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  20. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  21. package/dist/repository/gitWorkspace.js +7 -4
  22. package/dist/repository/taskBaseFreshness.js +4 -2
  23. package/dist/run/agentRun.js +4 -4
  24. package/dist/runtime/agentHost.js +767 -158
  25. package/dist/runtime/builtinAgentDrivers.js +1 -5
  26. package/dist/runtime/codexAppServerRuntime.js +67 -60
  27. package/dist/runtime/exactControlPlane.js +7 -2
  28. package/dist/runtime/index.js +6 -2
  29. package/dist/runtime/launchBroker.js +30 -8
  30. package/dist/runtime/providerAuthorityFence.js +24 -0
  31. package/dist/runtime/providerControl.js +63 -0
  32. package/dist/runtime/providerRecoveryDecision.js +55 -0
  33. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  34. package/dist/runtime/runtimeBinding.js +20 -11
  35. package/dist/runtime/structuredProviderHost.js +476 -0
  36. package/dist/runtime/tmuxAdapters.js +143 -42
  37. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  38. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  39. package/dist/scheduler/wakeReason.js +1 -0
  40. package/dist/storage/migration/productionRegistry.js +111 -0
  41. package/dist/storage/sqliteStore.js +2 -0
  42. package/dist/storage/taskStore.js +3 -1
  43. package/dist/task/completionReadiness.js +43 -0
  44. package/dist/task/nextAction.js +6 -4
  45. package/dist/task/publicationReference.js +1 -0
  46. package/dist/tmux/tmuxManager.js +1 -1
  47. package/dist/workItem/workItem.js +12 -0
  48. package/dist/workspace/workItemChangeSetManager.js +2 -1
  49. package/i18n/README.zh-CN.md +11 -8
  50. package/package.json +1 -1
  51. package/skills/yui-leader/SKILL.md +8 -3
  52. package/skills/yui-runtime/SKILL.md +7 -2
@@ -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.initialPromptRunId === undefined
220
+ ...(planned.initialTurnRunId === undefined
220
221
  ? {}
221
- : { initialPromptRunId: planned.initialPromptRunId })
222
+ : { initialTurnRunId: planned.initialTurnRunId })
222
223
  });
223
224
  const yuiHome = planned.launch.env.YUI_HOME;
224
225
  const childLifecycle = planned.launch.childLifecycle;
225
- // Custom/legacy planners do not yet expose the Agent Host contract. Keep
226
- // their direct launch behavior for compatibility while all built-in
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.initialPromptRunId !== undefined
249
- ? { initialPromptRunId: planned.initialPromptRunId }
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.initialPromptRunId === request.runId
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.providerInput === undefined
292
+ ...(planned.launch.providerControl === undefined
284
293
  ? {}
285
- : { providerInput: planned.launch.providerInput })
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 providerChildLaunched = false;
306
- let launchPromptAlreadySubmitted = false;
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
- providerChildLaunched = hostCreated && planned.launch.deferProviderStart !== true;
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 !== "accepted") {
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
- else {
331
- providerChildLaunched = planned.launch.deferProviderStart !== true;
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 (providerChildLaunched
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
- ...(providerChildLaunched && hostCreated && planned.initialPromptRunId !== undefined
363
- ? { initialPromptRunId: planned.initialPromptRunId }
421
+ ...(providerAcknowledged && planned.initialTurnRunId !== undefined
422
+ ? { initialTurnRunId: planned.initialTurnRunId }
364
423
  : {}),
365
- ...((launchPromptAlreadySubmitted || (providerChildLaunched && !hostCreated))
366
- && planned.initialPromptRunId !== undefined
367
- ? { launchPromptUncertainRunId: planned.initialPromptRunId }
424
+ ...(providerDeliveryUnknown && planned.initialTurnRunId !== undefined
425
+ ? { initialTurnDeliveryUnknownRunId: planned.initialTurnRunId }
368
426
  : {}),
369
- ...(nativeSessionId === undefined ? {} : { nativeSessionId })
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
- /** Non-blocking receipt-backed tmux push; it never interprets provider output. */
447
- export class TmuxPromptPushAdapter {
448
- tmux;
449
- readiness;
450
- constructor(tmux, readiness) {
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
- const readinessProbe = this.readiness(request.binding.adapterId);
457
- const outcome = this.tmux.sendRoleInputOnceIfReadyAsync === undefined
458
- ? this.tmux.sendRoleInputOnceIfReady(ref.hostId, ref.roleName, request.envelope.id, request.envelope.text, readinessProbe)
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
- return outcome === "not-ready" ? "busy" : "delivered";
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) {