@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,601 @@
1
+ import { randomBytes, randomUUID } from 'node:crypto';
2
+ import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { COMMUNITY_NATIVE_CHILD_PROTOCOL, COMMUNITY_NATIVE_CONTROL_PROTOCOL, } from './community-native-child.js';
5
+ import { communityNativeRuntime, inspectCommunityNativeInstall, resolveCommunityNativeChildEntry, resolveCommunityNativePaths, } from './community-native-lifecycle.js';
6
+ import { withCommunityOperationLock } from './community-operation-lock.js';
7
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
8
+ const SHA256 = /^sha256:[a-f0-9]{64}$/;
9
+ const READY_TIMEOUT_MS = 15_000;
10
+ const STOP_TIMEOUT_MS = 15_000;
11
+ const POLL_MS = 100;
12
+ const MAX_LOG_TAIL_BYTES = 4 * 1024 * 1024;
13
+ function validPort(value) {
14
+ return Number.isSafeInteger(value) && Number(value) >= 1 && Number(value) <= 65_535;
15
+ }
16
+ function samePath(left, right) {
17
+ return path.relative(path.resolve(left), path.resolve(right)) === '' &&
18
+ path.relative(path.resolve(right), path.resolve(left)) === '';
19
+ }
20
+ function throwIfAborted(signal) {
21
+ if (signal?.aborted)
22
+ throw new Error('community_operation_aborted');
23
+ }
24
+ function exactKeys(input, required, optional = []) {
25
+ const keys = Object.keys(input);
26
+ if (required.some((key) => !keys.includes(key)) ||
27
+ keys.some((key) => !required.includes(key) && !optional.includes(key)))
28
+ throw new Error('community_cockpit_process_schema_invalid');
29
+ }
30
+ function assertPlainDirectory(candidate, code) {
31
+ const stats = lstatSync(candidate);
32
+ if (!stats.isDirectory() || stats.isSymbolicLink() || !samePath(realpathSync.native(candidate), candidate)) {
33
+ throw new Error(code);
34
+ }
35
+ }
36
+ function assertPlainFile(candidate, parent, code) {
37
+ const stats = lstatSync(candidate);
38
+ if (!stats.isFile() || stats.isSymbolicLink() ||
39
+ !samePath(realpathSync.native(candidate), candidate) || !samePath(path.dirname(candidate), parent))
40
+ throw new Error(code);
41
+ }
42
+ function assertPaths(paths) {
43
+ assertPlainDirectory(paths.instanceRoot, 'community_cockpit_instance_root_unsafe');
44
+ assertPlainDirectory(paths.runtimeRoot, 'community_cockpit_runtime_root_unsafe');
45
+ if (existsSync(paths.logRoot))
46
+ assertPlainDirectory(paths.logRoot, 'community_cockpit_log_root_unsafe');
47
+ for (const candidate of [paths.processPath, paths.identityPath, paths.controlPath]) {
48
+ if (existsSync(candidate))
49
+ assertPlainFile(candidate, paths.runtimeRoot, 'community_cockpit_runtime_file_unsafe');
50
+ }
51
+ if (existsSync(paths.logPath))
52
+ assertPlainFile(paths.logPath, paths.logRoot, 'community_cockpit_log_file_unsafe');
53
+ }
54
+ function atomicJsonWrite(targetPath, value) {
55
+ const parent = path.dirname(targetPath);
56
+ assertPlainDirectory(parent, 'community_cockpit_runtime_root_unsafe');
57
+ if (existsSync(targetPath))
58
+ assertPlainFile(targetPath, parent, 'community_cockpit_runtime_file_unsafe');
59
+ const temporary = path.join(parent, `.${path.basename(targetPath)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
60
+ try {
61
+ writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`, {
62
+ encoding: 'utf8', flag: 'wx', mode: 0o600,
63
+ });
64
+ renameSync(temporary, targetPath);
65
+ }
66
+ finally {
67
+ rmSync(temporary, { force: true });
68
+ }
69
+ }
70
+ function readJson(targetPath, code) {
71
+ try {
72
+ return JSON.parse(readFileSync(targetPath, 'utf8'));
73
+ }
74
+ catch {
75
+ throw new Error(code);
76
+ }
77
+ }
78
+ export function resolveCommunityCockpitPaths(input = {}) {
79
+ const native = resolveCommunityNativePaths(input);
80
+ return {
81
+ dataRoot: native.dataRoot,
82
+ instanceRoot: native.instanceRoot,
83
+ runtimeRoot: native.runtimeRoot,
84
+ processPath: path.join(native.runtimeRoot, 'native-cockpit-process.json'),
85
+ identityPath: path.join(native.runtimeRoot, 'native-cockpit-identity.json'),
86
+ controlPath: path.join(native.runtimeRoot, 'native-cockpit-control.json'),
87
+ logRoot: native.logRoot,
88
+ logPath: path.join(native.logRoot, 'native-cockpit.log'),
89
+ };
90
+ }
91
+ function parseProcess(value, paths) {
92
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
93
+ throw new Error('community_cockpit_process_invalid');
94
+ }
95
+ const input = value;
96
+ exactKeys(input, [
97
+ 'schemaVersion', 'protocol', 'service', 'instanceName', 'launchId', 'pid', 'status',
98
+ 'startedAt', 'updatedAt', 'sourceFingerprint', 'hostEntry', 'port', 'serverPort',
99
+ 'identityPath', 'controlPath', 'logPath',
100
+ ], ['hostPid', 'exitCode', 'signal', 'failure']);
101
+ if (input.schemaVersion !== 1 || input.protocol !== COMMUNITY_NATIVE_CHILD_PROTOCOL || input.service !== 'cockpit' ||
102
+ !UUID.test(String(input.launchId)) || !Number.isSafeInteger(input.pid) || input.pid < 1 ||
103
+ (input.hostPid !== undefined && (!Number.isSafeInteger(input.hostPid) || input.hostPid < 1)) ||
104
+ !['starting', 'running', 'exited', 'failed'].includes(input.status) ||
105
+ Number.isNaN(Date.parse(String(input.startedAt))) || Number.isNaN(Date.parse(String(input.updatedAt))) ||
106
+ !SHA256.test(String(input.sourceFingerprint)) || !path.isAbsolute(String(input.hostEntry)) ||
107
+ !validPort(input.port) || !validPort(input.serverPort) || input.port === input.serverPort ||
108
+ !samePath(String(input.identityPath), paths.identityPath) ||
109
+ !samePath(String(input.controlPath), paths.controlPath) || !samePath(String(input.logPath), paths.logPath))
110
+ throw new Error('community_cockpit_process_schema_invalid');
111
+ return input;
112
+ }
113
+ function readProcess(paths) {
114
+ assertPaths(paths);
115
+ if (!existsSync(paths.processPath))
116
+ return null;
117
+ return parseProcess(readJson(paths.processPath, 'community_cockpit_process_json_invalid'), paths);
118
+ }
119
+ function writeProcess(paths, record) {
120
+ assertPaths(paths);
121
+ atomicJsonWrite(paths.processPath, record);
122
+ }
123
+ function parseIdentity(paths, record) {
124
+ const value = readJson(paths.identityPath, 'community_cockpit_identity_json_invalid');
125
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
126
+ throw new Error('community_cockpit_identity_invalid');
127
+ }
128
+ const input = value;
129
+ const keys = Object.keys(input).sort().join(',');
130
+ if (keys !== 'controlPath,hostEntry,hostPid,launchId,pid,port,protocol,schemaVersion,sourceFingerprint,startedAt') {
131
+ throw new Error('community_cockpit_identity_schema_invalid');
132
+ }
133
+ if (input.schemaVersion !== 1 || input.protocol !== COMMUNITY_NATIVE_CHILD_PROTOCOL ||
134
+ input.launchId !== record.launchId || input.pid !== record.pid ||
135
+ !Number.isSafeInteger(input.hostPid) || input.hostPid < 1 || input.startedAt !== record.startedAt ||
136
+ input.sourceFingerprint !== record.sourceFingerprint || !samePath(input.hostEntry, record.hostEntry) ||
137
+ !samePath(input.controlPath, paths.controlPath) || input.port !== record.port)
138
+ throw new Error('community_cockpit_identity_mismatch');
139
+ return input;
140
+ }
141
+ function parseControl(paths) {
142
+ const value = readJson(paths.controlPath, 'community_cockpit_control_json_invalid');
143
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
144
+ throw new Error('community_cockpit_control_invalid');
145
+ }
146
+ const input = value;
147
+ if (Object.keys(input).sort().join(',') !== 'command,launchId,protocol,requestedAt,schemaVersion' ||
148
+ input.schemaVersion !== 1 || input.protocol !== COMMUNITY_NATIVE_CONTROL_PROTOCOL ||
149
+ !UUID.test(String(input.launchId)) || input.command !== 'stop' ||
150
+ Number.isNaN(Date.parse(String(input.requestedAt))))
151
+ throw new Error('community_cockpit_control_schema_invalid');
152
+ return input;
153
+ }
154
+ function removeControlIfOwned(paths, launchId) {
155
+ if (!existsSync(paths.controlPath))
156
+ return;
157
+ const control = parseControl(paths);
158
+ if (control.launchId !== launchId)
159
+ throw new Error('community_cockpit_control_launch_mismatch');
160
+ rmSync(paths.controlPath, { force: true });
161
+ }
162
+ function removeIdentityIfOwned(paths, record) {
163
+ if (!existsSync(paths.identityPath))
164
+ return;
165
+ parseIdentity(paths, record);
166
+ rmSync(paths.identityPath, { force: true });
167
+ }
168
+ function writeStopRequest(paths, launchId, now) {
169
+ if (existsSync(paths.controlPath)) {
170
+ const existing = parseControl(paths);
171
+ if (existing.launchId === launchId)
172
+ return;
173
+ throw new Error('community_cockpit_control_launch_mismatch');
174
+ }
175
+ atomicJsonWrite(paths.controlPath, {
176
+ schemaVersion: 1,
177
+ protocol: COMMUNITY_NATIVE_CONTROL_PROTOCOL,
178
+ launchId,
179
+ command: 'stop',
180
+ requestedAt: now().toISOString(),
181
+ });
182
+ }
183
+ function statusBase(state, port, serverPort = state.server.publicPort) {
184
+ return {
185
+ origin: `http://127.0.0.1:${port}`,
186
+ serverOrigin: `http://127.0.0.1:${serverPort}`,
187
+ port,
188
+ };
189
+ }
190
+ async function observeCockpit(params) {
191
+ const record = params.processRecord;
192
+ const identityPresent = existsSync(params.paths.identityPath);
193
+ const port = record?.port ?? params.state.cockpit.port;
194
+ const base = statusBase(params.state, port, record?.serverPort);
195
+ if (!record) {
196
+ return {
197
+ ...base,
198
+ runtimeState: identityPresent ? 'identity-conflict' : 'stopped',
199
+ recoverable: !identityPresent,
200
+ process: null,
201
+ identity: null,
202
+ supervisorAlive: false,
203
+ hostAlive: false,
204
+ health: 'not-checked',
205
+ reason: identityPresent ? 'community_cockpit_identity_without_process_record' : null,
206
+ };
207
+ }
208
+ const supervisorAlive = params.runtime.processExists(record.pid);
209
+ let identity = null;
210
+ if (identityPresent) {
211
+ try {
212
+ identity = parseIdentity(params.paths, record);
213
+ }
214
+ catch (error) {
215
+ return {
216
+ ...base,
217
+ runtimeState: 'identity-conflict', recoverable: false, process: record, identity: null,
218
+ supervisorAlive, hostAlive: record.hostPid ? params.runtime.processExists(record.hostPid) : false,
219
+ health: 'not-checked',
220
+ reason: error instanceof Error ? error.message : 'community_cockpit_identity_invalid',
221
+ };
222
+ }
223
+ }
224
+ const hostPid = identity?.hostPid ?? record.hostPid;
225
+ const hostAlive = hostPid ? params.runtime.processExists(hostPid) : false;
226
+ const active = record.status === 'starting' || record.status === 'running';
227
+ if (!active) {
228
+ if (!supervisorAlive && !hostAlive) {
229
+ return {
230
+ ...base,
231
+ runtimeState: 'stopped', recoverable: true, process: record, identity,
232
+ supervisorAlive: false, hostAlive: false, health: 'not-checked',
233
+ reason: identity ? 'community_cockpit_terminal_identity_stale' : null,
234
+ };
235
+ }
236
+ return {
237
+ ...base,
238
+ runtimeState: 'identity-conflict', recoverable: false, process: record, identity,
239
+ supervisorAlive, hostAlive, health: 'not-checked',
240
+ reason: 'community_cockpit_terminal_record_with_live_or_unowned_process',
241
+ };
242
+ }
243
+ if (!supervisorAlive && !hostAlive) {
244
+ return {
245
+ ...base,
246
+ runtimeState: 'crashed', recoverable: true, process: record, identity,
247
+ supervisorAlive: false, hostAlive: false, health: 'not-checked',
248
+ reason: 'community_cockpit_process_disappeared',
249
+ };
250
+ }
251
+ if (record.instanceName !== params.state.instanceName) {
252
+ return {
253
+ ...base,
254
+ runtimeState: 'identity-conflict', recoverable: false, process: record, identity,
255
+ supervisorAlive, hostAlive, health: 'not-checked',
256
+ reason: 'community_cockpit_process_instance_mismatch',
257
+ };
258
+ }
259
+ if (!identity) {
260
+ return {
261
+ ...base,
262
+ runtimeState: 'identity-conflict', recoverable: false, process: record, identity: null,
263
+ supervisorAlive, hostAlive, health: 'not-checked',
264
+ reason: 'community_cockpit_live_process_without_matching_identity',
265
+ };
266
+ }
267
+ if (!supervisorAlive && hostAlive) {
268
+ return {
269
+ ...base,
270
+ runtimeState: 'orphaned', recoverable: false, process: record, identity,
271
+ supervisorAlive, hostAlive, health: 'not-checked', reason: 'community_cockpit_host_orphaned',
272
+ };
273
+ }
274
+ if (supervisorAlive && !hostAlive) {
275
+ return {
276
+ ...base,
277
+ runtimeState: 'unhealthy', recoverable: false, process: record, identity,
278
+ supervisorAlive, hostAlive, health: 'unhealthy', reason: 'community_cockpit_host_process_missing',
279
+ };
280
+ }
281
+ const healthy = await params.runtime.health(`${base.origin}/_aops/cockpit-health`);
282
+ return {
283
+ ...base,
284
+ runtimeState: healthy ? 'running' : record.status === 'starting' ? 'starting' : 'unhealthy',
285
+ recoverable: false,
286
+ process: record,
287
+ identity,
288
+ supervisorAlive,
289
+ hostAlive,
290
+ health: healthy ? 'healthy' : 'unhealthy',
291
+ reason: healthy ? null : 'community_cockpit_health_failed',
292
+ };
293
+ }
294
+ export async function inspectCommunityNativeCockpit(params = {}) {
295
+ const install = inspectCommunityNativeInstall(params);
296
+ if (install.status !== 'installed' || !install.state) {
297
+ throw new Error(`community_native_not_installed:${install.status}:${install.error ?? 'run_setup_init'}`);
298
+ }
299
+ const paths = resolveCommunityCockpitPaths(params);
300
+ return observeCockpit({
301
+ state: install.state,
302
+ paths,
303
+ processRecord: readProcess(paths),
304
+ runtime: params.runtime ?? communityNativeRuntime,
305
+ });
306
+ }
307
+ function cockpitEnvironment(params) {
308
+ const env = { ...(params.env ?? process.env) };
309
+ for (const key of Object.keys(env)) {
310
+ const upper = key.toUpperCase();
311
+ if (/^PG[A-Z0-9_]+$/i.test(key) || /^(?:AOPS|PROJECTMAN|DOCMAN|AGENTSPACE|CHATV3|AUTHV2|KANBAN|SPRINT|TASKER|FILEMAN|SYS)_/i.test(key) ||
312
+ /(?:^|_)(?:AUTH|COOKIE|DATABASE|ENCRYPT|JWT|PASSWORD|PG|POSTGRES|SECRET|SESSION|SQLITE|TOKEN)(?:_|$)/i.test(key) ||
313
+ /_(?:REPO|PG|SQLITE)_URL$/i.test(key) || /^NODE_/i.test(key) || /^DYLD_/i.test(key) ||
314
+ ['DATABASE_URL', 'LD_PRELOAD', 'LD_LIBRARY_PATH', 'SSLKEYLOGFILE', 'HTTP_PROXY', 'HTTPS_PROXY',
315
+ 'ALL_PROXY', 'NO_PROXY', 'ADDRESS_HEADER', 'XFF_DEPTH', 'PROTOCOL_HEADER', 'HOST_HEADER'].includes(upper))
316
+ delete env[key];
317
+ }
318
+ return {
319
+ ...env,
320
+ NODE_ENV: 'production',
321
+ AOPS_NATIVE_LAUNCH_ID: params.launchId,
322
+ AOPS_NATIVE_STARTED_AT: params.startedAt,
323
+ AOPS_NATIVE_SOURCE_FINGERPRINT: params.state.source.sourceFingerprint,
324
+ AOPS_NATIVE_HOST_ENTRY: params.state.build.hostEntry,
325
+ AOPS_NATIVE_IDENTITY_PATH: params.paths.identityPath,
326
+ AOPS_NATIVE_CONTROL_PATH: params.paths.controlPath,
327
+ AOPS_NATIVE_PORT: String(params.state.cockpit.port),
328
+ COMMUNITY_PORT: String(params.state.cockpit.port),
329
+ AOPS_COMMUNITY_HOST_MODE: 'cockpit',
330
+ AOPS_COMMUNITY_SERVER_PORT: String(params.state.server.publicPort),
331
+ };
332
+ }
333
+ async function stopSpawnedCockpit(params) {
334
+ writeStopRequest(params.paths, params.processRecord.launchId, params.now);
335
+ let identity = null;
336
+ const pollCount = Math.max(1, Math.ceil(params.timeoutMs / POLL_MS));
337
+ for (let attempt = 0; attempt < pollCount; attempt += 1) {
338
+ if (existsSync(params.paths.identityPath)) {
339
+ identity = parseIdentity(params.paths, params.processRecord);
340
+ }
341
+ const supervisorAlive = params.runtime.processExists(params.processRecord.pid);
342
+ const hostAlive = identity ? params.runtime.processExists(identity.hostPid) : false;
343
+ if (!supervisorAlive && !hostAlive) {
344
+ if (existsSync(params.paths.identityPath))
345
+ removeIdentityIfOwned(params.paths, params.processRecord);
346
+ removeControlIfOwned(params.paths, params.processRecord.launchId);
347
+ return;
348
+ }
349
+ await params.runtime.sleep(POLL_MS);
350
+ }
351
+ const supervisorAlive = params.runtime.processExists(params.processRecord.pid);
352
+ const hostAlive = identity ? params.runtime.processExists(identity.hostPid) : false;
353
+ if (!supervisorAlive && !hostAlive) {
354
+ if (existsSync(params.paths.identityPath))
355
+ removeIdentityIfOwned(params.paths, params.processRecord);
356
+ removeControlIfOwned(params.paths, params.processRecord.launchId);
357
+ return;
358
+ }
359
+ if (identity) {
360
+ throw new Error('community_cockpit_child_cleanup_timeout:verified_processes_left_running');
361
+ }
362
+ await params.handle.terminate();
363
+ if (params.runtime.processExists(params.processRecord.pid)) {
364
+ throw new Error('community_cockpit_child_cleanup_timeout:supervisor_still_running');
365
+ }
366
+ removeControlIfOwned(params.paths, params.processRecord.launchId);
367
+ }
368
+ async function waitUntilReady(params) {
369
+ const deadline = Date.now() + params.timeoutMs;
370
+ const child = {};
371
+ void params.handle.wait.then((value) => { child.exit = value; }, (error) => { child.failure = error; });
372
+ try {
373
+ while (Date.now() < deadline) {
374
+ throwIfAborted(params.signal);
375
+ if (child.failure)
376
+ throw new Error('community_cockpit_child_launch_failed');
377
+ if (child.exit) {
378
+ throw new Error(`community_cockpit_child_exited_before_ready:${child.exit.exitCode ?? child.exit.signal ?? 'unknown'}`);
379
+ }
380
+ if (existsSync(params.paths.identityPath)) {
381
+ const identity = parseIdentity(params.paths, params.processRecord);
382
+ if (await params.runtime.health(`http://127.0.0.1:${params.state.cockpit.port}/_aops/cockpit-health`, params.signal)) {
383
+ return identity;
384
+ }
385
+ }
386
+ await params.runtime.sleep(POLL_MS, params.signal);
387
+ }
388
+ throw new Error('community_cockpit_child_ready_timeout');
389
+ }
390
+ catch (error) {
391
+ await stopSpawnedCockpit(params);
392
+ throw error;
393
+ }
394
+ }
395
+ async function withCockpitLifecycleLock(params, operation, callback) {
396
+ const install = inspectCommunityNativeInstall(params);
397
+ if (install.status !== 'installed' || !install.state) {
398
+ throw new Error(`community_native_not_installed:${install.status}:${install.error ?? 'run_setup_init'}`);
399
+ }
400
+ const paths = resolveCommunityCockpitPaths(params);
401
+ return (params.withOperationLock ?? withCommunityOperationLock)({ instanceDirectory: paths.runtimeRoot, operation, signal: params.signal }, callback);
402
+ }
403
+ async function startCommunityNativeCockpitUnlocked(params = {}) {
404
+ throwIfAborted(params.signal);
405
+ const install = inspectCommunityNativeInstall(params);
406
+ if (install.status !== 'installed' || !install.state) {
407
+ throw new Error(`community_native_not_installed:${install.status}:${install.error ?? 'run_setup_init'}`);
408
+ }
409
+ const state = install.state;
410
+ const paths = resolveCommunityCockpitPaths(params);
411
+ const runtime = params.runtime ?? communityNativeRuntime;
412
+ const prior = readProcess(paths);
413
+ const observed = await observeCockpit({ state, paths, processRecord: prior, runtime });
414
+ if (observed.runtimeState === 'running') {
415
+ return {
416
+ action: 'already-running', state, paths, process: observed.process,
417
+ origin: observed.origin, serverOrigin: observed.serverOrigin,
418
+ };
419
+ }
420
+ if (observed.runtimeState !== 'stopped' && observed.runtimeState !== 'crashed') {
421
+ throw new Error(`community_cockpit_process_active:${observed.runtimeState}:${observed.reason ?? 'run_cockpit_status'}`);
422
+ }
423
+ if (prior)
424
+ removeIdentityIfOwned(paths, prior);
425
+ if (prior)
426
+ removeControlIfOwned(paths, prior.launchId);
427
+ mkdirSync(paths.logRoot, { recursive: true, mode: 0o700 });
428
+ assertPaths(paths);
429
+ const now = params.now ?? (() => new Date());
430
+ const launchId = (params.createId ?? randomUUID)();
431
+ const startedAt = now().toISOString();
432
+ const handle = await runtime.spawnChild({
433
+ childEntry: resolveCommunityNativeChildEntry(),
434
+ cwd: state.source.root,
435
+ env: cockpitEnvironment({ state, paths, launchId, startedAt, env: params.env }),
436
+ mode: 'detached',
437
+ logPath: paths.logPath,
438
+ });
439
+ let record = {
440
+ schemaVersion: 1,
441
+ protocol: COMMUNITY_NATIVE_CHILD_PROTOCOL,
442
+ service: 'cockpit',
443
+ instanceName: state.instanceName,
444
+ launchId,
445
+ pid: handle.pid,
446
+ status: 'starting',
447
+ startedAt,
448
+ updatedAt: startedAt,
449
+ sourceFingerprint: state.source.sourceFingerprint,
450
+ hostEntry: state.build.hostEntry,
451
+ port: state.cockpit.port,
452
+ serverPort: state.server.publicPort,
453
+ identityPath: paths.identityPath,
454
+ controlPath: paths.controlPath,
455
+ logPath: paths.logPath,
456
+ };
457
+ writeProcess(paths, record);
458
+ try {
459
+ const identity = await waitUntilReady({
460
+ state, paths, processRecord: record, handle, runtime,
461
+ timeoutMs: params.readyTimeoutMs ?? READY_TIMEOUT_MS,
462
+ now,
463
+ signal: params.signal,
464
+ });
465
+ record = { ...record, hostPid: identity.hostPid, status: 'running', updatedAt: now().toISOString() };
466
+ writeProcess(paths, record);
467
+ }
468
+ catch (error) {
469
+ const liveAfterFailure = runtime.processExists(record.pid) || (() => {
470
+ try {
471
+ return existsSync(paths.identityPath) && runtime.processExists(parseIdentity(paths, record).hostPid);
472
+ }
473
+ catch {
474
+ return false;
475
+ }
476
+ })();
477
+ record = {
478
+ ...record,
479
+ status: liveAfterFailure ? 'starting' : 'failed',
480
+ updatedAt: now().toISOString(),
481
+ failure: error instanceof Error ? error.message.slice(0, 300) : 'community_cockpit_launch_failed',
482
+ };
483
+ writeProcess(paths, record);
484
+ throw error;
485
+ }
486
+ handle.detach();
487
+ return {
488
+ action: 'started', state, paths, process: record,
489
+ origin: `http://127.0.0.1:${state.cockpit.port}`,
490
+ serverOrigin: `http://127.0.0.1:${state.server.publicPort}`,
491
+ };
492
+ }
493
+ export async function startCommunityNativeCockpit(params = {}) {
494
+ return withCockpitLifecycleLock(params, 'start', () => startCommunityNativeCockpitUnlocked(params));
495
+ }
496
+ async function stopCommunityNativeCockpitUnlocked(params = {}) {
497
+ throwIfAborted(params.signal);
498
+ const install = inspectCommunityNativeInstall(params);
499
+ if (install.status !== 'installed' || !install.state) {
500
+ throw new Error(`community_native_not_installed:${install.status}:${install.error ?? 'run_setup_init'}`);
501
+ }
502
+ const paths = resolveCommunityCockpitPaths(params);
503
+ const runtime = params.runtime ?? communityNativeRuntime;
504
+ const now = params.now ?? (() => new Date());
505
+ const observed = await observeCockpit({
506
+ state: install.state,
507
+ paths,
508
+ processRecord: readProcess(paths),
509
+ runtime,
510
+ });
511
+ if (observed.runtimeState === 'stopped' || observed.runtimeState === 'crashed') {
512
+ return { status: 'already-stopped', state: install.state, process: observed.process };
513
+ }
514
+ if (!observed.process || !observed.identity || !observed.supervisorAlive ||
515
+ !['starting', 'running', 'unhealthy'].includes(observed.runtimeState)) {
516
+ throw new Error(`community_cockpit_stop_refused:${observed.runtimeState}:${observed.reason ?? 'identity_not_verified'}`);
517
+ }
518
+ writeStopRequest(paths, observed.process.launchId, now);
519
+ const deadline = Date.now() + (params.timeoutMs ?? STOP_TIMEOUT_MS);
520
+ while (Date.now() < deadline) {
521
+ throwIfAborted(params.signal);
522
+ if (existsSync(paths.identityPath))
523
+ parseIdentity(paths, observed.process);
524
+ const supervisorAlive = runtime.processExists(observed.process.pid);
525
+ const hostAlive = runtime.processExists(observed.identity.hostPid);
526
+ if (!supervisorAlive && !hostAlive && !existsSync(paths.identityPath)) {
527
+ removeControlIfOwned(paths, observed.process.launchId);
528
+ const stopped = {
529
+ ...observed.process,
530
+ status: 'exited',
531
+ updatedAt: now().toISOString(),
532
+ exitCode: 0,
533
+ signal: null,
534
+ };
535
+ delete stopped.failure;
536
+ writeProcess(paths, stopped);
537
+ return { status: 'stopped', state: install.state, process: stopped };
538
+ }
539
+ await runtime.sleep(POLL_MS, params.signal);
540
+ }
541
+ throw new Error('community_cockpit_stop_timeout:no_unverified_pid_kill');
542
+ }
543
+ export async function stopCommunityNativeCockpit(params = {}) {
544
+ return withCockpitLifecycleLock(params, 'stop', () => stopCommunityNativeCockpitUnlocked(params));
545
+ }
546
+ export async function restartCommunityNativeCockpit(params = {}) {
547
+ return withCockpitLifecycleLock(params, 'restart', async () => {
548
+ await stopCommunityNativeCockpitUnlocked(params);
549
+ return startCommunityNativeCockpitUnlocked(params);
550
+ });
551
+ }
552
+ export async function healthCommunityNativeCockpit(params = {}) {
553
+ const runtime = params.runtime ?? communityNativeRuntime;
554
+ const status = await inspectCommunityNativeCockpit(params);
555
+ if (status.runtimeState !== 'running' || status.health !== 'healthy') {
556
+ return { ...status, apiHealth: 'not-checked', apiReady: false };
557
+ }
558
+ const apiReady = await runtime.health(`${status.origin}/api/health`, params.signal);
559
+ return {
560
+ ...status,
561
+ apiHealth: apiReady ? 'healthy' : 'unhealthy',
562
+ apiReady,
563
+ };
564
+ }
565
+ export function readCommunityCockpitLogs(params = {}) {
566
+ const install = inspectCommunityNativeInstall(params);
567
+ if (install.status !== 'installed' || !install.state) {
568
+ throw new Error(`community_native_not_installed:${install.status}:${install.error ?? 'run_setup_init'}`);
569
+ }
570
+ const paths = resolveCommunityCockpitPaths(params);
571
+ assertPaths(paths);
572
+ const tail = Number(params.tail ?? 100);
573
+ if (!Number.isSafeInteger(tail) || tail < 1 || tail > 10_000)
574
+ throw new Error('community_logs_tail_invalid');
575
+ if (!existsSync(paths.logPath))
576
+ return { content: '', lineCount: 0, truncated: false, logPath: paths.logPath };
577
+ const stats = lstatSync(paths.logPath);
578
+ const byteLength = Math.min(stats.size, MAX_LOG_TAIL_BYTES);
579
+ const start = stats.size - byteLength;
580
+ const buffer = Buffer.alloc(byteLength);
581
+ const fd = openSync(paths.logPath, 'r');
582
+ try {
583
+ if (byteLength > 0)
584
+ readSync(fd, buffer, 0, byteLength, start);
585
+ }
586
+ finally {
587
+ closeSync(fd);
588
+ }
589
+ let content = buffer.toString('utf8');
590
+ let truncated = start > 0;
591
+ if (truncated) {
592
+ const firstNewline = content.indexOf('\n');
593
+ content = firstNewline >= 0 ? content.slice(firstNewline + 1) : '';
594
+ }
595
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
596
+ if (lines.at(-1) === '')
597
+ lines.pop();
598
+ const selected = lines.slice(-tail);
599
+ truncated ||= selected.length < lines.length;
600
+ return { content: selected.join('\n'), lineCount: selected.length, truncated, logPath: paths.logPath };
601
+ }
@@ -0,0 +1,59 @@
1
+ export const COMMUNITY_COMMAND_ABORT_MESSAGE = 'community_operation_aborted';
2
+ export class CommunityCommandAbortedError extends Error {
3
+ code = 'COMMUNITY_OPERATION_ABORTED';
4
+ constructor() {
5
+ super(COMMUNITY_COMMAND_ABORT_MESSAGE);
6
+ this.name = 'CommunityCommandAbortedError';
7
+ }
8
+ }
9
+ export const communityCommandAbortRuntime = Object.freeze({
10
+ createController: () => new AbortController(),
11
+ addSignalListener: (signal, listener) => process.on(signal, listener),
12
+ removeSignalListener: (signal, listener) => process.off(signal, listener),
13
+ });
14
+ export function throwIfCommunityCommandAborted(signal) {
15
+ if (signal?.aborted)
16
+ throw new CommunityCommandAbortedError();
17
+ }
18
+ export async function withCommunityCommandAbortScope(runtime, callback) {
19
+ const controller = runtime.createController();
20
+ const registrations = [];
21
+ const abort = () => {
22
+ if (!controller.signal.aborted)
23
+ controller.abort(new CommunityCommandAbortedError());
24
+ };
25
+ let failed = false;
26
+ let failure;
27
+ let result;
28
+ try {
29
+ for (const signal of ['SIGINT', 'SIGTERM']) {
30
+ registrations.push(signal);
31
+ runtime.addSignalListener(signal, abort);
32
+ }
33
+ throwIfCommunityCommandAborted(controller.signal);
34
+ result = await callback(controller.signal);
35
+ throwIfCommunityCommandAborted(controller.signal);
36
+ }
37
+ catch (error) {
38
+ failed = true;
39
+ failure = error;
40
+ }
41
+ let cleanupFailed = false;
42
+ let cleanupFailure;
43
+ for (const signal of registrations) {
44
+ try {
45
+ runtime.removeSignalListener(signal, abort);
46
+ }
47
+ catch (error) {
48
+ if (!cleanupFailed) {
49
+ cleanupFailed = true;
50
+ cleanupFailure = error;
51
+ }
52
+ }
53
+ }
54
+ if (failed)
55
+ throw failure;
56
+ if (cleanupFailed)
57
+ throw cleanupFailure;
58
+ return result;
59
+ }