@zq-silk/yui 0.15.9 → 0.15.11

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 (100) hide show
  1. package/dist/agent/launchEnvironment.js +7 -0
  2. package/dist/cli/commandCatalog.js +26 -6
  3. package/dist/cli/interactionPolicy.js +3 -3
  4. package/dist/cli/updateOrchestrator.js +24 -1
  5. package/dist/cli/updatePorts.js +7 -3
  6. package/dist/cli/upgradeCommand.js +42 -2
  7. package/dist/cli.js +336 -93
  8. package/dist/commands/globalRoleCommands.js +314 -4
  9. package/dist/commands/projectCommands.js +6 -7
  10. package/dist/commands/releaseCommands.js +18 -0
  11. package/dist/commands/taskActor.js +25 -0
  12. package/dist/commands/taskCommands.js +511 -83
  13. package/dist/commands/taskIntegrationCommands.js +16 -38
  14. package/dist/commands/taskIntegrationQueueCommands.js +1 -1
  15. package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
  16. package/dist/commands/taskRoleRuntimeStatus.js +35 -0
  17. package/dist/context/taskContext.js +38 -2
  18. package/dist/controller/agentHostObservation.js +155 -0
  19. package/dist/controller/clientRuntime.js +17 -2
  20. package/dist/controller/controller.js +3 -0
  21. package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
  22. package/dist/controller/globalInputDelivery.js +119 -0
  23. package/dist/controller/jobControl.js +6 -2
  24. package/dist/controller/resourceInventory.js +14 -4
  25. package/dist/controller/resourceInventoryLinux.js +2 -6
  26. package/dist/controller/runtime.js +81 -6
  27. package/dist/controller/runtimeEventInbox.js +32 -3
  28. package/dist/controller/runtimeEventProcessor.js +26 -6
  29. package/dist/controller/runtimeHookRunFence.js +75 -19
  30. package/dist/controller/structuredProviderObservation.js +133 -70
  31. package/dist/coordination/workMailboxQueue.js +5 -0
  32. package/dist/execution/workItemExecutionProjection.js +1 -1
  33. package/dist/executor/agentExecutor.js +64 -4
  34. package/dist/executor/executorRegistry.js +3 -0
  35. package/dist/executor/fileRoleLaunchPlanner.js +78 -118
  36. package/dist/integration/deliveryObligation.js +2 -1
  37. package/dist/integration/gitIntegrationService.js +312 -382
  38. package/dist/integration/integrationAttempt.js +30 -4
  39. package/dist/integration/integrationQueueService.js +7 -7
  40. package/dist/integration/integrationSourceApplication.js +323 -0
  41. package/dist/message/globalInterrupt.js +33 -0
  42. package/dist/message/inputControlResolution.js +106 -0
  43. package/dist/message/message.js +367 -0
  44. package/dist/message/messageContinuation.js +126 -3
  45. package/dist/message/taskInterrupt.js +34 -0
  46. package/dist/observability/orchestrationMetrics.js +1 -1
  47. package/dist/release/releaseHandover.js +22 -0
  48. package/dist/release/releaseWorkflowPorts.js +15 -7
  49. package/dist/repository/gitWorkspace.js +72 -15
  50. package/dist/repository/taskWorkspaceCoordinator.js +134 -0
  51. package/dist/repository/taskWorkspacePreparer.js +120 -49
  52. package/dist/repository/workItemCandidateSnapshot.js +34 -0
  53. package/dist/resources/resourceDiscovery.js +3 -2
  54. package/dist/runtime/agentHost.js +152 -72
  55. package/dist/runtime/agentHostCompatibility.js +127 -0
  56. package/dist/runtime/agentHostProtocol.js +53 -0
  57. package/dist/runtime/executionEnvironment.js +0 -19
  58. package/dist/runtime/launchBroker.js +6 -0
  59. package/dist/runtime/sessionReconciliation.js +4 -4
  60. package/dist/runtime/taskRuntimeIsolation.js +30 -6
  61. package/dist/runtime/tmuxAdapters.js +5 -3
  62. package/dist/scheduler/operatorEvent.js +4 -0
  63. package/dist/scheduler/taskExecutionProjection.js +12 -1
  64. package/dist/scheduler/wakeReason.js +7 -1
  65. package/dist/scheduler/wakeupQueue.js +2 -0
  66. package/dist/setup/setupCommand.js +26 -8
  67. package/dist/storage/homeLayout.js +130 -0
  68. package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
  69. package/dist/storage/migrations/integrationContinuation.js +104 -0
  70. package/dist/storage/migrations/unifyHomeLayout.js +925 -0
  71. package/dist/storage/sqliteSchema.js +136 -4
  72. package/dist/storage/sqliteStore.js +40 -1
  73. package/dist/storage/storageVersions.js +1 -1
  74. package/dist/storage/storeRpc.js +1 -0
  75. package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
  76. package/dist/task/archiveDiagnostics.js +128 -0
  77. package/dist/task/nextAction.js +44 -11
  78. package/dist/web/assets/client/app.js +55 -0
  79. package/dist/web/assets/client/components.js +1 -0
  80. package/dist/web/assets/client/i18n.js +6 -0
  81. package/dist/web/assets/client/taskSurface.js +106 -0
  82. package/dist/web/assets/client/view.js +7 -4
  83. package/dist/web/assets/shell.js +23 -0
  84. package/dist/web/assets/styles/layout.js +1 -1
  85. package/dist/web/assets/styles/widgets.js +12 -0
  86. package/dist/web/webServer.js +117 -1
  87. package/dist/web/webSnapshot.js +4 -3
  88. package/dist/web/webTaskSurface.js +222 -5
  89. package/dist/workspace/workItemChangeSetManager.js +18 -2
  90. package/docs/release-workflow.md +39 -0
  91. package/docs/release-workflow.zh-CN.md +29 -0
  92. package/docs/sqlite-control-plane-design.md +223 -1
  93. package/docs/testing/verification-levels.md +24 -0
  94. package/docs/testing/verification-levels.zh-CN.md +11 -0
  95. package/package.json +1 -1
  96. package/skills/yui-leader/references/execution.md +151 -49
  97. package/skills/yui-leader/references/integration.md +52 -2
  98. package/skills/yui-operator/SKILL.md +6 -1
  99. package/skills/yui-runtime/SKILL.md +27 -0
  100. package/skills/yui-runtime/references/publication.md +20 -0
