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
package/ts/classes.cli.ts CHANGED
@@ -7,10 +7,13 @@ import {
7
7
  type IControllerStatus,
8
8
  } from '../ts_interfaces/index.js';
9
9
  import {
10
- controllerDataDirectory,
11
10
  defaultControllerPort,
12
11
  } from './classes.config.js';
13
- import { ensureControllerDataRoot } from './functions.controllerdataroot.js';
12
+ import { resolveAGLHomePaths } from './classes.aglhome.js';
13
+ import {
14
+ ensureControllerDataRoot,
15
+ preflightAGLHomeMigration,
16
+ } from './functions.controllerdataroot.js';
14
17
  import type { IControllerRuntimeOverrides } from './interfaces.config.js';
15
18
  import type {
16
19
  IControllerReadyIpcMessage,
@@ -41,8 +44,8 @@ import {
41
44
  upgradePackageTransitionSource,
42
45
  upgradePackageTransitionTarget,
43
46
  type UpgradeStartLease,
44
- upgradeTokenEnvironmentVariable,
45
47
  } from './classes.upgradecoordinator.js';
48
+ import { upgradeTokenEnvironmentVariable } from './constants.upgradeenvironment.js';
46
49
  import {
47
50
  createUpgradeWorkerPayload,
48
51
  launchDetachedUpgradeWorker,
@@ -82,13 +85,14 @@ Commands:
82
85
  temp-password Mint a temporary browser login password (max 24 hours)
83
86
  help Show this help
84
87
 
85
- First-start options:
88
+ Startup options:
86
89
  --port <port> Controller port (default: 4097)
87
90
  --opencode-port <port> Local-only OpenCode port (default: 4098)
88
- --directory <path> Fixed OpenCode workspace (default: current directory)
91
+ --directory <path> Transiently register this existing directory on this start
92
+ (also valid later; omitted: do not create a project)
89
93
  --projects-root <path> Default base for relative project paths and suggestions
90
- (default: workspace; explicitly typed absolute projects may
91
- be outside it; adopted once, then immutable)
94
+ (default: user home; explicitly typed absolute projects may
95
+ be outside it; immutable after initialization)
92
96
  --public-origin <origin> Browser origin used for WebAuthn
93
97
  --rp-id <hostname> WebAuthn relying-party ID
94
98
  --behind-tls-proxy Trust an explicitly configured TLS reverse proxy
@@ -107,11 +111,10 @@ Upgrade requirements:
107
111
  on Linux and macOS. Normal progress mode prints the detached worker log before
108
112
  stopping anything; --json emits it in the terminal document.
109
113
 
110
- ${currentRuntimeName} stores its data in an embedded database engine by default
111
- (under $XDG_CONFIG_HOME/hcon/smartdb, default
112
- ~/.config/hcon/smartdb; repository checkouts use ./.nogit/smartdb;
113
- override the directory
114
- with HARNESS_CONTROLLER_DB_DIR). Set HARNESS_CONTROLLER_MONGO_URL /
114
+ ${currentRuntimeName} owns one private AGL_HOME (default
115
+ $XDG_CONFIG_HOME/agl, normally ~/.config/agl; repository checkouts use
116
+ ./.nogit/agl). The embedded database lives under $AGL_HOME/database; override
117
+ the database directory with HARNESS_CONTROLLER_DB_DIR. Set HARNESS_CONTROLLER_MONGO_URL /
115
118
  HARNESS_CONTROLLER_MONGO_DB to use an external MongoDB-compatible server
116
119
  instead. temp-password writes to the controller database directly, so run
117
120
  it with the same database environment as the running controller; in
@@ -120,10 +123,10 @@ password authenticates the browser UI without a passkey until it expires.
120
123
 
121
124
  The first successful start prints a one-time setup code. Runtime configuration
122
125
  is immutable after initialization; use the same --port for later management.
123
- Detached starts append ${currentRuntimeName} errors to a log file under
124
- $XDG_CONFIG_HOME/harness-controller (default ~/.config/harness-controller;
125
- repository checkouts use ./.nogit). Upgrade workers write a separate private
126
- log under that retained legacy root and print its path before work begins.
126
+ --directory is transient and may be supplied on any stopped start.
127
+ Detached starts append ${currentRuntimeName} errors to
128
+ $AGL_HOME/logs/controller-<port>.log. Upgrade workers write a separate private
129
+ log in $AGL_HOME/logs and print its path before work begins.
127
130
  In --json mode the path is retained before work begins and emitted at completion.
128
131
  `.trim();
129
132
 
@@ -258,17 +261,23 @@ const readPortOption = (
258
261
 
259
262
  export const parseRuntimeOverrides = (
260
263
  argvArg: TParsedArguments,
261
- ): IControllerRuntimeOverrides => ({
262
- controllerPort: readPortOption(argvArg, 'port', 'port', defaultControllerPort)!,
263
- publicOrigin: readStringOption(argvArg, 'publicOrigin', 'public-origin'),
264
- rpId: readStringOption(argvArg, 'rpId', 'rp-id'),
265
- workspaceDirectory: readStringOption(argvArg, 'directory', 'directory'),
266
- projectsRoot: readStringOption(argvArg, 'projectsRoot', 'projects-root'),
267
- opencodePort: readPortOption(argvArg, 'opencodePort', 'opencode-port'),
268
- behindTlsProxy: readOption(argvArg, 'behindTlsProxy', 'behind-tls-proxy') === undefined
269
- ? undefined
270
- : readBooleanOption(argvArg, 'behindTlsProxy', 'behind-tls-proxy'),
271
- });
264
+ ): IControllerRuntimeOverrides => {
265
+ const initialProjectDirectory = readStringOption(argvArg, 'directory', 'directory');
266
+ const projectsRoot = readStringOption(argvArg, 'projectsRoot', 'projects-root');
267
+ return {
268
+ controllerPort: readPortOption(argvArg, 'port', 'port', defaultControllerPort)!,
269
+ publicOrigin: readStringOption(argvArg, 'publicOrigin', 'public-origin'),
270
+ rpId: readStringOption(argvArg, 'rpId', 'rp-id'),
271
+ initialProjectDirectory: initialProjectDirectory === undefined
272
+ ? undefined
273
+ : plugins.path.resolve(initialProjectDirectory),
274
+ projectsRoot: projectsRoot === undefined ? undefined : plugins.path.resolve(projectsRoot),
275
+ opencodePort: readPortOption(argvArg, 'opencodePort', 'opencode-port'),
276
+ behindTlsProxy: readOption(argvArg, 'behindTlsProxy', 'behind-tls-proxy') === undefined
277
+ ? undefined
278
+ : readBooleanOption(argvArg, 'behindTlsProxy', 'behind-tls-proxy'),
279
+ };
280
+ };
272
281
 
273
282
  const currentStatusExpectation = {
274
283
  packageName: controllerPackageName,
@@ -341,7 +350,7 @@ const detachedLogRotateBytes = 1024 * 1024;
341
350
  class RetainedControllerStartupError extends Error {}
342
351
 
343
352
  const resolveDetachedLogFilePath = (portArg: number): string => {
344
- return plugins.path.join(controllerDataDirectory(), `controller-${portArg}.log`);
353
+ return plugins.path.join(resolveAGLHomePaths().logs, `controller-${portArg}.log`);
345
354
  };
346
355
 
347
356
  const openDetachedLogFile = async (
@@ -397,12 +406,13 @@ const acquireStartAdmission = async (
397
406
  if (process.platform === 'win32') return undefined;
398
407
  const installation = await tryResolveCurrentPnpmGlobalInstallation();
399
408
  if (!installation) return undefined;
409
+ const upgradeToken = process.env[upgradeTokenEnvironmentVariable];
400
410
  const coordinator = new UpgradeCoordinator(installation.globalRoot);
401
411
  return await coordinator.acquireStartLease({
402
412
  token: createUpgradeToken(),
403
413
  cliPath: installation.cliPath,
404
414
  command: commandArg,
405
- upgradeToken: process.env[upgradeTokenEnvironmentVariable],
415
+ upgradeToken,
406
416
  });
407
417
  };
408
418
 
@@ -410,7 +420,9 @@ const serializeRuntimeOptions = (overridesArg: IControllerRuntimeOverrides): str
410
420
  const args = ['--port', String(overridesArg.controllerPort)];
411
421
  if (overridesArg.publicOrigin !== undefined) args.push('--public-origin', overridesArg.publicOrigin);
412
422
  if (overridesArg.rpId !== undefined) args.push('--rp-id', overridesArg.rpId);
413
- if (overridesArg.workspaceDirectory !== undefined) args.push('--directory', overridesArg.workspaceDirectory);
423
+ if (overridesArg.initialProjectDirectory !== undefined) {
424
+ args.push('--directory', overridesArg.initialProjectDirectory);
425
+ }
414
426
  if (overridesArg.projectsRoot !== undefined) args.push('--projects-root', overridesArg.projectsRoot);
415
427
  if (overridesArg.opencodePort !== undefined) args.push('--opencode-port', String(overridesArg.opencodePort));
416
428
  if (overridesArg.behindTlsProxy === true) args.push('--behind-tls-proxy');
@@ -609,6 +621,7 @@ const startDetachedWithoutAdmission = async (argvArg: TParsedArguments): Promise
609
621
  `An ${currentRuntimeName} process is already starting or running (PID ${existingProcess.identity.pid}).`,
610
622
  );
611
623
  }
624
+ await ensureControllerDataRoot();
612
625
  const upgradeToken = process.env[upgradeTokenEnvironmentVariable];
613
626
  let upgradeGlobalRoot: string | undefined;
614
627
  if (upgradeToken) {
@@ -616,7 +629,8 @@ const startDetachedWithoutAdmission = async (argvArg: TParsedArguments): Promise
616
629
  }
617
630
  const logFilePath = resolveDetachedLogFilePath(overrides.controllerPort);
618
631
  const logFileHandle = await openDetachedLogFile(logFilePath);
619
- let child: plugins.childProcess.ChildProcess;
632
+ let child: plugins.childProcess.ChildProcess | undefined;
633
+ let spawnError: unknown;
620
634
  try {
621
635
  const childEnvironment = { ...process.env };
622
636
  delete childEnvironment[upgradeTokenEnvironmentVariable];
@@ -624,7 +638,7 @@ const startDetachedWithoutAdmission = async (argvArg: TParsedArguments): Promise
624
638
  process.execPath,
625
639
  [cliPath, '__serve', ...serializeRuntimeOptions(overrides)],
626
640
  {
627
- cwd: process.cwd(),
641
+ cwd: plugins.os.homedir(),
628
642
  detached: true,
629
643
  env: childEnvironment,
630
644
  shell: false,
@@ -633,10 +647,45 @@ const startDetachedWithoutAdmission = async (argvArg: TParsedArguments): Promise
633
647
  stdio: ['ignore', 'ignore', logFileHandle.fd, 'ipc'],
634
648
  },
635
649
  );
636
- } finally {
650
+ } catch (errorArg) {
651
+ spawnError = errorArg;
652
+ }
653
+ let logCloseError: unknown;
654
+ try {
637
655
  await logFileHandle.close();
656
+ } catch (errorArg) {
657
+ try {
658
+ await logFileHandle.close();
659
+ } catch (retryErrorArg) {
660
+ logCloseError = new AggregateError(
661
+ [errorArg, retryErrorArg],
662
+ 'The detached controller log handle could not be closed.',
663
+ );
664
+ }
638
665
  }
639
- if (!child.pid) throw new Error('Detached controller did not provide a process ID.');
666
+ if (spawnError !== undefined || logCloseError !== undefined) {
667
+ const errors: unknown[] = [spawnError, logCloseError]
668
+ .filter((errorArg) => errorArg !== undefined);
669
+ if (child) {
670
+ try {
671
+ if (child.exitCode === null && child.signalCode === null) child.kill('SIGKILL');
672
+ if (!await waitForSpawnedChildExit(child, 2_000)) {
673
+ throw new Error('The gated detached controller survived startup-handle cleanup.');
674
+ }
675
+ } catch (errorArg) {
676
+ errors.push(errorArg);
677
+ } finally {
678
+ if (child.connected) child.disconnect();
679
+ child.unref();
680
+ }
681
+ }
682
+ if (errors.length === 1) throw errors[0];
683
+ throw new AggregateError(
684
+ errors,
685
+ 'Detached controller spawn or log-handle cleanup failed.',
686
+ );
687
+ }
688
+ if (!child?.pid) throw new Error('Detached controller did not provide a process ID.');
640
689
  let spawnedIdentity: Awaited<ReturnType<typeof inspectControllerProcess>> = null;
641
690
  try {
642
691
  for (let attempt = 0; attempt < 20; attempt++) {
@@ -718,6 +767,7 @@ const startDetachedWithoutAdmission = async (argvArg: TParsedArguments): Promise
718
767
  };
719
768
 
720
769
  const startDetached = async (argvArg: TParsedArguments): Promise<void> => {
770
+ await preflightAGLHomeMigration();
721
771
  const lease = await acquireStartAdmission('start');
722
772
  try {
723
773
  await startDetachedWithoutAdmission(argvArg);
@@ -727,6 +777,7 @@ const startDetached = async (argvArg: TParsedArguments): Promise<void> => {
727
777
  };
728
778
 
729
779
  const runForegroundWithAdmission = async (argvArg: TParsedArguments): Promise<void> => {
780
+ await preflightAGLHomeMigration();
730
781
  const lease = await acquireStartAdmission('foreground');
731
782
  delete process.env[upgradeTokenEnvironmentVariable];
732
783
  try {
@@ -761,6 +812,9 @@ const runControllerProcess = async (
761
812
  } else {
762
813
  providedSetupCode = readProvidedSetupCode();
763
814
  }
815
+ if (upgradeToken !== undefined) {
816
+ process.env[upgradeTokenEnvironmentVariable] = upgradeToken;
817
+ }
764
818
  const controller = new OpenCodeController({
765
819
  processMode: detachedArg ? 'detached' : 'foreground',
766
820
  runtimeOverrides: parseRuntimeOverrides(argvArg),
@@ -923,6 +977,7 @@ export const createCurrentPackageUpgradeTransaction = async (
923
977
  };
924
978
 
925
979
  const upgradeController = async (argvArg: TParsedArguments): Promise<void> => {
980
+ await preflightAGLHomeMigration();
926
981
  if (process.platform !== 'linux' && process.platform !== 'darwin') {
927
982
  throw new Error(`${currentCliName} upgrade is currently supported on Linux and macOS only.`);
928
983
  }
@@ -936,6 +991,7 @@ const upgradeController = async (argvArg: TParsedArguments): Promise<void> => {
936
991
  `Port ${port} is occupied by an incompatible service or controller version; refusing to upgrade.`,
937
992
  );
938
993
  }
994
+ if (!status) await ensureControllerDataRoot();
939
995
  const payload = createUpgradeWorkerPayload({
940
996
  port,
941
997
  gracePeriodMs,
@@ -1196,6 +1252,7 @@ const mintTempPasswordWithoutAdmission = async (argvArg: TParsedArguments): Prom
1196
1252
  };
1197
1253
 
1198
1254
  const mintTempPassword = async (argvArg: TParsedArguments): Promise<void> => {
1255
+ await preflightAGLHomeMigration();
1199
1256
  const lease = await acquireStartAdmission('temp-password');
1200
1257
  try {
1201
1258
  await mintTempPasswordWithoutAdmission(argvArg);
@@ -1205,6 +1262,7 @@ const mintTempPassword = async (argvArg: TParsedArguments): Promise<void> => {
1205
1262
  };
1206
1263
 
1207
1264
  export const runCli = async (testArgvArg?: string[]): Promise<void> => {
1265
+ process.env.AGL_HOME = resolveAGLHomePaths().root;
1208
1266
  activeRuntimeArguments = (testArgvArg ?? process.argv).slice(2);
1209
1267
  if (
1210
1268
  activeRuntimeArguments.some((argumentArg) => argumentArg === '--help' || argumentArg === '-h')
@@ -1,4 +1,8 @@
1
1
  import * as plugins from './plugins.js';
2
+ import {
3
+ resolveAGLHomePaths,
4
+ type IAGLHomeOptions,
5
+ } from './classes.aglhome.js';
2
6
  import type {
3
7
  IControllerDatabaseConfig,
4
8
  IControllerRuntimeConfig,
@@ -10,22 +14,7 @@ export const defaultControllerPort = 4097;
10
14
  export const defaultOpenCodePort = 4098;
11
15
  export const trustedProxySecretEnvironmentVariable = 'HARNESS_CONTROLLER_TRUSTED_PROXY_SECRET';
12
16
 
13
- export interface IControllerDataDirectoryOptions {
14
- packageRoot?: string;
15
- homeDirectory?: string;
16
- environment?: NodeJS.ProcessEnv;
17
- }
18
-
19
- export interface IControllerDataRootPaths {
20
- legacyRoot: string;
21
- targetRoot: string;
22
- migrationRequired: boolean;
23
- }
24
-
25
- const modulePackageRoot = plugins.path.resolve(
26
- plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url)),
27
- '..',
28
- );
17
+ export interface IControllerDataDirectoryOptions extends IAGLHomeOptions {}
29
18
 
30
19
  const assertPort = (valueArg: number, nameArg: string): number => {
31
20
  if (!Number.isSafeInteger(valueArg) || valueArg < 1 || valueArg > 65535) {
@@ -105,73 +94,18 @@ const resolveDirectory = async (directoryArg: string): Promise<string> => {
105
94
  const resolved = await plugins.fs.promises.realpath(plugins.path.resolve(directoryArg));
106
95
  const stats = await plugins.fs.promises.stat(resolved);
107
96
  if (!stats.isDirectory()) {
108
- throw new Error(`OpenCode workspace is not a directory: ${resolved}`);
97
+ throw new Error(`Configured path is not a directory: ${resolved}`);
109
98
  }
110
99
  return resolved;
111
100
  };
112
101
 
113
- const isDevelopmentCheckout = (packageRootArg: string): boolean => {
114
- try {
115
- const stats = plugins.fs.lstatSync(plugins.path.join(packageRootArg, '.git'));
116
- return stats.isDirectory() || stats.isFile();
117
- } catch {
118
- return false;
119
- }
120
- };
121
-
122
- const xdgRoot = (
123
- environmentValueArg: string | undefined,
124
- fallbackArg: string,
125
- ): string => {
126
- const candidate = environmentValueArg?.trim();
127
- return candidate && plugins.path.isAbsolute(candidate) ? candidate : fallbackArg;
128
- };
129
-
130
- export const controllerDataRootPaths = (
131
- optionsArg: IControllerDataDirectoryOptions = {},
132
- ): IControllerDataRootPaths => {
133
- const packageRoot = optionsArg.packageRoot ?? modulePackageRoot;
134
- if (isDevelopmentCheckout(packageRoot)) {
135
- const checkoutRoot = plugins.path.join(packageRoot, '.nogit');
136
- return {
137
- legacyRoot: checkoutRoot,
138
- targetRoot: checkoutRoot,
139
- migrationRequired: false,
140
- };
141
- }
142
- const environment = optionsArg.environment ?? process.env;
143
- const homeDirectory = optionsArg.homeDirectory ?? plugins.os.homedir();
144
- const configHome = xdgRoot(
145
- environment.XDG_CONFIG_HOME,
146
- plugins.path.join(homeDirectory, '.config'),
147
- );
148
- return {
149
- legacyRoot: plugins.path.join(configHome, 'harness-controller'),
150
- targetRoot: plugins.path.join(configHome, 'hcon'),
151
- migrationRequired: true,
152
- };
153
- };
154
-
155
- /** Retained location for controller and upgrade logs across the A to B transition. */
156
102
  export const controllerDataDirectory = (
157
103
  optionsArg: IControllerDataDirectoryOptions = {},
158
- ): string => controllerDataRootPaths(optionsArg).legacyRoot;
104
+ ): string => resolveAGLHomePaths(optionsArg).root;
159
105
 
160
106
  /** @deprecated Use controllerDataDirectory(). */
161
107
  export const controllerStateDirectory = controllerDataDirectory;
162
108
 
163
- const legacyControllerStateDirectory = (
164
- optionsArg: IControllerDataDirectoryOptions,
165
- ): string => {
166
- const environment = optionsArg.environment ?? process.env;
167
- const homeDirectory = optionsArg.homeDirectory ?? plugins.os.homedir();
168
- const stateHome = xdgRoot(
169
- environment.XDG_STATE_HOME,
170
- plugins.path.join(homeDirectory, '.local', 'state'),
171
- );
172
- return plugins.path.join(stateHome, 'harness-controller');
173
- };
174
-
175
109
  export const readDatabaseConfig = (
176
110
  optionsArg: IControllerDataDirectoryOptions = {},
177
111
  ): IControllerDatabaseConfig => {
@@ -187,22 +121,13 @@ export const readDatabaseConfig = (
187
121
  }
188
122
  const embeddedDataDirectoryOverride = environment.HARNESS_CONTROLLER_DB_DIR?.trim();
189
123
  const embeddedDataDirectory = embeddedDataDirectoryOverride
190
- || plugins.path.join(controllerDataRootPaths(optionsArg).targetRoot, 'smartdb');
124
+ || resolveAGLHomePaths(optionsArg).database;
191
125
  if (!plugins.path.isAbsolute(embeddedDataDirectory)) {
192
126
  throw new Error('HARNESS_CONTROLLER_DB_DIR must be an absolute path.');
193
127
  }
194
- const packageRoot = optionsArg.packageRoot ?? modulePackageRoot;
195
128
  return {
196
129
  mongoDbName,
197
130
  embeddedDataDirectory,
198
- ...(!embeddedDataDirectoryOverride && !isDevelopmentCheckout(packageRoot)
199
- ? {
200
- legacyEmbeddedDataDirectory: plugins.path.join(
201
- legacyControllerStateDirectory(optionsArg),
202
- 'smartdb',
203
- ),
204
- }
205
- : {}),
206
131
  };
207
132
  };
208
133
 
@@ -240,17 +165,13 @@ export const resolveInitialRuntimeConfig = async (
240
165
  throw new Error('Local public-origin port must match the controller listening port.');
241
166
  }
242
167
  const rpId = normalizeRpId(overridesArg.rpId ?? origin.hostname, origin.hostname);
243
- const workspaceDirectory = await resolveDirectory(
244
- overridesArg.workspaceDirectory ?? process.cwd(),
245
- );
246
168
  const projectsRoot = await resolveDirectory(
247
- overridesArg.projectsRoot ?? workspaceDirectory,
169
+ overridesArg.projectsRoot ?? plugins.os.homedir(),
248
170
  );
249
171
  return {
250
172
  controllerPort,
251
173
  publicOrigin: origin.origin,
252
174
  rpId,
253
- workspaceDirectory,
254
175
  projectsRoot,
255
176
  opencodePort,
256
177
  tlsMode,
@@ -308,16 +229,6 @@ export const resolveStoredRuntimeConfig = async (
308
229
  if (overridesArg.opencodePort !== undefined && overridesArg.opencodePort !== storedArg.opencodePort) {
309
230
  throw new Error('opencodePort is immutable after controller initialization.');
310
231
  }
311
- if (overridesArg.workspaceDirectory !== undefined) {
312
- const requestedDirectory = await resolveDirectory(overridesArg.workspaceDirectory);
313
- if (requestedDirectory !== storedArg.workspaceDirectory) {
314
- throw new Error('The OpenCode workspace is immutable after controller initialization.');
315
- }
316
- }
317
- const storedDirectory = await resolveDirectory(storedArg.workspaceDirectory);
318
- if (storedDirectory !== storedArg.workspaceDirectory) {
319
- throw new Error('Stored OpenCode workspace is no longer its canonical real path.');
320
- }
321
232
  if (storedArg.tlsMode === 'proxy') {
322
233
  if (overridesArg.behindTlsProxy === false) {
323
234
  throw new Error('Stored TLS proxy mode is immutable.');
@@ -327,25 +238,17 @@ export const resolveStoredRuntimeConfig = async (
327
238
  throw new Error('Stored localhost mode cannot be changed to TLS mode.');
328
239
  }
329
240
  }
330
- // Pre-v2 documents carry no projectsRoot: the first v2 start adopts one —
331
- // from --projects-root when given, else the workspace — and it is immutable
332
- // from then on like every other stored boundary.
333
- let projectsRoot: string;
334
- if (storedArg.projectsRoot === undefined) {
335
- projectsRoot = await resolveDirectory(
336
- overridesArg.projectsRoot ?? storedArg.workspaceDirectory,
337
- );
338
- } else {
339
- projectsRoot = await resolveDirectory(storedArg.projectsRoot);
340
- if (projectsRoot !== storedArg.projectsRoot) {
341
- throw new Error('Stored projects root is no longer its canonical real path.');
342
- }
343
- if (overridesArg.projectsRoot !== undefined) {
344
- const requestedProjectsRoot = await resolveDirectory(overridesArg.projectsRoot);
345
- if (requestedProjectsRoot !== storedArg.projectsRoot) {
346
- throw new Error('The projects root is immutable after it has been adopted.');
347
- }
241
+ if (
242
+ !plugins.path.isAbsolute(storedArg.projectsRoot)
243
+ || plugins.path.normalize(storedArg.projectsRoot) !== storedArg.projectsRoot
244
+ ) {
245
+ throw new Error('Stored projects root is not an absolute normalized path.');
246
+ }
247
+ if (overridesArg.projectsRoot !== undefined) {
248
+ const requestedProjectsRoot = await resolveDirectory(overridesArg.projectsRoot);
249
+ if (requestedProjectsRoot !== storedArg.projectsRoot) {
250
+ throw new Error('The projects root is immutable after it has been adopted.');
348
251
  }
349
252
  }
350
- return { ...storedArg, projectsRoot };
253
+ return { ...storedArg };
351
254
  };