agl 21.0.2 → 22.0.1

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 (73) hide show
  1. package/changelog.md +19 -0
  2. package/dist_serve/bundle.js +520 -509
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/classes.aglhome.d.ts +25 -0
  5. package/dist_ts/classes.aglhome.js +65 -0
  6. package/dist_ts/classes.authmodels.js +7 -12
  7. package/dist_ts/classes.authstore.d.ts +1 -2
  8. package/dist_ts/classes.authstore.js +2 -34
  9. package/dist_ts/classes.cli.js +93 -35
  10. package/dist_ts/classes.config.d.ts +2 -11
  11. package/dist_ts/classes.config.js +14 -80
  12. package/dist_ts/classes.controller.d.ts +4 -1
  13. package/dist_ts/classes.controller.js +109 -46
  14. package/dist_ts/classes.embeddeddb.js +6 -5
  15. package/dist_ts/classes.gitreversion.js +3 -2
  16. package/dist_ts/classes.upgradecoordinator.d.ts +6 -1
  17. package/dist_ts/classes.upgradecoordinator.js +588 -177
  18. package/dist_ts/classes.upgradetransaction.js +46 -12
  19. package/dist_ts/classes.uploadmanager.d.ts +12 -0
  20. package/dist_ts/classes.uploadmanager.js +204 -2
  21. package/dist_ts/constants.upgradeenvironment.d.ts +2 -0
  22. package/dist_ts/constants.upgradeenvironment.js +3 -0
  23. package/dist_ts/functions.controllerdataroot.d.ts +4 -5
  24. package/dist_ts/functions.controllerdataroot.js +4 -29
  25. package/dist_ts/functions.embeddeddb.d.ts +1 -1
  26. package/dist_ts/functions.embeddeddb.js +8 -3
  27. package/dist_ts/functions.runtimeenvironment.js +2 -1
  28. package/dist_ts/index.d.ts +1 -0
  29. package/dist_ts/index.js +2 -1
  30. package/dist_ts/interfaces.config.d.ts +5 -7
  31. package/dist_ts_migration/classes.documentmigrationrunner.js +5 -1
  32. package/dist_ts_migration/index.d.ts +4 -0
  33. package/dist_ts_migration/index.js +5 -1
  34. package/dist_ts_migration/v23_aglhome.d.ts +91 -0
  35. package/dist_ts_migration/v23_aglhome.js +1775 -0
  36. package/dist_ts_migration/v23_runtimeconfig.d.ts +11 -0
  37. package/dist_ts_migration/v23_runtimeconfig.js +87 -0
  38. package/dist_ts_migration/v24_legacyterminallayout.d.ts +18 -0
  39. package/dist_ts_migration/v24_legacyterminallayout.js +79 -0
  40. package/dist_ts_migration/v24_legacyterminallayoutmigration.d.ts +6 -0
  41. package/dist_ts_migration/v24_legacyterminallayoutmigration.js +67 -0
  42. package/dist_ts_migration/v2_controllerdataroot.d.ts +5 -0
  43. package/dist_ts_migration/v2_controllerdataroot.js +176 -15
  44. package/package.json +1 -1
  45. package/readme.md +116 -35
  46. package/readme.plan.md +24 -9
  47. package/ts/00_commitinfo_data.ts +1 -1
  48. package/ts/classes.aglhome.ts +116 -0
  49. package/ts/classes.authmodels.ts +5 -13
  50. package/ts/classes.authstore.ts +1 -54
  51. package/ts/classes.cli.ts +92 -34
  52. package/ts/classes.config.ts +20 -117
  53. package/ts/classes.controller.ts +146 -52
  54. package/ts/classes.embeddeddb.ts +5 -4
  55. package/ts/classes.gitreversion.ts +3 -2
  56. package/ts/classes.upgradecoordinator.ts +668 -191
  57. package/ts/classes.upgradetransaction.ts +46 -11
  58. package/ts/classes.uploadmanager.ts +231 -1
  59. package/ts/constants.upgradeenvironment.ts +2 -0
  60. package/ts/functions.controllerdataroot.ts +11 -47
  61. package/ts/functions.embeddeddb.ts +13 -2
  62. package/ts/functions.runtimeenvironment.ts +1 -0
  63. package/ts/index.ts +1 -0
  64. package/ts/interfaces.config.ts +5 -7
  65. package/ts_migration/classes.documentmigrationrunner.ts +4 -0
  66. package/ts_migration/index.ts +4 -0
  67. package/ts_migration/v23_aglhome.ts +2101 -0
  68. package/ts_migration/v23_runtimeconfig.ts +112 -0
  69. package/ts_migration/v24_legacyterminallayout.ts +131 -0
  70. package/ts_migration/v24_legacyterminallayoutmigration.ts +88 -0
  71. package/ts_migration/v2_controllerdataroot.ts +174 -16
  72. package/ts_web/00_commitinfo_data.ts +1 -1
  73. package/ts_web/elements.harnesscontrollerapp.ts +32 -2
