@zq-silk/yui 0.15.8 → 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.
- package/ARCHITECTURE.md +2 -0
- package/ARCHITECTURE.zh-CN.md +151 -0
- package/README.md +211 -14
- package/dist/agent/launchEnvironment.js +7 -0
- package/dist/artifacts/artifactCapability.js +74 -0
- package/dist/artifacts/artifactCommitLock.js +249 -0
- package/dist/artifacts/artifactPaths.js +151 -0
- package/dist/artifacts/gitArtifactRef.js +146 -0
- package/dist/artifacts/managedGit.js +332 -0
- package/dist/artifacts/taskArtifactRepository.js +277 -0
- package/dist/cli/commandCatalog.js +40 -16
- package/dist/cli/interactionPolicy.js +3 -3
- package/dist/cli/updateOrchestrator.js +24 -1
- package/dist/cli/updatePorts.js +7 -3
- package/dist/cli/upgradeCommand.js +42 -2
- package/dist/cli.js +403 -93
- package/dist/commands/globalRoleCommands.js +314 -4
- package/dist/commands/operatorCommands.js +33 -2
- package/dist/commands/projectCommands.js +6 -7
- package/dist/commands/releaseCommands.js +18 -0
- package/dist/commands/taskActivationCommands.js +22 -0
- package/dist/commands/taskActor.js +25 -0
- package/dist/commands/taskCommands.js +846 -155
- package/dist/commands/taskIntegrationCommands.js +16 -38
- package/dist/commands/taskIntegrationQueueCommands.js +1 -1
- package/dist/commands/taskRemoteDeliveryCommand.js +6 -6
- package/dist/commands/taskRoleRuntimeStatus.js +35 -0
- package/dist/context/runContextPack.js +28 -16
- package/dist/context/taskContext.js +64 -5
- package/dist/controller/agentHostObservation.js +155 -0
- package/dist/controller/clientRuntime.js +17 -2
- package/dist/controller/controller.js +11 -2
- package/dist/controller/fileSchedulerStoreAdapter.js +446 -13
- package/dist/controller/globalInputDelivery.js +119 -0
- package/dist/controller/jobControl.js +6 -2
- package/dist/controller/resourceInventory.js +14 -4
- package/dist/controller/resourceInventoryLinux.js +2 -6
- package/dist/controller/runtime.js +81 -6
- package/dist/controller/runtimeEventInbox.js +32 -3
- package/dist/controller/runtimeEventProcessor.js +26 -6
- package/dist/controller/runtimeHookRunFence.js +75 -19
- package/dist/controller/structuredProviderObservation.js +133 -70
- package/dist/coordination/workMailboxQueue.js +5 -0
- package/dist/execution/workItemExecutionProjection.js +1 -1
- package/dist/executor/agentExecutor.js +64 -4
- package/dist/executor/executorRegistry.js +3 -0
- package/dist/executor/fileRoleLaunchPlanner.js +78 -118
- package/dist/integration/deliveryObligation.js +2 -1
- package/dist/integration/gitIntegrationService.js +312 -382
- package/dist/integration/integrationAttempt.js +30 -4
- package/dist/integration/integrationQueueService.js +7 -7
- package/dist/integration/integrationSourceApplication.js +323 -0
- package/dist/kernel/builtinCapabilities.js +32 -24
- package/dist/message/globalInterrupt.js +33 -0
- package/dist/message/inputControlResolution.js +106 -0
- package/dist/message/message.js +423 -0
- package/dist/message/messageContinuation.js +126 -3
- package/dist/message/taskInterrupt.js +34 -0
- package/dist/observability/orchestrationMetrics.js +1 -1
- package/dist/plugins/pluginService.js +11 -3
- package/dist/release/releaseHandover.js +22 -0
- package/dist/release/releaseWorkflowPorts.js +15 -7
- package/dist/repository/gitWorkspace.js +72 -15
- package/dist/repository/taskWorkspaceCoordinator.js +134 -0
- package/dist/repository/taskWorkspacePreparer.js +120 -49
- package/dist/repository/workItemCandidateSnapshot.js +34 -0
- package/dist/resources/projectResource.js +0 -48
- package/dist/resources/projectResourceService.js +3 -81
- package/dist/resources/resourceDiscovery.js +3 -2
- package/dist/runtime/agentHost.js +152 -72
- package/dist/runtime/agentHostCompatibility.js +127 -0
- package/dist/runtime/agentHostProtocol.js +53 -0
- package/dist/runtime/executionEnvironment.js +0 -19
- package/dist/runtime/launchBroker.js +6 -0
- package/dist/runtime/sessionReconciliation.js +4 -4
- package/dist/runtime/taskRuntimeIsolation.js +30 -6
- package/dist/runtime/tmuxAdapters.js +5 -3
- package/dist/scheduler/operatorEvent.js +4 -0
- package/dist/scheduler/taskExecutionProjection.js +12 -1
- package/dist/scheduler/wakeReason.js +7 -1
- package/dist/scheduler/wakeupQueue.js +2 -0
- package/dist/setup/setupCommand.js +29 -16
- package/dist/storage/homeLayout.js +130 -0
- package/dist/storage/migrations/artifactsToGit.js +338 -0
- package/dist/storage/migrations/collapseWorktreeLayout.js +963 -0
- package/dist/storage/migrations/integrationContinuation.js +104 -0
- package/dist/storage/migrations/submitIntent.js +126 -0
- package/dist/storage/migrations/unifyHomeLayout.js +925 -0
- package/dist/storage/sqliteSchema.js +173 -7
- package/dist/storage/sqliteStore.js +41 -22
- package/dist/storage/storageVersions.js +1 -1
- package/dist/storage/storeRpc.js +2 -1
- package/dist/storage/upgrade/upgradeOrchestrator.js +95 -2
- package/dist/task/archiveDiagnostics.js +128 -0
- package/dist/task/nextAction.js +44 -11
- package/dist/task/taskActivation.js +26 -0
- package/dist/task/taskActivationService.js +85 -69
- package/dist/task/taskSubmission.js +236 -0
- package/dist/web/assets/client/app.js +58 -2
- package/dist/web/assets/client/components.js +1 -0
- package/dist/web/assets/client/i18n.js +6 -0
- package/dist/web/assets/client/taskSurface.js +202 -7
- package/dist/web/assets/client/view.js +7 -4
- package/dist/web/assets/shell.js +23 -0
- package/dist/web/assets/styles/layout.js +1 -1
- package/dist/web/assets/styles/widgets.js +12 -0
- package/dist/web/webServer.js +135 -4
- package/dist/web/webSnapshot.js +4 -3
- package/dist/web/webTaskSurface.js +225 -8
- package/dist/workItem/workItem.js +14 -10
- package/dist/workspace/workItemChangeSetManager.js +18 -2
- package/docs/agent-result-consumption.md +2 -0
- package/docs/agent-result-consumption.zh-CN.md +81 -0
- package/docs/agent-runtime-drivers.md +2 -0
- package/docs/agent-runtime-drivers.zh-CN.md +77 -0
- package/docs/architecture/README.md +44 -32
- package/docs/architecture/README.zh-CN.md +43 -0
- package/docs/architecture/capabilities-and-resources.md +118 -79
- package/docs/architecture/capabilities-and-resources.zh-CN.md +83 -0
- package/docs/managed-turn-and-session-runtime.md +2 -0
- package/docs/managed-turn-and-session-runtime.zh-CN.md +180 -0
- package/docs/observability/README.md +2 -0
- package/docs/observability/README.zh-CN.md +71 -0
- package/docs/plugin-sdk.md +320 -217
- package/docs/plugin-sdk.zh-CN.md +293 -0
- package/docs/provider-runtime.md +2 -0
- package/docs/provider-runtime.zh-CN.md +132 -0
- package/docs/release-workflow.md +41 -0
- package/docs/release-workflow.zh-CN.md +266 -0
- package/docs/roles-and-configuration.md +2 -0
- package/docs/roles-and-configuration.zh-CN.md +96 -0
- package/docs/sqlite-control-plane-design.md +225 -1
- package/docs/sqlite-control-plane-design.zh-CN.md +62 -0
- package/docs/task-dag-semantics.md +80 -57
- package/docs/task-dag-semantics.zh-CN.md +59 -0
- package/docs/task-delivery.md +2 -0
- package/docs/task-delivery.zh-CN.md +82 -0
- package/docs/task-local-identity.md +2 -0
- package/docs/task-local-identity.zh-CN.md +58 -0
- package/docs/testing/verification-levels.md +26 -0
- package/docs/testing/verification-levels.zh-CN.md +80 -0
- package/i18n/README.zh-CN.md +199 -10
- package/package.json +2 -1
- package/skills/yui-leader/SKILL.md +88 -331
- package/skills/yui-leader/references/execution.md +405 -0
- package/skills/yui-leader/references/integration.md +52 -2
- package/skills/yui-leader/references/planning.md +109 -0
- package/skills/yui-leader/references/task-plugins.md +8 -4
- package/skills/yui-operator/SKILL.md +22 -4
- package/skills/yui-runtime/SKILL.md +27 -0
- package/skills/yui-runtime/references/publication.md +20 -0
|
@@ -380,12 +380,12 @@ const taskChildren = [
|
|
|
380
380
|
{ name: "show", summary: "Show a Task.", usage: "yui task show <id>" },
|
|
381
381
|
{
|
|
382
382
|
name: "artifact",
|
|
383
|
-
summary: "Save
|
|
384
|
-
sections: [{ id: "manage", title: "Commands", entries: ["list", "
|
|
383
|
+
summary: "Save Task files in local Git and read current or commit-pinned content.",
|
|
384
|
+
sections: [{ id: "manage", title: "Commands", entries: ["list", "read", "save"] }],
|
|
385
385
|
children: [
|
|
386
386
|
{ name: "list", summary: "List saved Task artifacts.", usage: "yui task artifact list <task>" },
|
|
387
|
-
{ name: "
|
|
388
|
-
{ name: "save", summary: "Save
|
|
387
|
+
{ name: "read", summary: "Read a file at HEAD or an exact commit.", usage: "yui task artifact read <task> <relative-path> [<commit>]" },
|
|
388
|
+
{ name: "save", summary: "Save and locally commit one file.", usage: "yui task artifact save <task> <relative-path> <content> [--message <text>] [--expected-head <commit>]", options: ["--message", "--expected-head"] }
|
|
389
389
|
]
|
|
390
390
|
},
|
|
391
391
|
{
|
|
@@ -408,8 +408,8 @@ const taskChildren = [
|
|
|
408
408
|
},
|
|
409
409
|
{
|
|
410
410
|
name: "archive",
|
|
411
|
-
summary: "Archive a Task
|
|
412
|
-
usage: "yui task archive <id> (--integrated [--force]
|
|
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",
|
|
@@ -445,14 +445,29 @@ const taskChildren = [
|
|
|
445
445
|
},
|
|
446
446
|
{
|
|
447
447
|
name: "send",
|
|
448
|
-
summary: "Send a Task message.",
|
|
449
|
-
usage: "yui task message send <id> (<body>|--body-file <path|->) [--wake-policy leader|none] [--to <role> --work-item <id>|--review-round <id>]",
|
|
450
|
-
options: ["--body-file", "--wake-policy", "--to", "--work-item", "--review-round"],
|
|
448
|
+
summary: "Send a Task message. An unaddressed user/operator message carries a submission intent (record|discuss|develop) and an optional idempotency key.",
|
|
449
|
+
usage: "yui task message send <id> (<body>|--body-file <path|->) [--intent record|discuss|develop] [--request-id <key>] [--wake-policy leader|none] [--to <role> --work-item <id>|--review-round <id>]",
|
|
450
|
+
options: ["--body-file", "--intent", "--request-id", "--wake-policy", "--to", "--work-item", "--review-round"],
|
|
451
451
|
optionValues: {
|
|
452
|
+
"--intent": ["record", "discuss", "develop"],
|
|
452
453
|
"--wake-policy": ["leader", "none"]
|
|
453
454
|
},
|
|
454
455
|
fileOptions: ["--body-file"]
|
|
455
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
|
+
},
|
|
456
471
|
{
|
|
457
472
|
name: "list",
|
|
458
473
|
summary: "List Task messages.",
|
|
@@ -621,7 +636,7 @@ const taskChildren = [
|
|
|
621
636
|
summary: "Manage Roles within a Task.",
|
|
622
637
|
sections: [{ id: "manage", title: "Commands", entries: [
|
|
623
638
|
"add", "list", "status", "show", "update", "remove", "bind", "unbind",
|
|
624
|
-
"session", "view", "takeover", "release"
|
|
639
|
+
"session", "interrupt", "view", "takeover", "release"
|
|
625
640
|
] }],
|
|
626
641
|
children: [
|
|
627
642
|
{
|
|
@@ -679,6 +694,12 @@ const taskChildren = [
|
|
|
679
694
|
summary: "Attach read-only to an independent Provider presentation surface.",
|
|
680
695
|
usage: "yui task role view <task> <role>"
|
|
681
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
|
+
},
|
|
682
703
|
{
|
|
683
704
|
name: "takeover",
|
|
684
705
|
summary: "Enter the PTY input gateway for an independent Provider process.",
|
|
@@ -705,7 +726,7 @@ const taskChildren = [
|
|
|
705
726
|
children: [
|
|
706
727
|
{
|
|
707
728
|
name: "create",
|
|
708
|
-
summary: "Create a work
|
|
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.",
|
|
709
730
|
usage: "yui task work create <task> <title> [--project <project> ...] [--base-ref <project>=<ref> ...] [--objective <text>] [--accept <criterion> ...] [--after <work> ...] [--role <name>]",
|
|
710
731
|
options: ["--project", "--base-ref", "--objective", "--accept", "--after", "--role"]
|
|
711
732
|
},
|
|
@@ -921,7 +942,7 @@ const taskChildren = [
|
|
|
921
942
|
},
|
|
922
943
|
{
|
|
923
944
|
name: "abort",
|
|
924
|
-
summary: "Abandon
|
|
945
|
+
summary: "Abandon an unadvanced Integration while preserving evidence; reconcile an already-applied target.",
|
|
925
946
|
usage: "yui task integration abort <task>/<integration> --reason <text>",
|
|
926
947
|
options: ["--reason"]
|
|
927
948
|
},
|
|
@@ -1286,9 +1307,12 @@ export const ROOT_COMMAND = buildNode({
|
|
|
1286
1307
|
},
|
|
1287
1308
|
{
|
|
1288
1309
|
name: "submit",
|
|
1289
|
-
summary: "Submit work through the Operator.",
|
|
1290
|
-
usage: "yui operator submit (<body>|--body-file <path|->) [--task <id>]",
|
|
1291
|
-
options: ["--task", "--body-file"],
|
|
1310
|
+
summary: "Submit work through the Operator with a submission intent (record|discuss|develop); a task-less submit opens a Draft. An optional idempotency key makes a retry safe.",
|
|
1311
|
+
usage: "yui operator submit (<body>|--body-file <path|->) [--task <id>] [--intent record|discuss|develop] [--request-id <key>]",
|
|
1312
|
+
options: ["--task", "--body-file", "--intent", "--request-id"],
|
|
1313
|
+
optionValues: {
|
|
1314
|
+
"--intent": ["record", "discuss", "develop"]
|
|
1315
|
+
},
|
|
1292
1316
|
fileOptions: ["--body-file"]
|
|
1293
1317
|
}
|
|
1294
1318
|
]
|
|
@@ -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
|
-
|
|
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?.();
|
package/dist/cli/updatePorts.js
CHANGED
|
@@ -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 =
|
|
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);
|