@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.
Files changed (38) hide show
  1. package/ARCHITECTURE.md +27 -28
  2. package/README.md +35 -46
  3. package/dist/cli/commandCatalog.js +49 -14
  4. package/dist/cli/interactionPolicy.js +4 -10
  5. package/dist/cli/invocationRouter.js +2 -1
  6. package/dist/cli.js +73 -21
  7. package/dist/commands/taskCommands.js +108 -53
  8. package/dist/controller/fileSchedulerStoreAdapter.js +389 -70
  9. package/dist/controller/resourceInventory.js +9 -5
  10. package/dist/controller/runtime.js +80 -7
  11. package/dist/controller/runtimeLaunchCoordinator.js +18 -78
  12. package/dist/controller/structuredProviderObservation.js +273 -0
  13. package/dist/executor/agentAdapter.js +40 -0
  14. package/dist/executor/agentExecutor.js +31 -7
  15. package/dist/executor/executorRegistry.js +11 -49
  16. package/dist/executor/fileRoleLaunchPlanner.js +115 -37
  17. package/dist/lifecycle/canonicalLifecycleEvent.js +5 -2
  18. package/dist/run/agentRun.js +2 -2
  19. package/dist/runtime/agentHost.js +767 -158
  20. package/dist/runtime/builtinAgentDrivers.js +1 -5
  21. package/dist/runtime/codexAppServerRuntime.js +67 -60
  22. package/dist/runtime/exactControlPlane.js +7 -2
  23. package/dist/runtime/index.js +6 -2
  24. package/dist/runtime/launchBroker.js +30 -8
  25. package/dist/runtime/providerAuthorityFence.js +24 -0
  26. package/dist/runtime/providerControl.js +63 -0
  27. package/dist/runtime/providerRecoveryDecision.js +55 -0
  28. package/dist/runtime/providerRuntimeIdentity.js +269 -19
  29. package/dist/runtime/runtimeBinding.js +20 -11
  30. package/dist/runtime/structuredProviderHost.js +476 -0
  31. package/dist/runtime/tmuxAdapters.js +143 -42
  32. package/dist/scheduler/activeRoleRunDelivery.js +206 -120
  33. package/dist/scheduler/leaderWakeupProcessor.js +141 -16
  34. package/dist/storage/migration/productionRegistry.js +111 -0
  35. package/dist/storage/taskStore.js +1 -1
  36. package/dist/tmux/tmuxManager.js +1 -1
  37. package/i18n/README.zh-CN.md +11 -8
  38. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { createHash } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
2
  import { isDeepStrictEqual } from "node:util";
3
3
  import { createRunAssignment } from "../context/runContextContract.js";
4
4
  import { buildRunContextPack, buildRunContextDelta, contextSnapshotDeltaRefIds, expandRunContextRef, freezeRunContextSnapshot } from "../context/runContextPack.js";
