@timurproko/a1 0.2.7-dev.1232 → 0.2.7-dev.1234
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/dist/composition/owned-ui.js +13 -79
- package/dist/composition/release-update-observation.d.ts +22 -0
- package/dist/composition/release-update-observation.js +91 -0
- package/dist/composition/resident-tab-bridge.d.ts +16 -0
- package/dist/composition/resident-tab-bridge.js +45 -16
- package/dist/composition/startup-route.d.ts +8 -0
- package/dist/composition/startup-route.js +20 -0
- package/dist/contracts/owned-ui/session-workflows.d.ts +3 -0
- package/dist/foundation/release/bootstrap.js +46 -36
- package/dist/foundation/release/progress-palette.generated.d.ts +1 -1
- package/dist/foundation/release/progress-palette.generated.js +1 -1
- package/dist/foundation/release/update-activation.js +8 -2
- package/dist/foundation/resident-tabs/bridge-client.d.ts +3 -0
- package/dist/foundation/resident-tabs/bridge-client.js +7 -1
- package/dist/foundation/resident-tabs/continuity.d.ts +12 -0
- package/dist/foundation/resident-tabs/continuity.js +55 -0
- package/dist/foundation/resident-tabs/index.d.ts +3 -0
- package/dist/foundation/resident-tabs/index.js +4 -0
- package/dist/foundation/resident-tabs/maintenance.d.ts +1 -1
- package/dist/foundation/resident-tabs/release-holds.js +11 -8
- package/dist/foundation/resident-tabs/runtime-control.d.ts +38 -0
- package/dist/foundation/resident-tabs/runtime-control.js +73 -0
- package/dist/foundation/startup/startup-descriptor.js +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
- package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
- package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +3 -3
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +4 -1
- package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/owned-editor.js +8 -1
- package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +14 -3
- package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
- package/dist/integrations/pi/components/upstream/components/session-selector-search.d.ts +4 -0
- package/dist/integrations/pi/components/upstream/components/session-selector-search.js +87 -23
- package/dist/integrations/pi/components/upstream/components/session-selector.d.ts +3 -2
- package/dist/integrations/pi/components/upstream/components/session-selector.js +18 -13
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +6 -0
- package/dist/integrations/pi/components/upstream/components/tool-execution.js +14 -2
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +5 -4
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
- package/dist/integrations/pi/components/upstream/external-editor.d.ts +1 -0
- package/dist/integrations/pi/components/upstream/external-editor.js +13 -6
- package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +3 -1
- package/dist/integrations/pi/components/upstream/history/editor-core.js +9 -2
- package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
- package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
- package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
- package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
- package/dist/integrations/pi/components/upstream/model-search.d.ts +11 -1
- package/dist/integrations/pi/components/upstream/model-search.js +21 -0
- package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
- package/dist/integrations/pi/engine/adapter.js +6 -1
- package/dist/integrations/pi/engine/diagnostics-state.d.ts +9 -0
- package/dist/integrations/pi/engine/diagnostics-state.js +34 -0
- package/dist/integrations/pi/engine/resources/changelog.json +4 -4
- package/dist/integrations/pi/engine/view-state.d.ts +2 -1
- package/dist/integrations/pi/engine/view-state.js +4 -16
- package/dist/integrations/pi/startup-public.js +324 -309
- package/dist/integrations/pi/startup-public.manifest.json +1156 -846
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-arm64/terminal-host.node +0 -0
- package/dist/native/darwin-x64/manifest.json +1 -1
- package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
- package/dist/native/darwin-x64/terminal-host.node +0 -0
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
- package/dist/native/linux-x64/terminal-host.node +0 -0
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.node +0 -0
- package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
- package/dist/native/win32-x64/terminal-host.node +0 -0
- package/dist/runtime-payload-inventory.json +8178 -5237
- package/dist/ui/settings/progress-palette.generated.d.ts +1 -1
- package/dist/ui/settings/progress-palette.generated.js +1 -1
- package/docs/architecture/native-fixture-isolation.md +23 -0
- package/docs/architecture/resident-tabs.md +23 -4
- package/package.json +10 -5
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SuggestionDiagnosticCapture } from "../features/prompt-suggestions/diagnostics.js";
|
|
2
1
|
import { PRODUCT_IDENTITY } from "../product-identity.js";
|
|
3
2
|
import { PromptHistoryService } from "../features/prompt-history/service.js";
|
|
4
3
|
import { PromptImageSidecar } from "../features/prompt-history/image-sidecar.js";
|
|
@@ -19,9 +18,9 @@ import { createOwnedRouteHost } from "./settings-route-host.js";
|
|
|
19
18
|
import { renderPiShellChangelogLines } from "../integrations/pi/components/shell-presenters-info.js";
|
|
20
19
|
import { nativeHyperlinkStyle } from "../ui/components/spans.js";
|
|
21
20
|
import { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
|
|
22
|
-
import {
|
|
21
|
+
import { bindResidentPresentation, createResidentClientIdentity, closeResidentUtilityRoute, correlateResidentTabSubmission, currentBackendSessionFile, residentRouteHostOptions, residentTabKeybindings } from "./resident-tab-bridge.js";
|
|
23
22
|
import { SessionRuntimeRegistry } from "./session-runtime-registry.js";
|
|
24
|
-
import { readPackageVersion, StartupReleaseNote } from "./startup-route.js";
|
|
23
|
+
import { deferStartupReleaseUpdateObservation, readPackageVersion, StartupReleaseNote } from "./startup-route.js";
|
|
25
24
|
import { createOwnedSettingsManager, watchSharedSettings } from "./owned-settings.js";
|
|
26
25
|
export { bindResidentTabBridge, correlateResidentTabSubmission } from "./resident-tab-bridge.js";
|
|
27
26
|
export function residentStartupPresentation(resident, fallbackNotice) {
|
|
@@ -97,6 +96,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
97
96
|
await host.dispose();
|
|
98
97
|
throw error;
|
|
99
98
|
}
|
|
99
|
+
const clientIdentity = createResidentClientIdentity(update => backend.extensions.announceReleaseUpdate(update));
|
|
100
100
|
let recoveryDegraded = false;
|
|
101
101
|
let presentRecoveryDegraded = (_degraded) => { };
|
|
102
102
|
const tabRecovery = options.tab === undefined ? null : new ResidentTabRecovery(options.tab.dataDir, {
|
|
@@ -162,7 +162,7 @@ export async function composeOwnedUi(options = {}) {
|
|
|
162
162
|
};
|
|
163
163
|
const diagnosticDestination = options.suggestionDiagnosticsPath ?? process.env[PRODUCT_IDENTITY.environment.suggestionDiagnostics];
|
|
164
164
|
const suggestionDiagnostics = settings !== null && ownedSurfaces && diagnosticDestination?.trim()
|
|
165
|
-
? new SuggestionDiagnosticCapture({ enabled: true, destination: diagnosticDestination }) : null;
|
|
165
|
+
? await import("../features/prompt-suggestions/diagnostics.js").then(module => new module.SuggestionDiagnosticCapture({ enabled: true, destination: diagnosticDestination })) : null;
|
|
166
166
|
const promptSuggestions = settings === null || !ownedSurfaces ? null : {
|
|
167
167
|
...(suggestionDiagnostics === null ? {} : { diagnostics: suggestionDiagnostics }),
|
|
168
168
|
generator: backend,
|
|
@@ -234,11 +234,6 @@ export async function composeOwnedUi(options = {}) {
|
|
|
234
234
|
// Invariant: one terminal host owns the process terminal; the session presenter reaches it only through
|
|
235
235
|
// the host and paints once attached.
|
|
236
236
|
terminalHost = createdTerminalHost = new OwnedUiTerminalHost(sessionTerminalHostOptions(presenterOptions));
|
|
237
|
-
if (tabBridge !== null) {
|
|
238
|
-
removeControllerBoundaryInput = terminalHost.runtime.addPreInputListener(data => ({
|
|
239
|
-
data: tabBridge.consumeControllerBoundaryInput(data),
|
|
240
|
-
}));
|
|
241
|
-
}
|
|
242
237
|
presenter = new OwnedUiSessionPresenter(terminalHost, presenterOptions);
|
|
243
238
|
presentRecoveryDegraded = degraded => {
|
|
244
239
|
if (!degraded)
|
|
@@ -249,12 +244,11 @@ export async function composeOwnedUi(options = {}) {
|
|
|
249
244
|
if (recoveryDegraded)
|
|
250
245
|
presentRecoveryDegraded(true);
|
|
251
246
|
terminalHost.attach(presenter);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
247
|
+
({ unsubscribeTabBridge, removeControllerBoundaryInput } = await bindResidentPresentation(tabBridge, backend, tabRecovery, {
|
|
248
|
+
autoName: settings?.value("tabsAutoName") ?? true,
|
|
249
|
+
openRoute: route => presenter.openOwnedRoute(route),
|
|
250
|
+
onClientVersion: clientIdentity.observe,
|
|
251
|
+
}, terminalHost.runtime));
|
|
258
252
|
}
|
|
259
253
|
catch (error) {
|
|
260
254
|
await createdTerminalHost?.dispose().catch(() => undefined);
|
|
@@ -269,13 +263,15 @@ export async function composeOwnedUi(options = {}) {
|
|
|
269
263
|
// Rationale: pinned Pi's version check is unreachable because the owned shell never runs its
|
|
270
264
|
// InteractiveMode, so A1 checks its own channel. The result is never awaited on the startup path;
|
|
271
265
|
// settings are read here because the runner resolves them only just before start.
|
|
272
|
-
const releaseUpdates =
|
|
266
|
+
const releaseUpdates = deferStartupReleaseUpdateObservation({
|
|
273
267
|
backend,
|
|
274
268
|
configDir: productPaths.configDir,
|
|
275
269
|
mode: settings === null || !ownedSurfaces ? "auto" : settings.value("updateCheck"),
|
|
276
|
-
packageVersion: releaseNote.packageVersion, notificationVersion: options.notificationVersion,
|
|
270
|
+
packageVersion: releaseNote.packageVersion, readPackageVersion, notificationVersion: options.notificationVersion,
|
|
277
271
|
checkForNewerRelease: options.checkForNewerRelease,
|
|
272
|
+
clientVersion: clientIdentity.current,
|
|
278
273
|
});
|
|
274
|
+
clientIdentity.onChange(releaseUpdates.clientChanged);
|
|
279
275
|
const exitNoticePath = process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
|
|
280
276
|
// Security: tools the agent runs must never see, or rewrite, this instance's notice.
|
|
281
277
|
delete process.env[PRODUCT_IDENTITY.environment.exitNoticePath];
|
|
@@ -343,68 +339,6 @@ function createPromptHistoryConfiguration(settings, backend, ownedSurfaces) {
|
|
|
343
339
|
...forScope(scope, sessionId),
|
|
344
340
|
};
|
|
345
341
|
}
|
|
346
|
-
function createStartupReleaseUpdateObservation(input) {
|
|
347
|
-
const controller = new AbortController();
|
|
348
|
-
let timer;
|
|
349
|
-
let announcedVersion = null;
|
|
350
|
-
let eligible = input.mode === "auto";
|
|
351
|
-
let cadence = null;
|
|
352
|
-
let nextDelay = null;
|
|
353
|
-
let started = false;
|
|
354
|
-
const observe = async () => {
|
|
355
|
-
// Performance: registry code and its locks stay outside the first-frame startup graph.
|
|
356
|
-
const releases = await import("../foundation/release/latest-release.js");
|
|
357
|
-
cadence = releases.UPDATE_CHECK_INTERVAL_MS;
|
|
358
|
-
nextDelay = cadence;
|
|
359
|
-
const check = input.checkForNewerRelease ?? releases.checkForNewerRelease;
|
|
360
|
-
const runningVersion = input.packageVersion ?? await readPackageVersion();
|
|
361
|
-
const discoveryVersion = input.mode === "notify" ? input.notificationVersion ?? runningVersion : runningVersion;
|
|
362
|
-
const options = {
|
|
363
|
-
runningVersion: discoveryVersion,
|
|
364
|
-
configDir: input.configDir,
|
|
365
|
-
interactive: process.stdout.isTTY === true,
|
|
366
|
-
settingMode: input.mode,
|
|
367
|
-
signal: controller.signal,
|
|
368
|
-
onNextCheckDelay: milliseconds => { nextDelay = milliseconds; },
|
|
369
|
-
};
|
|
370
|
-
// Invariant: opt-outs never arm a recurring timer, nor read the cache or probe the registry.
|
|
371
|
-
if (input.checkForNewerRelease === undefined && releases.startupReleaseCheckSkipReason(options) !== null) {
|
|
372
|
-
eligible = false;
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const release = await check(options);
|
|
376
|
-
if (release === null || controller.signal.aborted)
|
|
377
|
-
return;
|
|
378
|
-
if (input.mode === "notify") {
|
|
379
|
-
input.backend.extensions.announceReleaseUpdate(release);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
if (input.mode === "auto") {
|
|
383
|
-
const installed = await import("../foundation/release/automatic-update.js")
|
|
384
|
-
.then(module => module.requestAutomaticUpdate({ release, runningVersion, signal: controller.signal }));
|
|
385
|
-
if (installed !== null && installed !== announcedVersion && !controller.signal.aborted) {
|
|
386
|
-
announcedVersion = installed;
|
|
387
|
-
input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
const run = async () => {
|
|
392
|
-
try {
|
|
393
|
-
await observe();
|
|
394
|
-
}
|
|
395
|
-
catch { /* Rationale: best effort; retry on the next interval. */ }
|
|
396
|
-
if (eligible && cadence !== null && !controller.signal.aborted) {
|
|
397
|
-
timer = setTimeout(() => { void run(); }, nextDelay ?? cadence);
|
|
398
|
-
timer.unref?.();
|
|
399
|
-
}
|
|
400
|
-
};
|
|
401
|
-
return {
|
|
402
|
-
start() { if (started || controller.signal.aborted)
|
|
403
|
-
return; started = true; void run(); },
|
|
404
|
-
stop() { controller.abort(); if (timer !== undefined)
|
|
405
|
-
clearTimeout(timer); },
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
342
|
export { watchSharedSettings } from "./owned-settings.js";
|
|
409
343
|
function settingsRouteOptions(tabBridge, keybindings, agentDir, cwd) {
|
|
410
344
|
return {
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AvailableRelease, StartupReleaseCheckOptions, StartupUpdateMode } from "../foundation/release/latest-release.js";
|
|
2
|
+
import type { ComposedSessionBackend } from "./session-backend.js";
|
|
3
|
+
export interface StartupReleaseUpdateObservationOptions {
|
|
4
|
+
readonly backend: Pick<ComposedSessionBackend, "extensions">;
|
|
5
|
+
readonly configDir: string;
|
|
6
|
+
readonly mode: StartupUpdateMode;
|
|
7
|
+
readonly packageVersion: string | null;
|
|
8
|
+
readonly readPackageVersion: () => Promise<string>;
|
|
9
|
+
readonly notificationVersion: string | undefined;
|
|
10
|
+
readonly checkForNewerRelease: ((options: StartupReleaseCheckOptions) => Promise<AvailableRelease | null>) | undefined;
|
|
11
|
+
readonly clientVersion?: () => string | null;
|
|
12
|
+
}
|
|
13
|
+
/** Client readiness is unrelated to the retained Agent's engine version or execution lifecycle. */
|
|
14
|
+
declare class ReleaseUpdateObservation {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(input: StartupReleaseUpdateObservationOptions);
|
|
17
|
+
start(): void;
|
|
18
|
+
stop(): void;
|
|
19
|
+
clientChanged(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createStartupReleaseUpdateObservation(input: StartupReleaseUpdateObservationOptions): ReleaseUpdateObservation;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/** Client readiness is unrelated to the retained Agent's engine version or execution lifecycle. */
|
|
2
|
+
class ReleaseUpdateObservation {
|
|
3
|
+
#input;
|
|
4
|
+
#controller = new AbortController();
|
|
5
|
+
#timer;
|
|
6
|
+
#announced = null;
|
|
7
|
+
#eligible;
|
|
8
|
+
#cadence = null;
|
|
9
|
+
#nextDelay = null;
|
|
10
|
+
#started = false;
|
|
11
|
+
#running = false;
|
|
12
|
+
#refresh = false;
|
|
13
|
+
constructor(input) { this.#input = input; this.#eligible = input.mode === "auto"; }
|
|
14
|
+
async #observe() {
|
|
15
|
+
// Performance: registry modules and their locks stay outside the first-frame startup graph.
|
|
16
|
+
const releases = await import("../foundation/release/latest-release.js");
|
|
17
|
+
this.#cadence = releases.UPDATE_CHECK_INTERVAL_MS;
|
|
18
|
+
this.#nextDelay = this.#cadence;
|
|
19
|
+
const input = this.#input;
|
|
20
|
+
const check = input.checkForNewerRelease ?? releases.checkForNewerRelease;
|
|
21
|
+
const client = input.clientVersion?.();
|
|
22
|
+
const runningVersion = client ?? input.packageVersion ?? await input.readPackageVersion();
|
|
23
|
+
const options = {
|
|
24
|
+
runningVersion: client ?? (input.mode === "notify" ? input.notificationVersion ?? runningVersion : runningVersion),
|
|
25
|
+
configDir: input.configDir, interactive: process.stdout.isTTY === true, settingMode: input.mode,
|
|
26
|
+
signal: this.#controller.signal, onNextCheckDelay: delay => { this.#nextDelay = delay; },
|
|
27
|
+
};
|
|
28
|
+
if (input.checkForNewerRelease === undefined && releases.startupReleaseCheckSkipReason(options) !== null) {
|
|
29
|
+
this.#eligible = false;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const release = await check(options);
|
|
33
|
+
if (release === null || this.#controller.signal.aborted)
|
|
34
|
+
return;
|
|
35
|
+
const current = input.clientVersion?.();
|
|
36
|
+
if (current && !releases.isNewerRelease(release.version, current))
|
|
37
|
+
return;
|
|
38
|
+
if (input.mode === "notify") {
|
|
39
|
+
input.backend.extensions.announceReleaseUpdate(release);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (input.mode !== "auto")
|
|
43
|
+
return;
|
|
44
|
+
const installed = await import("../foundation/release/automatic-update.js")
|
|
45
|
+
.then(module => module.requestAutomaticUpdate({ release, runningVersion, signal: this.#controller.signal }));
|
|
46
|
+
const actual = input.clientVersion?.();
|
|
47
|
+
if (installed === null || installed === this.#announced || this.#controller.signal.aborted
|
|
48
|
+
|| actual && !releases.isNewerRelease(installed, actual))
|
|
49
|
+
return;
|
|
50
|
+
this.#announced = installed;
|
|
51
|
+
input.backend.extensions.announceReleaseUpdate({ status: "installed", version: installed });
|
|
52
|
+
}
|
|
53
|
+
async #run() {
|
|
54
|
+
if (this.#running) {
|
|
55
|
+
this.#refresh = true;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.#running = true;
|
|
59
|
+
try {
|
|
60
|
+
await this.#observe();
|
|
61
|
+
}
|
|
62
|
+
catch { /* Rationale: observation is best effort and never interrupts execution. */ }
|
|
63
|
+
this.#running = false;
|
|
64
|
+
if (this.#refresh && !this.#controller.signal.aborted) {
|
|
65
|
+
this.#refresh = false;
|
|
66
|
+
void this.#run();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (this.#eligible && this.#cadence !== null && !this.#controller.signal.aborted) {
|
|
70
|
+
this.#timer = setTimeout(() => { void this.#run(); }, this.#nextDelay ?? this.#cadence);
|
|
71
|
+
this.#timer.unref?.();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
start() {
|
|
75
|
+
if (this.#started || this.#controller.signal.aborted)
|
|
76
|
+
return;
|
|
77
|
+
this.#started = true;
|
|
78
|
+
void this.#run();
|
|
79
|
+
}
|
|
80
|
+
stop() { this.#controller.abort(); if (this.#timer !== undefined)
|
|
81
|
+
clearTimeout(this.#timer); }
|
|
82
|
+
clientChanged() {
|
|
83
|
+
this.#announced = null;
|
|
84
|
+
if (!this.#started || this.#controller.signal.aborted || this.#input.mode === "no")
|
|
85
|
+
return;
|
|
86
|
+
if (this.#timer !== undefined)
|
|
87
|
+
clearTimeout(this.#timer);
|
|
88
|
+
void this.#run();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function createStartupReleaseUpdateObservation(input) { return new ReleaseUpdateObservation(input); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OwnedUiReleaseUpdate } from "../contracts/owned-ui/index.js";
|
|
1
2
|
import type { TabBridgeClient } from "../foundation/resident-tabs/bridge-client.js";
|
|
2
3
|
import { type ResidentTabRoute } from "../foundation/resident-tabs/routes.js";
|
|
3
4
|
import type { ResidentTabRecovery } from "../foundation/resident-tabs/recovery.js";
|
|
@@ -20,6 +21,21 @@ export declare function closeResidentUtilityRoute(tabBridge: Pick<TabBridgeClien
|
|
|
20
21
|
export declare function bindResidentTabBridge(tabBridge: TabBridgeClient, backend: Pick<ComposedSessionBackend, "identity" | "session" | "workflows" | "generateResidentTabName">, recovery?: Pick<ResidentTabRecovery, "settled"> | null, options?: {
|
|
21
22
|
readonly autoName?: boolean;
|
|
22
23
|
readonly openRoute?: (route: ResidentTabRoute) => void;
|
|
24
|
+
readonly onClientVersion?: (version: string) => void;
|
|
23
25
|
}): Promise<() => void>;
|
|
26
|
+
export declare function createResidentClientIdentity(announce: (update: OwnedUiReleaseUpdate) => void): {
|
|
27
|
+
current: () => string | null;
|
|
28
|
+
onChange: (listener: () => void) => void;
|
|
29
|
+
observe: (next: string) => void;
|
|
30
|
+
};
|
|
31
|
+
/** Input ownership is established before the presenter starts, and released if bridge binding fails. */
|
|
32
|
+
export declare function bindResidentPresentation(bridge: TabBridgeClient | null, backend: Parameters<typeof bindResidentTabBridge>[1], recovery: Parameters<typeof bindResidentTabBridge>[2], options: Parameters<typeof bindResidentTabBridge>[3], input: {
|
|
33
|
+
addPreInputListener(listener: (data: string) => {
|
|
34
|
+
data: string;
|
|
35
|
+
}): () => void;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
unsubscribeTabBridge: () => void;
|
|
38
|
+
removeControllerBoundaryInput: () => void;
|
|
39
|
+
}>;
|
|
24
40
|
export declare function correlateResidentTabSubmission(recovery: Pick<ResidentTabRecovery, "correlated" | "settled">, backend: Pick<ComposedSessionBackend, "identity" | "session">, submissionId: string, entryId: string): Promise<void>;
|
|
25
41
|
export declare function currentBackendSessionFile(backend: Pick<ComposedSessionBackend, "identity">): string | null;
|
|
@@ -112,22 +112,8 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
|
|
|
112
112
|
}), false, pendingRequests, queuedInput);
|
|
113
113
|
};
|
|
114
114
|
announceSession();
|
|
115
|
-
const unsubscribeCommands = typeof tabBridge.onCommand === "function"
|
|
116
|
-
|
|
117
|
-
// keeps its resident terminal current while the holder withholds only its client surfaces.
|
|
118
|
-
if (command.type === "rename") {
|
|
119
|
-
void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
|
|
120
|
-
}
|
|
121
|
-
else if (command.type === "route") {
|
|
122
|
-
if (options.openRoute !== undefined)
|
|
123
|
-
options.openRoute(command.route);
|
|
124
|
-
else
|
|
125
|
-
void backend.workflows.executeWorkflow({ command: command.route, argument: "" }).catch(() => { });
|
|
126
|
-
}
|
|
127
|
-
else if (command.type === "stop") {
|
|
128
|
-
void backend.session.dispose().catch(() => { });
|
|
129
|
-
}
|
|
130
|
-
}) : () => { };
|
|
115
|
+
const unsubscribeCommands = typeof tabBridge.onCommand === "function"
|
|
116
|
+
? tabBridge.onCommand(command => applyBridgeCommand(command, backend, options)) : () => { };
|
|
131
117
|
const unsubscribeEvents = backend.session.onEvent(event => {
|
|
132
118
|
if (event.type === "session-lifecycle") {
|
|
133
119
|
const wasWorking = working;
|
|
@@ -163,6 +149,49 @@ export async function bindResidentTabBridge(tabBridge, backend, recovery = null,
|
|
|
163
149
|
unsubscribeEvents();
|
|
164
150
|
};
|
|
165
151
|
}
|
|
152
|
+
function applyBridgeCommand(command, backend, options) {
|
|
153
|
+
// Invariant: visibility never pauses resident rendering or autonomous work.
|
|
154
|
+
if (command.type === "client")
|
|
155
|
+
options.onClientVersion?.(command.version);
|
|
156
|
+
else if (command.type === "rename")
|
|
157
|
+
void backend.workflows.executeWorkflow({ command: "name", argument: command.name }).catch(() => { });
|
|
158
|
+
else if (command.type === "route") {
|
|
159
|
+
if (options.openRoute !== undefined)
|
|
160
|
+
options.openRoute(command.route);
|
|
161
|
+
else
|
|
162
|
+
void backend.workflows.executeWorkflow({ command: command.route, argument: "" }).catch(() => { });
|
|
163
|
+
}
|
|
164
|
+
else if (command.type === "stop")
|
|
165
|
+
void backend.session.dispose().catch(() => { });
|
|
166
|
+
}
|
|
167
|
+
export function createResidentClientIdentity(announce) {
|
|
168
|
+
let version = null;
|
|
169
|
+
let changed;
|
|
170
|
+
return {
|
|
171
|
+
current: () => version,
|
|
172
|
+
onChange: (listener) => { changed = listener; },
|
|
173
|
+
observe: (next) => {
|
|
174
|
+
if (next === version)
|
|
175
|
+
return;
|
|
176
|
+
version = next;
|
|
177
|
+
announce({ status: "client-connected", version: next });
|
|
178
|
+
changed?.();
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/** Input ownership is established before the presenter starts, and released if bridge binding fails. */
|
|
183
|
+
export async function bindResidentPresentation(bridge, backend, recovery, options, input) {
|
|
184
|
+
if (bridge === null)
|
|
185
|
+
return { unsubscribeTabBridge() { }, removeControllerBoundaryInput() { } };
|
|
186
|
+
const removeControllerBoundaryInput = input.addPreInputListener(data => ({ data: bridge.consumeControllerBoundaryInput(data) }));
|
|
187
|
+
try {
|
|
188
|
+
return { unsubscribeTabBridge: await bindResidentTabBridge(bridge, backend, recovery, options), removeControllerBoundaryInput };
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
removeControllerBoundaryInput();
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
166
195
|
export async function correlateResidentTabSubmission(recovery, backend, submissionId, entryId) {
|
|
167
196
|
await recovery.correlated(submissionId, entryId);
|
|
168
197
|
// Concurrency: a normal prompt command can resolve after the ready transition already fired.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OwnedUiShellEngineOptions } from "../app/session-shell/index.js";
|
|
2
2
|
import type { ReleaseNoteCatalog } from "../features/owned-ui/release-notes.js";
|
|
3
|
+
import type { StartupReleaseUpdateObservationOptions } from "./release-update-observation.js";
|
|
3
4
|
type StartupRoute = NonNullable<OwnedUiShellEngineOptions["startupRoute"]>;
|
|
4
5
|
export interface StartupReleaseNoteOptions {
|
|
5
6
|
readonly ownedSurfaces: boolean;
|
|
@@ -11,6 +12,13 @@ export interface StartupReleaseNoteOptions {
|
|
|
11
12
|
readonly configDir: string;
|
|
12
13
|
}
|
|
13
14
|
export declare function readPackageVersion(): Promise<string>;
|
|
15
|
+
type UpdateObservationModule = Pick<typeof import("./release-update-observation.js"), "createStartupReleaseUpdateObservation">;
|
|
16
|
+
/** Optional update checks load after first paint; disposal also fences an unfinished module load. */
|
|
17
|
+
export declare function deferStartupReleaseUpdateObservation(options: StartupReleaseUpdateObservationOptions, load?: () => Promise<UpdateObservationModule>): {
|
|
18
|
+
start(): void;
|
|
19
|
+
stop(): void;
|
|
20
|
+
clientChanged(): void;
|
|
21
|
+
};
|
|
14
22
|
/**
|
|
15
23
|
* The release notes bare A1 ships and the one note this version shows once per profile on startup:
|
|
16
24
|
* the packaged catalog (or why it could not be read), the running version, and the claim that
|
|
@@ -6,6 +6,26 @@ export async function readPackageVersion() {
|
|
|
6
6
|
throw new Error("A1 package version is unavailable");
|
|
7
7
|
return version;
|
|
8
8
|
}
|
|
9
|
+
/** Optional update checks load after first paint; disposal also fences an unfinished module load. */
|
|
10
|
+
export function deferStartupReleaseUpdateObservation(options, load = () => import("./release-update-observation.js")) {
|
|
11
|
+
let observation;
|
|
12
|
+
let started = false, stopped = false;
|
|
13
|
+
return {
|
|
14
|
+
start() {
|
|
15
|
+
if (started || stopped)
|
|
16
|
+
return;
|
|
17
|
+
started = true;
|
|
18
|
+
void load().then(module => {
|
|
19
|
+
if (stopped)
|
|
20
|
+
return;
|
|
21
|
+
observation = module.createStartupReleaseUpdateObservation(options);
|
|
22
|
+
observation.start();
|
|
23
|
+
}).catch(() => { });
|
|
24
|
+
},
|
|
25
|
+
stop() { stopped = true; observation?.stop(); },
|
|
26
|
+
clientChanged() { observation?.clientChanged(); },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
9
29
|
/**
|
|
10
30
|
* The release notes bare A1 ships and the one note this version shows once per profile on startup:
|
|
11
31
|
* the packaged catalog (or why it could not be read), the running version, and the claim that
|
|
@@ -339,6 +339,9 @@ export interface OwnedUiVisualExtensionSupport {
|
|
|
339
339
|
}
|
|
340
340
|
/** A newer release found by the startup check, or one installed for the next restart. */
|
|
341
341
|
export type OwnedUiReleaseUpdate = {
|
|
342
|
+
readonly status: "client-connected";
|
|
343
|
+
readonly version: string;
|
|
344
|
+
} | {
|
|
342
345
|
readonly status?: "available";
|
|
343
346
|
readonly version: string;
|
|
344
347
|
readonly command: string;
|
|
@@ -19,7 +19,7 @@ import { createRestartSeal, readRestartCertifiedRelease, releaseCertificationDoc
|
|
|
19
19
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
20
20
|
import { markStartupPhase } from "../startup/index.js";
|
|
21
21
|
import { PRIVATE_ENVIRONMENT, isCurrentLaunchContract, launchContractTarget, readLaunchContext, withLaunchContext } from "../launch-context/index.js";
|
|
22
|
-
import { ensureTerminalHost, readResidentTabLaunchSettings, selectResidentHost } from "../resident-tabs/index.js";
|
|
22
|
+
import { assertResidentContinuity, ensureTerminalHost, readResidentTabLaunchSettings, selectResidentHost } from "../resident-tabs/index.js";
|
|
23
23
|
const RELEASE_RESELECTION_MESSAGE = "a1-release-reselection";
|
|
24
24
|
export async function runBootstrap(options) {
|
|
25
25
|
return await runBootstrapAttempt(options, 1);
|
|
@@ -38,41 +38,7 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
38
38
|
const paths = resolveProductPaths(environment);
|
|
39
39
|
await mkdir(paths.runtimeDir, { recursive: true, mode: 0o700 });
|
|
40
40
|
const launchSelected = async (release) => {
|
|
41
|
-
|
|
42
|
-
// Compatibility: public launch preparation supplies PI_CODING_AGENT_DIR; the fallback keeps
|
|
43
|
-
// direct SDK/bootstrap callers hermetic while retaining their former config-root read.
|
|
44
|
-
const agentProfileDir = environment.PI_CODING_AGENT_DIR ?? paths.configDir;
|
|
45
|
-
const residentLaunchSettings = launchProfileId === "a1"
|
|
46
|
-
? readResidentTabLaunchSettings(agentProfileDir, PRODUCT_IDENTITY.commandName, paths.configDir, launchProfileId)
|
|
47
|
-
: null;
|
|
48
|
-
const resident = await selectResidentHost({
|
|
49
|
-
profileId: launchProfileId,
|
|
50
|
-
currentLaunchContract: isCurrentLaunchContract(release),
|
|
51
|
-
solo: launchMode === "solo",
|
|
52
|
-
interactive: options.interactiveTerminal ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
53
|
-
ensureHost: async () => (options.ensureTerminalHost ?? ensureTerminalHost)({
|
|
54
|
-
releaseRoot: release.releaseRoot,
|
|
55
|
-
profileRoot: environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
56
|
-
dataDir: paths.dataDir,
|
|
57
|
-
runtimeDir: paths.runtimeDir,
|
|
58
|
-
environment,
|
|
59
|
-
maxConcurrentStarts: (await residentLaunchSettings).maxConcurrentStarts,
|
|
60
|
-
unresponsiveRestartSeconds: (await residentLaunchSettings).unresponsiveRestartSeconds,
|
|
61
|
-
}),
|
|
62
|
-
});
|
|
63
|
-
if (resident !== null) {
|
|
64
|
-
residentContext = resident.status === "ready" ? {
|
|
65
|
-
residentRootExecutable: resident.executable.executable,
|
|
66
|
-
residentRootArguments: JSON.stringify([
|
|
67
|
-
resident.executable.entry,
|
|
68
|
-
"attach", "--endpoint", resident.endpoint, "--cwd", process.cwd(), "--clear-exit-ms", String(RESIDENT_DETACH_INTERVAL_MS),
|
|
69
|
-
"--profile", environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
70
|
-
"--settings-file", resolve(agentProfileDir, "settings.json"), "--accent-color", (await residentLaunchSettings).accentColor,
|
|
71
|
-
"--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
|
|
72
|
-
"--release-root", release.releaseRoot, "--node-executable", process.execPath,
|
|
73
|
-
]),
|
|
74
|
-
} : { residentFallback: resident.reason };
|
|
75
|
-
}
|
|
41
|
+
const residentContext = await residentLaunchContext(options, release, environment);
|
|
76
42
|
const result = await launchUi(release, environment, sessionArgs, residentContext);
|
|
77
43
|
if (!result.releaseSuperseded)
|
|
78
44
|
return result.code;
|
|
@@ -218,6 +184,16 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
218
184
|
const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
219
185
|
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
220
186
|
}
|
|
187
|
+
try {
|
|
188
|
+
await assertResidentContinuity({ releaseRoot: candidate.releaseRoot, dataDir: paths.dataDir, environment,
|
|
189
|
+
profileRoot: environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir, runtimeDir: paths.runtimeDir });
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
const fallback = await launchRetainedActive(await stateStore.read(), paths, environment, launchSelected, excludedRetainedReleaseId);
|
|
193
|
+
if (fallback !== null)
|
|
194
|
+
return fallback;
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
221
197
|
state = await stateStore.activateForLaunch(candidate.releaseId, !activeIsLaunchable ? activeId ?? undefined : undefined);
|
|
222
198
|
if (state.references.active !== candidate.releaseId) {
|
|
223
199
|
const retainedRecord = state.references.active === null ? undefined : state.releases[state.references.active];
|
|
@@ -312,6 +288,38 @@ async function runBootstrapAttempt(options, retriesRemaining) {
|
|
|
312
288
|
await ensureSupervisor(selected, environment);
|
|
313
289
|
return await launchSelected(selected);
|
|
314
290
|
}
|
|
291
|
+
/** Select only client routing; a resident reconnect never asks execution to restart. */
|
|
292
|
+
async function residentLaunchContext(options, release, environment) {
|
|
293
|
+
const paths = resolveProductPaths(environment);
|
|
294
|
+
const profileId = options.launchIntent?.profileId ?? "a1";
|
|
295
|
+
const mode = options.launchIntent?.launchMode ?? (profileId === "a1" ? "tabs" : "solo");
|
|
296
|
+
const agentProfileDir = environment.PI_CODING_AGENT_DIR ?? paths.configDir;
|
|
297
|
+
const residentLaunchSettings = profileId === "a1" ? readResidentTabLaunchSettings(agentProfileDir, PRODUCT_IDENTITY.commandName, paths.configDir, profileId) : null;
|
|
298
|
+
const resident = await selectResidentHost({
|
|
299
|
+
profileId, currentLaunchContract: isCurrentLaunchContract(release), solo: mode === "solo",
|
|
300
|
+
interactive: options.interactiveTerminal ?? (process.stdin.isTTY === true && process.stdout.isTTY === true),
|
|
301
|
+
ensureHost: async () => (options.ensureTerminalHost ?? ensureTerminalHost)({
|
|
302
|
+
releaseRoot: release.releaseRoot, profileRoot: environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
303
|
+
dataDir: paths.dataDir, runtimeDir: paths.runtimeDir, environment,
|
|
304
|
+
maxConcurrentStarts: (await residentLaunchSettings).maxConcurrentStarts, unresponsiveRestartSeconds: (await residentLaunchSettings).unresponsiveRestartSeconds,
|
|
305
|
+
}),
|
|
306
|
+
});
|
|
307
|
+
if (resident === null)
|
|
308
|
+
return {};
|
|
309
|
+
if (resident.status !== "ready")
|
|
310
|
+
return { residentFallback: resident.reason };
|
|
311
|
+
return {
|
|
312
|
+
residentRootExecutable: resident.executable.executable,
|
|
313
|
+
residentRootArguments: JSON.stringify([
|
|
314
|
+
resident.executable.entry, "attach", "--workspace", "--endpoint", resident.endpoint,
|
|
315
|
+
"--cwd", process.cwd(), "--clear-exit-ms", String(RESIDENT_DETACH_INTERVAL_MS),
|
|
316
|
+
"--profile", environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir,
|
|
317
|
+
"--settings-file", resolve(agentProfileDir, "settings.json"), "--accent-color", (await residentLaunchSettings).accentColor,
|
|
318
|
+
"--data-dir", paths.dataDir, "--runtime-dir", paths.runtimeDir,
|
|
319
|
+
"--release-root", release.releaseRoot, "--node-executable", process.execPath,
|
|
320
|
+
]),
|
|
321
|
+
};
|
|
322
|
+
}
|
|
315
323
|
/**
|
|
316
324
|
* Start the retained active cohort when the installed payload cannot be taken. The
|
|
317
325
|
* release already carries certified evidence, so this reuses it rather than
|
|
@@ -493,6 +501,8 @@ async function activatePendingAfterBlockerExit(candidate, stateStore, paths, env
|
|
|
493
501
|
return;
|
|
494
502
|
const diagnosticsPath = await certifyMaterializedRelease(candidate, paths.dataDir);
|
|
495
503
|
await stateStore.approve(candidate.releaseId, diagnosticsPath);
|
|
504
|
+
await assertResidentContinuity({ releaseRoot: candidate.releaseRoot, dataDir: paths.dataDir, environment,
|
|
505
|
+
profileRoot: environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir, runtimeDir: paths.runtimeDir });
|
|
496
506
|
if (!await requestIdleOwnershipRelease(endpoint, candidate.releaseId, 2_000))
|
|
497
507
|
return;
|
|
498
508
|
if (!await releaseVerifiedIdleOwner(endpoint, paths.dataDir))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generated by scripts/pi/sync-progress-palette.ts from A1's named accent projection
|
|
3
|
-
* over pinned Pi 1.
|
|
3
|
+
* over pinned Pi 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5. Do not edit directly.
|
|
4
4
|
*/
|
|
5
5
|
export declare const SHARED_PROGRESS_PALETTE: Readonly<{
|
|
6
6
|
blue: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generated by scripts/pi/sync-progress-palette.ts from A1's named accent projection
|
|
3
|
-
* over pinned Pi 1.
|
|
3
|
+
* over pinned Pi 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5. Do not edit directly.
|
|
4
4
|
*/
|
|
5
5
|
export const SHARED_PROGRESS_PALETTE = Object.freeze({
|
|
6
6
|
"blue": {
|
|
@@ -2,7 +2,7 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
5
|
+
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
|
|
6
6
|
import { certifyMaterializedRelease, ensureSupervisor } from "./bootstrap.js";
|
|
7
7
|
import { CohortStateStore } from "./cohort-state.js";
|
|
8
8
|
import { encodeFrame, LineFrameDecoder } from "../protocol/index.js";
|
|
@@ -51,8 +51,14 @@ export async function activateInstalledRelease(request, callbacks) {
|
|
|
51
51
|
callbacks.onWarmup?.("started");
|
|
52
52
|
await warmMaterializedRelease(candidate, request.environment);
|
|
53
53
|
callbacks.onWarmup?.("completed");
|
|
54
|
+
const { assertResidentContinuity } = await import("../resident-tabs/index.js");
|
|
55
|
+
const paths = resolveProductPaths(request.environment);
|
|
56
|
+
const continuity = { releaseRoot: candidate.releaseRoot, dataDir: request.dataDir, environment: request.environment,
|
|
57
|
+
profileRoot: request.environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir, runtimeDir: paths.runtimeDir };
|
|
58
|
+
await assertResidentContinuity(continuity);
|
|
54
59
|
await ensureSupervisor(candidate, request.environment);
|
|
55
|
-
// Invariant:
|
|
60
|
+
// Invariant: revalidate after supervision, before committing client activation or restart readiness.
|
|
61
|
+
await assertResidentContinuity(continuity);
|
|
56
62
|
await stateStore.activate(candidate.releaseId);
|
|
57
63
|
await callbacks.phase("active-reference-committed");
|
|
58
64
|
}
|
|
@@ -14,6 +14,9 @@ export interface TabAttachment {
|
|
|
14
14
|
export type TabBridgeCommand = {
|
|
15
15
|
readonly type: "visibility";
|
|
16
16
|
readonly visible: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
readonly type: "client";
|
|
19
|
+
readonly version: string;
|
|
17
20
|
} | {
|
|
18
21
|
readonly type: "rename";
|
|
19
22
|
readonly name: string;
|
|
@@ -71,7 +71,7 @@ export class TabBridgeClient {
|
|
|
71
71
|
transport.once("connect", () => {
|
|
72
72
|
this.#send("hello", {
|
|
73
73
|
role: "bridge", generation: 1, build: this.#options.build ?? "a1", releaseId: this.#options.credentials.releaseId,
|
|
74
|
-
features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-route", "bridge-stop", "bridge-shortcuts", "controller-boundary", "recovery-journal"],
|
|
74
|
+
features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-route", "bridge-stop", "bridge-shortcuts", "controller-boundary", "recovery-journal", "client-identity"],
|
|
75
75
|
token: [...Buffer.from(this.#options.credentials.token, "base64")], tabId: this.#options.credentials.tabId,
|
|
76
76
|
pid: this.#options.pid ?? process.pid, startIdentity: this.#options.startIdentity ?? `${process.pid}:node`,
|
|
77
77
|
}, ++this.#request);
|
|
@@ -231,6 +231,12 @@ export class TabBridgeClient {
|
|
|
231
231
|
this.#fail();
|
|
232
232
|
return;
|
|
233
233
|
}
|
|
234
|
+
if (message.type === "bridge.client" && typeof message.body["version"] === "string") {
|
|
235
|
+
const version = message.body["version"];
|
|
236
|
+
if (version.length <= 128 && /^\d+\.\d+\.\d+(?:[-+][a-z0-9.+-]+)?$/iu.test(version))
|
|
237
|
+
this.#emitCommand({ type: "client", version });
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
234
240
|
if (message.type === "bridge.visibility" && typeof message.body["visible"] === "boolean") {
|
|
235
241
|
this.#emitCommand({ type: "visibility", visible: message.body["visible"] });
|
|
236
242
|
return;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ContinuityOptions {
|
|
2
|
+
readonly releaseRoot: string;
|
|
3
|
+
readonly dataDir: string;
|
|
4
|
+
readonly profileRoot: string;
|
|
5
|
+
readonly runtimeDir: string;
|
|
6
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
7
|
+
readonly inspect?: (options: Omit<ContinuityOptions, "inspect">) => Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
/** Activation authority requires a stable authenticated source-contract set, never an idle Agent. */
|
|
10
|
+
export declare function assertResidentContinuity(options: ContinuityOptions): Promise<void>;
|
|
11
|
+
/** Native inspection/import is read-only toward execution and never starts or stops a source. */
|
|
12
|
+
export declare function inspectResidentContinuity(options: Omit<ContinuityOptions, "inspect">): Promise<string>;
|