@@ -122,6 +122,13 @@ export function operationalAgentEnvironment(adapterId, source) {
122
122
  || `${dirname(process.execPath)}:/usr/local/bin:/usr/bin:/bin`,
123
123
  HOME: source.HOME || homedir(),
124
124
  TERM: usableInteractiveTerminal(source.TERM),
125
+ // Base fallback only. Every managed Task launch has its isolation
126
+ // environment (TMPDIR = the Home-side runtime `roots.temporary`) assigned
127
+ // OVER this by the launch planner, so this branch is reached only by
128
+ // non-isolated global/ad-hoc launches with no managed runtime. Those run in
129
+ // the operator's own context, where their inherited TMPDIR (or the system
130
+ // temp) is the correct scratch — forcing it under control Home would
131
+ // misroute user scratch into Yui-managed storage.
125
132
  TMPDIR: source.TMPDIR || tmpdir()
126
133
  };
127
134
  }
@@ -408,8 +408,8 @@ const taskChildren = [
408
408
  },
409
409
  {
410
410
  name: "archive",
411
- summary: "Archive a Task after confirming the main worktree outcome.",
412
- usage: "yui task archive <id> (--integrated [--force]|--abandon)",
411
+ summary: "Archive a terminal Task; explicit --force commits despite delivery/cleanup warnings, retaining unsafe resources.",
412
+ usage: "yui task archive <id> (--integrated|--abandon) [--force]",
413
413
  options: ["--integrated", "--abandon", "--force"]
414
414
  },
415
415
  { name: "reconcile", summary: "Run one immediate Controller reconciliation.", usage: "yui task reconcile <id>" },
