@timurproko/a1 0.1.8-dev.560 → 0.1.8-dev.572
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/README.md +13 -11
- package/bin/guardian.js +5 -0
- package/dist/app/session-shell/session-shell-root.js +13 -8
- package/dist/app/session-shell/session-shell.js +14 -13
- package/dist/app/session-shell/session-viewport-controller.js +48 -30
- package/dist/cli/dispatch.d.ts +3 -1
- package/dist/cli/dispatch.js +56 -56
- package/dist/cli/package-messages.d.ts +4 -3
- package/dist/cli/package-messages.js +23 -11
- package/dist/foundation/launch-guardian/main.js +1 -2
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/release/bootstrap.js +72 -29
- package/dist/foundation/release/cohort-state.d.ts +2 -0
- package/dist/foundation/release/cohort-state.js +35 -0
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/ordinary-launch-selection.d.ts +14 -0
- package/dist/foundation/release/ordinary-launch-selection.js +15 -0
- package/dist/foundation/supervision/server.js +15 -4
- package/dist/integrations/pi/components/shell-footer-status.d.ts +4 -3
- package/dist/integrations/pi/components/shell-footer-status.js +53 -15
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -2
- package/dist/integrations/pi/engine/compaction-progress.d.ts +1 -1
- package/dist/integrations/pi/engine/compaction-progress.js +1 -0
- package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
- package/dist/integrations/pi/engine/repository-pr.js +6 -6
- package/dist/integrations/pi/engine/session-runtime.js +5 -4
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +5 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/progress-status.d.ts +9 -2
- package/dist/ui/components/progress-status.js +24 -3
- package/dist/ui/components/transcript-viewport.d.ts +9 -3
- package/dist/ui/components/transcript-viewport.js +90 -132
- package/docs/architecture/ui-reference-provenance.md +8 -5
- package/docs/openspec-archive-automation.md +2 -2
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
/** Supported subset of pinned Pi's package-manager CLI presentation. */
|
|
3
3
|
export type PackageCliVerb = "install" | "remove" | "list" | "update";
|
|
4
|
+
export type PackageCommandNamespace = "direct" | "pi";
|
|
4
5
|
export type PackageSyntaxDiagnostic = {
|
|
5
6
|
readonly verb: PackageCliVerb;
|
|
6
7
|
readonly kind: "missing-source";
|
|
@@ -18,9 +19,9 @@ export type PackageSyntaxDiagnostic = {
|
|
|
18
19
|
readonly message: string;
|
|
19
20
|
};
|
|
20
21
|
type MessageStyle = Pick<typeof chalk, "red" | "dim" | "bold">;
|
|
21
|
-
export declare function packageCommandUsage(verb: PackageCliVerb): string;
|
|
22
|
+
export declare function packageCommandUsage(verb: PackageCliVerb, namespace?: PackageCommandNamespace): string;
|
|
22
23
|
/** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
|
|
23
|
-
export declare function renderPackageSyntax(diagnostic: PackageSyntaxDiagnostic, style?: MessageStyle): string;
|
|
24
|
+
export declare function renderPackageSyntax(diagnostic: PackageSyntaxDiagnostic, namespace?: PackageCommandNamespace, style?: MessageStyle): string;
|
|
24
25
|
/** Pi's help layout, with unsupported options and examples deliberately absent. */
|
|
25
|
-
export declare function packageCommandHelp(verb: PackageCliVerb, style?: MessageStyle): string;
|
|
26
|
+
export declare function packageCommandHelp(verb: PackageCliVerb, namespace?: PackageCommandNamespace, style?: MessageStyle): string;
|
|
26
27
|
export {};
|
|
@@ -2,21 +2,26 @@
|
|
|
2
2
|
// at 914cf1472e715297caa30db4b9535d534a9eb718 (MIT); grammar is restricted to A1's supported subset.
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { PRODUCT_TEXT } from "../product-identity.js";
|
|
5
|
-
export function packageCommandUsage(verb) {
|
|
6
|
-
const command =
|
|
5
|
+
export function packageCommandUsage(verb, namespace = "pi") {
|
|
6
|
+
const command = namespace === "direct"
|
|
7
|
+
? `${PRODUCT_TEXT.commandName} ${verb}`
|
|
8
|
+
: `${PRODUCT_TEXT.commandName} pi ${verb}`;
|
|
7
9
|
if (verb === "install" || verb === "remove")
|
|
8
10
|
return `${command} <source>`;
|
|
11
|
+
if (verb === "update" && namespace === "direct") {
|
|
12
|
+
return `${command} [--develop [preview-or-version]|--models|--extensions|source]`;
|
|
13
|
+
}
|
|
9
14
|
if (verb === "update")
|
|
10
15
|
return `${command} [source|--extensions|--models]`;
|
|
11
16
|
return command;
|
|
12
17
|
}
|
|
13
18
|
/** Diagnostic facts stay unstyled until the terminal boundary decides color support. */
|
|
14
|
-
export function renderPackageSyntax(diagnostic, style = chalk) {
|
|
15
|
-
const usage = packageCommandUsage(diagnostic.verb);
|
|
19
|
+
export function renderPackageSyntax(diagnostic, namespace = "pi", style = chalk) {
|
|
20
|
+
const usage = packageCommandUsage(diagnostic.verb, namespace);
|
|
16
21
|
let message;
|
|
17
22
|
if (diagnostic.kind === "unknown-option") {
|
|
18
23
|
return `${style.red(`Unknown option ${diagnostic.value} for "${diagnostic.verb}".`)}\n`
|
|
19
|
-
+ `${style.dim(`Use "${PRODUCT_TEXT.commandName}
|
|
24
|
+
+ `${style.dim(`Use "${PRODUCT_TEXT.commandName} help" or "${usage}".`)}\n`;
|
|
20
25
|
}
|
|
21
26
|
if (diagnostic.kind === "missing-source")
|
|
22
27
|
message = `Missing ${diagnostic.verb} source.`;
|
|
@@ -27,9 +32,16 @@ export function renderPackageSyntax(diagnostic, style = chalk) {
|
|
|
27
32
|
return `${style.red(message)}\n${style.dim(`Usage: ${usage}`)}\n`;
|
|
28
33
|
}
|
|
29
34
|
/** Pi's help layout, with unsupported options and examples deliberately absent. */
|
|
30
|
-
export function packageCommandHelp(verb, style = chalk) {
|
|
31
|
-
const command = `${PRODUCT_TEXT.commandName} pi`;
|
|
32
|
-
const header =
|
|
35
|
+
export function packageCommandHelp(verb, namespace = "pi", style = chalk) {
|
|
36
|
+
const command = namespace === "direct" ? PRODUCT_TEXT.commandName : `${PRODUCT_TEXT.commandName} pi`;
|
|
37
|
+
const header = verb === "update" && namespace === "direct"
|
|
38
|
+
? `${style.bold("Usage:")}\n`
|
|
39
|
+
+ ` ${command} update\n`
|
|
40
|
+
+ ` ${command} update --develop [preview-or-version]\n`
|
|
41
|
+
+ ` ${command} update --models\n`
|
|
42
|
+
+ ` ${command} update --extensions\n`
|
|
43
|
+
+ ` ${command} update <source>\n\n`
|
|
44
|
+
: `${style.bold("Usage:")}\n ${packageCommandUsage(verb, namespace)}\n\n`;
|
|
33
45
|
switch (verb) {
|
|
34
46
|
case "install":
|
|
35
47
|
return header + `Install a package and add it to settings.
|
|
@@ -55,14 +67,14 @@ Examples:
|
|
|
55
67
|
case "list":
|
|
56
68
|
return header + "List installed packages from user settings.\n\n";
|
|
57
69
|
case "update":
|
|
58
|
-
return header +
|
|
70
|
+
return header + `${namespace === "direct" ? "Update A1, installed packages, or model catalogs." : "Update installed packages or model catalogs."}
|
|
59
71
|
|
|
60
72
|
Options:
|
|
61
|
-
--extensions Update installed packages only
|
|
73
|
+
${namespace === "direct" ? " --develop [target] Update to the development channel or one preview\n" : ""} --extensions Update installed packages only
|
|
62
74
|
--models Refresh model catalogs only
|
|
63
75
|
|
|
64
76
|
Short forms:
|
|
65
|
-
${command} update --extensions Update installed packages only
|
|
77
|
+
${namespace === "direct" ? ` ${command} update Update A1 to the stable release\n ${command} update --develop Update A1 to the development preview\n` : ""} ${command} update --extensions Update installed packages only
|
|
66
78
|
${command} update --models Refresh model catalogs only
|
|
67
79
|
${command} update <source> Update one package
|
|
68
80
|
|
|
@@ -2,10 +2,9 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
3
3
|
import { readLaunchContext } from "../launch-context/index.js";
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
|
-
import { assertLaunchProfileId, sessionSelectionArguments } from "../lifecycle/index.js";
|
|
5
|
+
import { assertLaunchProfileId, resolveCohortEndpoint, resolveProductPaths, sessionSelectionArguments } from "../lifecycle/index.js";
|
|
6
6
|
import { closeVerifiedContainment, DarwinNativeProcessInspector, LinuxNativeProcessInspector, NativeGuardianContainment, resolveProcessGuardianPath, verifyProcessGuardianArtifact, WindowsNativeProcessInspector, } from "../process-containment/index.js";
|
|
7
7
|
import { SupervisorClient } from "../protocol/index.js";
|
|
8
|
-
import { resolveCohortEndpoint, resolveProductPaths } from "../supervision/index.js";
|
|
9
8
|
import { markStartupPhase } from "../startup/index.js";
|
|
10
9
|
export async function runLaunchGuardian(options) {
|
|
11
10
|
assertLaunchProfileId(options.profileId);
|
|
@@ -54,7 +54,7 @@ export interface CommandResult {
|
|
|
54
54
|
readonly ok: boolean;
|
|
55
55
|
readonly revision: number;
|
|
56
56
|
readonly error?: {
|
|
57
|
-
readonly code: "invalid-command" | "not-found" | "stale-generation" | "capability-error" | "containment-unsupported" | "ownership-error" | "driver-error";
|
|
57
|
+
readonly code: "invalid-command" | "not-found" | "stale-generation" | "capability-error" | "containment-unsupported" | "ownership-error" | "release-superseded" | "driver-error";
|
|
58
58
|
readonly message: string;
|
|
59
59
|
};
|
|
60
60
|
}
|
|
@@ -5,6 +5,7 @@ import { connect } from "node:net";
|
|
|
5
5
|
import { platform } from "node:os";
|
|
6
6
|
import { resolve } from "node:path";
|
|
7
7
|
import { selectCohortLaunch } from "./cohort-selection.js";
|
|
8
|
+
import { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
|
|
8
9
|
import { CohortStateStore } from "./cohort-state.js";
|
|
9
10
|
import { assertLaunchProfileId, createSupervisorStartupAttempt, readSupervisorStartupResult, resolveCohortEndpoint, resolveProductPaths, sessionSelectionArguments } from "../lifecycle/index.js";
|
|
10
11
|
import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
|
|
@@ -18,7 +19,11 @@ import { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDoc
|
|
|
18
19
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
19
20
|
import { markStartupPhase } from "../startup/index.js";
|
|
20
21
|
import { launchContractTarget, readLaunchContext, withLaunchContext } from "../launch-context/index.js";
|
|
22
|
+
const RELEASE_RESELECTION_MESSAGE = "a1-release-reselection";
|
|
21
23
|
export async function runBootstrap(options) {
|
|
24
|
+
return await runBootstrapAttempt(options, 1);
|
|
25
|
+
}
|
|
26
|
+
async function runBootstrapAttempt(options, retriesRemaining) {
|
|
22
27
|
const environment = withLaunchContext(options.environment ?? process.env, { launchProfile: options.launchIntent?.profileId ?? "a1" });
|
|
23
28
|
await markStartupPhase(environment, "bootstrap-start");
|
|
24
29
|
const launchProfileId = options.launchIntent?.profileId ?? "a1";
|
|
@@ -29,6 +34,15 @@ export async function runBootstrap(options) {
|
|
|
29
34
|
const output = options.output ?? process.stderr;
|
|
30
35
|
const paths = resolveProductPaths(environment);
|
|
31
36
|
await mkdir(paths.runtimeDir, { recursive: true, mode: 0o700 });
|
|
37
|
+
const launchSelected = async (release) => {
|
|
38
|
+
const result = await launchUi(release, environment, sessionArgs);
|
|
39
|
+
if (!result.releaseSuperseded)
|
|
40
|
+
return result.code;
|
|
41
|
+
if (retriesRemaining > 0)
|
|
42
|
+
return await runBootstrapAttempt(options, retriesRemaining - 1);
|
|
43
|
+
output.write(`${PRODUCT_TEXT.diagnostic("active release changed repeatedly during startup; launch could not converge")}\n`);
|
|
44
|
+
return 1;
|
|
45
|
+
};
|
|
32
46
|
const stateStore = new CohortStateStore(paths.dataDir);
|
|
33
47
|
let state = await stateStore.read();
|
|
34
48
|
const launchDuringUpdate = async () => {
|
|
@@ -41,7 +55,7 @@ export async function runBootstrap(options) {
|
|
|
41
55
|
return null;
|
|
42
56
|
const retained = await readCertifiedReleaseManifest(prior, resolve(paths.dataDir, "releases"));
|
|
43
57
|
await ensureSupervisor(retained, environment);
|
|
44
|
-
return await
|
|
58
|
+
return await launchSelected(retained);
|
|
45
59
|
};
|
|
46
60
|
const duringUpdate = await launchDuringUpdate();
|
|
47
61
|
if (duringUpdate !== null)
|
|
@@ -82,7 +96,7 @@ export async function runBootstrap(options) {
|
|
|
82
96
|
&& endpoint.contentDigest === active.contentDigest;
|
|
83
97
|
if (endpointMatches && probe === "live-verified") {
|
|
84
98
|
const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
|
|
85
|
-
return await
|
|
99
|
+
return await launchSelected(retained);
|
|
86
100
|
}
|
|
87
101
|
if (endpoint === null || probe === "dead") {
|
|
88
102
|
if (endpoint)
|
|
@@ -100,7 +114,7 @@ export async function runBootstrap(options) {
|
|
|
100
114
|
await markStartupPhase(environment, "replacement-supervisor-start");
|
|
101
115
|
await ensureSupervisor(retained, environment);
|
|
102
116
|
await markStartupPhase(environment, "replacement-supervisor-ready");
|
|
103
|
-
return await
|
|
117
|
+
return await launchSelected(retained);
|
|
104
118
|
}
|
|
105
119
|
}
|
|
106
120
|
// Concurrency: an installation being replaced is not a reason to refuse a launch. While
|
|
@@ -112,7 +126,7 @@ export async function runBootstrap(options) {
|
|
|
112
126
|
candidate = await materializeRelease(options.packageRoot, paths.dataDir);
|
|
113
127
|
}
|
|
114
128
|
catch (error) {
|
|
115
|
-
const fallback = await launchRetainedActive(state, paths, environment,
|
|
129
|
+
const fallback = await launchRetainedActive(state, paths, environment, launchSelected);
|
|
116
130
|
if (fallback === null)
|
|
117
131
|
throw error;
|
|
118
132
|
return fallback;
|
|
@@ -121,24 +135,32 @@ export async function runBootstrap(options) {
|
|
|
121
135
|
const afterMaterialization = await launchDuringUpdate();
|
|
122
136
|
if (afterMaterialization !== null)
|
|
123
137
|
return afterMaterialization;
|
|
138
|
+
state = await stateStore.read();
|
|
139
|
+
const ordinarySelection = !activeIsLaunchable
|
|
140
|
+
? candidate.releaseId
|
|
141
|
+
: selectOrdinaryLaunchReleaseId(candidate, state.references.active === null ? undefined : state.releases[state.references.active]);
|
|
142
|
+
if (ordinarySelection !== candidate.releaseId) {
|
|
143
|
+
const retainedRecord = state.releases[ordinarySelection];
|
|
144
|
+
if (!retainedRecord || retainedRecord.approval !== "approved")
|
|
145
|
+
throw new Error("selected active release is not approved");
|
|
146
|
+
const retained = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
|
|
147
|
+
await ensureSupervisor(retained, environment);
|
|
148
|
+
return await launchSelected(retained);
|
|
149
|
+
}
|
|
124
150
|
await stateStore.recordCandidate(candidate);
|
|
125
151
|
state = await stateStore.read();
|
|
126
|
-
if (
|
|
152
|
+
if (state.releases[candidate.releaseId]?.approval !== "approved") {
|
|
127
153
|
const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
128
154
|
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
129
|
-
await stateStore.activate(candidate.releaseId);
|
|
130
|
-
state = await stateStore.read();
|
|
131
155
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
141
|
-
state = await stateStore.read();
|
|
156
|
+
state = await stateStore.activateForLaunch(candidate.releaseId, !activeIsLaunchable ? activeId ?? undefined : undefined);
|
|
157
|
+
if (state.references.active !== candidate.releaseId) {
|
|
158
|
+
const retainedRecord = state.references.active === null ? undefined : state.releases[state.references.active];
|
|
159
|
+
if (!retainedRecord || retainedRecord.approval !== "approved")
|
|
160
|
+
throw new Error("ordinary launch has no approved active release");
|
|
161
|
+
const retained = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
|
|
162
|
+
await ensureSupervisor(retained, environment);
|
|
163
|
+
return await launchSelected(retained);
|
|
142
164
|
}
|
|
143
165
|
// Protocol: the candidate's own endpoint decides whether this launch attaches or starts a supervisor.
|
|
144
166
|
// A cohort other than this one is not in the way: it listens somewhere else.
|
|
@@ -146,7 +168,7 @@ export async function runBootstrap(options) {
|
|
|
146
168
|
endpoint = await readEndpointMetadata(endpointPaths.endpointMetadataPath);
|
|
147
169
|
if (endpoint === null) {
|
|
148
170
|
const legacy = await readEndpointMetadata(paths.endpointMetadataPath);
|
|
149
|
-
if (legacy
|
|
171
|
+
if (legacy?.releaseId === candidate.releaseId) {
|
|
150
172
|
endpoint = legacy;
|
|
151
173
|
endpointPaths = { endpoint: paths.endpoint, endpointMetadataPath: paths.endpointMetadataPath };
|
|
152
174
|
}
|
|
@@ -170,11 +192,14 @@ export async function runBootstrap(options) {
|
|
|
170
192
|
await removeEndpointArtifacts(endpointPaths.endpointMetadataPath, endpointPaths.endpoint);
|
|
171
193
|
}
|
|
172
194
|
if (decision.action === "launch-retained-ui") {
|
|
173
|
-
const
|
|
195
|
+
const retainedRecord = state.releases[decision.releaseId];
|
|
196
|
+
const retained = retainedRecord?.approval === "approved"
|
|
197
|
+
? await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"))
|
|
198
|
+
: await verifyMaterializedRelease(decision.releaseRoot, undefined, resolve(paths.dataDir, "releases"));
|
|
174
199
|
if (decision.recordPending && endpoint) {
|
|
175
200
|
await stateStore.blockPending("candidate activation deferred by live non-resumable instances", endpoint.ownership.nonResumableInstanceIds);
|
|
176
201
|
}
|
|
177
|
-
const code = await
|
|
202
|
+
const code = await launchSelected(retained);
|
|
178
203
|
if (decision.recordPending)
|
|
179
204
|
await activatePendingAfterBlockerExit(candidate, stateStore, paths, environment);
|
|
180
205
|
return code;
|
|
@@ -204,15 +229,23 @@ export async function runBootstrap(options) {
|
|
|
204
229
|
const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
205
230
|
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
206
231
|
}
|
|
207
|
-
|
|
208
|
-
await stateStore.
|
|
209
|
-
|
|
232
|
+
const activated = (await stateStore.read()).references.active === candidate.releaseId
|
|
233
|
+
? await stateStore.read()
|
|
234
|
+
: await stateStore.activateForLaunch(candidate.releaseId);
|
|
235
|
+
if (activated.references.active === candidate.releaseId)
|
|
236
|
+
selected = candidate;
|
|
237
|
+
else {
|
|
238
|
+
const retainedRecord = activated.references.active === null ? undefined : activated.releases[activated.references.active];
|
|
239
|
+
if (!retainedRecord || retainedRecord.approval !== "approved")
|
|
240
|
+
throw new Error("ordinary launch has no approved active release");
|
|
241
|
+
selected = await readCertifiedReleaseManifest(retainedRecord, resolve(paths.dataDir, "releases"));
|
|
242
|
+
}
|
|
210
243
|
}
|
|
211
244
|
else {
|
|
212
245
|
selected = await readMaterializedRelease(decision.releaseRoot);
|
|
213
246
|
}
|
|
214
247
|
await ensureSupervisor(selected, environment);
|
|
215
|
-
return await
|
|
248
|
+
return await launchSelected(selected);
|
|
216
249
|
}
|
|
217
250
|
/**
|
|
218
251
|
* Start the retained active cohort when the installed payload cannot be taken. The
|
|
@@ -220,7 +253,7 @@ export async function runBootstrap(options) {
|
|
|
220
253
|
* certifying anything new, and reports no selection when there is nothing safe
|
|
221
254
|
* to fall back to.
|
|
222
255
|
*/
|
|
223
|
-
async function launchRetainedActive(state, paths, environment,
|
|
256
|
+
async function launchRetainedActive(state, paths, environment, launch) {
|
|
224
257
|
const activeId = state.references.active;
|
|
225
258
|
const active = activeId === null ? undefined : state.releases[activeId];
|
|
226
259
|
if (!active || active.approval !== "approved")
|
|
@@ -228,8 +261,7 @@ async function launchRetainedActive(state, paths, environment, sessionArgs, outp
|
|
|
228
261
|
try {
|
|
229
262
|
const retained = await readCertifiedReleaseManifest(active, resolve(paths.dataDir, "releases"));
|
|
230
263
|
await ensureSupervisor(retained, environment);
|
|
231
|
-
|
|
232
|
-
return await launchUi(retained, environment, sessionArgs);
|
|
264
|
+
return await launch(retained);
|
|
233
265
|
}
|
|
234
266
|
catch {
|
|
235
267
|
return null;
|
|
@@ -300,13 +332,24 @@ async function launchUi(release, environment, sessionArgs) {
|
|
|
300
332
|
await markStartupPhase(environment, "bootstrap-selected");
|
|
301
333
|
const entry = await resolveReleaseEntryPoint(release, "bin/guardian.js");
|
|
302
334
|
return await new Promise((resolvePromise, rejectPromise) => {
|
|
335
|
+
let releaseSuperseded = false;
|
|
303
336
|
const child = spawn(process.execPath, [entry, ...sessionArgs], {
|
|
304
337
|
env: releaseEnvironment(environment, release, readLaunchContext(environment, "profile").launchProfile),
|
|
305
|
-
stdio: "inherit",
|
|
338
|
+
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
306
339
|
windowsHide: false,
|
|
307
340
|
});
|
|
341
|
+
child.on("message", message => {
|
|
342
|
+
if (message && typeof message === "object" && "type" in message && message.type === RELEASE_RESELECTION_MESSAGE) {
|
|
343
|
+
releaseSuperseded = true;
|
|
344
|
+
}
|
|
345
|
+
});
|
|
308
346
|
child.once("error", rejectPromise);
|
|
309
|
-
child.once("close", (code, signal) => resolvePromise(
|
|
347
|
+
child.once("close", (code, signal) => resolvePromise({
|
|
348
|
+
code: releaseSuperseded
|
|
349
|
+
? code ?? 1
|
|
350
|
+
: restoreAfterOwnedExit(readLaunchContext(environment).launchProfile === "a1", code, signal),
|
|
351
|
+
releaseSuperseded,
|
|
352
|
+
}));
|
|
310
353
|
});
|
|
311
354
|
}
|
|
312
355
|
/**
|
|
@@ -140,6 +140,8 @@ export declare class CohortStateStore {
|
|
|
140
140
|
recordCandidate(release: MaterializedRelease): Promise<CohortState>;
|
|
141
141
|
approve(releaseId: string, diagnosticsPath: string): Promise<CohortState>;
|
|
142
142
|
activate(releaseId: string): Promise<CohortState>;
|
|
143
|
+
/** Atomically advances ordinary launch without allowing an older installation to roll active state back. */
|
|
144
|
+
activateForLaunch(releaseId: string, replaceUnlaunchableActiveId?: string): Promise<CohortState>;
|
|
143
145
|
blockPending(reason: string, blockerGenerationIds: readonly string[]): Promise<CohortState>;
|
|
144
146
|
rollback(ownershipReleased: boolean): Promise<CohortState>;
|
|
145
147
|
setRetention(releaseIds: readonly string[]): Promise<CohortState>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
|
|
4
5
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
5
6
|
export const RELEASE_COHORT_SCHEMA = PRODUCT_IDENTITY.protocol.releaseCohortSchema;
|
|
6
7
|
const RELEASE_ID_PATTERN = /^[0-9A-Za-z.+_-]+-[a-f0-9]{20}$/;
|
|
@@ -135,6 +136,40 @@ export class CohortStateStore {
|
|
|
135
136
|
};
|
|
136
137
|
});
|
|
137
138
|
}
|
|
139
|
+
/** Atomically advances ordinary launch without allowing an older installation to roll active state back. */
|
|
140
|
+
async activateForLaunch(releaseId, replaceUnlaunchableActiveId) {
|
|
141
|
+
return await this.update(current => {
|
|
142
|
+
const release = requiredRelease(current, releaseId);
|
|
143
|
+
if (release.approval !== "approved")
|
|
144
|
+
throw new Error(`cannot activate unverified release ${releaseId}`);
|
|
145
|
+
const selectedId = current.references.active === replaceUnlaunchableActiveId
|
|
146
|
+
? releaseId
|
|
147
|
+
: selectOrdinaryLaunchReleaseId(release, current.references.active === null ? undefined : current.releases[current.references.active]);
|
|
148
|
+
if (selectedId !== releaseId) {
|
|
149
|
+
return {
|
|
150
|
+
...current,
|
|
151
|
+
references: {
|
|
152
|
+
...current.references,
|
|
153
|
+
pending: current.references.pending === releaseId ? null : current.references.pending,
|
|
154
|
+
approved: selectedId,
|
|
155
|
+
},
|
|
156
|
+
activation: activation("idle", null),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const prior = current.references.active;
|
|
160
|
+
return {
|
|
161
|
+
...current,
|
|
162
|
+
references: {
|
|
163
|
+
...current.references,
|
|
164
|
+
active: releaseId,
|
|
165
|
+
pending: null,
|
|
166
|
+
approved: releaseId,
|
|
167
|
+
rollback: prior && prior !== releaseId ? prior : current.references.rollback,
|
|
168
|
+
},
|
|
169
|
+
activation: activation("idle", null),
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
138
173
|
async blockPending(reason, blockerGenerationIds) {
|
|
139
174
|
return await this.update(current => ({ ...current, activation: { ...activation("blocked", reason), blockerGenerationIds: [...blockerGenerationIds] } }));
|
|
140
175
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { certifyMaterializedRelease, ensureSupervisor, recordParentCertifiedRelease, releaseEnvironment, releaseVerifiedIdleOwner, runBootstrap, startSupervisor, waitForProcessExit, waitForVerifiedEndpoint, } from "./bootstrap.js";
|
|
2
2
|
export type { BootstrapOptions, SupervisorStartupAttempt } from "./bootstrap.js";
|
|
3
3
|
export { selectCohortLaunch } from "./cohort-selection.js";
|
|
4
|
+
export { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
|
|
4
5
|
export type { CohortLaunchDecision, OwnershipProbe } from "./cohort-selection.js";
|
|
5
6
|
export { CohortStateStore, RELEASE_COHORT_SCHEMA, emptyState, planProtectedReleases } from "./cohort-state.js";
|
|
6
7
|
export type { ActiveReleaseTransactionReference, CohortState, EndpointOwnership, ExternalReleaseHold, ExternalReleaseHoldAuthority, OrphanReleaseDisposition, ProtectedReleaseInputs, ProtectedReleasePlan, ReleaseApproval, ReleaseCleanupDiagnostic, ReleaseCleanupDisposition, ReleaseCleanupStage, ReleaseCleanupState, ReleaseCleanupWorkerRun, ReleaseCleanupWorkerStatus, ReleaseCleanupWorkerSummary, ReleaseRecord, ReleaseReferences, ReleaseRetentionReconciliation, SupervisorEndpointMetadata, } from "./cohort-state.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { certifyMaterializedRelease, ensureSupervisor, recordParentCertifiedRelease, releaseEnvironment, releaseVerifiedIdleOwner, runBootstrap, startSupervisor, waitForProcessExit, waitForVerifiedEndpoint, } from "./bootstrap.js";
|
|
2
2
|
export { selectCohortLaunch } from "./cohort-selection.js";
|
|
3
|
+
export { selectOrdinaryLaunchReleaseId } from "./ordinary-launch-selection.js";
|
|
3
4
|
export { CohortStateStore, RELEASE_COHORT_SCHEMA, emptyState, planProtectedReleases } from "./cohort-state.js";
|
|
4
5
|
export { DEPENDENCY_LAYER_MANIFEST, DEPENDENCY_LAYER_SCHEMA, RUNTIME_PAYLOAD_INVENTORY, dependencyLayerCertificationPath, dependencyReference, generateDependencyRuntimePayload, legacyDependencyLayerCertificationPath, materializeDependencyLayer, readCertifiedDependencyLayer, selectDependencyRuntimePayload, selectPublishedDependencyRuntimePayload, verifyDependencyLayer, } from "./dependency-layer.js";
|
|
5
6
|
export { listRecordedEndpoints, liveReleaseIds, probeOwnership, readEndpointMetadata, removeEndpointArtifacts, sweepDeadEndpoints } from "./endpoints.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface OrdinaryLaunchRelease {
|
|
2
|
+
readonly releaseId: string;
|
|
3
|
+
readonly packageVersion: string;
|
|
4
|
+
}
|
|
5
|
+
interface ApprovedOrdinaryLaunchRelease extends OrdinaryLaunchRelease {
|
|
6
|
+
readonly approval: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Ordinary launch has no rollback authority: it may advance to its installed package, but an
|
|
10
|
+
* older package installation must follow a newer approved active release instead of moving the
|
|
11
|
+
* shared selector backward. Explicit update/rollback uses CohortStateStore.activate directly.
|
|
12
|
+
*/
|
|
13
|
+
export declare function selectOrdinaryLaunchReleaseId(candidate: OrdinaryLaunchRelease, active?: ApprovedOrdinaryLaunchRelease): string;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { compare, valid as validSemver } from "semver";
|
|
2
|
+
/**
|
|
3
|
+
* Ordinary launch has no rollback authority: it may advance to its installed package, but an
|
|
4
|
+
* older package installation must follow a newer approved active release instead of moving the
|
|
5
|
+
* shared selector backward. Explicit update/rollback uses CohortStateStore.activate directly.
|
|
6
|
+
*/
|
|
7
|
+
export function selectOrdinaryLaunchReleaseId(candidate, active) {
|
|
8
|
+
if (!active || active.approval !== "approved")
|
|
9
|
+
return candidate.releaseId;
|
|
10
|
+
const candidateVersion = validSemver(candidate.packageVersion);
|
|
11
|
+
const activeVersion = validSemver(active.packageVersion);
|
|
12
|
+
if (!candidateVersion || !activeVersion)
|
|
13
|
+
throw new Error("ordinary launch release versions must be valid semantic versions");
|
|
14
|
+
return compare(activeVersion, candidateVersion) >= 0 ? active.releaseId : candidate.releaseId;
|
|
15
|
+
}
|
|
@@ -271,7 +271,7 @@ export class SupervisorServer {
|
|
|
271
271
|
let result;
|
|
272
272
|
try {
|
|
273
273
|
if (command.type === "create-launch-instance")
|
|
274
|
-
this.#createLaunchInstance(command, socket, clientId);
|
|
274
|
+
await this.#createLaunchInstance(command, socket, clientId);
|
|
275
275
|
if (command.type === "activate-launch-instance")
|
|
276
276
|
this.#activateLaunchInstance(command, socket, clientId);
|
|
277
277
|
if (command.type === "begin-launch-instance-stop")
|
|
@@ -289,17 +289,28 @@ export class SupervisorServer {
|
|
|
289
289
|
}
|
|
290
290
|
catch (error) {
|
|
291
291
|
const message = error instanceof Error ? error.message : String(error);
|
|
292
|
-
const
|
|
292
|
+
const explicitCode = error instanceof Error && "code" in error ? error.code : undefined;
|
|
293
|
+
const code = explicitCode === "release-superseded"
|
|
294
|
+
? "release-superseded"
|
|
295
|
+
: /not found/i.test(message) ? "not-found"
|
|
296
|
+
: /owner|ownership|authenticated/i.test(message) ? "ownership-error" : "driver-error";
|
|
293
297
|
result = { requestId: command.requestId, ok: false, revision: this.#revision, error: { code, message } };
|
|
294
298
|
}
|
|
295
299
|
this.#results.set(resultKey, result);
|
|
296
300
|
this.#send(socket, { type: "command-result", result });
|
|
297
301
|
}
|
|
298
|
-
#createLaunchInstance(command, socket, clientId) {
|
|
302
|
+
async #createLaunchInstance(command, socket, clientId) {
|
|
299
303
|
assertNativeProcessIdentity(command.guardianIdentity);
|
|
300
304
|
const existing = this.store.loadLaunchInstance(command.instanceId);
|
|
305
|
+
if (!existing && this.readActiveReleaseId) {
|
|
306
|
+
const active = await this.readActiveReleaseId().catch(() => null);
|
|
307
|
+
if (active !== null)
|
|
308
|
+
this.#superseded = active !== this.release.releaseId;
|
|
309
|
+
}
|
|
301
310
|
if (this.#superseded && !existing) {
|
|
302
|
-
throw new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer
|
|
311
|
+
throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`release ${this.release.releaseId} is no longer active for new sessions`)), {
|
|
312
|
+
code: "release-superseded",
|
|
313
|
+
});
|
|
303
314
|
}
|
|
304
315
|
if (existing) {
|
|
305
316
|
if (existing.ownerClientId !== clientId
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { OwnedUiSessionViewModel } from "../../../contracts/owned-ui/index.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type KeybindingsConfig } from "./upstream/adjacent/core/keybindings.js";
|
|
3
|
+
import { type PiShellViewComponentPort, type PiShellStatusPort, type PiShellProgressStatusPresentation, type PiShellQueuedInputPort, type PiShellHeaderPort, type PiShellResourceEntry, type PiShellLoadedResourcesPort, type PiShellHeaderOptions, type PiShellEditorOptions } from "./shell-shared-facade.js";
|
|
3
4
|
export declare function createPiShellHeader(options?: PiShellHeaderOptions): PiShellHeaderPort;
|
|
4
5
|
export declare function createPiShellLoadedResources(resources: readonly PiShellResourceEntry[], initialExpanded?: boolean): PiShellLoadedResourcesPort;
|
|
5
|
-
export declare function createPiShellStatus(view: OwnedUiSessionViewModel,
|
|
6
|
+
export declare function createPiShellStatus(view: OwnedUiSessionViewModel, progressStatus: PiShellProgressStatusPresentation, runtime?: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellStatusPort;
|
|
6
7
|
export declare function createPiShellFooter(view: OwnedUiSessionViewModel, cwd: string, profile?: "pi" | "a1", showLevel?: () => boolean): PiShellViewComponentPort;
|
|
7
|
-
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport"): PiShellQueuedInputPort;
|
|
8
|
+
export declare function createPiQueuedInputStatus(submissions: readonly string[], presentation?: "pinned" | "custom-viewport", getKeybindings?: () => KeybindingsConfig): PiShellQueuedInputPort;
|