@@ -129,6 +129,7 @@ import {
129
129
  resolveInitialRuntimeConfig,
130
130
  resolveStoredRuntimeConfig,
131
131
  } from './classes.config.js';
132
+ import { resolveAGLHomePaths } from './classes.aglhome.js';
132
133
  import { ensureControllerDataRootForStartup } from './functions.controllerdataroot.js';
133
134
  import { SmartDataAuthStore } from './classes.authstore.js';
134
135
  import { PasskeyManager } from './classes.passkeymanager.js';
@@ -301,6 +302,10 @@ import {
301
302
  resolveCurrentPnpmGlobalInstallation,
302
303
  resolveInheritedUpgradeTargetInstallation,
303
304
  } from './classes.upgradetransaction.js';
305
+ import {
306
+ upgradeCoordinationRootEnvironmentVariable,
307
+ upgradeTokenEnvironmentVariable,
308
+ } from './constants.upgradeenvironment.js';
304
309
 
305
310
  const currentCliName = String(controllerPackageName) === upgradePackageTransitionSource.packageName
306
311
  ? upgradePackageTransitionSource.cliName
@@ -1373,6 +1378,13 @@ interface IControllerHarnessSessionEnumeration {
1373
1378
  flex: PromiseSettledResult<IControllerSession[]>;
1374
1379
  }
1375
1380
 
