@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,552 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { compactPayload, normalizeNonEmpty } from './command.js';
5
+ import { parseFrontmatterDocument, renderFrontmatterDocument } from './memory-workspace.js';
6
+ import { resolveRepoFirstWorkspaceRoot } from './repo-first-storage.js';
7
+ const DISCUSSION_TURN_KINDS = [
8
+ 'statement',
9
+ 'question',
10
+ 'answer',
11
+ 'objection',
12
+ 'concession',
13
+ 'proposal',
14
+ 'final-stance',
15
+ ];
16
+ function isRecord(value) {
17
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
18
+ }
19
+ function toStringArray(value) {
20
+ return Array.isArray(value)
21
+ ? value.map((entry) => normalizeNonEmpty(entry)).filter((entry) => Boolean(entry))
22
+ : [];
23
+ }
24
+ function uniqueStrings(values) {
25
+ const seen = new Set();
26
+ const result = [];
27
+ for (const value of values) {
28
+ const normalized = normalizeNonEmpty(value);
29
+ if (!normalized)
30
+ continue;
31
+ const key = normalized.toLowerCase();
32
+ if (seen.has(key))
33
+ continue;
34
+ seen.add(key);
35
+ result.push(normalized);
36
+ }
37
+ return result;
38
+ }
39
+ export function normalizeDiscussionSlug(value) {
40
+ return value
41
+ .toLowerCase()
42
+ .replace(/[^a-z0-9]+/g, '-')
43
+ .replace(/^-+|-+$/g, '')
44
+ .slice(0, 80) || 'item';
45
+ }
46
+ function slugify(value) {
47
+ return normalizeDiscussionSlug(value);
48
+ }
49
+ function numberField(value) {
50
+ return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
51
+ }
52
+ function booleanField(value) {
53
+ return typeof value === 'boolean' ? value : undefined;
54
+ }
55
+ function normalizeTopicStatus(value) {
56
+ const normalized = normalizeNonEmpty(value);
57
+ if (normalized === 'concluding' || normalized === 'concluded' || normalized === 'abandoned')
58
+ return normalized;
59
+ return 'active';
60
+ }
61
+ function normalizeTurnOrder(value) {
62
+ return normalizeNonEmpty(value) === 'free' ? 'free' : 'alternating';
63
+ }
64
+ function normalizeTurnKind(value) {
65
+ const normalized = normalizeNonEmpty(value)?.toLowerCase();
66
+ if (DISCUSSION_TURN_KINDS.includes(normalized))
67
+ return normalized;
68
+ return 'statement';
69
+ }
70
+ function normalizeRules(frontmatter) {
71
+ const rawRules = isRecord(frontmatter.rules) ? frontmatter.rules : {};
72
+ return {
73
+ turnOrder: normalizeTurnOrder(rawRules.turnOrder ?? frontmatter.turnOrder),
74
+ maxTurns: numberField(rawRules.maxTurns ?? frontmatter.maxTurns),
75
+ minTurnsBeforeConclude: numberField(rawRules.minTurnsBeforeConclude ?? frontmatter.minTurnsBeforeConclude),
76
+ requireQuestionAnswer: booleanField(rawRules.requireQuestionAnswer ?? frontmatter.requireQuestionAnswer) === true,
77
+ };
78
+ }
79
+ function normalizeLineage(value) {
80
+ if (!isRecord(value))
81
+ return undefined;
82
+ const kind = normalizeNonEmpty(value.kind);
83
+ if (kind !== 'follow-up' && kind !== 'fork' && kind !== 'version')
84
+ return undefined;
85
+ return {
86
+ kind,
87
+ parentTopicId: normalizeNonEmpty(value.parentTopicId),
88
+ parentTopicPath: normalizeNonEmpty(value.parentTopicPath),
89
+ referencedOutputs: uniqueStrings(toStringArray(value.referencedOutputs)),
90
+ referencedTurnRefs: uniqueStrings(toStringArray(value.referencedTurnRefs)),
91
+ referencedMemoryRefs: uniqueStrings(toStringArray(value.referencedMemoryRefs)),
92
+ };
93
+ }
94
+ function normalizeOutputKind(value, fallbackFileName) {
95
+ const normalized = normalizeNonEmpty(value);
96
+ if (normalized === 'final-stance' ||
97
+ normalized === 'consensus' ||
98
+ normalized === 'disagreement' ||
99
+ normalized === 'open-questions' ||
100
+ normalized === 'decision' ||
101
+ normalized === 'agent-summary') {
102
+ return normalized;
103
+ }
104
+ const fromFile = normalizeNonEmpty(fallbackFileName)?.replace(/\.md$/i, '');
105
+ if (fromFile === 'consensus' || fromFile === 'disagreement' || fromFile === 'open-questions')
106
+ return fromFile;
107
+ if (fromFile?.endsWith('-final-stance'))
108
+ return 'final-stance';
109
+ return 'decision';
110
+ }
111
+ export function resolveDiscussionWorkspacePaths(context) {
112
+ const rootDir = path.join(resolveRepoFirstWorkspaceRoot(context), 'agentspace', 'discussions');
113
+ return {
114
+ rootDir,
115
+ topicsDir: path.join(rootDir, 'topics'),
116
+ };
117
+ }
118
+ export function buildDiscussionTopicDirName(topic) {
119
+ return `${slugify(topic.slug ?? topic.title)}-${topic.localId.slice(0, 8)}`;
120
+ }
121
+ export function buildDiscussionTurnFileName(turn) {
122
+ return `${String(turn.seq).padStart(4, '0')}-${slugify(turn.agentId)}-${slugify(turn.kind)}.md`;
123
+ }
124
+ export function buildDiscussionOutputFileName(output) {
125
+ if (output.outputKind === 'final-stance') {
126
+ return `${slugify(output.agentId ?? 'agent')}-final-stance.md`;
127
+ }
128
+ return `${output.outputKind}.md`;
129
+ }
130
+ export function normalizeDiscussionTopic(frontmatter, body) {
131
+ const localId = normalizeNonEmpty(frontmatter.localId) ?? randomUUID();
132
+ const title = normalizeNonEmpty(frontmatter.title) ?? 'Untitled discussion';
133
+ const participants = uniqueStrings(toStringArray(frontmatter.participants));
134
+ return {
135
+ localId,
136
+ slug: normalizeDiscussionSlug(normalizeNonEmpty(frontmatter.slug) ?? title),
137
+ projectId: normalizeNonEmpty(frontmatter.projectId),
138
+ scopeId: normalizeNonEmpty(frontmatter.scopeId),
139
+ sessionLocalId: normalizeNonEmpty(frontmatter.sessionLocalId),
140
+ sessionPath: normalizeNonEmpty(frontmatter.sessionPath),
141
+ title,
142
+ objective: normalizeNonEmpty(frontmatter.objective),
143
+ question: normalizeNonEmpty(frontmatter.question),
144
+ participants,
145
+ initiatorAgentId: normalizeNonEmpty(frontmatter.initiatorAgentId) ?? participants[0],
146
+ status: normalizeTopicStatus(frontmatter.status),
147
+ subjectType: normalizeNonEmpty(frontmatter.subjectType),
148
+ subjectId: normalizeNonEmpty(frontmatter.subjectId),
149
+ tags: uniqueStrings(toStringArray(frontmatter.tags)),
150
+ abandonedAt: normalizeNonEmpty(frontmatter.abandonedAt),
151
+ abandonedReason: normalizeNonEmpty(frontmatter.abandonedReason),
152
+ lineage: normalizeLineage(frontmatter.lineage),
153
+ createdAt: normalizeNonEmpty(frontmatter.createdAt) ?? new Date().toISOString(),
154
+ updatedAt: normalizeNonEmpty(frontmatter.updatedAt) ?? normalizeNonEmpty(frontmatter.createdAt) ?? new Date().toISOString(),
155
+ storage: 'local-cache',
156
+ rules: normalizeRules(frontmatter),
157
+ raw: compactPayload({
158
+ ...frontmatter,
159
+ notes: normalizeNonEmpty(body),
160
+ }),
161
+ };
162
+ }
163
+ export function normalizeDiscussionTurn(frontmatter, body) {
164
+ return {
165
+ topicLocalId: normalizeNonEmpty(frontmatter.topicLocalId) ?? '',
166
+ turnId: normalizeNonEmpty(frontmatter.turnId) ?? randomUUID(),
167
+ seq: numberField(frontmatter.seq) ?? 0,
168
+ agentId: normalizeNonEmpty(frontmatter.agentId) ?? 'agent',
169
+ kind: normalizeTurnKind(frontmatter.kind),
170
+ replyToTurnId: normalizeNonEmpty(frontmatter.replyToTurnId),
171
+ addressedTo: normalizeNonEmpty(frontmatter.addressedTo),
172
+ createdAt: normalizeNonEmpty(frontmatter.createdAt) ?? new Date().toISOString(),
173
+ storage: 'local-cache',
174
+ content: body.trim(),
175
+ raw: frontmatter,
176
+ };
177
+ }
178
+ export function normalizeDiscussionOutput(frontmatter, body, fallbackFileName) {
179
+ return {
180
+ topicLocalId: normalizeNonEmpty(frontmatter.topicLocalId) ?? '',
181
+ outputKind: normalizeOutputKind(frontmatter.outputKind, fallbackFileName),
182
+ agentId: normalizeNonEmpty(frontmatter.agentId),
183
+ ownerAgentId: normalizeNonEmpty(frontmatter.ownerAgentId),
184
+ createdAt: normalizeNonEmpty(frontmatter.createdAt) ?? new Date().toISOString(),
185
+ updatedAt: normalizeNonEmpty(frontmatter.updatedAt) ?? normalizeNonEmpty(frontmatter.createdAt) ?? new Date().toISOString(),
186
+ storage: 'local-cache',
187
+ content: body.trim(),
188
+ raw: frontmatter,
189
+ };
190
+ }
191
+ export function buildDiscussionTopicFrontmatter(topic) {
192
+ return compactPayload({
193
+ schemaVersion: 2,
194
+ entityType: 'agentspace.discussion-topic',
195
+ localId: topic.localId,
196
+ slug: topic.slug,
197
+ projectId: topic.projectId,
198
+ scopeId: topic.scopeId,
199
+ sessionLocalId: topic.sessionLocalId,
200
+ sessionPath: topic.sessionPath,
201
+ title: topic.title,
202
+ objective: topic.objective,
203
+ question: topic.question,
204
+ participants: topic.participants,
205
+ initiatorAgentId: topic.initiatorAgentId,
206
+ status: topic.status,
207
+ subjectType: topic.subjectType,
208
+ subjectId: topic.subjectId,
209
+ tags: topic.tags.length > 0 ? topic.tags : undefined,
210
+ abandonedAt: topic.abandonedAt,
211
+ abandonedReason: topic.abandonedReason,
212
+ lineage: topic.lineage,
213
+ rules: compactPayload({
214
+ turnOrder: topic.rules.turnOrder,
215
+ maxTurns: topic.rules.maxTurns,
216
+ minTurnsBeforeConclude: topic.rules.minTurnsBeforeConclude,
217
+ requireQuestionAnswer: topic.rules.requireQuestionAnswer,
218
+ }),
219
+ createdAt: topic.createdAt,
220
+ updatedAt: topic.updatedAt,
221
+ storage: topic.storage,
222
+ });
223
+ }
224
+ export function buildDiscussionTurnFrontmatter(turn) {
225
+ return compactPayload({
226
+ schemaVersion: 2,
227
+ entityType: 'agentspace.discussion-turn',
228
+ topicLocalId: turn.topicLocalId,
229
+ turnId: turn.turnId,
230
+ seq: turn.seq,
231
+ agentId: turn.agentId,
232
+ kind: turn.kind,
233
+ replyToTurnId: turn.replyToTurnId,
234
+ addressedTo: turn.addressedTo,
235
+ createdAt: turn.createdAt,
236
+ storage: turn.storage,
237
+ });
238
+ }
239
+ export function buildDiscussionOutputFrontmatter(output) {
240
+ return compactPayload({
241
+ schemaVersion: 2,
242
+ entityType: 'agentspace.discussion-output',
243
+ topicLocalId: output.topicLocalId,
244
+ outputKind: output.outputKind,
245
+ agentId: output.agentId,
246
+ ownerAgentId: output.ownerAgentId,
247
+ createdAt: output.createdAt,
248
+ updatedAt: output.updatedAt,
249
+ storage: output.storage,
250
+ });
251
+ }
252
+ export function buildDiscussionTopicDocument(topic) {
253
+ const body = normalizeNonEmpty(topic.raw?.notes) ?? '';
254
+ return renderFrontmatterDocument(buildDiscussionTopicFrontmatter(topic), body);
255
+ }
256
+ export function buildDiscussionTurnDocument(turn) {
257
+ return renderFrontmatterDocument(buildDiscussionTurnFrontmatter(turn), turn.content);
258
+ }
259
+ export function buildDiscussionOutputDocument(output) {
260
+ return renderFrontmatterDocument(buildDiscussionOutputFrontmatter(output), output.content);
261
+ }
262
+ export function createDiscussionTopic(params) {
263
+ const now = new Date().toISOString();
264
+ const participants = uniqueStrings(params.participants);
265
+ return {
266
+ localId: randomUUID(),
267
+ slug: normalizeDiscussionSlug(params.slug ?? params.title),
268
+ projectId: params.projectId,
269
+ scopeId: params.scopeId,
270
+ sessionLocalId: params.sessionLocalId,
271
+ sessionPath: params.sessionPath,
272
+ title: params.title,
273
+ objective: params.objective,
274
+ question: params.question,
275
+ participants,
276
+ initiatorAgentId: normalizeNonEmpty(params.initiatorAgentId) ?? participants[0],
277
+ status: 'active',
278
+ subjectType: params.subjectType,
279
+ subjectId: params.subjectId,
280
+ tags: uniqueStrings(params.tags ?? []),
281
+ lineage: params.lineage,
282
+ createdAt: now,
283
+ updatedAt: now,
284
+ storage: 'local-cache',
285
+ rules: {
286
+ turnOrder: params.rules?.turnOrder ?? 'alternating',
287
+ maxTurns: params.rules?.maxTurns,
288
+ minTurnsBeforeConclude: params.rules?.minTurnsBeforeConclude,
289
+ requireQuestionAnswer: params.rules?.requireQuestionAnswer === true,
290
+ },
291
+ };
292
+ }
293
+ export function createDiscussionTurn(params) {
294
+ return {
295
+ topicLocalId: params.topicLocalId,
296
+ turnId: randomUUID(),
297
+ seq: params.seq,
298
+ agentId: params.agentId,
299
+ kind: params.kind,
300
+ content: params.content,
301
+ replyToTurnId: params.replyToTurnId,
302
+ addressedTo: params.addressedTo,
303
+ createdAt: new Date().toISOString(),
304
+ storage: 'local-cache',
305
+ };
306
+ }
307
+ export function createDiscussionOutput(params) {
308
+ const now = new Date().toISOString();
309
+ return {
310
+ topicLocalId: params.topicLocalId,
311
+ outputKind: params.outputKind,
312
+ agentId: params.agentId,
313
+ ownerAgentId: params.ownerAgentId,
314
+ createdAt: now,
315
+ updatedAt: now,
316
+ storage: 'local-cache',
317
+ content: params.content,
318
+ };
319
+ }
320
+ async function readDiscussionTurnFiles(turnsDir) {
321
+ try {
322
+ const files = await fs.readdir(turnsDir);
323
+ const entries = [];
324
+ for (const fileName of files.filter((entry) => entry.endsWith('.md')).sort()) {
325
+ const filePath = path.join(turnsDir, fileName);
326
+ const raw = await fs.readFile(filePath, 'utf8');
327
+ const parsed = parseFrontmatterDocument(raw);
328
+ entries.push({ fileName, filePath, turn: normalizeDiscussionTurn(parsed.frontmatter, parsed.body) });
329
+ }
330
+ return entries.sort((left, right) => left.turn.seq - right.turn.seq || left.fileName.localeCompare(right.fileName));
331
+ }
332
+ catch {
333
+ return [];
334
+ }
335
+ }
336
+ async function readDiscussionOutputFiles(outputsDir) {
337
+ try {
338
+ const files = await fs.readdir(outputsDir);
339
+ const entries = [];
340
+ for (const fileName of files.filter((entry) => entry.endsWith('.md')).sort()) {
341
+ const filePath = path.join(outputsDir, fileName);
342
+ const raw = await fs.readFile(filePath, 'utf8');
343
+ const parsed = parseFrontmatterDocument(raw);
344
+ entries.push({
345
+ fileName,
346
+ filePath,
347
+ output: normalizeDiscussionOutput(parsed.frontmatter, parsed.body, fileName),
348
+ });
349
+ }
350
+ return entries.sort((left, right) => left.fileName.localeCompare(right.fileName));
351
+ }
352
+ catch {
353
+ return [];
354
+ }
355
+ }
356
+ export async function readDiscussionTopicRecords(topicsDir) {
357
+ try {
358
+ const entries = await fs.readdir(topicsDir, { withFileTypes: true });
359
+ const records = [];
360
+ for (const entry of entries.filter((candidate) => candidate.isDirectory()).sort((left, right) => left.name.localeCompare(right.name))) {
361
+ const dirPath = path.join(topicsDir, entry.name);
362
+ const topicFilePath = path.join(dirPath, 'topic.md');
363
+ try {
364
+ const raw = await fs.readFile(topicFilePath, 'utf8');
365
+ const parsed = parseFrontmatterDocument(raw);
366
+ records.push({
367
+ dirName: entry.name,
368
+ dirPath,
369
+ topicFilePath,
370
+ topic: normalizeDiscussionTopic(parsed.frontmatter, parsed.body),
371
+ turns: await readDiscussionTurnFiles(path.join(dirPath, 'turns')),
372
+ outputs: await readDiscussionOutputFiles(path.join(dirPath, 'outputs')),
373
+ });
374
+ }
375
+ catch {
376
+ continue;
377
+ }
378
+ }
379
+ return records.sort((left, right) => right.topic.updatedAt.localeCompare(left.topic.updatedAt));
380
+ }
381
+ catch {
382
+ return [];
383
+ }
384
+ }
385
+ export function normalizeDiscussionTopicRecord(record) {
386
+ return compactPayload({
387
+ id: record.topic.localId,
388
+ localId: record.topic.localId,
389
+ slug: record.topic.slug,
390
+ topicSlug: record.topic.slug,
391
+ title: record.topic.title,
392
+ objective: record.topic.objective,
393
+ question: record.topic.question,
394
+ participants: record.topic.participants,
395
+ initiatorAgentId: record.topic.initiatorAgentId,
396
+ status: record.topic.status,
397
+ subjectType: record.topic.subjectType,
398
+ subjectId: record.topic.subjectId,
399
+ tags: record.topic.tags,
400
+ abandonedAt: record.topic.abandonedAt,
401
+ abandonedReason: record.topic.abandonedReason,
402
+ lineage: record.topic.lineage,
403
+ projectId: record.topic.projectId,
404
+ scopeId: record.topic.scopeId,
405
+ scope: record.topic.sessionLocalId ? 'session-bound' : 'standalone',
406
+ sessionLocalId: record.topic.sessionLocalId,
407
+ sessionPath: record.topic.sessionPath,
408
+ createdAt: record.topic.createdAt,
409
+ updatedAt: record.topic.updatedAt,
410
+ storage: record.topic.storage,
411
+ rules: record.topic.rules,
412
+ topicPath: record.dirName,
413
+ turnCount: record.turns.length,
414
+ outputCount: record.outputs.length,
415
+ });
416
+ }
417
+ export function normalizeDiscussionTurnRecord(turn) {
418
+ return compactPayload({
419
+ turnId: turn.turnId,
420
+ seq: turn.seq,
421
+ agentId: turn.agentId,
422
+ kind: turn.kind,
423
+ replyToTurnId: turn.replyToTurnId,
424
+ addressedTo: turn.addressedTo,
425
+ createdAt: turn.createdAt,
426
+ storage: turn.storage,
427
+ content: turn.content,
428
+ });
429
+ }
430
+ export function normalizeDiscussionOutputRecord(output) {
431
+ return compactPayload({
432
+ outputKind: output.outputKind,
433
+ agentId: output.agentId,
434
+ ownerAgentId: output.ownerAgentId,
435
+ createdAt: output.createdAt,
436
+ updatedAt: output.updatedAt,
437
+ storage: output.storage,
438
+ content: output.content,
439
+ });
440
+ }
441
+ export function discussionTopicMatchesSelector(record, selector) {
442
+ return selectDiscussionTopicRecordsBySelector([record], selector).length > 0;
443
+ }
444
+ function formatCandidateTitle(title) {
445
+ const trimmed = title.length > 80 ? `${title.slice(0, 77)}...` : title;
446
+ return JSON.stringify(trimmed);
447
+ }
448
+ export function formatDiscussionTopicSelectorCandidate(record) {
449
+ const shortId = record.topic.localId.slice(0, 8);
450
+ return `${record.topic.slug} [shortId=${shortId}, status=${record.topic.status}, title=${formatCandidateTitle(record.topic.title)}, path=${record.dirName}]`;
451
+ }
452
+ export function formatDiscussionTopicSelectorCandidates(records, limit = 5) {
453
+ const visible = records.slice(0, limit).map(formatDiscussionTopicSelectorCandidate);
454
+ const suffix = records.length > limit ? `; ... +${records.length - limit} more` : '';
455
+ return `${visible.join('; ')}${suffix}`;
456
+ }
457
+ export function buildDiscussionTopicNotFoundMessage(selector) {
458
+ return `Repo-first discussion topic was not found for selector "${selector}". Discover current slugs with: aops-cli discuss list --limit 20 --json.`;
459
+ }
460
+ export function buildDiscussionTopicAmbiguousSelectorMessage(selector, records) {
461
+ return `Discussion selector "${selector}" is ambiguous across ${records.length} candidates. Candidates: ${formatDiscussionTopicSelectorCandidates(records)}. Retry with the intended full localId or --short-id <8char>. Use --prefer-active only when exactly one active topic should win. Inspect with: aops-cli discuss list --status active --limit 20 --json.`;
462
+ }
463
+ function discussionSelectorWarning(matchedBy) {
464
+ if (matchedBy === 'dirName')
465
+ return 'legacy folder-name selector matched; prefer topic slug';
466
+ if (matchedBy === 'shortId')
467
+ return 'legacy short-id selector matched; prefer topic slug';
468
+ return undefined;
469
+ }
470
+ export function selectDiscussionTopicRecordsBySelectorDetailed(records, selector, options = {}) {
471
+ const normalized = selector.trim().toLowerCase();
472
+ if (!normalized)
473
+ return { records: [], cliDeprecationWarnings: [] };
474
+ const ranks = options.explicitShortId
475
+ ? [
476
+ {
477
+ matchedBy: 'shortId',
478
+ records: records.filter((record) => record.topic.localId.slice(0, 8).toLowerCase() === normalized),
479
+ },
480
+ ]
481
+ : [
482
+ {
483
+ matchedBy: 'localId',
484
+ records: records.filter((record) => record.topic.localId.toLowerCase() === normalized),
485
+ },
486
+ {
487
+ matchedBy: 'slug',
488
+ records: records.filter((record) => record.topic.slug.toLowerCase() === normalized),
489
+ },
490
+ {
491
+ matchedBy: 'dirName',
492
+ records: records.filter((record) => record.dirName.toLowerCase() === normalized),
493
+ },
494
+ {
495
+ matchedBy: 'shortId',
496
+ records: records.filter((record) => record.topic.localId.slice(0, 8).toLowerCase() === normalized),
497
+ },
498
+ ];
499
+ const match = ranks.find((entry) => entry.records.length > 0);
500
+ const warning = options.explicitShortId ? undefined : discussionSelectorWarning(match?.matchedBy);
501
+ return {
502
+ records: match?.records ?? [],
503
+ matchedBy: match?.matchedBy,
504
+ cliDeprecationWarnings: warning ? [warning] : [],
505
+ };
506
+ }
507
+ export function selectDiscussionTopicRecordsBySelector(records, selector) {
508
+ return selectDiscussionTopicRecordsBySelectorDetailed(records, selector).records;
509
+ }
510
+ export async function writeDiscussionTopicFile(topicsDir, topic) {
511
+ const dirName = buildDiscussionTopicDirName(topic);
512
+ const dirPath = path.join(topicsDir, dirName);
513
+ await fs.mkdir(dirPath, { recursive: true });
514
+ const topicFilePath = path.join(dirPath, 'topic.md');
515
+ await fs.writeFile(topicFilePath, buildDiscussionTopicDocument(topic), 'utf8');
516
+ return { dirName, dirPath, topicFilePath };
517
+ }
518
+ export async function overwriteDiscussionTopicFile(topicFilePath, topic) {
519
+ await fs.mkdir(path.dirname(topicFilePath), { recursive: true });
520
+ await fs.writeFile(topicFilePath, buildDiscussionTopicDocument(topic), 'utf8');
521
+ }
522
+ export async function writeDiscussionTurnFile(turnsDir, turn) {
523
+ await fs.mkdir(turnsDir, { recursive: true });
524
+ const fileName = buildDiscussionTurnFileName(turn);
525
+ const filePath = path.join(turnsDir, fileName);
526
+ try {
527
+ await fs.writeFile(filePath, buildDiscussionTurnDocument(turn), { encoding: 'utf8', flag: 'wx' });
528
+ }
529
+ catch (error) {
530
+ const code = error?.code;
531
+ if (code === 'EEXIST') {
532
+ throw new Error(`Turn sequence collision for ${fileName}. Re-read the topic state and retry the turn.`);
533
+ }
534
+ throw error;
535
+ }
536
+ return { fileName, filePath };
537
+ }
538
+ export async function writeDiscussionOutputFile(outputsDir, output, options = {}) {
539
+ await fs.mkdir(outputsDir, { recursive: true });
540
+ const fileName = buildDiscussionOutputFileName(output);
541
+ const filePath = path.join(outputsDir, fileName);
542
+ if (options.overwrite !== true) {
543
+ try {
544
+ await fs.access(filePath);
545
+ return { fileName, filePath, created: false };
546
+ }
547
+ catch {
548
+ }
549
+ }
550
+ await fs.writeFile(filePath, buildDiscussionOutputDocument(output), 'utf8');
551
+ return { fileName, filePath, created: true };
552
+ }