@aopslabs/aops 0.2.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 (171) hide show
  1. package/LICENSE +19 -0
  2. package/NOTICE +7 -0
  3. package/README.md +203 -0
  4. package/assets/agent-assets/core/SKILL.md +49 -0
  5. package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
  6. package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
  7. package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
  8. package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
  9. package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
  10. package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
  11. package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
  12. package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
  13. package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
  14. package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
  15. package/assets/agent-assets/core/references/view/SKILL.md +58 -0
  16. package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
  17. package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
  18. package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
  19. package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
  20. package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
  21. package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
  22. package/assets/agent-assets/core/user-guides/docman.md +162 -0
  23. package/assets/agent-assets/core/user-guides/projectman.md +212 -0
  24. package/assets/agent-assets/core/user-guides/sys.md +94 -0
  25. package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
  26. package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
  27. package/assets/r6-remote-session-baseline.json +19 -0
  28. package/assets/skills/aops-install/SKILL.md +231 -0
  29. package/assets/skills/aops-install/agents/openai.yaml +4 -0
  30. package/dist/commands/activity.js +352 -0
  31. package/dist/commands/agent-profile.js +653 -0
  32. package/dist/commands/agent.js +710 -0
  33. package/dist/commands/agents-md.js +125 -0
  34. package/dist/commands/api.js +84 -0
  35. package/dist/commands/archive.js +873 -0
  36. package/dist/commands/artifact.js +535 -0
  37. package/dist/commands/assets.js +1414 -0
  38. package/dist/commands/auth/device-login.js +1 -0
  39. package/dist/commands/auth/legacy-export.js +177 -0
  40. package/dist/commands/auth/login.js +426 -0
  41. package/dist/commands/chat.js +1363 -0
  42. package/dist/commands/chatv3.js +1684 -0
  43. package/dist/commands/checkpoint.js +549 -0
  44. package/dist/commands/commercial-artifact.js +475 -0
  45. package/dist/commands/commercial-license.js +594 -0
  46. package/dist/commands/community-auth.js +62 -0
  47. package/dist/commands/community-cockpit.js +185 -0
  48. package/dist/commands/community-console.js +109 -0
  49. package/dist/commands/community-doctor.js +335 -0
  50. package/dist/commands/community-server.js +2062 -0
  51. package/dist/commands/community-setup.js +284 -0
  52. package/dist/commands/discuss.js +1448 -0
  53. package/dist/commands/doc.js +5219 -0
  54. package/dist/commands/experience.js +922 -0
  55. package/dist/commands/host.js +1127 -0
  56. package/dist/commands/init.js +118 -0
  57. package/dist/commands/memory.js +3129 -0
  58. package/dist/commands/mission.js +562 -0
  59. package/dist/commands/official-catalog.js +134 -0
  60. package/dist/commands/plan.js +84 -0
  61. package/dist/commands/playbook.js +415 -0
  62. package/dist/commands/pm/context.js +6 -0
  63. package/dist/commands/pm/index.js +701 -0
  64. package/dist/commands/pm/projectman.js +6266 -0
  65. package/dist/commands/project.js +1080 -0
  66. package/dist/commands/prompt.js +1024 -0
  67. package/dist/commands/repo-sync.js +1907 -0
  68. package/dist/commands/resource.js +527 -0
  69. package/dist/commands/skill.js +1211 -0
  70. package/dist/commands/start-disciplines.js +510 -0
  71. package/dist/commands/start.js +1343 -0
  72. package/dist/commands/target.js +309 -0
  73. package/dist/commands/version.js +42 -0
  74. package/dist/commands/view.js +1852 -0
  75. package/dist/launchers/cockpit.js +28 -0
  76. package/dist/launchers/options.js +74 -0
  77. package/dist/launchers/server.js +41 -0
  78. package/dist/lib/agent-assets/envelope.js +83 -0
  79. package/dist/lib/agent-assets/gateway.js +15 -0
  80. package/dist/lib/agent-assets/guards.js +23 -0
  81. package/dist/lib/agent-assets/hosted-discovery.js +148 -0
  82. package/dist/lib/agent-assets/hosted-package-input.js +154 -0
  83. package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
  84. package/dist/lib/agent-assets/native-fs.js +589 -0
  85. package/dist/lib/agent-assets/package-manifest.js +324 -0
  86. package/dist/lib/agent-assets/portable-path.js +67 -0
  87. package/dist/lib/agent-assets/release-input.js +177 -0
  88. package/dist/lib/agent-assets/roots.js +45 -0
  89. package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
  90. package/dist/lib/agent-assets/runtime-targets.js +50 -0
  91. package/dist/lib/agent-assets/store-reader.js +1212 -0
  92. package/dist/lib/agent-assets/store-types.js +1 -0
  93. package/dist/lib/agent-assets/store-writer.js +1484 -0
  94. package/dist/lib/agent-assets/types.js +1 -0
  95. package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
  96. package/dist/lib/commercial-final-readiness.js +504 -0
  97. package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
  98. package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
  99. package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
  100. package/dist/lib/community-client-contract.js +164 -0
  101. package/dist/lib/community-cockpit-lifecycle.js +601 -0
  102. package/dist/lib/community-command-abort.js +59 -0
  103. package/dist/lib/community-diagnostic.js +237 -0
  104. package/dist/lib/community-docker-adapter.js +498 -0
  105. package/dist/lib/community-home.js +31 -0
  106. package/dist/lib/community-instance-contract.js +148 -0
  107. package/dist/lib/community-instance-layout.js +97 -0
  108. package/dist/lib/community-lifecycle.js +1631 -0
  109. package/dist/lib/community-migration-snapshot.js +770 -0
  110. package/dist/lib/community-native-application-recovery.js +425 -0
  111. package/dist/lib/community-native-child.js +240 -0
  112. package/dist/lib/community-native-database-recovery.js +456 -0
  113. package/dist/lib/community-native-lifecycle.js +2458 -0
  114. package/dist/lib/community-native-migration.js +339 -0
  115. package/dist/lib/community-native-postgres.js +596 -0
  116. package/dist/lib/community-operation-journal.js +773 -0
  117. package/dist/lib/community-operation-lock.js +596 -0
  118. package/dist/lib/community-release-resolver.js +291 -0
  119. package/dist/lib/community-release-verifier.js +475 -0
  120. package/dist/lib/community-repo-discovery.js +252 -0
  121. package/dist/lib/community-setup-server-env.js +203 -0
  122. package/dist/lib/external-url.js +110 -0
  123. package/dist/lib/official-catalog-gateway.js +108 -0
  124. package/dist/lib/official-catalog.js +364 -0
  125. package/dist/lib/server-bootstrap.js +240 -0
  126. package/dist/lib/setup-agent-assets-bridge.js +176 -0
  127. package/dist/lib/setup-agent-assets-release.js +52 -0
  128. package/dist/lib/setup-external-postgres.js +108 -0
  129. package/dist/lib/setup-init-orchestrator.js +990 -0
  130. package/dist/lib/setup-install-guide.js +52 -0
  131. package/dist/lib/setup-installer-release.js +47 -0
  132. package/dist/lib/setup-local-postgres.js +329 -0
  133. package/dist/lib/setup-official-catalog-bridge.js +148 -0
  134. package/dist/lib/setup-readiness.js +888 -0
  135. package/dist/lib/tui-launcher.js +59 -0
  136. package/dist/main.js +150 -0
  137. package/dist/seeds/agents-md.js +54 -0
  138. package/dist/utils/agent-gateway.js +275 -0
  139. package/dist/utils/agents-md.js +281 -0
  140. package/dist/utils/api.js +255 -0
  141. package/dist/utils/chatv3-session-store.js +185 -0
  142. package/dist/utils/command.js +67 -0
  143. package/dist/utils/community-host-runtime.js +376 -0
  144. package/dist/utils/config.js +1015 -0
  145. package/dist/utils/discussion-workspace.js +552 -0
  146. package/dist/utils/experience-workspace.js +326 -0
  147. package/dist/utils/guide-paths.js +67 -0
  148. package/dist/utils/hosted-sugar.js +56 -0
  149. package/dist/utils/hosted-workspace.js +229 -0
  150. package/dist/utils/memory-workspace.js +537 -0
  151. package/dist/utils/operator-runtime.js +199 -0
  152. package/dist/utils/password-crypto.js +39 -0
  153. package/dist/utils/playbook-workspace.js +479 -0
  154. package/dist/utils/project-context.js +255 -0
  155. package/dist/utils/prompts.js +70 -0
  156. package/dist/utils/refresh-race.js +28 -0
  157. package/dist/utils/repo-config.js +109 -0
  158. package/dist/utils/repo-first-projectman.js +194 -0
  159. package/dist/utils/repo-first-storage.js +13 -0
  160. package/dist/utils/repo-first-view.js +305 -0
  161. package/dist/utils/session-guidance.js +278 -0
  162. package/dist/utils/session-state.js +137 -0
  163. package/dist/utils/table.js +39 -0
  164. package/dist/utils/transient-fs.js +49 -0
  165. package/dist/utils/workspace-root.js +1 -0
  166. package/launchers/aops-cockpit.ps1 +15 -0
  167. package/launchers/aops-cockpit.sh +16 -0
  168. package/launchers/aops-server.ps1 +15 -0
  169. package/launchers/aops-server.sh +16 -0
  170. package/native/tui/win32-x64/aops-tui.exe +0 -0
  171. package/package.json +81 -0