@@ -7,7 +7,8 @@ import { CliError, dataError, roleNotFound, runtimeError, taskNotFound, usageErr
7
7
  import { createTaskEvent } from "../event/taskEvent.js";
8
8
  import { clearMatchingLeaderStallAttention, isRoleRunStalled, RUN_PROGRESS_EVENT, RUN_RECOVERED_EVENT } from "../scheduler/roleRunStall.js";
9
9
  import { readCommandText } from "./textInput.js";
10
- import { createRoleSessionSet, roleAgentSessionResumeMode } from "../executor/agentExecutor.js";
10
+ import { createRoleSessionSet, roleAgentSessionResumeMode, updateTaskRoleProviderRuntime } from "../executor/agentExecutor.js";
11
+ import { currentProviderActivation, transferProviderAuthority } from "../runtime/providerRuntimeIdentity.js";
11
12
  import { resolveEffectiveLaunch } from "../executor/effectiveLaunch.js";
12
13
  import { defaultTableWidth, renderTable } from "../output/table.js";
13
14
  import { formatTimestamp } from "../output/timePresentation.js";
@@ -16,7 +17,7 @@ import { createTaskMessage, taskMessageAuthorLabel } from "../message/message.js
16
17
  import { cancelInputRequest } from "../input/inputRequest.js";
17
18
  import { recoverExactAgentRun, terminalizeExactTaskRun, validateExactRunReviewRound } from "../lifecycle/exactRunTerminalization.js";
18
19
  import { resetTaskRoleSessionGeneration } from "../lifecycle/taskRoleSessionReset.js";
19
- import { activeRoleAgentBinding, copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
20
+ import { copyGlobalRoleToTaskRole, createRole, createRoleAgentBinding, switchActiveRoleAgent, unbindRoleAgent, updateRole, updateRoleStatus } from "../role/role.js";
20
21
  import { agentRunDeliveryReceiptId, createAgentRun, withAgentRunContextSnapshot } from "../run/agentRun.js";
21
22
  import { projectRunRecovery, readRunRecoveryFacts } from "../run/recoveryProjection.js";
22
23
  import { matchYieldReceipt } from "../run/yieldReceipt.js";
@@ -240,7 +241,6 @@ export function runTaskCommand(args, store, options = {}) {
240
241
  case "milestone": return taskMilestoneCommand(rest, store, options);
241
242
  case "event": return taskEventCommand(rest, store);
242
243
  case "continuation": return taskContinuationCommand(rest, store);
243
- case "enter": return enterTaskRoleAlias(rest, store, options);
244
244
  default:
245
245
  throw usageError(command === undefined
246
246
  ? "Task command is required."
@@ -429,8 +429,8 @@ function updateTaskCommand(args, store, options) {
429
429
  /** Compatibility helper for call sites that cannot yet handle foreground enter. */
430
430
  export function runTaskOutputCommand(args, store, options = {}) {
431
431
  const execution = runTaskCommand(args, store, options);
432
- if (execution.kind === "enter") {
433
- throw runtimeError("Task role enter requires foreground tmux handoff by the CLI.");
432
+ if (execution.kind !== "output") {
433
+ throw runtimeError("Task Role foreground runtime control requires the CLI.");
434
434
  }
435
435
  return execution.output;
436
436
  }
@@ -1192,8 +1192,12 @@ function taskRoleCommand(args, store, options) {
1192
1192
  return output(unbindTaskRole(rest, store, options));
1193
1193
  if (command === "reset")
1194
1194
  return resetTaskRole(rest, store, options);
1195
- if (command === "enter")
1196
- return enterTaskRole(rest, store, options);
1195
+ if (command === "view")
1196
+ return viewTaskRole(rest, store);
1197
+ if (command === "takeover")
1198
+ return transferTaskRoleAuthority(rest, store, options, "takeover");
1199
+ if (command === "release")
1200
+ return transferTaskRoleAuthority(rest, store, options, "release");
1197
1201
  throw usageError(command === undefined
1198
1202
  ? "Task role command is required."
1199
1203
  : `Unknown command: task role ${command}`);
@@ -1506,60 +1510,111 @@ function unbindTaskRole(args, store, options) {
1506
1510
  });
1507
1511
  return `Unbound Agent ${args[2]} from ${result.taskId}/${result.name}\n`;
1508
1512
  }
1509
- function enterTaskRole(args, store, _options) {
1510
- const usage = "Task role enter usage: yui task role enter <task> <role> "
1511
- + "[--read-only | --read-write].";
1512
- const parsed = parseTail(args, new Set(), usage, new Set(["--read-only", "--read-write"]));
1513
- exactPositionals(parsed.positionals, 2, usage);
1514
- if (parsed.options.has("--read-only") && parsed.options.has("--read-write")) {
1515
- throw usageError("--read-only and --read-write are mutually exclusive.", usage);
1516
- }
1517
- const task = requireTask(store, parsed.positionals[0]);
1513
+ function viewTaskRole(args, store) {
1514
+ const usage = "Task role view usage: yui task role view <task> <role>.";
1515
+ exactPositionals(args, 2, usage);
1516
+ const task = requireTask(store, args[0]);
1518
1517
  if (task.status !== "active") {
1519
- throw usageError(inactiveTaskMessage(task, "entering a role session"));
1518
+ throw usageError(inactiveTaskMessage(task, "viewing a role session"));
1520
1519
  }
1521
- const role = requireRole(store, task.id, parsed.positionals[1]);
1522
- const access = parsed.options.has("--read-write") ? "read-write" : "read-only";
1523
- if (access === "read-write") {
1524
- assertTaskRoleWritableAttachAvailable(store, task.id, role.name);
1520
+ const role = requireRole(store, task.id, args[1]);
1521
+ const session = store.getRoleSession(task.id, role.name);
1522
+ if (session === null || session.status === "stopped" || session.status === "broken") {
1523
+ throw usageError(`Task Role has no live Provider view: ${task.id}/${role.name}.`);
1525
1524
  }
1526
1525
  return {
1527
- kind: "enter",
1526
+ kind: "view",
1528
1527
  taskId: task.id,
1529
1528
  roleName: role.name,
1530
- access,
1531
- output: `Attaching to ${role.name} for ${task.id} (${access})\n`
1529
+ access: "read-only",
1530
+ output: `Viewing ${role.name} for ${task.id} (read-only)\n`
1532
1531
  };
1533
1532
  }
1534
- /** Re-run after the tmux writer lease exists to close attach/launch races. */
1535
- export function assertTaskRoleWritableAttachAvailable(store, taskId, roleName, options = {}) {
1536
- const role = requireRole(store, taskId, roleName);
1537
- if (store.getActiveAgentRun(taskId, roleName) !== null) {
1538
- throw usageError(`Role has an active managed Run; writable attach is unavailable: ${taskId}/${roleName}.`);
1539
- }
1540
- const session = store.getRoleSession(taskId, roleName);
1541
- if (activeRoleAgentBinding(role).adapterId === "claude"
1542
- && ((session !== null
1543
- && session.status !== "stopped"
1544
- && session.status !== "broken")
1545
- || options.isManagedProcessRunning?.() === true)) {
1546
- throw usageError(`A managed Claude process is still running; writable attach is unavailable: ${taskId}/${roleName}.`);
1547
- }
1548
- }
1549
- function enterTaskRoleAlias(args, store, options) {
1550
- const usage = "Task enter usage: yui task enter <task> [role] "
1551
- + "[--read-only | --read-write].";
1552
- const parsed = parseTail(args, new Set(), usage, new Set(["--read-only", "--read-write"]));
1553
- if (parsed.positionals.length < 1 || parsed.positionals.length > 2
1554
- || parsed.positionals.some((value) => value.trim().length === 0)) {
1555
- throw usageError(usage);
1533
+ function transferTaskRoleAuthority(args, store, options, action) {
1534
+ const usage = `Task role ${action} usage: yui task role ${action} <task> <role>.`;
1535
+ exactPositionals(args, 2, usage);
1536
+ const now = clock(options);
1537
+ try {
1538
+ return store.transaction((tx) => {
1539
+ const task = requireTask(tx, args[0]);
1540
+ if (task.status !== "active") {
1541
+ throw usageError(inactiveTaskMessage(task, `${action} Provider authority`));
1542
+ }
1543
+ const role = requireRole(tx, task.id, args[1]);
1544
+ const sessions = tx.getTaskRoleSessionSet(task.id, role.name);
1545
+ const session = sessions?.sessions[role.activeAgentId];
1546
+ const binding = sessions?.providerBinding;
1547
+ if (sessions === null || sessions === undefined || session === undefined
1548
+ || binding === null || binding === undefined
1549
+ || session.launchId === undefined
1550
+ || session.status === "stopped" || session.status === "broken") {
1551
+ throw new Error(`Task Role has no live managed Provider: ${task.id}/${role.name}.`);
1552
+ }
1553
+ const activation = currentProviderActivation(binding);
1554
+ if (activation === null) {
1555
+ throw new Error(`Provider Activation is not live: ${task.id}/${role.name}.`);
1556
+ }
1557
+ if (action === "takeover") {
1558
+ const activeRun = tx.getActiveAgentRun(task.id, role.name);
1559
+ if (sessions.inFlight === null || activeRun?.id !== sessions.inFlight.runId) {
1560
+ throw new Error(`Task Role has no active managed Run for takeover: ${task.id}/${role.name}.`);
1561
+ }
1562
+ }
1563
+ if (action === "takeover"
1564
+ && binding.authority.owner !== "controller"
1565
+ && binding.authority.owner !== "human") {
1566
+ throw new Error(`Provider authority is not Controller-owned: ${task.id}/${role.name}.`);
1567
+ }
1568
+ if (action === "release"
1569
+ && binding.authority.owner !== "human"
1570
+ && binding.authority.owner !== "controller") {
1571
+ throw new Error(`Provider authority is not human-owned: ${task.id}/${role.name}.`);
1572
+ }
1573
+ const desiredOwner = action === "takeover" ? "human" : "controller";
1574
+ const unchanged = binding.authority.owner === desiredOwner;
1575
+ const updatedBinding = unchanged
1576
+ ? binding
1577
+ : transferProviderAuthority(binding, {
1578
+ expectedEpoch: binding.authority.epoch,
1579
+ expectedOwner: binding.authority.owner,
1580
+ owner: desiredOwner,
1581
+ holderId: action === "takeover" ? `human:${randomUUID()}` : activation.activationId,
1582
+ changedAt: now.toISOString()
1583
+ });
1584
+ const authority = updatedBinding.authority;
1585
+ if (authority.owner !== "controller" && authority.owner !== "human") {
1586
+ throw new Error("Provider authority transfer did not produce a writer.");
1587
+ }
1588
+ if (!unchanged) {
1589
+ tx.saveTaskRoleSessionSet(updateTaskRoleProviderRuntime(sessions, updatedBinding, now));
1590
+ recordTaskEvent(tx, task.id, "runtime.provider-authority-transferred", {
1591
+ role: role.name,
1592
+ owner: authority.owner,
1593
+ holderId: authority.holderId,
1594
+ epoch: String(authority.epoch)
1595
+ }, now);
1596
+ }
1597
+ return {
1598
+ kind: "authority",
1599
+ action,
1600
+ taskId: task.id,
1601
+ roleName: role.name,
1602
+ launchId: session.launchId,
1603
+ nativeSessionId: session.nativeSessionId,
1604
+ authority: {
1605
+ epoch: authority.epoch,
1606
+ owner: authority.owner,
1607
+ holderId: authority.holderId
1608
+ },
1609
+ output: action === "takeover"
1610
+ ? `Human authority ${unchanged ? "replayed" : "acquired"} for ${task.id}/${role.name} at epoch ${authority.epoch}.\n`
1611
+ : `Controller authority ${unchanged ? "replayed" : "restored"} for ${task.id}/${role.name} at epoch ${authority.epoch}.\n`
1612
+ };
1613
+ });
1614
+ }
1615
+ catch (error) {
1616
+ throw usageError(messageOf(error), usage);
1556
1617
  }
1557
- return enterTaskRole([
1558
- parsed.positionals[0],
1559
- parsed.positionals[1] ?? LEADER_ROLE,
1560
- ...(parsed.options.has("--read-only") ? ["--read-only"] : []),
1561
- ...(parsed.options.has("--read-write") ? ["--read-write"] : [])
1562
- ], store, options);
1563
1618
  }
1564
1619
  function taskWorkCommand(args, store, options) {
1565
1620
  const [command, ...rest] = args;