@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,364 @@
1
+ import { createHash } from 'node:crypto';
2
+ export const OFFICIAL_CATALOG_SCOPE_V1 = Object.freeze({
3
+ schemaVersion: 1,
4
+ slug: 'aops-official-catalog',
5
+ kind: 'agentspace-skill-catalog',
6
+ owner: 'aops-community-setup',
7
+ reserved: true,
8
+ });
9
+ export const OFFICIAL_CATALOG_META_KEY_V1 = 'aopsOfficialCatalog';
10
+ export const OFFICIAL_CATALOG_TOOL_IDS_V1 = Object.freeze({
11
+ inspect: 'agentspace.official-catalog.inspect',
12
+ reconcile: 'agentspace.official-catalog.reconcile',
13
+ rollback: 'agentspace.official-catalog.rollback',
14
+ });
15
+ export const OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1 = Object.freeze([]);
16
+ export class OfficialCatalogError extends Error {
17
+ code;
18
+ details;
19
+ constructor(code, message, details) {
20
+ super(message);
21
+ this.name = 'OfficialCatalogError';
22
+ this.code = code;
23
+ this.details = details;
24
+ }
25
+ }
26
+ function fail(code, message, details) {
27
+ throw new OfficialCatalogError(code, message, details);
28
+ }
29
+ function nonEmpty(value, label) {
30
+ if (typeof value !== 'string' || !value.trim())
31
+ fail('catalog_snapshot_invalid', `${label} must be non-empty.`);
32
+ return value;
33
+ }
34
+ function compareUtf8(left, right) {
35
+ return Buffer.compare(Buffer.from(left, 'utf8'), Buffer.from(right, 'utf8'));
36
+ }
37
+ function stableObject(value) {
38
+ if (value === null || typeof value !== 'object')
39
+ return JSON.stringify(value);
40
+ if (Array.isArray(value))
41
+ return `[${value.map(stableObject).join(',')}]`;
42
+ const record = value;
43
+ return `{${Object.keys(record).sort(compareUtf8).map((key) => `${JSON.stringify(key)}:${stableObject(record[key])}`).join(',')}}`;
44
+ }
45
+ function sha256(value) {
46
+ return createHash('sha256').update(value, 'utf8').digest('hex');
47
+ }
48
+ function sortedMap(input) {
49
+ return Object.freeze(Object.fromEntries(Object.entries(input).sort(([left], [right]) => compareUtf8(left, right))));
50
+ }
51
+ function assertCurrentVersionMap(value, code, label) {
52
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
53
+ fail(code, `${label} must be an object map.`);
54
+ }
55
+ for (const [name, recordId] of Object.entries(value)) {
56
+ if (!name.trim() || (recordId !== null && (typeof recordId !== 'string' || !recordId.trim()))) {
57
+ fail(code, `${label} contains an invalid name or version record id.`);
58
+ }
59
+ }
60
+ }
61
+ export function assertOfficialCatalogSnapshotV1(snapshot) {
62
+ if (snapshot.schemaVersion !== 1 ||
63
+ snapshot.scopeSlug !== OFFICIAL_CATALOG_SCOPE_V1.slug ||
64
+ (snapshot.state !== 'absent' && snapshot.state !== 'ready') ||
65
+ !Number.isSafeInteger(snapshot.catalogRevision) ||
66
+ snapshot.catalogRevision < 0 ||
67
+ !Array.isArray(snapshot.versions)) {
68
+ fail('catalog_snapshot_invalid', 'Official catalog snapshot does not match schemaVersion 1.');
69
+ }
70
+ assertCurrentVersionMap(snapshot.currentVersionMap, 'catalog_snapshot_invalid', 'currentVersionMap');
71
+ if (snapshot.state === 'absent') {
72
+ if (snapshot.scopeId !== null ||
73
+ snapshot.projectId !== null ||
74
+ snapshot.catalogRevision !== 0 ||
75
+ Object.keys(snapshot.currentVersionMap).length !== 0 ||
76
+ snapshot.versions.length !== 0 ||
77
+ snapshot.lastReceiptId !== null) {
78
+ fail('catalog_scope_conflict', 'An absent reserved catalog snapshot contains server-owned state.');
79
+ }
80
+ return;
81
+ }
82
+ nonEmpty(snapshot.scopeId, 'scopeId');
83
+ nonEmpty(snapshot.projectId, 'projectId');
84
+ for (const [name, recordId] of Object.entries(snapshot.currentVersionMap)) {
85
+ nonEmpty(name, 'currentVersionMap.name');
86
+ if (recordId !== null)
87
+ nonEmpty(recordId, `currentVersionMap.${name}`);
88
+ }
89
+ const recordIds = new Set();
90
+ const identities = new Set();
91
+ for (const version of snapshot.versions) {
92
+ if (version.status !== 'published' ||
93
+ version.inert !== true ||
94
+ !/^[a-f0-9]{64}$/.test(version.packageSha256) ||
95
+ !/^[a-f0-9]{64}$/.test(version.releaseSetSha256)) {
96
+ fail('catalog_snapshot_invalid', 'Reserved catalog version metadata is incomplete or untrusted.');
97
+ }
98
+ for (const [label, value] of Object.entries({
99
+ recordId: version.recordId,
100
+ skillId: version.skillId,
101
+ name: version.name,
102
+ versionId: version.versionId,
103
+ }))
104
+ nonEmpty(value, label);
105
+ if (recordIds.has(version.recordId))
106
+ fail('catalog_snapshot_invalid', 'Catalog snapshot repeats a version record id.');
107
+ const identity = `${version.name}\0${version.versionId}`;
108
+ if (identities.has(identity))
109
+ fail('catalog_snapshot_invalid', 'Catalog snapshot repeats a logical package version.');
110
+ recordIds.add(version.recordId);
111
+ identities.add(identity);
112
+ }
113
+ for (const [name, recordId] of Object.entries(snapshot.currentVersionMap)) {
114
+ if (recordId === null)
115
+ continue;
116
+ const selected = snapshot.versions.find((entry) => entry.recordId === recordId);
117
+ if (!selected || selected.name !== name) {
118
+ fail('catalog_scope_conflict', 'Reserved current-version mapping points outside its catalog snapshot.', { name });
119
+ }
120
+ }
121
+ }
122
+ function utf8Content(bytes, label) {
123
+ try {
124
+ return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
125
+ }
126
+ catch {
127
+ fail('catalog_release_invalid', `Official catalog package file is not valid UTF-8: ${label}.`);
128
+ }
129
+ }
130
+ export function buildOfficialCatalogPackageImports(inputs) {
131
+ const releaseSetSha256 = inputs[0]?.releaseSetSha256;
132
+ const identities = new Set();
133
+ const digests = new Set();
134
+ const packages = inputs.map((input) => {
135
+ if (!releaseSetSha256 || input.releaseSetSha256 !== releaseSetSha256 || input.releaseTrustClass !== 'signed-community-release') {
136
+ fail('catalog_release_invalid', 'Official catalog packages must share one signature-verified Community release set.');
137
+ }
138
+ if (input.packageRef.origin !== 'reserved-catalog' || input.manifest.assetKind !== 'skill-package') {
139
+ fail('catalog_release_invalid', 'Official catalog input is not an inert reserved-catalog package.');
140
+ }
141
+ const identity = `${input.manifest.name}\0${input.manifest.versionId}`;
142
+ if (identities.has(identity) || digests.has(input.manifest.packageSha256)) {
143
+ fail('catalog_release_invalid', 'Official catalog release repeats a package identity or digest.');
144
+ }
145
+ identities.add(identity);
146
+ digests.add(input.manifest.packageSha256);
147
+ const rows = new Map(input.manifest.files.map((row) => [row.path, row]));
148
+ const files = input.transferFiles.map((file) => {
149
+ const row = rows.get(file.path);
150
+ if (!row)
151
+ fail('catalog_release_invalid', 'Verified transfer contains a file outside its manifest.');
152
+ return Object.freeze({
153
+ path: file.path,
154
+ sha256: row.sha256,
155
+ byteLength: row.byteLength,
156
+ content: utf8Content(file.bytes, `${input.manifest.name}/${file.path}`),
157
+ });
158
+ }).sort((left, right) => compareUtf8(left.path, right.path));
159
+ return Object.freeze({
160
+ name: input.manifest.name,
161
+ version: input.manifest.version,
162
+ versionId: input.manifest.versionId,
163
+ packageSha256: input.manifest.packageSha256,
164
+ manifestSha256: input.manifestSha256,
165
+ entryFile: input.manifest.entryFile,
166
+ manifest: input.manifest,
167
+ files: Object.freeze(files),
168
+ meta: Object.freeze({
169
+ [OFFICIAL_CATALOG_META_KEY_V1]: Object.freeze({
170
+ schemaVersion: 1,
171
+ scopeSlug: OFFICIAL_CATALOG_SCOPE_V1.slug,
172
+ source: 'signed-community-release',
173
+ releaseSetSha256: input.releaseSetSha256,
174
+ manifestSha256: input.manifestSha256,
175
+ packageSha256: input.manifest.packageSha256,
176
+ inert: true,
177
+ }),
178
+ }),
179
+ });
180
+ });
181
+ return Object.freeze(packages.sort((left, right) => compareUtf8(`${left.name}\0${left.versionId}`, `${right.name}\0${right.versionId}`)));
182
+ }
183
+ export function buildOfficialCatalogReconcilePlan(options) {
184
+ assertOfficialCatalogSnapshotV1(options.snapshot);
185
+ const releaseSetSha256 = options.packages[0]?.meta.aopsOfficialCatalog.releaseSetSha256;
186
+ if (!releaseSetSha256 || !/^[a-f0-9]{64}$/.test(releaseSetSha256)) {
187
+ fail('catalog_release_invalid', 'A non-empty signed official catalog package set is required.');
188
+ }
189
+ if (options.packages.some((entry) => entry.meta.aopsOfficialCatalog.releaseSetSha256 !== releaseSetSha256)) {
190
+ fail('catalog_release_invalid', 'Official catalog reconcile cannot mix release sets.');
191
+ }
192
+ const existingByIdentity = new Map(options.snapshot.versions.map((entry) => [`${entry.name}\0${entry.versionId}`, entry]));
193
+ const desiredPackageVersionMap = {};
194
+ const actions = [];
195
+ for (const packageInput of options.packages) {
196
+ desiredPackageVersionMap[packageInput.name] = packageInput.versionId;
197
+ const existing = existingByIdentity.get(`${packageInput.name}\0${packageInput.versionId}`);
198
+ if (existing && (existing.packageSha256 !== packageInput.packageSha256 ||
199
+ existing.releaseSetSha256 !== releaseSetSha256)) {
200
+ fail('catalog_package_conflict', 'A reserved catalog version identity already exists with different signed provenance.', {
201
+ name: packageInput.name,
202
+ versionId: packageInput.versionId,
203
+ });
204
+ }
205
+ if (!existing) {
206
+ actions.push(Object.freeze({
207
+ name: packageInput.name,
208
+ action: 'append-version',
209
+ versionId: packageInput.versionId,
210
+ packageSha256: packageInput.packageSha256,
211
+ existingRecordId: null,
212
+ }));
213
+ continue;
214
+ }
215
+ actions.push(Object.freeze({
216
+ name: packageInput.name,
217
+ action: options.snapshot.currentVersionMap[packageInput.name] === existing.recordId ? 'unchanged' : 'set-current',
218
+ versionId: packageInput.versionId,
219
+ packageSha256: packageInput.packageSha256,
220
+ existingRecordId: existing.recordId,
221
+ }));
222
+ }
223
+ for (const name of Object.keys(options.snapshot.currentVersionMap).sort(compareUtf8)) {
224
+ if (Object.prototype.hasOwnProperty.call(desiredPackageVersionMap, name))
225
+ continue;
226
+ desiredPackageVersionMap[name] = null;
227
+ actions.push(Object.freeze({
228
+ name,
229
+ action: options.snapshot.currentVersionMap[name] === null ? 'unchanged' : 'clear-current',
230
+ versionId: null,
231
+ packageSha256: null,
232
+ existingRecordId: options.snapshot.currentVersionMap[name] ?? null,
233
+ }));
234
+ }
235
+ actions.sort((left, right) => compareUtf8(`${left.name}\0${left.action}`, `${right.name}\0${right.action}`));
236
+ const expectedCurrentVersionMap = sortedMap(options.snapshot.currentVersionMap);
237
+ const desired = sortedMap(desiredPackageVersionMap);
238
+ const idempotencyKey = options.idempotencyKey?.trim() || `official-catalog-reconcile-v1:${sha256(stableObject({
239
+ releaseSetSha256,
240
+ catalogRevision: options.snapshot.catalogRevision,
241
+ lastReceiptId: options.snapshot.lastReceiptId,
242
+ desired,
243
+ }))}`;
244
+ return Object.freeze({
245
+ schemaVersion: 1,
246
+ kind: 'aops-official-catalog-reconcile-plan-v1',
247
+ scope: OFFICIAL_CATALOG_SCOPE_V1,
248
+ releaseSetSha256,
249
+ expectedCatalogRevision: options.snapshot.catalogRevision,
250
+ expectedPreviousReceiptId: options.snapshot.lastReceiptId,
251
+ expectedCurrentVersionMap,
252
+ desiredPackageVersionMap: desired,
253
+ packages: Object.freeze([...options.packages]),
254
+ actions: Object.freeze(actions),
255
+ mutationRequired: actions.some((entry) => entry.action !== 'unchanged'),
256
+ activationEffects: OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1,
257
+ historyDeleteCount: 0,
258
+ idempotencyKey,
259
+ });
260
+ }
261
+ export function buildOfficialCatalogRollbackRequest(options) {
262
+ assertOfficialCatalogSnapshotV1(options.snapshot);
263
+ const receiptId = nonEmpty(options.receiptId, 'receiptId');
264
+ const idempotencyKey = options.idempotencyKey?.trim() || `official-catalog-rollback-v1:${sha256(stableObject({
265
+ receiptId,
266
+ catalogRevision: options.snapshot.catalogRevision,
267
+ lastReceiptId: options.snapshot.lastReceiptId,
268
+ }))}`;
269
+ return Object.freeze({
270
+ schemaVersion: 1,
271
+ kind: 'aops-official-catalog-rollback-request-v1',
272
+ scope: OFFICIAL_CATALOG_SCOPE_V1,
273
+ receiptId,
274
+ expectedCatalogRevision: options.snapshot.catalogRevision,
275
+ idempotencyKey,
276
+ deleteHistory: false,
277
+ activationEffects: OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1,
278
+ });
279
+ }
280
+ export function assertOfficialCatalogReceiptV1(receipt, operation) {
281
+ if (receipt.schemaVersion !== 1 ||
282
+ receipt.kind !== 'aops-official-catalog-receipt-v1' ||
283
+ receipt.operation !== operation ||
284
+ receipt.scopeSlug !== OFFICIAL_CATALOG_SCOPE_V1.slug ||
285
+ receipt.historyDeleteCount !== 0 ||
286
+ !Array.isArray(receipt.activationEffects) ||
287
+ receipt.activationEffects.length !== 0 ||
288
+ !Number.isSafeInteger(receipt.catalogRevision) ||
289
+ receipt.catalogRevision < 1 ||
290
+ !/^[a-f0-9]{64}$/.test(receipt.releaseSetSha256) ||
291
+ !Array.isArray(receipt.packageSha256) ||
292
+ receipt.packageSha256.some((digest) => !/^[a-f0-9]{64}$/.test(digest)) ||
293
+ !Number.isFinite(Date.parse(receipt.createdAt)))
294
+ fail('catalog_apply_result_invalid', 'Official catalog operation returned an invalid receipt.');
295
+ for (const [label, value] of Object.entries({
296
+ receiptId: receipt.receiptId,
297
+ scopeId: receipt.scopeId,
298
+ projectId: receipt.projectId,
299
+ }))
300
+ nonEmpty(value, label);
301
+ assertCurrentVersionMap(receipt.priorCurrentVersionMap, 'catalog_apply_result_invalid', 'priorCurrentVersionMap');
302
+ assertCurrentVersionMap(receipt.currentVersionMap, 'catalog_apply_result_invalid', 'currentVersionMap');
303
+ if (receipt.previousReceiptId !== null)
304
+ nonEmpty(receipt.previousReceiptId, 'previousReceiptId');
305
+ if (new Set(receipt.packageSha256).size !== receipt.packageSha256.length) {
306
+ fail('catalog_apply_result_invalid', 'Official catalog receipt repeats a package digest.');
307
+ }
308
+ }
309
+ export async function reconcileOfficialCatalog(options) {
310
+ if (!options.adapter)
311
+ fail('catalog_adapter_unavailable', 'Agentspace official-catalog operations are not available in this server build.');
312
+ const snapshot = await options.adapter.inspect();
313
+ const plan = buildOfficialCatalogReconcilePlan({
314
+ snapshot,
315
+ packages: options.packages,
316
+ idempotencyKey: options.idempotencyKey,
317
+ });
318
+ if (options.mode === 'preview' || !plan.mutationRequired)
319
+ return plan;
320
+ const receipt = await options.adapter.reconcile(plan, 'apply');
321
+ assertOfficialCatalogReceiptV1(receipt, 'reconcile');
322
+ if (receipt.releaseSetSha256 !== plan.releaseSetSha256) {
323
+ fail('catalog_apply_result_invalid', 'Official catalog receipt release digest differs from the verified plan.');
324
+ }
325
+ if (receipt.catalogRevision !== plan.expectedCatalogRevision + 1 ||
326
+ receipt.previousReceiptId !== plan.expectedPreviousReceiptId ||
327
+ stableObject(receipt.priorCurrentVersionMap) !== stableObject(plan.expectedCurrentVersionMap)) {
328
+ fail('catalog_apply_result_invalid', 'Official catalog receipt does not prove the accepted compare-and-swap baseline.');
329
+ }
330
+ const expectedDigests = [...new Set(plan.packages.map((entry) => entry.packageSha256))].sort(compareUtf8);
331
+ const receiptDigests = [...receipt.packageSha256].sort(compareUtf8);
332
+ if (stableObject(receiptDigests) !== stableObject(expectedDigests)) {
333
+ fail('catalog_apply_result_invalid', 'Official catalog receipt package digests differ from the signed reconcile plan.');
334
+ }
335
+ if (stableObject(Object.keys(receipt.currentVersionMap).sort(compareUtf8)) !== stableObject(Object.keys(plan.desiredPackageVersionMap).sort(compareUtf8))) {
336
+ fail('catalog_apply_result_invalid', 'Official catalog receipt current-map keys differ from the reserved reconcile plan.');
337
+ }
338
+ return receipt;
339
+ }
340
+ export async function rollbackOfficialCatalog(options) {
341
+ if (!options.adapter)
342
+ fail('catalog_adapter_unavailable', 'Agentspace official-catalog operations are not available in this server build.');
343
+ const snapshot = await options.adapter.inspect();
344
+ const request = buildOfficialCatalogRollbackRequest({
345
+ snapshot,
346
+ receiptId: options.receiptId,
347
+ idempotencyKey: options.idempotencyKey,
348
+ });
349
+ if (options.mode === 'preview') {
350
+ const preview = await options.adapter.rollback(request, 'preview');
351
+ if (stableObject(preview) !== stableObject(request)) {
352
+ fail('catalog_apply_result_invalid', 'Official catalog rollback preview changed the fail-closed local request.');
353
+ }
354
+ return preview;
355
+ }
356
+ const receipt = await options.adapter.rollback(request, 'apply');
357
+ assertOfficialCatalogReceiptV1(receipt, 'rollback');
358
+ if (receipt.catalogRevision !== snapshot.catalogRevision + 1 ||
359
+ receipt.previousReceiptId !== snapshot.lastReceiptId ||
360
+ stableObject(receipt.priorCurrentVersionMap) !== stableObject(snapshot.currentVersionMap)) {
361
+ fail('catalog_apply_result_invalid', 'Official catalog rollback receipt does not prove the accepted compare-and-swap baseline.');
362
+ }
363
+ return receipt;
364
+ }
@@ -0,0 +1,240 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { resolveAopsConfigDir } from '@aopslabs/aops-host-registration';
5
+ import { getDefaultAopsSqliteRepoUrl, inferAopsRepoDialect, } from '@aopslabs/aops-runtime-config';
6
+ export const DEFAULT_AUTH_PROVIDER = 'trusted-local';
7
+ export const DEFAULT_LOCAL_PRINCIPAL_ID = '00000000-0000-4000-8000-000000000001';
8
+ export const DEFAULT_LOCAL_PRINCIPAL_FULL_NAME = 'AOPS Local Operator';
9
+ export const DEFAULT_LOCAL_PRINCIPAL_ROLES = 'admin';
10
+ export const DEFAULT_LOCAL_PRINCIPAL_PERMISSIONS = '*';
11
+ export const BOOTSTRAP_BLOCK_START = '# >>> AOPS bootstrap >>>';
12
+ export const BOOTSTRAP_BLOCK_END = '# <<< AOPS bootstrap <<<';
13
+ const MANAGED_KEYS = [
14
+ 'AOPS_AUTH_PROVIDER',
15
+ 'AOPS_REPO_URL',
16
+ 'AOPS_SQLITE_URL',
17
+ 'AOPS_PG_URL',
18
+ 'DOCMAN_REPO_URL',
19
+ 'DOCMAN_SQLITE_URL',
20
+ 'DOCMAN_PG_URL',
21
+ 'AOPS_RUNNER_WEBHOOK_SECRET',
22
+ 'JWT_SECRET',
23
+ 'JWT_ENC_SECRET',
24
+ 'AOPS_ADMIN_KEY',
25
+ 'AOPS_LOCAL_PRINCIPAL_ID',
26
+ 'AOPS_LOCAL_PRINCIPAL_EMAIL',
27
+ 'AOPS_LOCAL_PRINCIPAL_FULL_NAME',
28
+ 'AOPS_LOCAL_PRINCIPAL_ROLES',
29
+ 'AOPS_LOCAL_PRINCIPAL_PERMISSIONS',
30
+ ];
31
+ const MANAGED_KEY_SET = new Set(MANAGED_KEYS);
32
+ const FILEMAN_MANAGED_KEYS = [
33
+ 'FILEMAN_REPO_URL',
34
+ 'FILEMAN_SQLITE_URL',
35
+ 'FILEMAN_PG_URL',
36
+ ];
37
+ export function getDefaultFilemanSqliteRepoUrl(processEnv = process.env) {
38
+ return pathToFileURL(path.join(resolveAopsConfigDir(processEnv), 'fileman.aops.sqlite')).href;
39
+ }
40
+ function normalizeNonEmpty(value) {
41
+ if (typeof value !== 'string')
42
+ return undefined;
43
+ const trimmed = value.trim();
44
+ return trimmed.length > 0 ? trimmed : undefined;
45
+ }
46
+ function stripWrappingQuotes(value) {
47
+ if (value.length < 2)
48
+ return value;
49
+ if ((value.startsWith('"') && value.endsWith('"')) ||
50
+ (value.startsWith("'") && value.endsWith("'"))) {
51
+ return value.slice(1, -1);
52
+ }
53
+ return value;
54
+ }
55
+ function extractAssignmentKey(line) {
56
+ const match = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/);
57
+ return match?.[1];
58
+ }
59
+ function quoteEnvValue(value) {
60
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value))
61
+ return value;
62
+ return JSON.stringify(value);
63
+ }
64
+ export function normalizeAuthProvider(value, fallback = DEFAULT_AUTH_PROVIDER) {
65
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
66
+ if (!normalized)
67
+ return fallback;
68
+ if (normalized === 'trusted-local' || normalized === 'trusted_local' || normalized === 'trusted') {
69
+ return 'trusted-local';
70
+ }
71
+ if (normalized === 'authv2-jwt-session' || normalized === 'authv2_jwt_session' ||
72
+ normalized === 'jwt-session' || normalized === 'jwt_session' ||
73
+ normalized === 'session' || normalized === 'remote-session') {
74
+ throw new Error('community_local_auth_provider_retired:use_trusted-local_or_configure_a_remote-session_target');
75
+ }
76
+ return fallback;
77
+ }
78
+ export function createRandomSecret(bytes = 24) {
79
+ return randomBytes(bytes).toString('hex');
80
+ }
81
+ export function parseDotEnvAssignments(content) {
82
+ const assignments = {};
83
+ for (const rawLine of content.split(/\r?\n/)) {
84
+ const line = rawLine.trim();
85
+ if (!line || line.startsWith('#'))
86
+ continue;
87
+ const match = rawLine.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
88
+ if (!match)
89
+ continue;
90
+ const [, key, rawValue] = match;
91
+ assignments[key] = stripWrappingQuotes(String(rawValue).trim());
92
+ }
93
+ return assignments;
94
+ }
95
+ function resolveExistingRepoUrl(existing) {
96
+ return (normalizeNonEmpty(existing.AOPS_REPO_URL) ??
97
+ normalizeNonEmpty(existing.AOPS_SQLITE_URL) ??
98
+ normalizeNonEmpty(existing.AOPS_PG_URL));
99
+ }
100
+ export function buildServerBootstrapPreset(params) {
101
+ const existing = params.existing ?? {};
102
+ const processEnv = params.processEnv ?? process.env;
103
+ const authProvider = normalizeAuthProvider(params.authProvider ?? existing.AOPS_AUTH_PROVIDER);
104
+ const repoUrl = normalizeNonEmpty(params.repoUrl) ??
105
+ resolveExistingRepoUrl(existing) ??
106
+ getDefaultAopsSqliteRepoUrl(processEnv);
107
+ return {
108
+ authProvider,
109
+ repoUrl,
110
+ webhookSecret: normalizeNonEmpty(existing.AOPS_RUNNER_WEBHOOK_SECRET) ?? createRandomSecret(24),
111
+ localPrincipalId: normalizeNonEmpty(existing.AOPS_LOCAL_PRINCIPAL_ID) ?? DEFAULT_LOCAL_PRINCIPAL_ID,
112
+ localPrincipalEmail: normalizeNonEmpty(existing.AOPS_LOCAL_PRINCIPAL_EMAIL) ?? '',
113
+ localPrincipalFullName: normalizeNonEmpty(existing.AOPS_LOCAL_PRINCIPAL_FULL_NAME) ??
114
+ DEFAULT_LOCAL_PRINCIPAL_FULL_NAME,
115
+ localPrincipalRoles: normalizeNonEmpty(existing.AOPS_LOCAL_PRINCIPAL_ROLES) ?? DEFAULT_LOCAL_PRINCIPAL_ROLES,
116
+ localPrincipalPermissions: normalizeNonEmpty(existing.AOPS_LOCAL_PRINCIPAL_PERMISSIONS) ??
117
+ DEFAULT_LOCAL_PRINCIPAL_PERMISSIONS,
118
+ };
119
+ }
120
+ export function buildServerBootstrapEnvMap(preset, options = {}) {
121
+ const repoDialect = inferAopsRepoDialect(preset.repoUrl);
122
+ const env = {
123
+ AOPS_REPO_URL: preset.repoUrl,
124
+ DOCMAN_REPO_URL: preset.repoUrl,
125
+ AOPS_AUTH_PROVIDER: preset.authProvider,
126
+ AOPS_RUNNER_WEBHOOK_SECRET: preset.webhookSecret,
127
+ };
128
+ if (repoDialect === 'sqlite') {
129
+ env.AOPS_SQLITE_URL = preset.repoUrl;
130
+ env.DOCMAN_SQLITE_URL = preset.repoUrl;
131
+ }
132
+ else {
133
+ env.AOPS_PG_URL = preset.repoUrl;
134
+ env.DOCMAN_PG_URL = preset.repoUrl;
135
+ }
136
+ if (options.includeFileman) {
137
+ const filemanRepoUrl = normalizeNonEmpty(options.filemanRepoUrl) ??
138
+ getDefaultFilemanSqliteRepoUrl(options.processEnv ?? process.env);
139
+ env.FILEMAN_REPO_URL = filemanRepoUrl;
140
+ env.FILEMAN_SQLITE_URL = filemanRepoUrl;
141
+ }
142
+ env.AOPS_LOCAL_PRINCIPAL_ID = preset.localPrincipalId;
143
+ env.AOPS_LOCAL_PRINCIPAL_FULL_NAME = preset.localPrincipalFullName;
144
+ env.AOPS_LOCAL_PRINCIPAL_ROLES = preset.localPrincipalRoles;
145
+ env.AOPS_LOCAL_PRINCIPAL_PERMISSIONS = preset.localPrincipalPermissions;
146
+ if (normalizeNonEmpty(preset.localPrincipalEmail)) {
147
+ env.AOPS_LOCAL_PRINCIPAL_EMAIL = preset.localPrincipalEmail;
148
+ }
149
+ return env;
150
+ }
151
+ export function renderServerBootstrapBlock(env, options) {
152
+ const authProvider = options?.authProvider ?? DEFAULT_AUTH_PROVIDER;
153
+ const repoUrl = options?.repoUrl ?? getDefaultAopsSqliteRepoUrl(process.env);
154
+ const repoDialect = inferAopsRepoDialect(repoUrl);
155
+ const includeFileman = options?.includeFileman === true ||
156
+ Boolean(normalizeNonEmpty(env.FILEMAN_REPO_URL) ?? normalizeNonEmpty(env.FILEMAN_SQLITE_URL));
157
+ const lines = [
158
+ BOOTSTRAP_BLOCK_START,
159
+ '# Managed by aops-cli setup server-env',
160
+ '# Storage target and auth provider live in the host-owned local env boundary.',
161
+ `# Effective auth provider: ${authProvider}`,
162
+ `# Effective repository dialect: ${repoDialect}`,
163
+ `# Effective repository target: ${repoUrl}`,
164
+ '',
165
+ '# Canonical storage target',
166
+ `AOPS_REPO_URL=${quoteEnvValue(env.AOPS_REPO_URL ?? repoUrl)}`,
167
+ repoDialect === 'sqlite'
168
+ ? `AOPS_SQLITE_URL=${quoteEnvValue(env.AOPS_SQLITE_URL ?? repoUrl)}`
169
+ : `AOPS_PG_URL=${quoteEnvValue(env.AOPS_PG_URL ?? repoUrl)}`,
170
+ `DOCMAN_REPO_URL=${quoteEnvValue(env.DOCMAN_REPO_URL ?? repoUrl)}`,
171
+ repoDialect === 'sqlite'
172
+ ? `DOCMAN_SQLITE_URL=${quoteEnvValue(env.DOCMAN_SQLITE_URL ?? repoUrl)}`
173
+ : `DOCMAN_PG_URL=${quoteEnvValue(env.DOCMAN_PG_URL ?? repoUrl)}`,
174
+ ];
175
+ if (includeFileman) {
176
+ const filemanRepoUrl = normalizeNonEmpty(env.FILEMAN_REPO_URL) ??
177
+ normalizeNonEmpty(env.FILEMAN_SQLITE_URL) ??
178
+ getDefaultFilemanSqliteRepoUrl(process.env);
179
+ lines.push('', '# Fileman plugin-owned storage (explicit opt-in)', '# Fileman remains plugin-owned; this avoids implicit fileman:sqlite:auto resolution.', `FILEMAN_REPO_URL=${quoteEnvValue(filemanRepoUrl)}`, `FILEMAN_SQLITE_URL=${quoteEnvValue(env.FILEMAN_SQLITE_URL ?? filemanRepoUrl)}`);
180
+ }
181
+ lines.push('', '# Trusted-local auth and host-owned runtime secrets', `AOPS_AUTH_PROVIDER=${quoteEnvValue(env.AOPS_AUTH_PROVIDER)}`, `AOPS_RUNNER_WEBHOOK_SECRET=${quoteEnvValue(env.AOPS_RUNNER_WEBHOOK_SECRET)}`);
182
+ lines.push('', '# Trusted-local principal', `AOPS_LOCAL_PRINCIPAL_ID=${quoteEnvValue(env.AOPS_LOCAL_PRINCIPAL_ID)}`, `AOPS_LOCAL_PRINCIPAL_FULL_NAME=${quoteEnvValue(env.AOPS_LOCAL_PRINCIPAL_FULL_NAME)}`, `AOPS_LOCAL_PRINCIPAL_ROLES=${quoteEnvValue(env.AOPS_LOCAL_PRINCIPAL_ROLES)}`, `AOPS_LOCAL_PRINCIPAL_PERMISSIONS=${quoteEnvValue(env.AOPS_LOCAL_PRINCIPAL_PERMISSIONS)}`);
183
+ if (normalizeNonEmpty(env.AOPS_LOCAL_PRINCIPAL_EMAIL)) {
184
+ lines.push(`AOPS_LOCAL_PRINCIPAL_EMAIL=${quoteEnvValue(env.AOPS_LOCAL_PRINCIPAL_EMAIL)}`);
185
+ }
186
+ lines.push('', BOOTSTRAP_BLOCK_END);
187
+ return `${lines.join('\n')}\n`;
188
+ }
189
+ function stripManagedAssignments(content, extraManagedKeys = []) {
190
+ const managedKeys = extraManagedKeys.length > 0
191
+ ? new Set([...MANAGED_KEY_SET, ...extraManagedKeys])
192
+ : MANAGED_KEY_SET;
193
+ const lines = content.split(/\r?\n/);
194
+ const kept = lines.filter((line) => {
195
+ const key = extractAssignmentKey(line);
196
+ return !(key && managedKeys.has(key));
197
+ });
198
+ return kept.join('\n').replace(/\n{3,}/g, '\n\n').trim();
199
+ }
200
+ export function upsertServerBootstrapBlock(params) {
201
+ const block = renderServerBootstrapBlock(params.env, {
202
+ authProvider: params.authProvider,
203
+ repoUrl: params.repoUrl,
204
+ includeFileman: params.includeFileman,
205
+ }).trimEnd();
206
+ const existingContent = params.existingContent ?? '';
207
+ const start = existingContent.indexOf(BOOTSTRAP_BLOCK_START);
208
+ const end = existingContent.indexOf(BOOTSTRAP_BLOCK_END);
209
+ if (start >= 0 && end >= start) {
210
+ const before = existingContent.slice(0, start).trimEnd();
211
+ const after = existingContent.slice(end + BOOTSTRAP_BLOCK_END.length).trimStart();
212
+ return [before, block, after].filter(Boolean).join('\n\n').trimEnd() + '\n';
213
+ }
214
+ const base = stripManagedAssignments(existingContent, params.includeFileman ? FILEMAN_MANAGED_KEYS : []);
215
+ return [base, block].filter(Boolean).join('\n\n').trimEnd() + '\n';
216
+ }
217
+ export function validateServerBootstrapEnv(assignments, options = {}) {
218
+ const processEnv = options.processEnv ?? process.env;
219
+ const authProvider = normalizeAuthProvider(options.authProvider ?? assignments.AOPS_AUTH_PROVIDER);
220
+ const repoUrl = normalizeNonEmpty(options.repoUrl) ??
221
+ resolveExistingRepoUrl(assignments) ??
222
+ getDefaultAopsSqliteRepoUrl(processEnv);
223
+ const repoDialect = inferAopsRepoDialect(repoUrl);
224
+ const errors = [];
225
+ const warnings = [];
226
+ if (!normalizeNonEmpty(repoUrl)) {
227
+ errors.push('AOPS runtime storage target is missing.');
228
+ }
229
+ if (!normalizeNonEmpty(assignments.AOPS_RUNNER_WEBHOOK_SECRET)) {
230
+ warnings.push('AOPS_RUNNER_WEBHOOK_SECRET is missing. Public webhook ingress will stay disabled.');
231
+ }
232
+ return {
233
+ ok: errors.length === 0,
234
+ authProvider,
235
+ repoUrl,
236
+ repoDialect,
237
+ errors,
238
+ warnings,
239
+ };
240
+ }