@@ -0,0 +1,888 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import { readAopsServerEnvConfig, resolveAopsServerEnvPath, } from '@aopslabs/aops-runtime-config';
4
+ import { inspectCommunityInstall } from './community-lifecycle.js';
5
+ import { buildCommunityPnpmInvocation, isCommunityNativeNpmPackageSource, inspectCommunityNativeInstall, inspectCommunityNativeSource, loadExternalPostgresUrl, loadExternalServerAuthConfig, resolveCommunityNativeCockpitPort, resolveCommunityNativeDefaultSourceRoot, } from './community-native-lifecycle.js';
6
+ import { buildCommunityInstanceContract, } from './community-instance-contract.js';
7
+ import { createCliApiClientFromOptions, fetchCliBootstrapHealth } from '../utils/api.js';
8
+ import { findApiTargetByBaseUrlReadOnly, getActiveApiTargetReadOnly, getApiTargetReadOnly, } from '../utils/config.js';
9
+ import { inspectSetupAgentAssets, } from './setup-agent-assets-bridge.js';
10
+ import { inspectLocalPostgres, } from './setup-local-postgres.js';
11
+ import { createCommunityDiagnostic } from './community-diagnostic.js';
12
+ export const SETUP_PATHS = [
13
+ {
14
+ id: 'native-external',
15
+ number: '1',
16
+ title: 'Npm server with your existing PostgreSQL',
17
+ },
18
+ {
19
+ id: 'native-container',
20
+ number: '2',
21
+ title: 'Npm server with automatic Docker PostgreSQL',
22
+ },
23
+ {
24
+ id: 'native-local',
25
+ number: '3',
26
+ title: 'Npm server with PostgreSQL installed on this computer',
27
+ },
28
+ {
29
+ id: 'cli-existing',
30
+ number: '4',
31
+ title: 'CLI connected to an existing AOPS server',
32
+ },
33
+ ];
34
+ export function normalizeBootstrapAuthContract(bootstrap) {
35
+ const auth = bootstrap.auth && typeof bootstrap.auth === 'object'
36
+ ? bootstrap.auth
37
+ : null;
38
+ const providerValue = bootstrap.authProvider ?? auth?.provider;
39
+ const authProvider = providerValue === 'trusted-local'
40
+ ? 'trusted-local'
41
+ : providerValue === 'remote-session' || providerValue === 'authv2-jwt-session'
42
+ ? 'remote-session'
43
+ : null;
44
+ const explicitAuthRequired = typeof bootstrap.authRequired === 'boolean' ? bootstrap.authRequired : null;
45
+ const authRequired = explicitAuthRequired !== null
46
+ ? explicitAuthRequired === (authProvider === 'remote-session')
47
+ ? explicitAuthRequired
48
+ : null
49
+ : authProvider !== null
50
+ ? authProvider === 'remote-session'
51
+ : null;
52
+ const firstAdminState = auth && typeof auth.firstAdminState === 'string'
53
+ ? auth.firstAdminState
54
+ : null;
55
+ return {
56
+ authProvider,
57
+ authRequired,
58
+ firstAdminState: firstAdminState === 'not-applicable' || firstAdminState === 'required' ||
59
+ firstAdminState === 'ready' || firstAdminState === 'blocked' || firstAdminState === 'unknown'
60
+ ? firstAdminState
61
+ : null,
62
+ };
63
+ }
64
+ function normalizeNonEmpty(value) {
65
+ if (typeof value !== 'string')
66
+ return undefined;
67
+ const normalized = value.trim();
68
+ return normalized || undefined;
69
+ }
70
+ function normalizeSafeApiBaseUrl(value) {
71
+ const normalized = normalizeNonEmpty(value);
72
+ if (!normalized)
73
+ return undefined;
74
+ try {
75
+ const parsed = new URL(normalized);
76
+ if (!['http:', 'https:'].includes(parsed.protocol) || parsed.username || parsed.password ||
77
+ parsed.search || parsed.hash || parsed.pathname !== '/') {
78
+ throw new Error('setup_api_base_url_invalid');
79
+ }
80
+ return parsed.origin;
81
+ }
82
+ catch {
83
+ throw new Error('setup_api_base_url_invalid');
84
+ }
85
+ }
86
+ function normalizePort(value) {
87
+ const port = Number(value ?? 5900);
88
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
89
+ throw new Error('setup_port_invalid');
90
+ }
91
+ return port;
92
+ }
93
+ export function parseSetupPath(value) {
94
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
95
+ if (!normalized)
96
+ return undefined;
97
+ return SETUP_PATHS.find((entry) => entry.id === normalized || entry.number === normalized)?.id;
98
+ }
99
+ function pathDescriptor(id) {
100
+ return id ? SETUP_PATHS.find((entry) => entry.id === id) : undefined;
101
+ }
102
+ function isLoopbackUrl(value) {
103
+ try {
104
+ const host = new URL(value).hostname.replace(/^\[|\]$/g, '').toLowerCase();
105
+ return host === 'localhost' || host === '::1' || /^127(?:\.\d{1,3}){3}$/.test(host);
106
+ }
107
+ catch {
108
+ return false;
109
+ }
110
+ }
111
+ function isLoopbackPostgresAtPort(value, port) {
112
+ try {
113
+ const parsed = new URL(value);
114
+ return isLoopbackUrl(value) && Number(parsed.port || 5432) === port;
115
+ }
116
+ catch {
117
+ return false;
118
+ }
119
+ }
120
+ function commandAvailable(command) {
121
+ const locator = process.platform === 'win32' ? 'where.exe' : 'which';
122
+ const result = spawnSync(locator, [command], {
123
+ encoding: 'utf8',
124
+ windowsHide: true,
125
+ timeout: 1_000,
126
+ stdio: 'ignore',
127
+ });
128
+ return result.status === 0;
129
+ }
130
+ function dockerRuntimeAvailable(compose) {
131
+ const info = spawnSync('docker', ['info', '--format', '{{json .ServerVersion}}'], {
132
+ encoding: 'utf8',
133
+ windowsHide: true,
134
+ timeout: 2_000,
135
+ stdio: 'ignore',
136
+ });
137
+ if (info.status !== 0)
138
+ return false;
139
+ if (!compose)
140
+ return true;
141
+ const composeVersion = spawnSync('docker', ['compose', 'version'], {
142
+ encoding: 'utf8',
143
+ windowsHide: true,
144
+ timeout: 2_000,
145
+ stdio: 'ignore',
146
+ });
147
+ return composeVersion.status === 0;
148
+ }
149
+ function safeReason(error, fallback) {
150
+ const message = error instanceof Error ? error.message : '';
151
+ return /^[a-z0-9_:-]+$/i.test(message) ? message : fallback;
152
+ }
153
+ function inferInstalledPath(native, oci) {
154
+ if (native.status === 'installed' && native.state?.profile === 'native-external-postgres') {
155
+ return 'native-external';
156
+ }
157
+ if (native.status === 'installed' && native.state?.profile === 'native-container-postgres') {
158
+ return 'native-container';
159
+ }
160
+ return undefined;
161
+ }
162
+ function installationStateCheck(selected, native, oci, desiredSecurity) {
163
+ if (!selected) {
164
+ return {
165
+ id: 'installation-state',
166
+ state: 'unknown',
167
+ required: false,
168
+ summary: 'Installation state will be evaluated after a path is selected.',
169
+ };
170
+ }
171
+ if (selected === 'cli-existing') {
172
+ return {
173
+ id: 'installation-state',
174
+ state: 'not-applicable',
175
+ required: false,
176
+ summary: 'The CLI-only path does not install a local server runtime.',
177
+ };
178
+ }
179
+ if (oci.status !== 'not-installed') {
180
+ return {
181
+ id: 'installation-state',
182
+ state: 'action-required',
183
+ required: true,
184
+ summary: 'A legacy application-image installation owns this local instance.',
185
+ next: 'Use the explicit server lifecycle to inspect or remove that installation before selecting an npm-server setup path.',
186
+ data: { blocking: true, legacyProfile: 'oci-managed-stack' },
187
+ };
188
+ }
189
+ const installedPath = inferInstalledPath(native, oci);
190
+ const compatibleInstalledPath = selected === 'native-local' && installedPath === 'native-external'
191
+ ? selected
192
+ : installedPath;
193
+ if (compatibleInstalledPath && compatibleInstalledPath !== selected) {
194
+ return {
195
+ id: 'installation-state',
196
+ state: 'action-required',
197
+ required: true,
198
+ summary: 'A different local installation profile already owns this instance.',
199
+ next: 'Use the installed path, or run the explicit migration/reset workflow before switching paths.',
200
+ data: { blocking: true, selectedPath: selected, installedPath },
201
+ };
202
+ }
203
+ if (native.status === 'runtime-conflict' || (native.status !== 'not-installed' && oci.status !== 'not-installed')) {
204
+ const diagnostic = createCommunityDiagnostic(new Error(native.error ?? 'community_instance_runtime_conflict'), {
205
+ code: 'setup_install_runtime_conflict',
206
+ phase: 'readiness',
207
+ summary: 'Conflicting local runtime state requires repair before setup can continue.',
208
+ nextActions: ['Run `aops doctor --json` and resolve the reported runtime conflict.'],
209
+ });
210
+ return {
211
+ id: 'installation-state',
212
+ state: 'action-required',
213
+ required: true,
214
+ summary: diagnostic.summary,
215
+ next: diagnostic.nextActions[0],
216
+ data: {
217
+ blocking: true,
218
+ reasonCode: diagnostic.cause.code,
219
+ phase: diagnostic.phase,
220
+ diagnostic,
221
+ },
222
+ };
223
+ }
224
+ const expected = selected === 'native-external' || selected === 'native-local'
225
+ ? native.status === 'installed' && native.state?.profile === 'native-external-postgres'
226
+ : selected === 'native-container'
227
+ ? native.status === 'installed' && native.state?.profile === 'native-container-postgres'
228
+ : false;
229
+ const partial = native.status === 'partial';
230
+ if (partial) {
231
+ const diagnostic = createCommunityDiagnostic(new Error(native.error ?? 'community_native_install_partial'), {
232
+ code: 'setup_install_state_incomplete',
233
+ phase: 'readiness',
234
+ });
235
+ return {
236
+ id: 'installation-state',
237
+ state: 'action-required',
238
+ required: true,
239
+ summary: diagnostic.summary,
240
+ next: diagnostic.nextActions[0] ?? 'Run `aops doctor --json` before resuming setup.',
241
+ data: {
242
+ blocking: true,
243
+ reasonCode: diagnostic.cause.code,
244
+ phase: diagnostic.phase,
245
+ diagnostic,
246
+ },
247
+ };
248
+ }
249
+ if (expected) {
250
+ const authProvider = native.state?.server?.authProvider;
251
+ const exposure = native.state?.server?.exposure;
252
+ const securityMatches = !desiredSecurity ||
253
+ (authProvider === desiredSecurity.authProvider &&
254
+ exposure === desiredSecurity.exposure);
255
+ if (!securityMatches) {
256
+ return {
257
+ id: 'installation-state',
258
+ state: 'action-required',
259
+ required: true,
260
+ summary: 'The installed server security mode differs from the selected mode.',
261
+ next: 'Apply the selected setup path to reconfigure server authentication and exposure.',
262
+ data: {
263
+ installed: true,
264
+ securityMatches: false,
265
+ authProvider: authProvider ?? null,
266
+ exposure: exposure ?? null,
267
+ desiredAuthProvider: desiredSecurity.authProvider,
268
+ desiredExposure: desiredSecurity.exposure,
269
+ },
270
+ };
271
+ }
272
+ return {
273
+ id: 'installation-state',
274
+ state: 'ready',
275
+ required: true,
276
+ summary: 'The selected local installation profile is present.',
277
+ data: {
278
+ installed: true,
279
+ securityMatches,
280
+ authProvider: authProvider ?? null,
281
+ exposure: exposure ?? null,
282
+ },
283
+ };
284
+ }
285
+ return {
286
+ id: 'installation-state',
287
+ state: 'action-required',
288
+ required: true,
289
+ summary: 'The selected local installation profile is not installed yet.',
290
+ next: 'Run `aops setup init --apply` to continue the selected setup path.',
291
+ };
292
+ }
293
+ async function probeEndpoint(options) {
294
+ try {
295
+ const api = await createCliApiClientFromOptions({
296
+ apiBaseUrl: options.apiBaseUrl,
297
+ targetName: options.targetName,
298
+ timeoutMs: options.timeoutMs,
299
+ readOnly: true,
300
+ });
301
+ const bootstrap = await fetchCliBootstrapHealth(api, {
302
+ timeoutMs: options.timeoutMs,
303
+ });
304
+ const { authProvider, authRequired, firstAdminState } = normalizeBootstrapAuthContract(bootstrap);
305
+ let authenticated = authRequired === false ? true : authRequired === true ? false : null;
306
+ let principalUserId = null;
307
+ if (authRequired === true) {
308
+ try {
309
+ const me = await api.client.requestXfJson('/api/auth/me', { method: 'GET' }, { retry401: false, timeoutMs: options.timeoutMs });
310
+ const userId = me.result?.data?.principal?.userId;
311
+ principalUserId = typeof userId === 'string' && userId.trim() ? userId.trim() : null;
312
+ authenticated =
313
+ me.status === 200 &&
314
+ me.result?.ok === true &&
315
+ Boolean(principalUserId) &&
316
+ me.result?.data?.authProvider === authProvider &&
317
+ me.result?.data?.authRequired === authRequired;
318
+ }
319
+ catch {
320
+ authenticated = false;
321
+ }
322
+ }
323
+ return {
324
+ reachable: true,
325
+ authRequired,
326
+ authProvider,
327
+ authenticated,
328
+ principalUserId,
329
+ firstAdminState,
330
+ };
331
+ }
332
+ catch {
333
+ return {
334
+ reachable: false,
335
+ authRequired: null,
336
+ authProvider: null,
337
+ authenticated: null,
338
+ principalUserId: null,
339
+ firstAdminState: null,
340
+ };
341
+ }
342
+ }
343
+ async function probeCockpit(origin, timeoutMs) {
344
+ const request = async (pathname) => {
345
+ try {
346
+ const response = await fetch(`${origin}${pathname}`, {
347
+ headers: pathname.startsWith('/api') ? { origin } : undefined,
348
+ signal: AbortSignal.timeout(timeoutMs),
349
+ });
350
+ return response.ok;
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ };
356
+ const [reachable, apiReachable] = await Promise.all([
357
+ request('/_aops/cockpit-health'),
358
+ request('/api/health'),
359
+ ]);
360
+ return { reachable, apiReachable, origin };
361
+ }
362
+ function agentAssetCheck(status) {
363
+ if (status.availability === 'task-136-pending') {
364
+ return {
365
+ id: 'agent-assets',
366
+ state: 'action-required',
367
+ required: false,
368
+ summary: status.summary,
369
+ next: status.nextActions[0],
370
+ data: status.data,
371
+ };
372
+ }
373
+ return status.state === 'ready'
374
+ ? {
375
+ id: 'agent-assets',
376
+ state: 'ready',
377
+ required: true,
378
+ summary: status.summary,
379
+ data: status.data,
380
+ }
381
+ : {
382
+ id: 'agent-assets',
383
+ state: 'action-required',
384
+ required: true,
385
+ summary: status.summary,
386
+ next: status.nextActions[0],
387
+ data: status.data,
388
+ };
389
+ }
390
+ export async function inspectSetupReadiness(options = {}) {
391
+ const processEnv = options.processEnv ?? process.env;
392
+ const cwd = path.resolve(options.cwd ?? process.cwd());
393
+ const probes = options.probes ?? {};
394
+ const explicitApiBaseUrl = normalizeSafeApiBaseUrl(options.apiBaseUrl);
395
+ const localPort = normalizePort(options.port);
396
+ const native = probes.nativeInspection ?? inspectCommunityNativeInstall({
397
+ instanceName: options.instance,
398
+ dataRoot: options.dataRoot,
399
+ });
400
+ const oci = probes.ociInspection ?? inspectCommunityInstall({
401
+ instanceName: options.instance,
402
+ dataRoot: options.dataRoot,
403
+ });
404
+ let activeTarget = probes.activeTarget ?? null;
405
+ if (probes.activeTarget === undefined) {
406
+ try {
407
+ activeTarget = getActiveApiTargetReadOnly() ?? null;
408
+ }
409
+ catch {
410
+ activeTarget = null;
411
+ }
412
+ }
413
+ const requestedTargetName = normalizeNonEmpty(options.targetName);
414
+ let selectedTarget = probes.selectedTarget ?? null;
415
+ if (probes.selectedTarget === undefined) {
416
+ try {
417
+ if (requestedTargetName) {
418
+ selectedTarget =
419
+ getApiTargetReadOnly(requestedTargetName) ?? null;
420
+ }
421
+ else if (explicitApiBaseUrl) {
422
+ selectedTarget =
423
+ findApiTargetByBaseUrlReadOnly(explicitApiBaseUrl) ?? null;
424
+ }
425
+ else {
426
+ selectedTarget = activeTarget;
427
+ }
428
+ }
429
+ catch {
430
+ selectedTarget = null;
431
+ }
432
+ }
433
+ if (selectedTarget &&
434
+ explicitApiBaseUrl &&
435
+ selectedTarget.apiBaseUrl !== explicitApiBaseUrl) {
436
+ selectedTarget = null;
437
+ }
438
+ if (probes.selectedTarget === undefined &&
439
+ probes.activeTarget !== undefined &&
440
+ !requestedTargetName &&
441
+ explicitApiBaseUrl) {
442
+ selectedTarget = activeTarget?.apiBaseUrl === explicitApiBaseUrl ? activeTarget : null;
443
+ }
444
+ const explicitPath = parseSetupPath(options.path);
445
+ const installedPath = inferInstalledPath(native, oci);
446
+ const activeTargetPath = selectedTarget && !isLoopbackUrl(selectedTarget.apiBaseUrl) ? 'cli-existing' : undefined;
447
+ const selected = explicitPath ?? installedPath ?? activeTargetPath;
448
+ const descriptor = pathDescriptor(selected);
449
+ const desiredSecurity = options.auth && options.exposure
450
+ ? Object.freeze({
451
+ authProvider: options.auth,
452
+ exposure: options.exposure,
453
+ })
454
+ : undefined;
455
+ const source = explicitPath
456
+ ? 'explicit'
457
+ : installedPath
458
+ ? 'installed-state'
459
+ : activeTargetPath
460
+ ? 'active-target'
461
+ : 'unselected';
462
+ const checks = [
463
+ descriptor
464
+ ? {
465
+ id: 'installation-path',
466
+ state: 'ready',
467
+ required: true,
468
+ summary: `Path ${descriptor.number} selected: ${descriptor.title}.`,
469
+ data: {
470
+ id: descriptor.id,
471
+ number: descriptor.number,
472
+ ...(desiredSecurity
473
+ ? {
474
+ authProvider: desiredSecurity.authProvider,
475
+ exposure: desiredSecurity.exposure,
476
+ desiredAuthProvider: desiredSecurity.authProvider,
477
+ desiredExposure: desiredSecurity.exposure,
478
+ }
479
+ : {}),
480
+ },
481
+ }
482
+ : {
483
+ id: 'installation-path',
484
+ state: 'action-required',
485
+ required: true,
486
+ summary: 'No installation path has been selected or inferred.',
487
+ next: 'Choose path 1, 2, 3, or 4.',
488
+ data: { choices: SETUP_PATHS },
489
+ },
490
+ ];
491
+ checks.push(installationStateCheck(selected, native, oci, desiredSecurity));
492
+ const explicitPostgresConfig = normalizeNonEmpty(options.postgresConfig);
493
+ const safePostgresConfig = explicitPostgresConfig
494
+ ? buildCommunityInstanceContract({
495
+ runtime: 'native',
496
+ postgres: 'external',
497
+ postgresConfig: explicitPostgresConfig,
498
+ postgresTls: options.postgresTls ?? 'require',
499
+ processEnv,
500
+ }).postgres.configRef
501
+ : undefined;
502
+ const envResolution = resolveAopsServerEnvPath({ explicitPath: safePostgresConfig }, processEnv);
503
+ let envSnapshot;
504
+ let envError;
505
+ try {
506
+ envSnapshot = readAopsServerEnvConfig(processEnv, envResolution.path);
507
+ }
508
+ catch (error) {
509
+ envError = safeReason(error, 'aops_server_env_invalid');
510
+ }
511
+ if (selected === 'native-external' || selected === 'native-local') {
512
+ const postgresEnvReady = Boolean(envSnapshot?.exists && envSnapshot.repoDialect === 'pg' && envSnapshot.repoUrl);
513
+ let envAuthProvider = null;
514
+ let envAuthError = null;
515
+ if (envSnapshot?.exists) {
516
+ const configuredProvider = String(envSnapshot.assignments.AOPS_AUTH_PROVIDER ?? 'trusted-local')
517
+ .trim()
518
+ .toLowerCase();
519
+ try {
520
+ if (configuredProvider !== 'trusted-local') {
521
+ throw new Error('community_local_auth_provider_retired:repair_with_trusted-local');
522
+ }
523
+ envAuthProvider = loadExternalServerAuthConfig(envResolution.path, 'trusted-local').authProvider;
524
+ }
525
+ catch (error) {
526
+ envAuthError = safeReason(error, 'aops_server_env_auth_invalid');
527
+ }
528
+ }
529
+ const authEnvReady = envAuthProvider !== null;
530
+ const authProviderMatches = !desiredSecurity || envAuthProvider === desiredSecurity.authProvider;
531
+ const selectedLocalPostgresPort = Number(options.localPostgresPort ?? 5432);
532
+ const localPostgresTargetMatches = selected !== 'native-local' || isLoopbackPostgresAtPort(String(envSnapshot?.repoUrl ?? ''), selectedLocalPostgresPort);
533
+ const postgresReady = postgresEnvReady && localPostgresTargetMatches;
534
+ const envReady = postgresReady && authEnvReady && authProviderMatches;
535
+ checks.push({
536
+ id: 'global-server-env',
537
+ state: envReady ? 'ready' : 'action-required',
538
+ required: true,
539
+ summary: envReady
540
+ ? 'The selected server env contains a PostgreSQL repository target.'
541
+ : postgresEnvReady && authEnvReady && !authProviderMatches
542
+ ? 'The selected server env authentication provider does not match the selected server mode.'
543
+ : postgresEnvReady && !authEnvReady
544
+ ? 'The selected server env has an invalid authentication provider or AuthV2 secret closure.'
545
+ : selected === 'native-local' && postgresEnvReady
546
+ ? 'The selected server env points to a non-local PostgreSQL target or a different local port.'
547
+ : 'The selected server env is missing, unsafe, or does not contain a PostgreSQL target.',
548
+ next: envReady
549
+ ? undefined
550
+ : postgresEnvReady && authEnvReady && !authProviderMatches
551
+ ? `Run \`aops setup server-env --auth ${desiredSecurity.authProvider}\` for the selected private env.`
552
+ : selected === 'native-local'
553
+ ? 'Path 3 creates a private local server env during apply; use `--postgres-config <private-path>` to preserve an existing remote env, or use path 1 for that remote target.'
554
+ : 'Run `aops setup server-env` or provide a safe `--postgres-config` file path.',
555
+ data: {
556
+ path: envResolution.path,
557
+ source: envResolution.source,
558
+ exists: envSnapshot?.exists ?? false,
559
+ repoDialect: envSnapshot?.repoDialect ?? null,
560
+ redactedRepoUrl: envSnapshot?.redactedRepoUrl ?? null,
561
+ postgresReady,
562
+ localPostgresTargetMatches,
563
+ authProvider: envAuthProvider,
564
+ desiredAuthProvider: desiredSecurity?.authProvider ?? null,
565
+ authProviderMatches,
566
+ authError: envAuthError,
567
+ error: envError ?? null,
568
+ blocking: selected === 'native-local' &&
569
+ postgresEnvReady &&
570
+ !localPostgresTargetMatches,
571
+ },
572
+ });
573
+ const installedPostgresTls = source === 'installed-state' &&
574
+ native.status === 'installed' &&
575
+ native.state?.profile === 'native-external-postgres'
576
+ ? native.state.postgres.tlsPolicy
577
+ : undefined;
578
+ const tlsPolicy = selected === 'native-local'
579
+ ? 'disable'
580
+ : options.postgresTls ?? installedPostgresTls;
581
+ if (!tlsPolicy) {
582
+ checks.push({
583
+ id: 'postgresql-tls',
584
+ state: 'action-required',
585
+ required: true,
586
+ summary: 'A PostgreSQL TLS policy has not been selected.',
587
+ next: 'Choose require (default), verify-full, or an explicit disable policy.',
588
+ });
589
+ }
590
+ else if (!envReady) {
591
+ checks.push({
592
+ id: 'postgresql-tls',
593
+ state: 'action-required',
594
+ required: true,
595
+ summary: 'PostgreSQL TLS readiness cannot be validated until the server env is ready.',
596
+ data: { policy: tlsPolicy },
597
+ });
598
+ }
599
+ else {
600
+ try {
601
+ loadExternalPostgresUrl(envResolution.path, tlsPolicy);
602
+ checks.push({
603
+ id: 'postgresql-tls',
604
+ state: 'ready',
605
+ required: true,
606
+ summary: `PostgreSQL configuration satisfies the ${tlsPolicy} TLS policy.`,
607
+ data: { policy: tlsPolicy },
608
+ });
609
+ }
610
+ catch (error) {
611
+ checks.push({
612
+ id: 'postgresql-tls',
613
+ state: 'action-required',
614
+ required: true,
615
+ summary: 'PostgreSQL or TLS configuration is not ready.',
616
+ next: 'Repair the selected server env and TLS root certificate reference.',
617
+ data: { policy: tlsPolicy, error: safeReason(error, 'postgresql_tls_config_invalid') },
618
+ });
619
+ }
620
+ }
621
+ }
622
+ else {
623
+ checks.push({
624
+ id: 'global-server-env',
625
+ state: 'not-applicable',
626
+ required: false,
627
+ summary: 'This path does not require an external PostgreSQL server env.',
628
+ data: { path: envResolution.path, source: envResolution.source },
629
+ });
630
+ checks.push({
631
+ id: 'postgresql-tls',
632
+ state: 'not-applicable',
633
+ required: false,
634
+ summary: 'External PostgreSQL TLS configuration is not used by this path.',
635
+ });
636
+ }
637
+ if (selected === 'native-local') {
638
+ const local = probes.localPostgres ?? await inspectLocalPostgres({
639
+ host: options.localPostgresHost,
640
+ port: options.localPostgresPort,
641
+ timeoutMs: options.timeoutMs,
642
+ });
643
+ const next = local.reachable
644
+ ? undefined
645
+ : [local.guidance.summary, ...local.guidance.commands, local.guidance.url].join(' ');
646
+ checks.push({
647
+ id: 'local-postgresql',
648
+ state: local.reachable ? 'ready' : 'action-required',
649
+ required: true,
650
+ summary: local.reachable
651
+ ? `A local PostgreSQL endpoint is reachable at ${local.host}:${local.port}.`
652
+ : local.status === 'installed-not-running'
653
+ ? `PostgreSQL tools were found, but no server is reachable at ${local.host}:${local.port}.`
654
+ : `PostgreSQL was not detected at ${local.host}:${local.port}.`,
655
+ next,
656
+ data: {
657
+ host: local.host,
658
+ port: local.port,
659
+ status: local.status,
660
+ psqlAvailable: local.psqlAvailable,
661
+ psqlVersion: local.psqlVersion,
662
+ install: local.reachable ? null : local.guidance,
663
+ },
664
+ });
665
+ }
666
+ else {
667
+ checks.push({
668
+ id: 'local-postgresql',
669
+ state: 'not-applicable',
670
+ required: false,
671
+ summary: 'Local PostgreSQL discovery and provisioning are not used by this path.',
672
+ });
673
+ }
674
+ const hasCommand = probes.commandAvailable ?? commandAvailable;
675
+ if (selected === 'native-external' || selected === 'native-container' || selected === 'native-local') {
676
+ const selectedSourceRoot = path.resolve(options.sourceRoot ?? resolveCommunityNativeDefaultSourceRoot(cwd));
677
+ let nativeSourceReady = false;
678
+ let npmPackage = false;
679
+ let nativeSourceError = null;
680
+ if (probes.commandAvailable) {
681
+ nativeSourceReady = options.sourceRoot ? hasCommand('pnpm') : true;
682
+ }
683
+ else {
684
+ try {
685
+ inspectCommunityNativeSource(selectedSourceRoot);
686
+ npmPackage = isCommunityNativeNpmPackageSource(selectedSourceRoot);
687
+ if (!npmPackage)
688
+ buildCommunityPnpmInvocation(selectedSourceRoot, ['--version'], processEnv);
689
+ nativeSourceReady = true;
690
+ }
691
+ catch (error) {
692
+ nativeSourceError = safeReason(error, 'community_native_source_runtime_invalid');
693
+ }
694
+ }
695
+ const needsDocker = selected === 'native-container';
696
+ const dockerReady = !needsDocker
697
+ ? true
698
+ : probes.commandAvailable
699
+ ? hasCommand('docker')
700
+ : hasCommand('docker') && dockerRuntimeAvailable(false);
701
+ const runtimeReady = nativeSourceReady && dockerReady;
702
+ checks.push({
703
+ id: 'runtime',
704
+ state: runtimeReady ? 'ready' : 'action-required',
705
+ required: true,
706
+ summary: runtimeReady
707
+ ? npmPackage
708
+ ? 'The installed npm server runtime is ready; no source build is required.'
709
+ : 'Native source runtime requirements are available.'
710
+ : needsDocker
711
+ ? 'The installed npm server runtime (or an explicit source checkout) and a running Docker daemon are required.'
712
+ : 'Install the matching @aopslabs/aops-server package separately, or provide a valid development checkout with pnpm 11.',
713
+ next: runtimeReady
714
+ ? undefined
715
+ : needsDocker
716
+ ? 'Install and start Docker Desktop/Engine, then retry the npm-server setup.'
717
+ : 'Install the matching npm server package or provide a valid Community source checkout.',
718
+ data: {
719
+ node: process.version,
720
+ sourceRoot: selectedSourceRoot,
721
+ sourceKind: npmPackage ? 'npm-package' : 'source-checkout',
722
+ nativeSource: nativeSourceReady,
723
+ docker: needsDocker ? dockerReady : null,
724
+ error: nativeSourceError,
725
+ },
726
+ });
727
+ }
728
+ else if (selected === 'cli-existing') {
729
+ checks.push({
730
+ id: 'runtime',
731
+ state: 'not-applicable',
732
+ required: false,
733
+ summary: 'The CLI-only path does not require a local server or container runtime.',
734
+ });
735
+ }
736
+ else {
737
+ checks.push({
738
+ id: 'runtime',
739
+ state: 'unknown',
740
+ required: false,
741
+ summary: 'Runtime requirements depend on the selected installation path.',
742
+ });
743
+ }
744
+ const endpointUrl = selected === 'cli-existing'
745
+ ? explicitApiBaseUrl ?? selectedTarget?.apiBaseUrl
746
+ : explicitApiBaseUrl ?? `http://127.0.0.1:${localPort}`;
747
+ const endpoint = probes.endpoint ?? (endpointUrl
748
+ ? await probeEndpoint({
749
+ apiBaseUrl: endpointUrl,
750
+ targetName: selected === 'cli-existing' ? selectedTarget?.name : undefined,
751
+ timeoutMs: options.timeoutMs ?? 1_500,
752
+ })
753
+ : {
754
+ reachable: false,
755
+ authRequired: null,
756
+ authProvider: null,
757
+ authenticated: null,
758
+ principalUserId: null,
759
+ firstAdminState: null,
760
+ });
761
+ checks.push({
762
+ id: 'host',
763
+ state: endpoint.reachable ? 'ready' : 'action-required',
764
+ required: true,
765
+ summary: endpoint.reachable
766
+ ? `${selected === 'cli-existing' ? 'Remote' : 'Local'} AOPS host is reachable.`
767
+ : `${selected === 'cli-existing' ? 'Remote' : 'Local'} AOPS host is not reachable yet.`,
768
+ next: endpoint.reachable
769
+ ? undefined
770
+ : selected === 'cli-existing'
771
+ ? 'Configure a reachable target or pass `--api-base-url`.'
772
+ : 'Install or start the selected local runtime.',
773
+ data: { apiBaseUrl: endpointUrl ?? null },
774
+ });
775
+ const localPath = selected === 'native-external' || selected === 'native-container' || selected === 'native-local';
776
+ if (localPath) {
777
+ const cockpitPort = native.state?.cockpit?.port ?? resolveCommunityNativeCockpitPort(localPort);
778
+ const cockpitOrigin = `http://127.0.0.1:${cockpitPort}`;
779
+ const cockpit = probes.cockpit ?? (native.status === 'installed'
780
+ ? await probeCockpit(cockpitOrigin, options.timeoutMs ?? 1_500)
781
+ : { reachable: false, apiReachable: false, origin: cockpitOrigin });
782
+ const ready = cockpit.reachable && cockpit.apiReachable;
783
+ checks.push({
784
+ id: 'cockpit',
785
+ state: ready ? 'ready' : 'action-required',
786
+ required: true,
787
+ summary: ready
788
+ ? `AOPS Cockpit is reachable at ${cockpit.origin} and its API proxy is healthy.`
789
+ : cockpit.reachable
790
+ ? `AOPS Cockpit is reachable at ${cockpit.origin}, but its AOPS Server proxy is unavailable.`
791
+ : `AOPS Cockpit is not reachable at ${cockpit.origin}.`,
792
+ next: ready ? undefined : cockpit.reachable
793
+ ? 'Run `aops server start`, then verify with `aops cockpit health`.'
794
+ : 'Run `aops cockpit start`, then verify with `aops cockpit health`.',
795
+ data: cockpit,
796
+ });
797
+ }
798
+ else {
799
+ checks.push({
800
+ id: 'cockpit',
801
+ state: 'not-applicable',
802
+ required: false,
803
+ summary: 'The CLI-only path does not manage a local Cockpit process.',
804
+ });
805
+ }
806
+ const targetRequired = selected === 'cli-existing';
807
+ const targetSelected = selectedTarget?.active === true ||
808
+ (selectedTarget !== null && activeTarget?.name === selectedTarget.name);
809
+ const targetProviderMatches = !selectedTarget || endpoint.authProvider === selectedTarget.authProvider;
810
+ const loginReady = targetProviderMatches &&
811
+ (endpoint.authRequired === false ||
812
+ (endpoint.authRequired === true && endpoint.authenticated === true));
813
+ checks.push({
814
+ id: 'target-login',
815
+ state: !targetRequired
816
+ ? 'not-applicable'
817
+ : !selectedTarget
818
+ ? 'action-required'
819
+ : !targetSelected
820
+ ? 'action-required'
821
+ : loginReady
822
+ ? 'ready'
823
+ : 'action-required',
824
+ required: targetRequired,
825
+ summary: !targetRequired
826
+ ? 'A remote target is not required for this local installation path.'
827
+ : !selectedTarget
828
+ ? explicitApiBaseUrl
829
+ ? 'The endpoint was probed, but no persistent existing-server target is selected.'
830
+ : 'No existing-server target is configured.'
831
+ : !targetSelected
832
+ ? 'The matching existing-server target is saved but is not selected.'
833
+ : loginReady
834
+ ? 'The selected target and authenticated session are ready.'
835
+ : endpoint.authRequired === null
836
+ ? 'The selected target is reachable, but its authentication contract could not be verified.'
837
+ : !targetProviderMatches
838
+ ? 'The selected target authentication provider does not match the server contract.'
839
+ : selectedTarget.hasCredentials
840
+ ? 'The selected target has stored credentials, but `/api/auth/me` did not verify them.'
841
+ : 'The selected target requires login credentials.',
842
+ next: targetRequired && !selectedTarget
843
+ ? 'Add and select an AOPS target.'
844
+ : targetRequired && !targetSelected
845
+ ? `Run \`aops target use ${selectedTarget?.name ?? '<name>'} --apply\`.`
846
+ : targetRequired && endpoint.authRequired === null
847
+ ? `Run \`aops target doctor${selectedTarget?.name ? ` ${selectedTarget.name}` : ''} --json\` and repair the server auth contract.`
848
+ : targetRequired && !targetProviderMatches
849
+ ? `Run \`aops target doctor${selectedTarget?.name ? ` ${selectedTarget.name}` : ''} --json\` and repair the provider mismatch.`
850
+ : targetRequired && !loginReady
851
+ ? `Run \`aops auth login${selectedTarget?.name ? ` --target ${selectedTarget.name}` : ''}\`.`
852
+ : undefined,
853
+ data: selectedTarget
854
+ ? {
855
+ name: selectedTarget.name,
856
+ apiBaseUrl: selectedTarget.apiBaseUrl,
857
+ authProvider: endpoint.authProvider ?? selectedTarget.authProvider,
858
+ tlsPolicy: selectedTarget.tlsPolicy ?? null,
859
+ active: targetSelected,
860
+ providerMatches: targetProviderMatches,
861
+ hasCredentials: selectedTarget.hasCredentials,
862
+ authenticated: endpoint.authenticated === true,
863
+ principalUserId: endpoint.principalUserId ?? null,
864
+ }
865
+ : {
866
+ apiBaseUrl: endpointUrl ?? null,
867
+ authProvider: endpoint.authProvider ?? null,
868
+ authenticated: false,
869
+ },
870
+ });
871
+ const assets = probes.agentAssets ?? await inspectSetupAgentAssets(options.agentAssetsProvider);
872
+ checks.push(agentAssetCheck(assets));
873
+ const requiredAction = checks.some((check) => check.required && check.state !== 'ready' && check.state !== 'not-applicable');
874
+ const nextActions = [...new Set(checks.map((check) => check.next).filter((value) => Boolean(value)))];
875
+ return Object.freeze({
876
+ schemaVersion: 1,
877
+ mutationFree: true,
878
+ status: requiredAction ? 'action-required' : 'ready',
879
+ path: Object.freeze({
880
+ id: descriptor?.id ?? null,
881
+ number: descriptor?.number ?? null,
882
+ title: descriptor?.title ?? null,
883
+ source,
884
+ }),
885
+ checks: Object.freeze(checks),
886
+ nextActions: Object.freeze(nextActions),
887
+ });
888
+ }