agl 21.0.2 → 22.0.0

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 (65) hide show
  1. package/changelog.md +9 -0
  2. package/dist_serve/bundle.js +1 -1
  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.js +2 -29
  8. package/dist_ts/classes.cli.js +93 -35
  9. package/dist_ts/classes.config.d.ts +2 -11
  10. package/dist_ts/classes.config.js +14 -80
  11. package/dist_ts/classes.controller.d.ts +4 -1
  12. package/dist_ts/classes.controller.js +64 -26
  13. package/dist_ts/classes.embeddeddb.js +6 -5
  14. package/dist_ts/classes.gitreversion.js +3 -2
  15. package/dist_ts/classes.upgradecoordinator.d.ts +6 -1
  16. package/dist_ts/classes.upgradecoordinator.js +588 -177
  17. package/dist_ts/classes.upgradetransaction.js +46 -12
  18. package/dist_ts/classes.uploadmanager.d.ts +12 -0
  19. package/dist_ts/classes.uploadmanager.js +204 -2
  20. package/dist_ts/constants.upgradeenvironment.d.ts +2 -0
  21. package/dist_ts/constants.upgradeenvironment.js +3 -0
  22. package/dist_ts/functions.controllerdataroot.d.ts +4 -5
  23. package/dist_ts/functions.controllerdataroot.js +4 -29
  24. package/dist_ts/functions.embeddeddb.d.ts +1 -1
  25. package/dist_ts/functions.embeddeddb.js +8 -3
  26. package/dist_ts/functions.runtimeenvironment.js +2 -1
  27. package/dist_ts/index.d.ts +1 -0
  28. package/dist_ts/index.js +2 -1
  29. package/dist_ts/interfaces.config.d.ts +5 -7
  30. package/dist_ts_migration/classes.documentmigrationrunner.js +3 -1
  31. package/dist_ts_migration/index.d.ts +2 -0
  32. package/dist_ts_migration/index.js +3 -1
  33. package/dist_ts_migration/v23_aglhome.d.ts +91 -0
  34. package/dist_ts_migration/v23_aglhome.js +1775 -0
  35. package/dist_ts_migration/v23_runtimeconfig.d.ts +11 -0
  36. package/dist_ts_migration/v23_runtimeconfig.js +87 -0
  37. package/dist_ts_migration/v2_controllerdataroot.d.ts +5 -0
  38. package/dist_ts_migration/v2_controllerdataroot.js +176 -15
  39. package/package.json +1 -1
  40. package/readme.md +116 -35
  41. package/readme.plan.md +24 -9
  42. package/ts/00_commitinfo_data.ts +1 -1
  43. package/ts/classes.aglhome.ts +116 -0
  44. package/ts/classes.authmodels.ts +5 -13
  45. package/ts/classes.authstore.ts +1 -39
  46. package/ts/classes.cli.ts +92 -34
  47. package/ts/classes.config.ts +20 -117
  48. package/ts/classes.controller.ts +92 -26
  49. package/ts/classes.embeddeddb.ts +5 -4
  50. package/ts/classes.gitreversion.ts +3 -2
  51. package/ts/classes.upgradecoordinator.ts +668 -191
  52. package/ts/classes.upgradetransaction.ts +46 -11
  53. package/ts/classes.uploadmanager.ts +231 -1
  54. package/ts/constants.upgradeenvironment.ts +2 -0
  55. package/ts/functions.controllerdataroot.ts +11 -47
  56. package/ts/functions.embeddeddb.ts +13 -2
  57. package/ts/functions.runtimeenvironment.ts +1 -0
  58. package/ts/index.ts +1 -0
  59. package/ts/interfaces.config.ts +5 -7
  60. package/ts_migration/classes.documentmigrationrunner.ts +2 -0
  61. package/ts_migration/index.ts +2 -0
  62. package/ts_migration/v23_aglhome.ts +2101 -0
  63. package/ts_migration/v23_runtimeconfig.ts +112 -0
  64. package/ts_migration/v2_controllerdataroot.ts +174 -16
  65. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -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
@@ -1443,6 +1448,7 @@ export class OpenCodeController {
1443
1448
  private flexAutoAcceptDrainTask?: Promise<void>;
1444
1449
  private runtimeConfig?: Awaited<ReturnType<typeof resolveInitialRuntimeConfig>>;
1445
1450
  private controllerId?: string;
1451
+ private openCodeRuntimeDirectory?: string;
1446
1452
  private authStore?: SmartDataAuthStore;
1447
1453
  private passkeyManager?: PasskeyManager;
1448
1454
  private openCodeSupervisor?: OpenCodeSupervisor;
@@ -1594,6 +1600,23 @@ export class OpenCodeController {
1594
1600
  });
