@timurproko/a1 0.2.7-dev.834 → 0.2.7-dev.835
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/bin/tab-launch-context.d.ts +1 -0
- package/bin/tab-launch-context.js +3 -3
- package/dist/cli/tabs.d.ts +1 -1
- package/dist/cli/tabs.js +10 -14
- package/dist/foundation/release/release-gc.js +19 -4
- package/dist/foundation/resident-tabs/bridge-client.d.ts +1 -0
- package/dist/foundation/resident-tabs/bridge-client.js +6 -1
- package/dist/foundation/resident-tabs/maintenance.d.ts +2 -6
- package/dist/foundation/resident-tabs/maintenance.js +5 -23
- package/dist/foundation/resident-tabs/release-holds.js +3 -14
- 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/product-identity.d.ts +1 -1
- package/dist/product-identity.js +1 -1
- package/dist/product-identity.json +1 -0
- package/docs/architecture/resident-tabs.md +9 -9
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
|
|
3
|
-
const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT"];
|
|
3
|
+
const TAB_ENVIRONMENT_NAMES = ["A1_TAB_ID", "A1_TAB_TOKEN", "A1_TAB_ENDPOINT", "A1_TAB_RELEASE_ID"];
|
|
4
4
|
|
|
5
5
|
/** @param {string} reason */
|
|
6
6
|
export function formatResidentFallbackNotice(reason) {
|
|
@@ -83,12 +83,12 @@ export function consumeTabLaunchContext(arguments_, environment) {
|
|
|
83
83
|
const incarnationArgument = uiArguments.indexOf("--tab-incarnation");
|
|
84
84
|
const tabIncarnation = incarnationArgument === -1 ? undefined : Number(uiArguments[incarnationArgument + 1]);
|
|
85
85
|
if (incarnationArgument !== -1) uiArguments.splice(incarnationArgument, 2);
|
|
86
|
-
const [tabId, token, endpoint] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
|
|
86
|
+
const [tabId, token, endpoint, releaseId] = TAB_ENVIRONMENT_NAMES.map(name => environment[name]);
|
|
87
87
|
for (const name of TAB_ENVIRONMENT_NAMES) delete environment[name];
|
|
88
88
|
if (!tabMode) return { uiArguments, tabCredentials: undefined, tabStandby: false };
|
|
89
89
|
if (!tabId || !token || !endpoint || !tabDataDir || tabDataDir.includes("\0")
|
|
90
90
|
|| typeof tabIncarnation !== "number" || !Number.isSafeInteger(tabIncarnation) || tabIncarnation < 1) {
|
|
91
91
|
throw new Error("A1 tab mode requires bridge credentials, recovery directory, and incarnation");
|
|
92
92
|
}
|
|
93
|
-
return { uiArguments, tabCredentials: { tabId, token, endpoint }, tabDataDir, tabIncarnation, tabStandby };
|
|
93
|
+
return { uiArguments, tabCredentials: { tabId, token, endpoint, ...(releaseId === undefined ? {} : { releaseId }) }, tabDataDir, tabIncarnation, tabStandby };
|
|
94
94
|
}
|
package/dist/cli/tabs.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { runTerminalHostMaintenance, writeDoctorBundle } from "../foundation/res
|
|
|
2
2
|
import type { TabsRequest } from "./tabs-request.js";
|
|
3
3
|
export type { TabsRequest } from "./tabs-request.js";
|
|
4
4
|
export interface TabsCommandOptions {
|
|
5
|
-
/** The
|
|
5
|
+
/** The invoking package, never a different active or retained release. */
|
|
6
6
|
readonly packageRoot?: string;
|
|
7
7
|
readonly environment?: NodeJS.ProcessEnv;
|
|
8
8
|
readonly stdout?: (message: string) => void;
|
package/dist/cli/tabs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveProductPaths } from "../foundation/lifecycle/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { materializeRelease } from "../foundation/release/index.js";
|
|
3
3
|
import { readResidentTabLaunchSettings, runTerminalHostMaintenance, writeDoctorBundle, } from "../foundation/resident-tabs/index.js";
|
|
4
4
|
import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../product-identity.js";
|
|
5
5
|
/** `a1 tabs`: inspect or stop resident tabs without attaching, and never start a host to report state. */
|
|
@@ -9,7 +9,7 @@ export async function runTabsCommand(request, options = {}) {
|
|
|
9
9
|
const stderr = options.stderr ?? (message => process.stderr.write(message));
|
|
10
10
|
const paths = resolveProductPaths(environment);
|
|
11
11
|
const profileRoot = environment[PRODUCT_IDENTITY.environment.profileHome] ?? paths.configDir;
|
|
12
|
-
const releaseRoots = await
|
|
12
|
+
const releaseRoots = await invokingReleaseRoots(paths.dataDir, options.packageRoot, environment);
|
|
13
13
|
const maintenance = options.maintenance ?? runTerminalHostMaintenance;
|
|
14
14
|
const report = await maintenance({
|
|
15
15
|
action: request.action === "doctor" ? "host-status" : request.action,
|
|
@@ -51,22 +51,18 @@ export async function runTabsCommand(request, options = {}) {
|
|
|
51
51
|
}
|
|
52
52
|
return report.exitCode === 0 ? 0 : 1;
|
|
53
53
|
}
|
|
54
|
-
async function
|
|
55
|
-
|
|
54
|
+
async function invokingReleaseRoots(dataDir, packageRoot, environment) {
|
|
55
|
+
if (!packageRoot)
|
|
56
|
+
return [];
|
|
57
|
+
if (environment[PRODUCT_IDENTITY.environment.developmentInstanceId])
|
|
58
|
+
return [packageRoot];
|
|
56
59
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (id && state.releases[id])
|
|
60
|
-
roots.push(state.releases[id].releaseRoot);
|
|
61
|
-
}
|
|
62
|
-
roots.push(...Object.values(state.releases).map(record => record.releaseRoot));
|
|
60
|
+
// Invariant: immutable materialization selects exact payload bytes without activating or starting a host.
|
|
61
|
+
return [(await materializeRelease(packageRoot, dataDir)).releaseRoot];
|
|
63
62
|
}
|
|
64
63
|
catch {
|
|
65
|
-
|
|
64
|
+
return [];
|
|
66
65
|
}
|
|
67
|
-
if (packageRoot)
|
|
68
|
-
roots.push(packageRoot);
|
|
69
|
-
return [...new Set(roots)];
|
|
70
66
|
}
|
|
71
67
|
const NO_TABS = "No resident tabs are running.\n";
|
|
72
68
|
function describe(request, report) {
|
|
@@ -316,14 +316,29 @@ async function residentReleaseHolds(dataDir, reader) {
|
|
|
316
316
|
const everything = () => [...new Set(records.map(record => record.releaseId))].map(releaseId => ({ authority: "resident", releaseId }));
|
|
317
317
|
if (!report.complete)
|
|
318
318
|
return everything();
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
if (report.releaseRoots.length === 0)
|
|
320
|
+
return [];
|
|
321
|
+
// Rationale: native Windows extended paths and Unix aliases must match the catalog's spelling.
|
|
322
|
+
const byRoot = new Map();
|
|
323
|
+
for (const record of records) {
|
|
324
|
+
const canonical = await realpath(record.releaseRoot).catch(() => record.releaseRoot);
|
|
325
|
+
byRoot.set(comparablePath(canonical), record.releaseId);
|
|
326
|
+
}
|
|
327
|
+
const directory = await realpath(resolve(dataDir, "releases")).catch(() => null);
|
|
328
|
+
if (directory === null)
|
|
329
|
+
return everything();
|
|
330
|
+
const store = comparablePath(directory);
|
|
321
331
|
const holds = new Set();
|
|
322
332
|
for (const root of report.releaseRoots) {
|
|
323
|
-
|
|
333
|
+
// Security: an unresolved live root may alias managed content; uncertainty must retain it.
|
|
334
|
+
const canonical = await realpath(root).catch(() => null);
|
|
335
|
+
if (canonical === null)
|
|
336
|
+
return everything();
|
|
337
|
+
const key = comparablePath(canonical);
|
|
338
|
+
const releaseId = byRoot.get(key);
|
|
324
339
|
if (releaseId)
|
|
325
340
|
holds.add(releaseId);
|
|
326
|
-
else if (
|
|
341
|
+
else if (key.startsWith(`${store}${sep}`))
|
|
327
342
|
return everything();
|
|
328
343
|
}
|
|
329
344
|
return [...holds].sort().map(releaseId => ({ authority: "resident", releaseId }));
|
|
@@ -70,7 +70,7 @@ export class TabBridgeClient {
|
|
|
70
70
|
this.#startWaiter = finish;
|
|
71
71
|
transport.once("connect", () => {
|
|
72
72
|
this.#send("hello", {
|
|
73
|
-
role: "bridge", generation: 1, build: this.#options.build ?? "a1",
|
|
73
|
+
role: "bridge", generation: 1, build: this.#options.build ?? "a1", releaseId: this.#options.credentials.releaseId,
|
|
74
74
|
features: ["bridge-status", "bridge-detach", "bridge-heartbeat", "bridge-restart", "bridge-visibility", "bridge-rename", "bridge-route", "bridge-stop", "bridge-shortcuts", "controller-boundary", "recovery-journal"],
|
|
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`,
|
|
@@ -208,6 +208,11 @@ export class TabBridgeClient {
|
|
|
208
208
|
}
|
|
209
209
|
#receive(message) {
|
|
210
210
|
if (message.type === "welcome" && message.body["generation"] === 1) {
|
|
211
|
+
const id = this.#options.credentials.releaseId;
|
|
212
|
+
if (id !== undefined && message.body["releaseId"] !== id) {
|
|
213
|
+
this.#fail(false);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
211
216
|
const advertised = message.body["features"];
|
|
212
217
|
this.#features = new Set(Array.isArray(advertised) ? advertised.filter((feature) => typeof feature === "string") : []);
|
|
213
218
|
this.#connected = true;
|
|
@@ -21,7 +21,7 @@ export interface MaintenanceOptions {
|
|
|
21
21
|
readonly profileRoot: string;
|
|
22
22
|
readonly dataDir: string;
|
|
23
23
|
readonly runtimeDir: string;
|
|
24
|
-
/**
|
|
24
|
+
/** The first root is the invoking release; other roots never substitute for it. */
|
|
25
25
|
readonly releaseRoots: readonly string[];
|
|
26
26
|
readonly environment: NodeJS.ProcessEnv;
|
|
27
27
|
readonly platform?: NodeJS.Platform;
|
|
@@ -33,11 +33,7 @@ export interface MaintenanceOptions {
|
|
|
33
33
|
readonly stdout: string;
|
|
34
34
|
}>;
|
|
35
35
|
}
|
|
36
|
-
/**
|
|
37
|
-
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
38
|
-
* another protocol generation, it retries once with the addon from the release that host
|
|
39
|
-
* recorded, after verifying that release's manifest; it never loads an unverified addon.
|
|
40
|
-
*/
|
|
36
|
+
/** Runs maintenance only through the invoking release; another cohort is never a retry target. */
|
|
41
37
|
export declare function runTerminalHostMaintenance(options: MaintenanceOptions): Promise<MaintenanceReport>;
|
|
42
38
|
/** The largest maintenance report accepted from the terminal host. */
|
|
43
39
|
export declare const MAX_MAINTENANCE_REPORT_BYTES: number;
|
|
@@ -4,26 +4,14 @@ import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
|
4
4
|
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
5
5
|
import { resolveTerminalHostEntryPath, resolveTerminalHostPath } from "./ensure-terminal-host.js";
|
|
6
6
|
import { verifyTerminalHostArtifact } from "./terminal-host-artifact.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Runs one maintenance form through a verified terminal host. When the running host speaks
|
|
10
|
-
* another protocol generation, it retries once with the addon from the release that host
|
|
11
|
-
* recorded, after verifying that release's manifest; it never loads an unverified addon.
|
|
12
|
-
*/
|
|
7
|
+
/** Runs maintenance only through the invoking release; another cohort is never a retry target. */
|
|
13
8
|
export async function runTerminalHostMaintenance(options) {
|
|
14
9
|
const platform = options.platform ?? process.platform;
|
|
15
10
|
const architecture = options.architecture ?? process.arch;
|
|
16
|
-
const command = await firstVerifiedHost(options, options.releaseRoots, platform, architecture);
|
|
11
|
+
const command = await firstVerifiedHost(options, options.releaseRoots.slice(0, 1), platform, architecture);
|
|
17
12
|
if (command === undefined)
|
|
18
|
-
return unavailable(options.action, "no verified terminal host
|
|
19
|
-
|
|
20
|
-
if (report.exitCode !== INCOMPATIBLE_EXIT || typeof report.releaseRoot !== "string")
|
|
21
|
-
return report;
|
|
22
|
-
const cohort = await firstVerifiedHost({ ...options, environment: {} }, [report.releaseRoot], platform, architecture);
|
|
23
|
-
if (cohort === undefined || cohort.entry === command.entry) {
|
|
24
|
-
return { ...report, message: "the running tabs use another version whose terminal host cannot be verified" };
|
|
25
|
-
}
|
|
26
|
-
return await invoke({ ...options, environment: installedEnvironment(options.environment) }, cohort);
|
|
13
|
+
return unavailable(options.action, "the invoking release has no verified terminal host");
|
|
14
|
+
return await invoke(options, command);
|
|
27
15
|
}
|
|
28
16
|
function arguments_(options) {
|
|
29
17
|
const command = options.action === "stop" ? ["stop", options.tabId ?? ""] : [options.action];
|
|
@@ -67,17 +55,11 @@ async function firstVerifiedHost(options, roots, platform, architecture) {
|
|
|
67
55
|
return { executable: process.execPath, entry: resolveTerminalHostEntryPath(root) };
|
|
68
56
|
}
|
|
69
57
|
catch {
|
|
70
|
-
//
|
|
58
|
+
// Invariant: a missing or altered invoking artifact fails closed.
|
|
71
59
|
}
|
|
72
60
|
}
|
|
73
61
|
return undefined;
|
|
74
62
|
}
|
|
75
|
-
function installedEnvironment(environment) {
|
|
76
|
-
const sanitized = { ...environment };
|
|
77
|
-
delete sanitized[PRODUCT_IDENTITY.environment.developmentInstanceId];
|
|
78
|
-
delete sanitized[PRODUCT_IDENTITY.environment.terminalHostPath];
|
|
79
|
-
return sanitized;
|
|
80
|
-
}
|
|
81
63
|
function unavailable(action, message) {
|
|
82
64
|
return { schema: "a1-tabs-maintenance-v1", command: action, host: "unavailable", message, exitCode: 3 };
|
|
83
65
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readdir
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
4
4
|
import { runBoundedHostCommand } from "./bounded-host-command.js";
|
|
@@ -51,19 +51,8 @@ async function hasResidentRecords(dataDir) {
|
|
|
51
51
|
catch (error) {
|
|
52
52
|
return !(error instanceof Error && "code" in error && error.code === "ENOENT");
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
await stat(resolve(dataDir, "tabs", profile, record));
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
return false;
|
|
54
|
+
// Invariant: native inspection, not a following stat, judges unknown or linked namespace entries.
|
|
55
|
+
return profiles.length > 0;
|
|
67
56
|
}
|
|
68
57
|
function incomplete(reason) {
|
|
69
58
|
return { complete: false, releaseRoots: [], uncertain: [reason] };
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "arm64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-26T12:38:01.432Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "arm64",
|
|
10
10
|
"target": "aarch64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-26T12:40:53.110Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "c42ed0b065515794627cd0064a5da4de4c84396460315615356aab340e73eef0",
|
|
16
|
+
"size": 5962320
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "darwin",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-26T12:38:07.232Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-apple-darwin",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-26T12:43:26.281Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "65f9a9556babcf6f361f643699142a854e564ef0c487d6709a48615a98dc8d75",
|
|
16
|
+
"size": 6497320
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-26T12:37:52.900Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
13
|
"sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-unknown-linux-gnu",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-26T12:40:25.329Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "234f73020b8f8cb31fd9a172f13c437255bb575a63466a3b369065e8866acead",
|
|
16
|
+
"size": 7236184
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
"platform": "win32",
|
|
8
8
|
"architecture": "x64",
|
|
9
9
|
"capability": "supported",
|
|
10
|
-
"builtAt": "2026-10-
|
|
10
|
+
"builtAt": "2026-10-26T12:38:48.590Z",
|
|
11
11
|
"artifact": {
|
|
12
12
|
"filename": "process-guardian.node",
|
|
13
|
-
"sha256": "
|
|
13
|
+
"sha256": "4136caabb0a807ff5b7c10e4b2cb1dd5e326c72bd3d7c61429e970879ac46f77",
|
|
14
14
|
"size": 402432
|
|
15
15
|
},
|
|
16
16
|
"provenance": {
|
|
Binary file
|
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
"architecture": "x64",
|
|
10
10
|
"target": "x86_64-pc-windows-msvc",
|
|
11
11
|
"capability": "supported",
|
|
12
|
-
"builtAt": "2026-10-
|
|
12
|
+
"builtAt": "2026-10-26T12:40:52.007Z",
|
|
13
13
|
"artifact": {
|
|
14
14
|
"filename": "terminal-host.node",
|
|
15
|
-
"sha256": "
|
|
16
|
-
"size":
|
|
15
|
+
"sha256": "a48fa7c8bdb979c8ca2834b280b590174b1574b16fc263aa5ad12ba1361a44a5",
|
|
16
|
+
"size": 6316032
|
|
17
17
|
},
|
|
18
18
|
"entry": {
|
|
19
19
|
"filename": "dist/foundation/resident-tabs/host-entry.js",
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
|
|
1
|
+
declare const ENVIRONMENT_KEYS: readonly ["clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath", "paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabReleaseId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId"];
|
|
2
2
|
declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
3
3
|
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile", "sessionRepositoryDirectory", "sessionWorktreeClaimDirectory"];
|
|
4
4
|
declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|
package/dist/product-identity.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import rawIdentity from "./product-identity.json" with { type: "json" };
|
|
2
2
|
const ENVIRONMENT_KEYS = [
|
|
3
3
|
"clipboardDiagnostics", "configDir", "dataDir", "databasePath", "developmentHistoryDataDir", "developmentInstanceId", "developmentRoot", "endpoint", "exitNoticePath",
|
|
4
|
-
"paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId",
|
|
4
|
+
"paneId", "probeTrace", "processGuardianPath", "prFooterPreview", "profileHome", "residentFixtureControlDir", "residentProgressObservationDir", "residentProgressRunId", "residentProgressTabId", "residentProgressTabIncarnation", "runtimeDir", "sessionRuntimeId", "skipVersionCheck", "startupTrace", "suggestionDiagnostics", "tabEndpoint", "tabId", "tabReleaseId", "tabToken", "tabsEndpoint", "terminalHostPath", "terminalSessionId",
|
|
5
5
|
];
|
|
6
6
|
const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
7
7
|
const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile", "sessionRepositoryDirectory", "sessionWorktreeClaimDirectory"];
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"suggestionDiagnostics": "A1_SUGGESTION_DIAGNOSTICS",
|
|
37
37
|
"tabEndpoint": "A1_TAB_ENDPOINT",
|
|
38
38
|
"tabId": "A1_TAB_ID",
|
|
39
|
+
"tabReleaseId": "A1_TAB_RELEASE_ID",
|
|
39
40
|
"tabToken": "A1_TAB_TOKEN",
|
|
40
41
|
"tabsEndpoint": "A1_TABS_ENDPOINT",
|
|
41
42
|
"terminalHostPath": "A1_TERMINAL_HOST_PATH",
|
|
@@ -175,13 +175,13 @@ Transport correctness does not rely on `timeBeginPeriod`. The existing per-role
|
|
|
175
175
|
|
|
176
176
|
The existing opt-in progress route (`A1_RESIDENT_PROGRESS_OBSERVATION_DIR` plus `A1_RESIDENT_PROGRESS_RUN_ID`) adds `pipe-frame-read`, `pipe-write-complete`, `outer-write-complete`, `wake-gap`, and `wake-repaint-requested` stages. Connection-level records may omit tab identity. `durationUs` is local monotonic completion/assembly time; `idleWaitUs` separately describes waiting for the next frame and is not delivery latency. Native records include `monotonicUs`, `clockEpoch`, and optional `clockDiscontinuity`. Consumers exclude negative, epoch-crossing, and clock-discontinuity-crossing derived samples, while retaining valid local completion durations. The queue remains bounded and asynchronous, with loss accounting and no terminal content or credentials. Outer-write completion is not proof of physical pixels.
|
|
177
177
|
|
|
178
|
-
For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts,
|
|
178
|
+
For recurring post-wake stalls, compare an existing resident client, a new client on the same resident host, and direct `a1 solo` in the same outer terminal. Use matched holder/attach revisions and local I/O durations to distinguish child progress, transport delay, and outer writes. If solo and other terminal applications also stall, do not assign the failure to resident pipes without further evidence. Simulated-gap fixtures do not certify physical sleep/wake recovery; do not automate workstation suspend. Updates retain old resident cohorts, but a new launch selects its own immutable release namespace rather than reusing their host. Existing roles keep their original code and lifecycle; installing an update never recycles running tabs.
|
|
179
179
|
|
|
180
180
|
### 3. Host scope, endpoints, writer authority, and credentials
|
|
181
181
|
|
|
182
|
-
- There is **one server per OS user
|
|
183
|
-
- Windows endpoint: `\\.\pipe\a1-tabs-<
|
|
184
|
-
- macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same
|
|
182
|
+
- There is **one server per OS user, canonical A1 profile root and immutable release identity**. Different releases coexist without migration; the supervisor remains forbidden from retaining runtime processes. A domain-separated hash of canonical profile and fixed originating release/addon identity selects the flat `<data>/tabs/<scope>` directory, credential files, registry, epochs and `server-start-<scope>.lock`.
|
|
183
|
+
- Windows endpoint: `\\.\pipe\a1-tabs-<scope[0:20]>`.
|
|
184
|
+
- macOS and Linux endpoint: a Unix domain socket `a1-tabs-<same scope>.sock` inside an owner-only (`0700`) per-user runtime directory (`$XDG_RUNTIME_DIR` when set, otherwise a private directory under the profile's runtime dir), kept short enough for the platform's socket path limit.
|
|
185
185
|
- Generation 1 admits holders over the authenticated owner endpoint. Dedicated `…-h-<tabId>` endpoints remain an optional later optimization.
|
|
186
186
|
- Hermetic override: `A1_TABS_ENDPOINT`, together with the `A1_*_DIR` overrides.
|
|
187
187
|
- **The endpoint is discovery; the writer lease is authority.** A starting server probes with a real handshake. If a live server answers, it exits as `already-running` and joins the winner. A failed probe does not authorize blind endpoint reclaim: the starter reads the owner marker, verifies pid and native start identity, requests termination, and waits for the owner-only OS-exclusive registry-writer lease. If the owner cannot be verified or the lease cannot be acquired, startup fails closed instead of creating split brain.
|
|
@@ -189,7 +189,7 @@ For recurring post-wake stalls, compare an existing resident client, a new clien
|
|
|
189
189
|
- **Access and authentication:**
|
|
190
190
|
- The endpoint, token files, secret file, registry, and writer lease are owner-only: Windows ACLs on the named pipe and files, `0700` directories and `0600` files on macOS and Linux, plus a peer-credential check (`SO_PEERCRED` / `getpeereid`) on every Unix socket connection.
|
|
191
191
|
- Clients present a random 32-byte client token from an owner-only file.
|
|
192
|
-
- One random owner-only profile secret persists outside
|
|
192
|
+
- One random owner-only profile/release secret persists outside its registry. Per-tab/per-incarnation holder and bridge credentials are derived from that secret and the non-secret tab/incarnation identity, so a replacement server can authenticate surviving processes without storing tab credentials.
|
|
193
193
|
- Holders and bridges also present pid and native start identity. Nothing unverifiable is adopted, signalled, terminated, or allowed to mutate state.
|
|
194
194
|
|
|
195
195
|
### 4. Detached start
|
|
@@ -326,10 +326,10 @@ Tab status is an A1-owned state machine fed by `bridge.status`:
|
|
|
326
326
|
### 10. Updates and version skew
|
|
327
327
|
|
|
328
328
|
- **Artifacts and process images.** The server and holders are Node runtimes that load the addon and fixed entry from immutable release directories, never from the mutable npm prefix. Activation therefore never overwrites a loaded addon or entry, and release retention keeps every release used by a verified resident process.
|
|
329
|
-
- **
|
|
330
|
-
- **
|
|
331
|
-
- **Retention.**
|
|
332
|
-
- **
|
|
329
|
+
- **Exact release admission.** Clients, holders and bridges present a release identity before capability negotiation. Matching protocol generation or display version does not permit cross-release attachment. Native clients also check the welcome identity; bridge identity is consumed from holder-private launch context before extensions load.
|
|
330
|
+
- **Independent activation.** A newer release starts or reuses its own host. Existing resident and solo processes remain untouched. Server recovery and child launches retain the originating fixed Node, entry and addon; the active-release pointer never retargets them. No automatic migration, handoff or idle recycling is performed.
|
|
331
|
+
- **Retention.** Read-only bounded collection scans every flat release namespace and legacy profile namespace. It keeps server, holder and child releases while identities verify and retains all candidates on unreadable, linked, oversized or unverifiable state. Package activation never overwrites loaded artifacts.
|
|
332
|
+
- **Maintenance and legacy state.** Maintenance resolves the invoking package's immutable materialization without activation or host startup, uses only that release's verified entry, and never retries another recorded cohort. List, stop, status and doctor remain release-local, including unresponsive-host termination. Legacy unversioned state is neither migrated nor adopted; it is read only for conservative retention. Shared configuration and session file locks remain profile-neutral.
|
|
333
333
|
|
|
334
334
|
### 11. Foreground UX
|
|
335
335
|
|
package/package.json
CHANGED