1381
+ interface IControllerHarnessSessionEnumerationTask {
1382
+ task: Promise<IControllerHarnessSessionEnumeration>;
1383
+ invalidated: boolean;
1384
+ settled: boolean;
1385
+ waiterCount: number;
1386
+ }
1387
+
1376
1388
  export class OpenCodeController {
1377
1389
  public readonly options: IControllerStartOptions;
1378
1390
 
@@ -1443,6 +1455,7 @@ export class OpenCodeController {
1443
1455
  private flexAutoAcceptDrainTask?: Promise<void>;
1444
1456
  private runtimeConfig?: Awaited<ReturnType<typeof resolveInitialRuntimeConfig>>;
1445
1457
  private controllerId?: string;
1458
+ private openCodeRuntimeDirectory?: string;
1446
1459
  private authStore?: SmartDataAuthStore;
1447
1460
  private passkeyManager?: PasskeyManager;
1448
1461
  private openCodeSupervisor?: OpenCodeSupervisor;
@@ -1503,7 +1516,7 @@ export class OpenCodeController {
1503
1516
  private readonly activeOperations = new Set<Promise<unknown>>();
1504
1517
  private readonly harnessSessionEnumerationTasks = new Map<
1505
1518
  string,
1506
- Promise<IControllerHarnessSessionEnumeration>
1519
+ IControllerHarnessSessionEnumerationTask
1507
1520
  >();
1508
1521
  private readonly sessionLayoutOperationTails = new Map<string, Promise<void>>();
1509
1522
  private readonly sessionLayoutOperationDepths = new Map<string, number>();
@@ -1594,6 +1607,23 @@ export class OpenCodeController {
1594
1607
  });
1595
1608
  }
1596
1609
 
1610
+ private captureInheritedUpgradeCoordination(): void {
1611
+ try {
1612
+ if (Boolean(this.options.upgradeToken) !== Boolean(this.options.upgradeGlobalRoot)) {
1613
+ throw new Error('Inherited upgrade coordination requires both its token and global root.');
1614
+ }
1615
+ if (!this.options.upgradeToken || !this.options.upgradeGlobalRoot) return;
1616
+ if (process.env[upgradeTokenEnvironmentVariable] !== this.options.upgradeToken) {
1617
+ throw new Error('The inherited upgrade coordination token changed before controller startup.');
1618
+ }
1619
+ this.upgradeToken = this.options.upgradeToken;
1620
+ this.upgradeCoordinator = new UpgradeCoordinator(this.options.upgradeGlobalRoot);
1621
+ } finally {
1622
+ delete process.env[upgradeTokenEnvironmentVariable];
1623
+ delete process.env[upgradeCoordinationRootEnvironmentVariable];
1624
+ }
1625
+ }
1626
+
1597
1627
  public async start(): Promise<IControllerStartResult> {
1598
1628
  if (this.lifecycleState === 'ready') {
1599
1629
  return { status: this.getStatus(), publicUrl: this.requireRuntimeConfig().publicOrigin };
@@ -1705,7 +1735,9 @@ export class OpenCodeController {
1705
1735
  }
1706
1736
 
1707
1737
  private async performStart(): Promise<IControllerStartResult> {
1738
+ process.env.AGL_HOME = resolveAGLHomePaths().root;
1708
1739
  this.lifecycleState = 'starting';
1740
+ this.captureInheritedUpgradeCoordination();
1709
1741
  const identity = await readControllerProcessIdentity(process.pid);
1710
1742
  this.assertStartActive();
1711
1743
  if (identity) {
@@ -1714,9 +1746,11 @@ export class OpenCodeController {
1714
1746
  }
1715
1747
  let upgradeCliPath: string | undefined;
1716
1748
  if (this.options.upgradeToken && this.options.upgradeGlobalRoot) {
1717
- this.upgradeToken = this.options.upgradeToken;
1718
- this.upgradeCoordinator = new UpgradeCoordinator(this.options.upgradeGlobalRoot);
1719
- const transaction = await this.upgradeCoordinator.readTransaction(this.options.upgradeToken);
1749
+ const inheritedCoordinator = this.upgradeCoordinator;
1750
+ if (!inheritedCoordinator || this.upgradeToken !== this.options.upgradeToken) {
1751
+ throw new Error('The inherited upgrade coordination was not captured before startup.');
1752
+ }
1753
+ const transaction = await inheritedCoordinator.readTransaction(this.options.upgradeToken);
1720
1754
  const installation = await resolveInheritedUpgradeTargetInstallation(transaction);
1721
1755
  const cliPath = await plugins.fs.promises.realpath(
1722
1756
  plugins.url.fileURLToPath(new URL('../cli.js', import.meta.url)),
@@ -1732,11 +1766,11 @@ export class OpenCodeController {
1732
1766
  cliName: currentCliName,
1733
1767
  cliRelativePath: './cli.js',
1734
1768
  });
1735
- this.upgradeStatus = await this.upgradeCoordinator.readSanitizedUpgradeStatus(
1769
+ this.upgradeStatus = await inheritedCoordinator.readSanitizedUpgradeStatus(
1736
1770
  this.options.upgradeToken,
1737
1771
  );
1738
1772
  upgradeCliPath = cliPath;
1739
- await this.upgradeCoordinator.recordController(this.options.upgradeToken, {
1773
+ await inheritedCoordinator.recordController(this.options.upgradeToken, {
1740
1774
  pid: process.pid,
1741
1775
  processGroupId: this.processGroupId,
1742
1776
  fingerprint: this.processFingerprint,
@@ -1872,6 +1906,17 @@ export class OpenCodeController {
1872
1906
  this.assertStartActive();
1873
1907
  this.runtimeConfig = runtimeConfig;
1874
1908
  this.controllerId = authDocument.id;
1909
+ this.openCodeRuntimeDirectory = plugins.path.join(
1910
+ dataDirectory,
1911
+ 'runtime',
1912
+ 'opencode',
1913
+ plugins.crypto.createHash('sha256').update(authDocument.id, 'utf8').digest('hex'),
1914
+ );
1915
+ await plugins.fs.promises.mkdir(this.openCodeRuntimeDirectory, {
1916
+ recursive: true,
1917
+ mode: 0o700,
1918
+ });
1919
+ await plugins.fs.promises.chmod(this.openCodeRuntimeDirectory, 0o700);
1875
1920
 
1876
1921
  const passkeyManager = new PasskeyManager({ store: authStore });
1877
1922
  this.passkeyManager = passkeyManager;
@@ -1972,16 +2017,11 @@ export class OpenCodeController {
1972
2017
  this.setupCodeExpiresAt = setupAuthority?.expiresAt.getTime();
1973
2018
  this.assertStartActive();
1974
2019
 
1975
- // v2 migration: the fixed workspace becomes the first registered project.
1976
- const pendingProjectRemovals = await this.ensureInitialProject(runtimeConfig.workspaceDirectory);
2020
+ const pendingProjectRemovals = await this.registerExplicitInitialProject(
2021
+ this.options.runtimeOverrides.initialProjectDirectory,
2022
+ );
1977
2023
  const startupProjectState = await this.reconcileProjectState(false);
1978
2024
  for (const project of pendingProjectRemovals) this.retiringProjectIds.add(project.id);
1979
- for (const project of startupProjectState.projects) {
1980
- await this.runSessionLayoutOperation(
1981
- project.id,
1982
- () => authStore.pruneTerminalLayoutEntries(project.id, []),
1983
- );
1984
- }
1985
2025
  const startupSettings = await authStore.getSettings();
1986
2026
  this.defaultModels = startupSettings.defaultModels;
1987
2027
  this.autoAcceptPermissions = startupSettings.autoAcceptPermissions === true;
@@ -2107,6 +2147,7 @@ export class OpenCodeController {
2107
2147
  database: flexDatabase,
2108
2148
  flexCredentialDirectory: plugins.path.join(
2109
2149
  dataDirectory,
2150
+ 'credentials',
2110
2151
  'flex-provider-credentials',
2111
2152
  plugins.crypto.createHash('sha256').update(controllerId, 'utf8').digest('hex'),
2112
2153
  ),
@@ -2212,7 +2253,8 @@ export class OpenCodeController {
2212
2253
  browserResourceHost = new ControllerBrowserResourceHost({
2213
2254
  runtimeDirectory: plugins.path.join(
2214
2255
  dataDirectory,
2215
- 'browser-runtime',
2256
+ 'runtime',
2257
+ 'browser',
2216
2258
  plugins.crypto.createHash('sha256').update(controllerId).digest('hex'),
2217
2259
  ),
2218
2260
  authorizeCapability: (requestArg) => (
@@ -2338,6 +2380,19 @@ export class OpenCodeController {
2338
2380
  };
2339
2381
  }
2340
2382
 
2383
+ private coordinatorForUpgradeToken(
2384
+ tokenArg: string,
2385
+ globalRootArg: string,
2386
+ ): UpgradeCoordinator {
2387
+ if (this.upgradeToken === tokenArg && this.upgradeCoordinator) {
2388
+ if (this.upgradeCoordinator.canonicalGlobalRoot !== globalRootArg) {
2389
+ throw new Error('The inherited upgrade coordination global root changed unexpectedly.');
2390
+ }
2391
+ return this.upgradeCoordinator;
2392
+ }
2393
+ return new UpgradeCoordinator(globalRootArg);
2394
+ }
2395
+
2341
2396
  private startOpenCodeEventStream(
2342
2397
  openCodeClientArg: OpenCodeClientAdapter,
2343
2398
  skipInitialStreamAdvanceArg = false,
@@ -2476,9 +2531,12 @@ export class OpenCodeController {
2476
2531
 
2477
2532
  private createOpenCodeSupervisor(): OpenCodeSupervisor {
2478
2533
  const runtimeConfig = this.requireRuntimeConfig();
2534
+ if (!this.openCodeRuntimeDirectory) {
2535
+ throw new Error('The OpenCode runtime directory is unavailable.');
2536
+ }
2479
2537
  let supervisor!: OpenCodeSupervisor;
2480
2538
  supervisor = new OpenCodeSupervisor({
2481
- directory: runtimeConfig.workspaceDirectory,
2539
+ directory: this.openCodeRuntimeDirectory,
2482
2540
  port: runtimeConfig.opencodePort,
2483
2541
  onChildExit: () => {
2484
2542
  this.openCodeSupervisorExitSignals.get(supervisor)?.resolve();
@@ -3192,6 +3250,7 @@ export class OpenCodeController {
3192
3250
  this.authStore = undefined;
3193
3251
  this.passkeyManager = undefined;
3194
3252
  this.controllerId = undefined;
3253
+ this.openCodeRuntimeDirectory = undefined;
3195
3254
  }
3196
3255
  });
3197
3256
  }
@@ -3312,7 +3371,10 @@ export class OpenCodeController {
3312
3371
  try {
3313
3372
  const installation = await resolveCurrentPnpmGlobalInstallation();
3314
3373
  const status = this.getStatus();
3315
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3374
+ const coordinator = this.coordinatorForUpgradeToken(
3375
+ requestArg.token,
3376
+ installation.globalRoot,
3377
+ );
3316
3378
  await coordinator.consumeLaunchGrant({
3317
3379
  token: requestArg.token,
3318
3380
  port: this.requireRuntimeConfig().controllerPort,
@@ -3372,7 +3434,10 @@ export class OpenCodeController {
3372
3434
  return this.runOperation(peer, async (signalArg) => (
3373
3435
  this.runUpgradePreparationAdmission(async () => {
3374
3436
  const installation = await resolveCurrentPnpmGlobalInstallation();
3375
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3437
+ const coordinator = this.coordinatorForUpgradeToken(
3438
+ requestArg.token,
3439
+ installation.globalRoot,
3440
+ );
3376
3441
  const status = this.getStatus();
3377
3442
  await coordinator.consumeControllerActionGrant({
3378
3443
  token: requestArg.token,
@@ -3406,7 +3471,10 @@ export class OpenCodeController {
3406
3471
  return this.runOperation(peer, async () => (
3407
3472
  this.runUpgradePreparationAdmission(async () => {
3408
3473
  const installation = await resolveCurrentPnpmGlobalInstallation();
3409
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3474
+ const coordinator = this.coordinatorForUpgradeToken(
3475
+ requestArg.token,
3476
+ installation.globalRoot,
3477
+ );
3410
3478
  if (await this.upgradePreparationReplayIsAccepted(
3411
3479
  coordinator,
3412
3480
  requestArg.token,
@@ -3454,7 +3522,10 @@ export class OpenCodeController {
3454
3522
  );
3455
3523
  return this.runOperation(peer, async () => {
3456
3524
  const installation = await resolveCurrentPnpmGlobalInstallation();
3457
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3525
+ const coordinator = this.coordinatorForUpgradeToken(
3526
+ requestArg.token,
3527
+ installation.globalRoot,
3528
+ );
3458
3529
  const status = this.getStatus();
3459
3530
  await coordinator.consumeControllerActionGrant({
3460
3531
  token: requestArg.token,
@@ -6187,20 +6258,17 @@ export class OpenCodeController {
6187
6258
  return projectsRoot;
6188
6259
  }
6189
6260
 
6190
- private async ensureInitialProject(
6191
- workspaceDirectoryArg: string,
6261
+ private async registerExplicitInitialProject(
6262
+ initialProjectDirectoryArg: string | undefined,
6192
6263
  ): Promise<IControllerProjectDocument[]> {
6193
6264
  const authStore = this.requireAuthStore();
6194
- const [activeProjects, pendingRemovals] = await Promise.all([
6195
- authStore.listProjects(),
6196
- authStore.listPendingProjectRemovals(),
6197
- ]);
6198
- if (activeProjects.length === 0 && pendingRemovals.length === 0) {
6199
- const workspaceDirectory = await resolveCanonicalDirectory(workspaceDirectoryArg);
6265
+ const pendingRemovals = await authStore.listPendingProjectRemovals();
6266
+ if (initialProjectDirectoryArg !== undefined) {
6267
+ const initialProjectDirectory = await resolveCanonicalDirectory(initialProjectDirectoryArg);
6200
6268
  await authStore.createProject(
6201
- plugins.path.basename(workspaceDirectory.directory) || 'workspace',
6202
- workspaceDirectory.directory,
6203
- workspaceDirectory,
6269
+ this.deriveProjectName(initialProjectDirectory.directory),
6270
+ initialProjectDirectory.directory,
6271
+ initialProjectDirectory,
6204
6272
  );
6205
6273
  }
6206
6274
  return pendingRemovals;
@@ -7293,31 +7361,55 @@ export class OpenCodeController {
7293
7361
  }
7294
7362
  }
7295
7363
 
7296
- private enumerateHarnessSessions(
7364
+ private async enumerateHarnessSessions(
7297
7365
  projectIdArg: string,
7298
7366
  directoryArg: string,
7299
7367
  signalArg: AbortSignal,
7300
7368
  ): Promise<IControllerHarnessSessionEnumeration> {
7301
- signalArg.throwIfAborted();
7302
- let sharedTask = this.harnessSessionEnumerationTasks.get(projectIdArg);
7303
- if (!sharedTask) {
7304
- const lifecycleSignal = this.operationAbortController.signal;
7305
- const operation = Promise.allSettled([
7306
- Promise.resolve().then(() => (
7307
- this.requireOpenCodeClient().listSessions(directoryArg, lifecycleSignal)
7308
- )),
7309
- Promise.resolve().then(() => this.listFlexSessions(projectIdArg, lifecycleSignal)),
7310
- ]).then(([openCode, flex]) => ({ openCode, flex }));
7311
- let trackedTask!: Promise<IControllerHarnessSessionEnumeration>;
7312
- trackedTask = this.trackOperationPromise(operation, () => {
7313
- if (this.harnessSessionEnumerationTasks.get(projectIdArg) === trackedTask) {
7369
+ while (true) {
7370
+ signalArg.throwIfAborted();
7371
+ let shared = this.harnessSessionEnumerationTasks.get(projectIdArg);
7372
+ if (!shared || shared.invalidated) {
7373
+ const lifecycleSignal = this.operationAbortController.signal;
7374
+ const operation = Promise.allSettled([
7375
+ Promise.resolve().then(() => (
7376
+ this.requireOpenCodeClient().listSessions(directoryArg, lifecycleSignal)
7377
+ )),
7378
+ Promise.resolve().then(() => this.listFlexSessions(projectIdArg, lifecycleSignal)),
7379
+ ]).then(([openCode, flex]) => ({ openCode, flex }));
7380
+ let taskRecord!: IControllerHarnessSessionEnumerationTask;
7381
+ let trackedTask!: Promise<IControllerHarnessSessionEnumeration>;
7382
+ trackedTask = this.trackOperationPromise(operation, () => {
7383
+ taskRecord.settled = true;
7384
+ const current = this.harnessSessionEnumerationTasks.get(projectIdArg);
7385
+ if (current === taskRecord && taskRecord.waiterCount === 0) {
7386
+ this.harnessSessionEnumerationTasks.delete(projectIdArg);
7387
+ }
7388
+ });
7389
+ taskRecord = {
7390
+ task: trackedTask,
7391
+ invalidated: false,
7392
+ settled: false,
7393
+ waiterCount: 0,
7394
+ };
7395
+ shared = taskRecord;
7396
+ this.harnessSessionEnumerationTasks.set(projectIdArg, shared);
7397
+ }
7398
+ shared.waiterCount += 1;
7399
+ try {
7400
+ const result = await this.waitForPromiseWithSignal(shared.task, signalArg);
7401
+ if (!shared.invalidated) return result;
7402
+ } finally {
7403
+ shared.waiterCount -= 1;
7404
+ if (
7405
+ shared.settled
7406
+ && shared.waiterCount === 0
7407
+ && this.harnessSessionEnumerationTasks.get(projectIdArg) === shared
7408
+ ) {
7314
7409
  this.harnessSessionEnumerationTasks.delete(projectIdArg);
7315
7410
  }
7316
- });
7317
- this.harnessSessionEnumerationTasks.set(projectIdArg, trackedTask);
7318
- sharedTask = trackedTask;
7411
+ }
7319
7412
  }
7320
- return this.waitForPromiseWithSignal(sharedTask, signalArg);
7321
7413
  }
7322
7414
 
7323
7415
  private toFlexSession(
@@ -11653,7 +11745,7 @@ export class OpenCodeController {
11653
11745
  signalArg?: AbortSignal,
11654
11746
  ): Promise<IControllerSession> {
11655
11747
  if (sessionIdArg.harnessId === 'opencode') {
11656
- return this.requireOpenCodeClient().getSession(
11748
+ return this.requireOpenCodeClient().getSessionSnapshot(
11657
11749
  directoryArg,
11658
11750
  sessionIdArg.nativeId,
11659
11751
  signalArg,
@@ -11910,6 +12002,9 @@ export class OpenCodeController {
11910
12002
  projectIdArg: string,
11911
12003
  sessionIdArg: IControllerRuntimeId,
11912
12004
  ): void {
12005
+ const enumeration = this.harnessSessionEnumerationTasks.get(projectIdArg);
12006
+ if (enumeration) enumeration.invalidated = true;
12007
+ this.harnessSessionEnumerationTasks.delete(projectIdArg);
11913
12008
  if (sessionIdArg.harnessId === 'opencode') {
11914
12009
  this.purgeOpenCodeStreamStateForSession(projectIdArg, sessionIdArg);
11915
12010
  } else if (sessionIdArg.harnessId === 'flex') {
@@ -14170,13 +14265,12 @@ export class OpenCodeController {
14170
14265
  if (!client) return;
14171
14266
  const deadline = AbortSignal.timeout(abortConversationsOnStopTimeoutMs);
14172
14267
  const directories = new Set<string>();
14173
- if (this.runtimeConfig) directories.add(this.runtimeConfig.workspaceDirectory);
14174
14268
  try {
14175
14269
  for (const project of (await this.authStore?.listProjects()) ?? []) {
14176
14270
  directories.add(project.directory);
14177
14271
  }
14178
14272
  } catch {
14179
- // Project registry unavailable — workspace sessions still get aborted.
14273
+ // Project registry unavailable; shutdown continues with owned resources.
14180
14274
  }
14181
14275
  for (const directory of directories) {
14182
14276
  try {
@@ -46,10 +46,8 @@ export class EmbeddedControllerDatabase {
46
46
  constructor(private readonly options: IEmbeddedControllerDatabaseOptions) {}
47
47
 
48
48
  public get socketPath(): string {
49
- // Unix socket paths are capped near 108 bytes (SUN_LEN), so the socket
50
- // lives in tmpdir under a short name derived from the data directory —
51
- // deterministic, so a second controller process attaches to the same
52
- // engine, and independent of how deep the data directory is nested.
49
+ // The hashed name is deterministic so another AGL process can attach to
50
+ // the same engine without owning its storage files.
53
51
  return embeddedDatabaseSocketPath(this.options.dataDirectory);
54
52
  }
55
53
 
@@ -74,6 +72,9 @@ export class EmbeddedControllerDatabase {
74
72
  if (this.localDb || this.migrationRunner?.hasOwnedResources) {
75
73
  throw new Error('Embedded database startup cleanup must complete before retrying.');
76
74
  }
75
+ const socketDirectory = plugins.path.dirname(this.socketPath);
76
+ await plugins.fs.promises.mkdir(socketDirectory, { recursive: true, mode: 0o700 });
77
+ await plugins.fs.promises.chmod(socketDirectory, 0o700);
77
78
  if (this.options.legacyDataDirectory) {
78
79
  const migrationRunner = new EmbeddedDatabaseMigrationRunner({
79
80
  sourceDirectory: this.options.legacyDataDirectory,
@@ -1,4 +1,5 @@
1
1
  import * as plugins from './plugins.js';
2
+ import { resolveAGLHomePaths } from './classes.aglhome.js';
2
3
  import {
3
4
  assertCanonicalDirectory,
4
5
  assertCanonicalFilesystemObject,
@@ -746,8 +747,8 @@ export class ControllerGitReversion {
746
747
  this.validateLimits();
747
748
  this.maximumPrivateTreeEntries = privateTreeTraversalLimit(this.limits);
748
749
  this.rootDirectory = optionsArg.temporaryRoot ?? plugins.path.join(
749
- plugins.os.tmpdir(),
750
- `harness-controller-reversion-${this.ownerKey}`,
750
+ resolveAGLHomePaths().gitReversion,
751
+ sha256(optionsArg.ownerId),
751
752
  );
752
753
  if (!plugins.path.isAbsolute(this.rootDirectory)) {
753
754
  throw new Error('The Git reversion temporary root must be absolute.');