@timurproko/a1 0.2.7-dev.1241 → 0.2.7-dev.1245

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +24 -2
  2. package/bin/cli.js +17 -0
  3. package/dist/app/session-shell/dock-chrome.js +1 -4
  4. package/dist/cli/capabilities.d.ts +2 -0
  5. package/dist/cli/capabilities.js +2 -1
  6. package/dist/cli/dispatch.d.ts +11 -0
  7. package/dist/cli/dispatch.js +22 -6
  8. package/dist/cli/package-messages.js +4 -3
  9. package/dist/cli/session-context.js +18 -1
  10. package/dist/cli/uninstall.d.ts +30 -0
  11. package/dist/cli/uninstall.js +117 -0
  12. package/dist/composition/release-update-observation.js +2 -0
  13. package/dist/composition/session-runtime-registry.d.ts +4 -7
  14. package/dist/composition/session-runtime-registry.js +51 -35
  15. package/dist/foundation/launch-guardian/exit-watcher.js +19 -10
  16. package/dist/foundation/lifecycle/native-claim-gate.d.ts +5 -0
  17. package/dist/foundation/lifecycle/native-claim-gate.js +41 -0
  18. package/dist/foundation/lifecycle/session-claim-lock.d.ts +13 -0
  19. package/dist/foundation/lifecycle/session-claim-lock.js +131 -0
  20. package/dist/foundation/lifecycle/session-repository-context.js +3 -104
  21. package/dist/foundation/process-containment/addon.d.ts +5 -0
  22. package/dist/foundation/release/automatic-update.d.ts +1 -1
  23. package/dist/foundation/release/automatic-update.js +1 -1
  24. package/dist/foundation/release/bootstrap.js +34 -31
  25. package/dist/foundation/release/cohort-selection.d.ts +0 -6
  26. package/dist/foundation/release/cohort-selection.js +5 -13
  27. package/dist/foundation/release/cohort-state.d.ts +2 -0
  28. package/dist/foundation/release/endpoint-lock.d.ts +2 -0
  29. package/dist/foundation/release/endpoint-lock.js +37 -0
  30. package/dist/foundation/release/endpoint-recovery.d.ts +10 -0
  31. package/dist/foundation/release/endpoint-recovery.js +45 -0
  32. package/dist/foundation/release/endpoints.d.ts +10 -2
  33. package/dist/foundation/release/endpoints.js +42 -24
  34. package/dist/foundation/release/index.d.ts +3 -1
  35. package/dist/foundation/release/index.js +3 -1
  36. package/dist/foundation/release/process-cleanup.js +12 -2
  37. package/dist/foundation/release/supervisor-identity.d.ts +15 -0
  38. package/dist/foundation/release/supervisor-identity.js +63 -0
  39. package/dist/foundation/release/update.js +17 -8
  40. package/dist/foundation/resident-tabs/boot-identity.d.ts +6 -0
  41. package/dist/foundation/resident-tabs/boot-identity.js +16 -0
  42. package/dist/foundation/resident-tabs/bounded-host-command.d.ts +2 -0
  43. package/dist/foundation/resident-tabs/bounded-host-command.js +26 -3
  44. package/dist/foundation/resident-tabs/continuity.js +11 -3
  45. package/dist/foundation/resident-tabs/runtime-control.js +2 -1
  46. package/dist/foundation/storage/control-store.d.ts +5 -3
  47. package/dist/foundation/storage/control-store.js +8 -7
  48. package/dist/foundation/supervision/main.js +3 -1
  49. package/dist/foundation/supervision/server.js +75 -53
  50. package/dist/integrations/pi/components/shell-footer-status.js +2 -4
  51. package/dist/integrations/pi/components/shell-shared-facade.d.ts +0 -3
  52. package/dist/integrations/pi/components/shell-shared-facade.js +0 -8
  53. package/dist/native/darwin-arm64/manifest.json +3 -3
  54. package/dist/native/darwin-arm64/process-guardian.node +0 -0
  55. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  56. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  57. package/dist/native/darwin-x64/manifest.json +3 -3
  58. package/dist/native/darwin-x64/process-guardian.node +0 -0
  59. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  60. package/dist/native/darwin-x64/terminal-host.node +0 -0
  61. package/dist/native/linux-x64/manifest.json +3 -3
  62. package/dist/native/linux-x64/process-guardian.node +0 -0
  63. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  64. package/dist/native/linux-x64/terminal-host.node +0 -0
  65. package/dist/native/win32-x64/manifest.json +3 -3
  66. package/dist/native/win32-x64/process-guardian.node +0 -0
  67. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  68. package/dist/native/win32-x64/terminal-host.node +0 -0
  69. package/docs/architecture/project-structure.md +4 -0
  70. package/docs/launch-recovery.md +23 -0
  71. package/docs/worktree-link-recovery.md +37 -0
  72. package/package.json +6 -3