1595
1601
  }
1596
1602
 
1603
+ private captureInheritedUpgradeCoordination(): void {
1604
+ try {
1605
+ if (Boolean(this.options.upgradeToken) !== Boolean(this.options.upgradeGlobalRoot)) {
1606
+ throw new Error('Inherited upgrade coordination requires both its token and global root.');
1607
+ }
1608
+ if (!this.options.upgradeToken || !this.options.upgradeGlobalRoot) return;
1609
+ if (process.env[upgradeTokenEnvironmentVariable] !== this.options.upgradeToken) {
1610
+ throw new Error('The inherited upgrade coordination token changed before controller startup.');
1611
+ }
1612
+ this.upgradeToken = this.options.upgradeToken;
1613
+ this.upgradeCoordinator = new UpgradeCoordinator(this.options.upgradeGlobalRoot);
1614
+ } finally {
1615
+ delete process.env[upgradeTokenEnvironmentVariable];
1616
+ delete process.env[upgradeCoordinationRootEnvironmentVariable];
1617
+ }
1618
+ }
1619
+
1597
1620
  public async start(): Promise<IControllerStartResult> {
1598
1621
  if (this.lifecycleState === 'ready') {
1599
1622
  return { status: this.getStatus(), publicUrl: this.requireRuntimeConfig().publicOrigin };
@@ -1705,7 +1728,9 @@ export class OpenCodeController {
1705
1728
  }
1706
1729
 
1707
1730
  private async performStart(): Promise<IControllerStartResult> {
1731
+ process.env.AGL_HOME = resolveAGLHomePaths().root;
1708
1732
  this.lifecycleState = 'starting';
1733
+ this.captureInheritedUpgradeCoordination();
1709
1734
  const identity = await readControllerProcessIdentity(process.pid);
1710
1735
  this.assertStartActive();
1711
1736
  if (identity) {
@@ -1714,9 +1739,11 @@ export class OpenCodeController {
1714
1739
  }
1715
1740
  let upgradeCliPath: string | undefined;
1716
1741
  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);
1742
+ const inheritedCoordinator = this.upgradeCoordinator;
1743
+ if (!inheritedCoordinator || this.upgradeToken !== this.options.upgradeToken) {
1744
+ throw new Error('The inherited upgrade coordination was not captured before startup.');
1745
+ }
1746
+ const transaction = await inheritedCoordinator.readTransaction(this.options.upgradeToken);
1720
1747
  const installation = await resolveInheritedUpgradeTargetInstallation(transaction);
1721
1748
  const cliPath = await plugins.fs.promises.realpath(
1722
1749
  plugins.url.fileURLToPath(new URL('../cli.js', import.meta.url)),
@@ -1732,11 +1759,11 @@ export class OpenCodeController {
1732
1759
  cliName: currentCliName,
1733
1760
  cliRelativePath: './cli.js',
1734
1761
  });
1735
- this.upgradeStatus = await this.upgradeCoordinator.readSanitizedUpgradeStatus(
1762
+ this.upgradeStatus = await inheritedCoordinator.readSanitizedUpgradeStatus(
1736
1763
  this.options.upgradeToken,
1737
1764
  );
1738
1765
  upgradeCliPath = cliPath;
1739
- await this.upgradeCoordinator.recordController(this.options.upgradeToken, {
1766
+ await inheritedCoordinator.recordController(this.options.upgradeToken, {
1740
1767
  pid: process.pid,
1741
1768
  processGroupId: this.processGroupId,
1742
1769
  fingerprint: this.processFingerprint,
@@ -1872,6 +1899,17 @@ export class OpenCodeController {
1872
1899
  this.assertStartActive();
1873
1900
  this.runtimeConfig = runtimeConfig;
1874
1901
  this.controllerId = authDocument.id;
1902
+ this.openCodeRuntimeDirectory = plugins.path.join(
1903
+ dataDirectory,
1904
+ 'runtime',
1905
+ 'opencode',
1906
+ plugins.crypto.createHash('sha256').update(authDocument.id, 'utf8').digest('hex'),
1907
+ );
1908
+ await plugins.fs.promises.mkdir(this.openCodeRuntimeDirectory, {
1909
+ recursive: true,
1910
+ mode: 0o700,
1911
+ });
1912
+ await plugins.fs.promises.chmod(this.openCodeRuntimeDirectory, 0o700);
1875
1913
 
1876
1914
  const passkeyManager = new PasskeyManager({ store: authStore });
1877
1915
  this.passkeyManager = passkeyManager;
@@ -1972,8 +2010,9 @@ export class OpenCodeController {
1972
2010
  this.setupCodeExpiresAt = setupAuthority?.expiresAt.getTime();
1973
2011
  this.assertStartActive();
1974
2012
 
1975
- // v2 migration: the fixed workspace becomes the first registered project.
1976
- const pendingProjectRemovals = await this.ensureInitialProject(runtimeConfig.workspaceDirectory);
2013
+ const pendingProjectRemovals = await this.registerExplicitInitialProject(
2014
+ this.options.runtimeOverrides.initialProjectDirectory,
2015
+ );
1977
2016
  const startupProjectState = await this.reconcileProjectState(false);
1978
2017
  for (const project of pendingProjectRemovals) this.retiringProjectIds.add(project.id);
1979
2018
  for (const project of startupProjectState.projects) {
@@ -2107,6 +2146,7 @@ export class OpenCodeController {
2107
2146
  database: flexDatabase,
2108
2147
  flexCredentialDirectory: plugins.path.join(
2109
2148
  dataDirectory,
2149
+ 'credentials',
2110
2150
  'flex-provider-credentials',
2111
2151
  plugins.crypto.createHash('sha256').update(controllerId, 'utf8').digest('hex'),
2112
2152
  ),
@@ -2212,7 +2252,8 @@ export class OpenCodeController {
2212
2252
  browserResourceHost = new ControllerBrowserResourceHost({
2213
2253
  runtimeDirectory: plugins.path.join(
2214
2254
  dataDirectory,
2215
- 'browser-runtime',
2255
+ 'runtime',
2256
+ 'browser',
2216
2257
  plugins.crypto.createHash('sha256').update(controllerId).digest('hex'),
2217
2258
  ),
2218
2259
  authorizeCapability: (requestArg) => (
@@ -2338,6 +2379,19 @@ export class OpenCodeController {
2338
2379
  };
2339
2380
  }
2340
2381
 
2382
+ private coordinatorForUpgradeToken(
2383
+ tokenArg: string,
2384
+ globalRootArg: string,
2385
+ ): UpgradeCoordinator {
2386
+ if (this.upgradeToken === tokenArg && this.upgradeCoordinator) {
2387
+ if (this.upgradeCoordinator.canonicalGlobalRoot !== globalRootArg) {
2388
+ throw new Error('The inherited upgrade coordination global root changed unexpectedly.');
2389
+ }
2390
+ return this.upgradeCoordinator;
2391
+ }
2392
+ return new UpgradeCoordinator(globalRootArg);
2393
+ }
2394
+
2341
2395
  private startOpenCodeEventStream(
2342
2396
  openCodeClientArg: OpenCodeClientAdapter,
2343
2397
  skipInitialStreamAdvanceArg = false,
@@ -2476,9 +2530,12 @@ export class OpenCodeController {
2476
2530
 
2477
2531
  private createOpenCodeSupervisor(): OpenCodeSupervisor {
2478
2532
  const runtimeConfig = this.requireRuntimeConfig();
2533
+ if (!this.openCodeRuntimeDirectory) {
2534
+ throw new Error('The OpenCode runtime directory is unavailable.');
2535
+ }
2479
2536
  let supervisor!: OpenCodeSupervisor;
2480
2537
  supervisor = new OpenCodeSupervisor({
2481
- directory: runtimeConfig.workspaceDirectory,
2538
+ directory: this.openCodeRuntimeDirectory,
2482
2539
  port: runtimeConfig.opencodePort,
2483
2540
  onChildExit: () => {
2484
2541
  this.openCodeSupervisorExitSignals.get(supervisor)?.resolve();
@@ -3192,6 +3249,7 @@ export class OpenCodeController {
3192
3249
  this.authStore = undefined;
3193
3250
  this.passkeyManager = undefined;
3194
3251
  this.controllerId = undefined;
3252
+ this.openCodeRuntimeDirectory = undefined;
3195
3253
  }
3196
3254
  });
3197
3255
  }
@@ -3312,7 +3370,10 @@ export class OpenCodeController {
3312
3370
  try {
3313
3371
  const installation = await resolveCurrentPnpmGlobalInstallation();
3314
3372
  const status = this.getStatus();
3315
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3373
+ const coordinator = this.coordinatorForUpgradeToken(
3374
+ requestArg.token,
3375
+ installation.globalRoot,
3376
+ );
3316
3377
  await coordinator.consumeLaunchGrant({
3317
3378
  token: requestArg.token,
3318
3379
  port: this.requireRuntimeConfig().controllerPort,
@@ -3372,7 +3433,10 @@ export class OpenCodeController {
3372
3433
  return this.runOperation(peer, async (signalArg) => (
3373
3434
  this.runUpgradePreparationAdmission(async () => {
3374
3435
  const installation = await resolveCurrentPnpmGlobalInstallation();
3375
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3436
+ const coordinator = this.coordinatorForUpgradeToken(
3437
+ requestArg.token,
3438
+ installation.globalRoot,
3439
+ );
3376
3440
  const status = this.getStatus();
3377
3441
  await coordinator.consumeControllerActionGrant({
3378
3442
  token: requestArg.token,
@@ -3406,7 +3470,10 @@ export class OpenCodeController {
3406
3470
  return this.runOperation(peer, async () => (
3407
3471
  this.runUpgradePreparationAdmission(async () => {
3408
3472
  const installation = await resolveCurrentPnpmGlobalInstallation();
3409
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3473
+ const coordinator = this.coordinatorForUpgradeToken(
3474
+ requestArg.token,
3475
+ installation.globalRoot,
3476
+ );
3410
3477
  if (await this.upgradePreparationReplayIsAccepted(
3411
3478
  coordinator,
3412
3479
  requestArg.token,
@@ -3454,7 +3521,10 @@ export class OpenCodeController {
3454
3521
  );
3455
3522
  return this.runOperation(peer, async () => {
3456
3523
  const installation = await resolveCurrentPnpmGlobalInstallation();
3457
- const coordinator = new UpgradeCoordinator(installation.globalRoot);
3524
+ const coordinator = this.coordinatorForUpgradeToken(
3525
+ requestArg.token,
3526
+ installation.globalRoot,
3527
+ );
3458
3528
  const status = this.getStatus();
3459
3529
  await coordinator.consumeControllerActionGrant({
3460
3530
  token: requestArg.token,
@@ -6187,20 +6257,17 @@ export class OpenCodeController {
6187
6257
  return projectsRoot;
6188
6258
  }
6189
6259
 
6190
- private async ensureInitialProject(
6191
- workspaceDirectoryArg: string,
6260
+ private async registerExplicitInitialProject(
6261
+ initialProjectDirectoryArg: string | undefined,
6192
6262
  ): Promise<IControllerProjectDocument[]> {
6193
6263
  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);
6264
+ const pendingRemovals = await authStore.listPendingProjectRemovals();
6265
+ if (initialProjectDirectoryArg !== undefined) {
6266
+ const initialProjectDirectory = await resolveCanonicalDirectory(initialProjectDirectoryArg);
6200
6267
  await authStore.createProject(
6201
- plugins.path.basename(workspaceDirectory.directory) || 'workspace',
6202
- workspaceDirectory.directory,
6203
- workspaceDirectory,
6268
+ this.deriveProjectName(initialProjectDirectory.directory),
6269
+ initialProjectDirectory.directory,
6270
+ initialProjectDirectory,
6204
6271
  );
6205
6272
  }
6206
6273
  return pendingRemovals;
@@ -14170,13 +14237,12 @@ export class OpenCodeController {
14170
14237
  if (!client) return;
14171
14238
  const deadline = AbortSignal.timeout(abortConversationsOnStopTimeoutMs);
14172
14239
  const directories = new Set<string>();
14173
- if (this.runtimeConfig) directories.add(this.runtimeConfig.workspaceDirectory);
14174
14240
  try {
14175
14241
  for (const project of (await this.authStore?.listProjects()) ?? []) {
14176
14242
  directories.add(project.directory);
14177
14243
  }
14178
14244
  } catch {
14179
- // Project registry unavailable workspace sessions still get aborted.
14245
+ // Project registry unavailable; shutdown continues with owned resources.
14180
14246
  }
14181
14247
  for (const directory of directories) {
14182
14248
  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.');