@@ -435,7 +435,7 @@ const taskChildren = [
435
435
  {
436
436
  name: "message",
437
437
  summary: "Manage durable Task messages.",
438
- sections: [{ id: "manage", title: "Commands", entries: ["send", "handoff", "list", "show", "update", "retire"] }],
438
+ sections: [{ id: "manage", title: "Commands", entries: ["send", "queue", "steer", "handoff", "list", "show", "update", "retire"] }],
439
439
  children: [
440
440
  {
441
441
  name: "handoff",
@@ -454,6 +454,20 @@ const taskChildren = [
454
454
  },
455
455
  fileOptions: ["--body-file"]
456
456
  },
457
+ {
458
+ name: "queue",
459
+ summary: "Queue an input for delivery at the recipient's next legal opportunity (idempotent by request id).",
460
+ usage: "yui task message queue <id> (<body>|--body-file <path|->) --request-id <id> [--to <role> --work-item <id>|--review-round <id>]",
461
+ options: ["--body-file", "--request-id", "--to", "--work-item", "--review-round"],
462
+ fileOptions: ["--body-file"]
463
+ },
464
+ {
465
+ name: "steer",
466
+ summary: "Steer only a Role's exact current native Turn; saved and reported without fallback when unsupported.",
467
+ usage: "yui task message steer <id> (<body>|--body-file <path|->) --request-id <id> --expected-target <turn> --to <role> [--work-item <id>|--review-round <id>]",
468
+ options: ["--body-file", "--request-id", "--expected-target", "--to", "--work-item", "--review-round"],
469
+ fileOptions: ["--body-file"]
470
+ },
457
471
  {
458
472
  name: "list",
459
473
  summary: "List Task messages.",
@@ -622,7 +636,7 @@ const taskChildren = [
622
636
  summary: "Manage Roles within a Task.",
623
637
  sections: [{ id: "manage", title: "Commands", entries: [
624
638
  "add", "list", "status", "show", "update", "remove", "bind", "unbind",
625
- "session", "view", "takeover", "release"
639
+ "session", "interrupt", "view", "takeover", "release"
626
640
  ] }],
627
641
  children: [
628
642
  {
@@ -680,6 +694,12 @@ const taskChildren = [
680
694
  summary: "Attach read-only to an independent Provider presentation surface.",
681
695
  usage: "yui task role view <task> <role>"
682
696
  },
697
+ {
698
+ name: "interrupt",
699
+ summary: "Interrupt a Role's exact current native Turn via native cancel; optionally deliver a saved Message once after a proven terminal.",
700
+ usage: "yui task role interrupt <task> <role> --expected-target <turn> [--then-message <task/message>] [--request-id <id>]",
701
+ options: ["--expected-target", "--then-message", "--request-id"]
702
+ },
683
703
  {
684
704
  name: "takeover",
685
705
  summary: "Enter the PTY input gateway for an independent Provider process.",
@@ -706,7 +726,7 @@ const taskChildren = [
706
726
  children: [
707
727
  {
708
728
  name: "create",
709
- summary: "Create a work item.",
729
+ summary: "Create a separately managed result; a coherent Task may use zero WorkItems. Omit --role for Leader-direct execution; --role (including leader) selects a managed AgentRun executor. Isolate writable direct work before editing, then submit, integrate and accept its Candidate.",
710
730
  usage: "yui task work create <task> <title> [--project <project> ...] [--base-ref <project>=<ref> ...] [--objective <text>] [--accept <criterion> ...] [--after <work> ...] [--role <name>]",
711
731
  options: ["--project", "--base-ref", "--objective", "--accept", "--after", "--role"]
712
732
  },
@@ -922,7 +942,7 @@ const taskChildren = [
922
942
  },
923
943
  {
924
944
  name: "abort",
925
- summary: "Abandon a running or blocked Integration Attempt.",
945
+ summary: "Abandon an unadvanced Integration while preserving evidence; reconcile an already-applied target.",
926
946
  usage: "yui task integration abort <task>/<integration> --reason <text>",
927
947
  options: ["--reason"]
928
948
  },
@@ -574,9 +574,9 @@ export const INTERACTION_POLICIES = Object.freeze([
574
574
  entity: "integration-attempt",
575
575
  provider: "integration-attempts",
576
576
  actionTarget: true,
577
- ...(command === "continue" ? { statuses: ["blocked", "validating"] } : {}),
578
- ...(command === "resolve" ? { statuses: ["blocked"] } : {}),
579
- ...(command === "abort" ? { statuses: ["running", "blocked"] } : {})
577
+ ...(command === "continue" ? { statuses: ["running", "conflicted", "blocked", "validating"] } : {}),
578
+ ...(command === "resolve" ? { statuses: ["blocked", "conflicted"] } : {}),
579
+ ...(command === "abort" ? { statuses: ["running", "conflicted", "blocked", "validating"] } : {})
580
580
  }],
581
581
  ...(command === "resolve"
582
582
  ? { trailingOptions: { "--option": "value", "--rationale": "value" } }
@@ -101,7 +101,30 @@ function runStagedUpdate(ports, staged, home) {
101
101
  const captured = captureControllerLifecycle(ports, staged.version, home);
102
102
  if ("outcome" in captured)
103
103
  return captured;
104
- return activateAndVerify(ports, staged, home, captured.lifecycle, preflight);
104
+ // An older Controller can finish a launch between preflight and drain.
105
+ // Recheck after its exact stop, before changing the install or storage.
106
+ let fencedPreflight;
107
+ try {
108
+ fencedPreflight = ports.preflight(staged, home);
109
+ }
110
+ catch (error) {
111
+ return restoreControllerOrReport(ports, home, captured.lifecycle, {
112
+ outcome: "aborted", phase: "preflight",
113
+ message: `Quiesced compatibility preflight failed: ${messageOf(error)}`,
114
+ action: "The install and storage are unchanged; inspect the compatibility failure before retrying.",
115
+ recoverable: true, version: staged.version
116
+ });
117
+ }
118
+ if (fencedPreflight.status === "blocked") {
119
+ return restoreControllerOrReport(ports, home, captured.lifecycle, {
120
+ outcome: "aborted", phase: "preflight",
121
+ message: fencedPreflight.message, action: fencedPreflight.action,
122
+ recoverable: true, version: staged.version,
123
+ ...(fencedPreflight.blockers === undefined ? {} : { blockers: fencedPreflight.blockers }),
124
+ ...(fencedPreflight.sceneUnchanged === true ? { sceneUnchanged: true } : {})
125
+ });
126
+ }
127
+ return activateAndVerify(ports, staged, home, captured.lifecycle, fencedPreflight);
105
128
  }
106
129
  finally {
107
130
  releaseHandover?.();
@@ -25,14 +25,15 @@
25
25
  * mismatch fails closed.
26
26
  */
27
27
  import { spawnSync } from "node:child_process";
28
- import { accessSync, constants, existsSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
29
- import { tmpdir } from "node:os";
28
+ import { accessSync, constants, existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync } from "node:fs";
30
29
  import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
31
30
  import { fileURLToPath } from "node:url";
32
31
  import { runtimeError } from "../errors/cliError.js";
33
32
  import { isConcreteVersion } from "../domain/validation.js";
34
33
  import { STORAGE_DOCTOR_CHECK_NAMES } from "../doctor/doctor.js";
35
34
  import { acquireHandoverLock } from "../release/runtimeRelease.js";
35
+ import { updateStagingRoot } from "../storage/homeLayout.js";
36
+ import { resolveYuiHome } from "../storage/taskStore.js";
36
37
  const PACKAGE_NAME = "@zq-silk/yui";
37
38
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
38
39
  function resolveExecutable(command, environmentPath) {
@@ -64,7 +65,7 @@ function failedSpawnResult(message) {
64
65
  };
65
66
  }
66
67
  /** Build the real ports. `spawn` is injectable so tests avoid real installs. */
67
- export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot = tmpdir()) {
68
+ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot = updateStagingRoot(resolveYuiHome(environment))) {
68
69
  // The production adapter must not let a later PATH change select a
69
70
  // different npm during staging, activation, or recovery. Test doubles are
70
71
  // intentionally left untouched so deterministic tests can dispatch on the
@@ -105,6 +106,9 @@ export function createUpdatePorts(environment, spawn = spawnSync, stagingRoot =
105
106
  throw runtimeError(`Refusing to stage a non-concrete version (${String(version)}): only an exact `
106
107
  + "major.minor.patch version can be pinned for an update.");
107
108
  }
109
+ // The Home staging parent may not exist yet on a Home that has never
110
+ // run an update; create it (private) before minting a throwaway prefix.
111
+ mkdirSync(stagingRoot, { recursive: true, mode: 0o700 });
108
112
  const stagingPath = mkdtempSync(join(stagingRoot, "yui-update-stage-"));
109
113
  let ownsStaging = true;
110
114
  try {
@@ -1,6 +1,8 @@
1
1
  /** `yui upgrade` plans or applies the supported linear storage migration chain. */
2
2
  import { runStorageUpgrade } from "../storage/upgrade/upgradeOrchestrator.js";
3
- import { ensureFileTaskController, stopFileTaskController } from "../controller/clientRuntime.js";
3
+ import { ensureFileTaskController, ensureFileTaskControllerIdentity, stopFileTaskController } from "../controller/clientRuntime.js";
4
+ import { callController, ControllerClientError } from "../core/controllerClient.js";
5
+ import { spawn } from "node:child_process";
4
6
  import { runtimeError, usageError } from "../errors/cliError.js";
5
7
  import { acquireHandoverLock, isForeignHandoverLockHeld, isHandoverLockHeld } from "../release/runtimeRelease.js";
6
8
  /**
@@ -43,12 +45,29 @@ async function runInteractiveUpgrade(home, environment) {
43
45
  const handover = acquireHandoverLock(home);
44
46
  let controllerWasRunning = false;
45
47
  try {
48
+ const previous = await captureUpgradeController(home);
46
49
  const stopped = await stopFileTaskController(home, {
47
50
  environment,
48
- handoverOwnerPid: process.pid
51
+ handoverOwnerPid: process.pid,
52
+ ...(previous === undefined ? {} : { expectedPid: previous.pid })
49
53
  });
50
54
  controllerWasRunning = stopped.stopped;
51
55
  const result = await runStorageUpgrade({ home, mode: "execute" });
56
+ if (controllerWasRunning && result.outcome === "blocked") {
57
+ if (previous === undefined) {
58
+ throw runtimeError("Upgrade was blocked without changing storage; the stopped Controller identity is unknown. Keep the Home quiesced.");
59
+ }
60
+ await ensureFileTaskControllerIdentity(home, previous.identity, {
61
+ environment, handoverOwnerPid: process.pid,
62
+ spawnController: (_home, launchEnvironment) => {
63
+ const child = spawn(previous.identity.executablePath, [...previous.identity.args], {
64
+ env: launchEnvironment, detached: true, stdio: "ignore"
65
+ });
66
+ child.unref();
67
+ return child.pid;
68
+ }
69
+ });
70
+ }
52
71
  if (controllerWasRunning
53
72
  && (result.outcome === "upgraded" || result.outcome === "already-current")) {
54
73
  try {
@@ -70,6 +89,27 @@ async function runInteractiveUpgrade(home, environment) {
70
89
  handover.release();
71
90
  }
72
91
  }
92
+ async function captureUpgradeController(home) {
93
+ let value;
94
+ try {
95
+ value = await callController(home, "controller.identity", {});
96
+ }
97
+ catch (error) {
98
+ if (error instanceof ControllerClientError && error.code === "CONTROLLER_NOT_RUNNING")
99
+ return undefined;
100
+ throw error;
101
+ }
102
+ const identity = value;
103
+ if (!Number.isSafeInteger(identity.pid) || identity.pid < 1
104
+ || typeof identity.executablePath !== "string" || !identity.executablePath
105
+ || !Array.isArray(identity.args) || identity.args.some(arg => typeof arg !== "string")
106
+ || typeof identity.version !== "string" || !identity.version) {
107
+ throw runtimeError("Cannot capture the exact Controller for a reversible upgrade preflight.");
108
+ }
109
+ return { pid: identity.pid, identity: {
110
+ executablePath: identity.executablePath, args: identity.args, version: identity.version
111
+ } };
112
+ }
73
113
  async function runUpdateOwnedUpgrade(home, environment) {
74
114
  const ownerText = environment.YUI_UPDATE_HANDOVER_OWNER_PID;
75
115
  const ownerPid = ownerText === undefined ? Number.NaN : Number(ownerText);