@@ -5,7 +5,7 @@ import { platform } from "node:os";
5
5
  import { dirname } from "node:path";
6
6
  import { assertLaunchInstanceOutcome, assertNativeProcessIdentity, assertProcessContainmentIdentity, } from "../lifecycle/index.js";
7
7
  import { encodeFrame, isCommandMessage, isControlHello, LineFrameDecoder, localControlHello, MAX_CONTROL_FRAME_BYTES, negotiateControlFeatures } from "../protocol/index.js";
8
- import { processIsAlive } from "../release/index.js";
8
+ import { listRecordedEndpoints, readEndpointMetadata, supervisorProcessState, currentSupervisorStartIdentity, withEndpointLock, processIsAlive } from "../release/index.js";
9
9
  import { ControlStore } from "../storage/index.js";
10
10
  import { resolveProductPaths } from "./paths.js";
11
11
  import { PRODUCT_IDENTITY, PRODUCT_TEXT } from "../../product-identity.js";
@@ -43,7 +43,7 @@ export async function commitEndpointMetadata(path, source, operations = {}) {
43
43
  export class SupervisorServer {
44
44
  id = randomUUID();
45
45
  bootNonce;
46
- pidStartIdentity = `${process.pid}:${Math.floor(Date.now() - process.uptime() * 1_000)}`;
46
+ pidStartIdentity = currentSupervisorStartIdentity();
47
47
  startedAt = new Date().toISOString();
48
48
  paths;
49
49
  #server = null;
@@ -84,7 +84,7 @@ export class SupervisorServer {
84
84
  this.supersededPollMs = supersededPollMs;
85
85
  this.bootNonce = bootNonce;
86
86
  this.paths = paths;
87
- for (const instance of store.loadActiveLaunchInstances())
87
+ for (const instance of store.loadActiveLaunchInstances(bootNonce))
88
88
  this.#instances.set(instance.id, instance);
89
89
  }
90
90
  snapshot() {
@@ -98,41 +98,60 @@ export class SupervisorServer {
98
98
  };
99
99
  }
100
100
  async listen() {
101
- if (this.#server)
102
- throw new Error("supervisor is already listening");
103
- await mkdir(this.paths.runtimeDir, { recursive: true, mode: 0o700 });
104
- await mkdir(this.paths.endpointsDir, { recursive: true, mode: 0o700 });
105
- if (platform() !== "win32") {
106
- if (this.paths.endpointDirectory)
107
- await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
108
- const existing = await lstat(this.paths.endpoint).catch(error => {
109
- if (error.code === "ENOENT")
110
- return null;
111
- throw error;
112
- });
113
- if (existing) {
114
- if (await endpointIsLive(this.paths.endpoint)) {
115
- throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
101
+ await withEndpointLock(this.paths.endpointMetadataPath, async () => {
102
+ if (this.#server)
103
+ throw new Error("supervisor is already listening");
104
+ await mkdir(this.paths.runtimeDir, { recursive: true, mode: 0o700 });
105
+ await mkdir(this.paths.endpointsDir, { recursive: true, mode: 0o700 });
106
+ const recorded = await readEndpointMetadata(this.paths.endpointMetadataPath);
107
+ if (recorded && await supervisorProcessState(recorded) !== "gone") {
108
+ throw Object.assign(new Error("another supervisor still owns this endpoint"), { code: "EADDRINUSE" });
109
+ }
110
+ if (platform() !== "win32") {
111
+ if (this.paths.endpointDirectory)
112
+ await ensureManagedEndpointDirectory(dirname(this.paths.endpoint));
113
+ const existing = await lstat(this.paths.endpoint).catch(error => {
114
+ if (error.code === "ENOENT")
115
+ return null;
116
+ throw error;
117
+ });
118
+ if (existing) {
119
+ if (await endpointIsLive(this.paths.endpoint)) {
120
+ throw Object.assign(new Error(PRODUCT_TEXT.diagnostic(`supervisor already owns ${this.paths.endpoint}`)), { code: "EADDRINUSE" });
121
+ }
122
+ await rm(this.paths.endpoint, { force: true });
116
123
  }
117
- await rm(this.paths.endpoint, { force: true });
118
124
  }
119
- }
120
- const server = createServer(socket => this.#attach(socket));
121
- this.#server = server;
122
- await new Promise((resolve, reject) => {
123
- server.once("error", reject);
124
- server.listen(this.paths.endpoint, () => {
125
- this.#ownsEndpoint = true;
126
- server.off("error", reject);
127
- resolve();
125
+ const server = createServer(socket => this.#attach(socket));
126
+ this.#server = server;
127
+ await new Promise((resolve, reject) => {
128
+ server.once("error", reject);
129
+ server.listen(this.paths.endpoint, () => {
130
+ this.#ownsEndpoint = true;
131
+ server.off("error", reject);
132
+ resolve();
133
+ });
134
+ });
135
+ // Concurrency: only the bound winner may reconcile shared control data. Other cohort boots
136
+ // remain protected even when temporarily unresponsive; their instances never become ours.
137
+ await withEndpointLock(this.paths.databasePath, async () => {
138
+ // Concurrency: all cohorts sharing SQLite serialize boot reconciliation plus publication, so a new
139
+ // cohort cannot insert instances between another cohort's inventory and stale-row update.
140
+ const protectedBoots = [];
141
+ for (const other of await listRecordedEndpoints(this.paths)) {
142
+ if (other.paths.endpointMetadataPath !== this.paths.endpointMetadataPath
143
+ && await supervisorProcessState(other.metadata) !== "gone")
144
+ protectedBoots.push(other.metadata.bootNonce);
145
+ }
146
+ this.store.reconcilePriorBootLaunchInstances(this.bootNonce, new Date().toISOString(), protectedBoots);
147
+ await this.#writeEndpointMetadata();
128
148
  });
149
+ if (this.readActiveReleaseId) {
150
+ const poll = setInterval(() => void this.#refreshCohortRole(), this.supersededPollMs);
151
+ poll.unref();
152
+ this.#supersededPoll = poll;
153
+ }
129
154
  });
130
- await this.#writeEndpointMetadata();
131
- if (this.readActiveReleaseId) {
132
- const poll = setInterval(() => void this.#refreshCohortRole(), this.supersededPollMs);
133
- poll.unref();
134
- this.#supersededPoll = poll;
135
- }
136
155
  }
137
156
  // Invariant: superseding a cohort never interrupts its held instances; it retires only when empty.
138
157
  async #refreshCohortRole() {
@@ -167,27 +186,29 @@ export class SupervisorServer {
167
186
  this.#supersededPoll = null;
168
187
  for (const client of this.#clients)
169
188
  client.destroy();
170
- try {
171
- await this.#metadataWrites.catch(() => undefined);
172
- if (this.#ownsEndpoint) {
173
- // Concurrency: remove only our publication, before releasing the listening endpoint.
174
- // A contender that failed to bind must never erase the successful owner's discovery file.
175
- const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
176
- if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
177
- await rm(this.paths.endpointMetadataPath, { force: true });
189
+ await withEndpointLock(this.paths.endpointMetadataPath, async () => {
190
+ try {
191
+ await this.#metadataWrites.catch(() => undefined);
192
+ if (this.#ownsEndpoint) {
193
+ // Concurrency: remove only our publication, before releasing the listening endpoint.
194
+ // A contender that failed to bind must never erase the successful owner's discovery file.
195
+ const metadata = await readFile(this.paths.endpointMetadataPath, "utf8").then(source => JSON.parse(source)).catch(() => null);
196
+ if (metadata?.supervisorId === this.id && metadata.bootNonce === this.bootNonce) {
197
+ await rm(this.paths.endpointMetadataPath, { force: true });
198
+ }
199
+ // Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
200
+ // let a replacement bind before the old server finishes releasing that path.
178
201
  }
179
- // Platform: Node unlinks its Unix socket when the server closes; unlinking it early would
180
- // let a replacement bind before the old server finishes releasing that path.
181
202
  }
182
- }
183
- finally {
184
- // Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
185
- if (this.#server)
186
- await new Promise(resolve => this.#server?.close(() => resolve()));
187
- this.#server = null;
188
- this.#ownsEndpoint = false;
189
- this.store.close();
190
- }
203
+ finally {
204
+ // Platform: a metadata sharing error must not leave a bound but undiscoverable listener.
205
+ if (this.#server)
206
+ await new Promise(resolve => this.#server?.close(() => resolve()));
207
+ this.#server = null;
208
+ this.#ownsEndpoint = false;
209
+ this.store.close();
210
+ }
211
+ });
191
212
  }
192
213
  async closeForReleaseReplacement(stopAgents) {
193
214
  await this.close(stopAgents);
@@ -518,6 +539,7 @@ export class SupervisorServer {
518
539
  supervisorId: this.id,
519
540
  pid: process.pid,
520
541
  pidStartIdentity: this.pidStartIdentity,
542
+ nativeStartIdentity: this.pidStartIdentity,
521
543
  bootNonce: this.bootNonce,
522
544
  endpoint: this.paths.endpoint,
523
545
  databasePath: this.paths.databasePath,
@@ -7,7 +7,7 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
7
7
  import { StatusIndicator, WorkingStatusIndicator } from "./upstream/components/status-indicator.js";
8
8
  import { protectPiPromptChipPresentation } from "./prompt-chip-presentation.js";
9
9
  import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
10
- import { createTuiFacade, ensureTheme, renderPiShellBlockedUpdateStatus, renderPiShellInstalledUpdateStatus, piShellVisibleWidth, } from "./shell-shared-facade.js";
10
+ import { createTuiFacade, ensureTheme, renderPiShellInstalledUpdateStatus, piShellVisibleWidth, } from "./shell-shared-facade.js";
11
11
  export function createPiShellHeader(options = {}) {
12
12
  ensureTheme();
13
13
  let expanded = options.expanded ?? false;
@@ -233,9 +233,7 @@ function appendInstalledUpdateStatus(rows, width, view, progressPresentation) {
233
233
  return rows;
234
234
  const status = view.diagnostics.some(diagnostic => diagnostic.code === "release-update-installed")
235
235
  ? renderPiShellInstalledUpdateStatus(width)
236
- : view.diagnostics.some(diagnostic => diagnostic.code === "release-update-blocked")
237
- ? renderPiShellBlockedUpdateStatus(width)
238
- : "";
236
+ : "";
239
237
  if (status.length === 0)
240
238
  return rows;
241
239
  const statusWidth = piShellVisibleWidth(status);
@@ -9,11 +9,8 @@ import { KeybindingsManager } from "./upstream/adjacent/core/keybindings.js";
9
9
  export declare const piShellVisibleWidth: typeof visibleWidth;
10
10
  export declare const piShellTruncateToWidth: (text: string, width: number) => string;
11
11
  export declare const INSTALLED_UPDATE_STATUS_TEXT = "Update installed, restart to update";
12
- export declare const BLOCKED_UPDATE_STATUS_TEXT = "Update prepared; activation blocked";
13
12
  /** Shared green restart notice for the pinned status and bare-A1 prompt overlay. */
14
13
  export declare function renderPiShellInstalledUpdateStatus(width: number): string;
15
- /** Sanitized warning for a certified candidate that cannot yet preserve resident continuity. */
16
- export declare function renderPiShellBlockedUpdateStatus(width: number): string;
17
14
  /** A clickable URL in pinned Pi's grammar: an OSC 8 link where the terminal supports one, the plain URL otherwise. */
18
15
  export declare const piShellHyperlink: (url: string, text?: string) => string;
19
16
  export interface PiShellComponentPort extends PresentationComponentPort {
@@ -5,7 +5,6 @@ import { ensurePiTheme, piTheme, } from "./theme.js";
5
5
  export const piShellVisibleWidth = visibleWidth;
6
6
  export const piShellTruncateToWidth = (text, width) => truncateToWidth(text, width, "");
7
7
  export const INSTALLED_UPDATE_STATUS_TEXT = "Update installed, restart to update";
8
- export const BLOCKED_UPDATE_STATUS_TEXT = "Update prepared; activation blocked";
9
8
  /** Shared green restart notice for the pinned status and bare-A1 prompt overlay. */
10
9
  export function renderPiShellInstalledUpdateStatus(width) {
11
10
  if (width <= 0)
@@ -13,13 +12,6 @@ export function renderPiShellInstalledUpdateStatus(width) {
13
12
  ensurePiTheme();
14
13
  return piShellTruncateToWidth(piTheme().fg("success", INSTALLED_UPDATE_STATUS_TEXT), width);
15
14
  }
16
- /** Sanitized warning for a certified candidate that cannot yet preserve resident continuity. */
17
- export function renderPiShellBlockedUpdateStatus(width) {
18
- if (width <= 0)
19
- return "";
20
- ensurePiTheme();
21
- return piShellTruncateToWidth(piTheme().fg("warning", BLOCKED_UPDATE_STATUS_TEXT), width);
22
- }
23
15
  /** A clickable URL in pinned Pi's grammar: an OSC 8 link where the terminal supports one, the plain URL otherwise. */
24
16
  export const piShellHyperlink = (url, text = url) => hyperlink(url, text);
25
17
  // Provenance: pinned from packages/coding-agent/src/core/slash-commands.ts at 914cf14.
@@ -7,11 +7,11 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "arm64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-05T06:57:12.752Z",
10
+ "builtAt": "2026-11-06T08:23:55.616Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "a87eaaffcd986b624c8b044b756446cc1bdaf4334a1775b97820542fe34f9fb7",
14
- "size": 456080
13
+ "sha256": "9ab579426034c752ef8ed53a789566f5b7cfe102149d30c28f4bdb60e73107c5",
14
+ "size": 456272
15
15
  },
16
16
  "provenance": {
17
17
  "language": "Rust",
@@ -9,11 +9,11 @@
9
9
  "architecture": "arm64",
10
10
  "target": "aarch64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-05T07:01:22.443Z",
12
+ "builtAt": "2026-11-06T08:27:51.656Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "084c00e2cf786f3a8c92deb5f3c7939b87f8f09e481559001acc96dc38c87349",
16
- "size": 6625792
15
+ "sha256": "b2548f95b9660b11b6d28fb4b35f254cde9940d9d19233517bb70507ffcf76ab",
16
+ "size": 6626528
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,11 +7,11 @@
7
7
  "platform": "darwin",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-05T06:57:19.328Z",
10
+ "builtAt": "2026-11-06T08:24:02.357Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "783a65f0de287a1c02cd5429dbe97bc8633f4cc8a7b92e3e961180b9bdcff814",
14
- "size": 444248
13
+ "sha256": "65f3a469a6faa9b578bc095e7559012775ee85e5b93cbc9451c62604f769146e",
14
+ "size": 452648
15
15
  },
16
16
  "provenance": {
17
17
  "language": "Rust",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-apple-darwin",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-05T07:04:03.142Z",
12
+ "builtAt": "2026-11-06T08:31:58.733Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "34a1dc77e44453dd8c60d170577615e228bc091c70c1bd86401be2bc849b105f",
16
- "size": 7213944
15
+ "sha256": "cd1103472889d53e2e039cf7673b2fe6e9e0937234591b1202234d0c1c357d2a",
16
+ "size": 7226904
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,11 +7,11 @@
7
7
  "platform": "linux",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-05T06:57:04.789Z",
10
+ "builtAt": "2026-11-06T08:23:51.242Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "118e5954e3864672bd8482ab7edf419cb27dcdf2533720316979b7f5cc5a53a5",
14
- "size": 491520
13
+ "sha256": "64232d4c827bb7f69c14612986f9b6780a0593d8842cd039a411b6bfe29cd5da",
14
+ "size": 500264
15
15
  },
16
16
  "provenance": {
17
17
  "language": "Rust",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-unknown-linux-gnu",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-05T07:00:29.693Z",
12
+ "builtAt": "2026-11-06T08:26:29.024Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "272d4279ceda5baabd5edac6f71b8ede554c0420f5fc7f205c4b4a3072fc4851",
16
- "size": 7997704
15
+ "sha256": "f5abfe9464780c9acdf85dfbf8a0f194bf0355188ab23feaa09cb7df84363088",
16
+ "size": 8010960
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -7,11 +7,11 @@
7
7
  "platform": "win32",
8
8
  "architecture": "x64",
9
9
  "capability": "supported",
10
- "builtAt": "2026-11-05T06:57:43.401Z",
10
+ "builtAt": "2026-11-06T08:24:41.351Z",
11
11
  "artifact": {
12
12
  "filename": "process-guardian.node",
13
- "sha256": "2414e9a2269c6cb9f0ce275f0ea7b8147b5078be54c4bbc5c53dadb41ddad9f4",
14
- "size": 402432
13
+ "sha256": "dab6b6aed5ab24435d7814fc5099f26707486ee187551ff1dfd939679682f32b",
14
+ "size": 414208
15
15
  },
16
16
  "provenance": {
17
17
  "language": "Rust",
@@ -9,11 +9,11 @@
9
9
  "architecture": "x64",
10
10
  "target": "x86_64-pc-windows-msvc",
11
11
  "capability": "supported",
12
- "builtAt": "2026-11-05T06:59:52.560Z",
12
+ "builtAt": "2026-11-06T08:29:00.280Z",
13
13
  "artifact": {
14
14
  "filename": "terminal-host.node",
15
- "sha256": "919ab8121819cd7bc886d64cb8e4f7a422c7263e2aba0afa51319baef9a05adc",
16
- "size": 7115264
15
+ "sha256": "00dc90f70d76ff36418a301d36dd406d143a6bf1da6b56e24ae5341b4e0c7d8e",
16
+ "size": 7131136
17
17
  },
18
18
  "entry": {
19
19
  "filename": "dist/foundation/resident-tabs/host-entry.js",
@@ -47,6 +47,8 @@ Three layer boundaries hold regardless of the DAG: `src/contracts/*` import noth
47
47
 
48
48
  `src/cli` contains command policy but delegates runtime work. `src/composition` is the concrete dependency-injection boundary: it may know both neutral contracts and Pi implementations, while product features receive vendor-neutral ports. Foundation modules never import product features. Pi package knowledge remains inside the Pi adapter owners.
49
49
 
50
+ Release-owned endpoint observation and recovery coordinate with supervision-owned publication and storage-owned boot reconciliation through the process guardian's OS-released gates; see [startup recovery](../launch-recovery.md) for the identity, concurrency, and diagnostic boundaries.
51
+
50
52
  Shared code moves to foundation only when it is product-agnostic, has a precise contract, and has more than one real consumer. Do not create `core`, `common`, `utils`, or `misc` dumping grounds.
51
53
 
52
54
  ## Feature ownership
@@ -113,6 +115,8 @@ Passing an absolute `path` to `EnterWorktree` is reserved for a worktree created
113
115
 
114
116
  Inventory is read-only and advisory because another runtime may win a later race. A successful link response confirming the exact canonical worktree is required before planning, implementation, test, or delivery-documentation edits. If inventory or linking fails, reports a live/unverifiable owner, loses a race, or confirms another path, stop task edits and report the blocker; use a separate fresh worktree rather than overriding, unlinking, or recovering that owner. A session resuming an existing delivery or switching streams atomically claims and links the exact worktree before editing it.
115
117
 
118
+ Mutation-gate corruption and transient startup-registration failures have a separate [worktree-link recovery procedure](../worktree-link-recovery.md). Automatic recovery never overrides a live or unverifiable editing claim; ambiguous legacy gates remain protected.
119
+
116
120
  The link acquires a cooperative live-session editing claim and switches bare A1's footer repository context and pull-request discovery. It does not change process or tool cwd, create a Git worktree lock, or register, release, or authorize cleanup. Clean unlink/disposal releases live editing authority; a crashed owner becomes reusable only after exact process death is verified. Repository commands therefore keep an explicit worktree path. The primary worktree stays on `develop` for integration and must not be used for task edits. Do not edit, adopt, move, or remove another session's worktree.
117
121
 
118
122
  An agent that directly chooses a transient file path for a PR/comment body, command payload, captured output, temporary patch/diff, or ad hoc log must place it beneath the linked worktree's exact `.artifacts/` root, never the primary checkout, another worktree, OS temp, home/desktop, or a sibling path. Such scratch remains ignored, unstaged, uncommitted, disposable, non-authoritative, and secret-free. This repository path rule does not relocate storage internally selected by tools, product runtime code, or hermetic tests.
@@ -0,0 +1,23 @@
1
+ # Startup recovery after process termination
2
+
3
+ A1 reconciles recoverable runtime ownership automatically when the user starts it again after a process crash or forced termination. Users should not need to find PIDs, delete session or control databases, restart a supervisor, or reboot to clear a stale record.
4
+
5
+ ## Recovery boundary
6
+
7
+ Supervisor metadata carries an additive native process-creation token. Endpoint handshakes still use the existing supervisor, boot, release, and opaque start-identity fields for compatibility; new supervisors use their native token in that start field as well. A failed probe is retried; native process inspection distinguishes a terminated process object or reused PID from the recorded owner. Older records remain readable: an absent process is reclaimable, while a live legacy PID is not treated as dead merely because its timestamp format differs. Windows and macOS native creation times strictly later than a legacy publication, with a one-second margin, can disprove that recorded ownership. Tick-only Linux legacy evidence remains conservative until absence or a matching handshake establishes the outcome.
8
+
9
+ Endpoint retirement requires the same publication and a fresh dead-owner observation under a native file-descriptor gate. The gate is never deleted; the OS releases its lock on exit, including forced exit. Supervisor binding, Unix stale-socket removal, metadata publication, and closure use that gate. Losing startups cannot delete the winner's endpoint. A separate shared-database gate serializes boot reconciliation and publication across cohorts. Only the bound winner reconciles old rows, and recorded surviving cohort boots remain protected.
10
+
11
+ Successful recovery is silent and continues the original profile, launch mode, and session selection. Saved sessions are not deleted or rewritten by endpoint recovery. Resident holders and their session-writer locks retain their independent ownership; restarting a supervisor is not permission to stop a resident tab or create a second session writer.
12
+
13
+ ## Exceptional failures
14
+
15
+ An uninspectable or genuinely unresponsive live owner is preserved after bounded retries. Empty saved instance lists are not authority to terminate it. The command returns a short recovery message instead of an uncaught stack or the old internal uncertain-live-ownership paragraph. Missing native recovery support or inaccessible runtime storage remains an installation or permission failure, not proof that an owner is dead.
16
+
17
+ A recovery attempt may replace one bounded `<endpoint-metadata>.recovery.json` summary containing only a reason, attempt count, and timestamp. It is diagnostic only, contains no session data or credentials, and never grants cleanup authority. The `.authority` gate files are also not user cleanup targets.
18
+
19
+ This restores launch availability, not killed process memory. Killing every Node process also kills the terminal-restoration watcher; no alternative executable is started to evade that limitation. Real OS permission failures, damaged installations, and persistently live unresponsive services cannot be converted into safe success by ignoring ownership.
20
+
21
+ ## Regression boundaries
22
+
23
+ The focused fixtures use isolated runtime/data roots and recorded fixture process identities. They cover supervisor, guardian, root-runtime, recovery-gate, PID-reuse, concurrent-startup, and Windows all-recorded-Node loss, plus native resident server/holder/tab loss in the resident lane. They never terminate processes by image name. Another managed runtime, a separate cohort, saved session bytes, and an unrelated sentinel process establish the preservation boundaries.
@@ -0,0 +1,37 @@
1
+ # Worktree-link recovery
2
+
3
+ A1 coordinates worktree-claim mutations separately from the editing claims themselves. Recovering a mutation gate never grants permission to take another live agent's worktree or to delete worktree contents.
4
+
5
+ ## Automatic recovery
6
+
7
+ Updated A1 holds an OS lock on the persistent `session-worktree-claims/mutation.authority` file. The OS releases it when the process exits, including a crash. `mutation.lock` is now a persistent directory that blocks older file-lock writers; its `owner.json` is only diagnostic metadata.
8
+
9
+ The next mutation safely recovers abandoned owner/pending metadata after taking native authority. Empty, truncated, NUL-filled, or otherwise malformed metadata does not prevent this recovery. One bounded `recovery.json` records the recovery reason/time, not the original bytes or session contents. A live native holder remains protected. Acquisition waits at most two seconds by default and honors cancellation.
10
+
11
+ If startup registration fails temporarily, the running session retries on its 30-second refresh interval. Slow attempts are coalesced rather than queued indefinitely. Once registration succeeds, retry the explicit worktree link in the same session. Switching an agent's session replaces its publication identity, and disposal drains publication before releasing claims.
12
+
13
+ Inventory remains read-only. It can conservatively show unverifiable status until a mutation has recovered abandoned state; inventory does not perform repair.
14
+
15
+ ## Older lock files and mixed versions
16
+
17
+ An existing regular-file `mutation.lock` belongs to the old protocol. Updated A1 cannot safely prove that no old writer/reclaimer is using it, even if a PID in the file is dead. It therefore retains the file and reports `worktree-legacy-recovery-required` rather than guessing or endlessly reporting generic busy.
18
+
19
+ For this one-time legacy case, stop all older A1 processes normally, move only that exact regular lock file to a backup while they are stopped, then start updated A1. Keep sessions, runtime records, associations, editing claims, Git locks, and worktrees. Reinstalling/downgrading alone does not clear persisted legacy state. Never move the new directory or delete `mutation.authority` while A1 is running.
20
+
21
+ When the old gate is absent, updated A1 initializes its compatibility barrier automatically. Older versions cannot mutate worktree claims after this point: all participating sessions must use the updated version. This intentionally prevents two incompatible mutation protocols from running concurrently. The installed native addon must support the new gate; there is no unsafe PID-file fallback if the addon is missing or outdated.
22
+
23
+ ## Diagnostics
24
+
25
+ | Code | Meaning / next step |
26
+ | --- | --- |
27
+ | `worktree-claim-busy` | A mutation still holds native authority. Retry the link shortly; runtime registration retries automatically. |
28
+ | `worktree-legacy-recovery-required` | Retained old-format gate. Use the stopped-process legacy procedure above. |
29
+ | `worktree-coordination-unavailable` | Missing or outdated native support. Repair/update the A1 installation. |
30
+ | `worktree-coordination-io` | Gate/metadata filesystem access failed. Check permissions and storage availability. |
31
+ | `worktree-coordination-boundary` | Unexpected file type, link, or entry. Retain it for inspection; do not clear the store. |
32
+ | `session-runtime-unavailable` | Runtime registration has not succeeded. Retry after the next refresh and address any specific coordination failure. |
33
+ | `process-inspection-unavailable` / `session-runtime-unverifiable` | Exact process identity cannot be checked. Repair inspection support; no ownership takeover is authorized. |
34
+ | `worktree-active-in-another-session` | Another live agent owns the target. Use a different worktree or have that owner release it. |
35
+ | `worktree-owner-unverifiable` | The editing claim itself is uncertain. Gate recovery cannot make that claim available. |
36
+
37
+ Diagnostics do not expose another session's process identity, session file, transcript, or credentials. The mechanism does not repair arbitrary damaged claim records, change tool cwd, or grant Git/cleanup authority.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.7-dev.1241",
3
+ "version": "0.2.7-dev.1245",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",
@@ -11,6 +11,9 @@
11
11
  "resident-control-v1",
12
12
  "resident-workspace-v1"
13
13
  ],
14
+ "updateCommandContracts": [
15
+ "update-release-v1"
16
+ ],
14
17
  "packageManager": "npm@11.13.0",
15
18
  "bin": {
16
19
  "a1": "bin/cli.js"
@@ -123,7 +126,7 @@
123
126
  "@mariozechner/clipboard": "^0.3.9"
124
127
  },
125
128
  "publication": {
126
- "source": "c3b7a3fd5d44c998b87847f0f79422bdbb459355",
127
- "pullRequest": 865
129
+ "source": "fc4a2588e22b6d90034465d80d7923c8fede91d8",
130
+ "pullRequest": 870
128
131
  }
129
132
  }